@tradejs/node 3.1.29-beta.282 → 3.1.29-beta.284
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backtest.d.mts +13 -2
- package/dist/backtest.d.ts +13 -2
- package/dist/backtest.js +161 -7
- package/dist/backtest.mjs +28 -6
- package/dist/{chunk-SKY2HNZU.mjs → chunk-K56EW2BB.mjs} +140 -1
- package/dist/diagnostics.d.mts +54 -1
- package/dist/diagnostics.d.ts +54 -1
- package/dist/diagnostics.js +329 -33
- package/dist/diagnostics.mjs +168 -5
- package/dist/mcp.d.mts +2 -2
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +134 -2
- package/dist/mcp.mjs +5 -2
- package/dist/registry.js +130 -1
- package/dist/registry.mjs +1 -1
- package/dist/runtimeDashboard.js +130 -1
- package/dist/runtimeDashboard.mjs +1 -1
- package/dist/runtimeStrategies.js +130 -1
- package/dist/runtimeStrategies.mjs +1 -1
- package/dist/strategies.js +130 -1
- package/dist/strategies.mjs +1 -1
- package/package.json +5 -5
package/dist/diagnostics.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
getRuntimeDeployment,
|
|
3
3
|
loadResolvedRuntimeStrategies
|
|
4
4
|
} from "./chunk-BO42QWAB.mjs";
|
|
5
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-K56EW2BB.mjs";
|
|
6
6
|
import "./chunk-UL5Z46JL.mjs";
|
|
7
7
|
import "./chunk-LZDXRXIU.mjs";
|
|
8
8
|
import "./chunk-YR55Q355.mjs";
|
|
@@ -95,10 +95,10 @@ var parseRuntimeEvidenceDeploymentSnapshot = (value) => {
|
|
|
95
95
|
};
|
|
96
96
|
var activeRuntimeEvidenceStrategies = (deployment) => deployment.enabled ? deployment.strategies.filter((strategy) => strategy.enabled) : [];
|
|
97
97
|
var unwrapRuntimeEvidenceRows = (value, field) => (Array.isArray(value) ? value : []).map((candidate) => {
|
|
98
|
-
const
|
|
99
|
-
const nested =
|
|
100
|
-
return nested ??
|
|
101
|
-
}).filter((
|
|
98
|
+
const record2 = isRecord(candidate) ? candidate : null;
|
|
99
|
+
const nested = record2 && isRecord(record2[field]) ? record2[field] : null;
|
|
100
|
+
return nested ?? record2;
|
|
101
|
+
}).filter((record2) => record2 != null);
|
|
102
102
|
var assertCurrentRuntimeEvidenceArtifact = (artifact) => {
|
|
103
103
|
if (!isRecord(artifact) || artifact.reportType !== "runtime-evidence") {
|
|
104
104
|
throw new Error("Invalid runtime evidence artifact");
|
|
@@ -840,7 +840,168 @@ var verifyRuntimeFeedbackReplaySource = async ({
|
|
|
840
840
|
});
|
|
841
841
|
return { feedback, runtimeEvidence };
|
|
842
842
|
};
|
|
843
|
+
|
|
844
|
+
// src/diagnostics/mcpFeedbackContract.ts
|
|
845
|
+
var MCP_FEEDBACK_CONTRACT = {
|
|
846
|
+
version: 1,
|
|
847
|
+
hostProtocolVersion: 2,
|
|
848
|
+
replayQueue: {
|
|
849
|
+
waitingStates: ["waiting_resources", "waiting_executor"],
|
|
850
|
+
automaticFailedRetries: false
|
|
851
|
+
},
|
|
852
|
+
artifactEncoding: "base64",
|
|
853
|
+
maximumChunkBytes: 48e3,
|
|
854
|
+
artifactFiles: ["payload", "manifest.json", ".complete", "replay.log"],
|
|
855
|
+
requiredTools: [
|
|
856
|
+
"tradejs_info",
|
|
857
|
+
"diagnostics_list_reports",
|
|
858
|
+
"diagnostics_get_report",
|
|
859
|
+
"artifact_get",
|
|
860
|
+
"job_get",
|
|
861
|
+
"job_get_logs"
|
|
862
|
+
]
|
|
863
|
+
};
|
|
864
|
+
var assertMcpFeedbackContract = (version, tools) => {
|
|
865
|
+
if (version !== MCP_FEEDBACK_CONTRACT.version)
|
|
866
|
+
throw new Error("MCP feedback contract version mismatch");
|
|
867
|
+
for (const name of MCP_FEEDBACK_CONTRACT.requiredTools)
|
|
868
|
+
if (!tools.some((tool) => tool.name === name))
|
|
869
|
+
throw new Error(`MCP feedback requires tool ${name}`);
|
|
870
|
+
const properties = tools.find((tool) => tool.name === "artifact_get")?.inputSchema.properties;
|
|
871
|
+
if (!properties || !("file" in properties))
|
|
872
|
+
throw new Error(
|
|
873
|
+
"MCP feedback requires artifact_get.file; refresh the client tool schema"
|
|
874
|
+
);
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
// src/diagnostics/mcpBundleDownload.ts
|
|
878
|
+
import { createHash as createHash4 } from "crypto";
|
|
879
|
+
import fs4 from "fs/promises";
|
|
880
|
+
import path4 from "path";
|
|
881
|
+
var McpReadTransportError = class extends Error {
|
|
882
|
+
constructor(message, retryAfterMs) {
|
|
883
|
+
super(message);
|
|
884
|
+
this.retryAfterMs = retryAfterMs;
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
888
|
+
var digest = (bytes) => createHash4("sha256").update(bytes).digest("hex");
|
|
889
|
+
var exists = async (file) => fs4.lstat(file).catch((error) => {
|
|
890
|
+
if (error.code === "ENOENT") return void 0;
|
|
891
|
+
throw error;
|
|
892
|
+
});
|
|
893
|
+
var checkFile = async (file) => {
|
|
894
|
+
const stat = await exists(file);
|
|
895
|
+
if (stat && (!stat.isFile() || stat.isSymbolicLink()))
|
|
896
|
+
throw new Error("Invalid local bundle file");
|
|
897
|
+
return stat;
|
|
898
|
+
};
|
|
899
|
+
var downloadMcpBundle = async ({
|
|
900
|
+
descriptor,
|
|
901
|
+
root,
|
|
902
|
+
contractVersion,
|
|
903
|
+
tools,
|
|
904
|
+
readChunk,
|
|
905
|
+
wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
906
|
+
}) => {
|
|
907
|
+
assertMcpFeedbackContract(contractVersion, tools);
|
|
908
|
+
if (!/^[a-f0-9]{64}$/.test(descriptor.id))
|
|
909
|
+
throw new Error("Invalid artifact identity");
|
|
910
|
+
const required = ["payload", "manifest.json", ".complete"];
|
|
911
|
+
if (descriptor.kind === "runtime-feedback-replay")
|
|
912
|
+
required.push("replay.log");
|
|
913
|
+
for (const name of required) {
|
|
914
|
+
const matches = descriptor.files.filter((f) => f.file === name);
|
|
915
|
+
if (matches.length !== 1 || !matches[0].available || !Number.isSafeInteger(matches[0].bytes) || matches[0].bytes < 0 || !/^[a-f0-9]{64}$/.test(matches[0].sha256))
|
|
916
|
+
throw new Error(`Incomplete bundle descriptor: ${name}`);
|
|
917
|
+
}
|
|
918
|
+
const bundleDir = path4.join(root, descriptor.id);
|
|
919
|
+
await fs4.mkdir(bundleDir, { recursive: true });
|
|
920
|
+
const dir = await fs4.lstat(bundleDir);
|
|
921
|
+
if (dir.isSymbolicLink()) throw new Error("Invalid bundle directory");
|
|
922
|
+
const lock = path4.join(bundleDir, ".download-lock");
|
|
923
|
+
await fs4.mkdir(lock);
|
|
924
|
+
try {
|
|
925
|
+
for (const name of [
|
|
926
|
+
...required.filter((f) => f !== ".complete"),
|
|
927
|
+
".complete"
|
|
928
|
+
]) {
|
|
929
|
+
const expected = descriptor.files.find((f) => f.file === name);
|
|
930
|
+
if (!expected) throw new Error("Missing bundle file");
|
|
931
|
+
const filename = name === "payload" ? descriptor.kind === "runtime-evidence" ? "runtime-evidence.json" : "replay-runtime-evidence.json" : name;
|
|
932
|
+
const target = path4.join(bundleDir, filename);
|
|
933
|
+
const verified = await checkFile(target);
|
|
934
|
+
if (verified) {
|
|
935
|
+
if (verified.size !== expected.bytes || digest(await fs4.readFile(target)) !== expected.sha256)
|
|
936
|
+
throw new Error("Local bundle checksum mismatch");
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
const partial = target + ".part";
|
|
940
|
+
const checkpoint = partial + ".json";
|
|
941
|
+
const identity = JSON.stringify({
|
|
942
|
+
artifactId: descriptor.id,
|
|
943
|
+
file: name,
|
|
944
|
+
bytes: expected.bytes,
|
|
945
|
+
sha256: expected.sha256
|
|
946
|
+
});
|
|
947
|
+
await checkFile(checkpoint);
|
|
948
|
+
const stat = await checkFile(partial);
|
|
949
|
+
if (stat) {
|
|
950
|
+
if (await fs4.readFile(checkpoint, "utf8") !== identity || stat.size > expected.bytes)
|
|
951
|
+
throw new Error("Artifact changed during download");
|
|
952
|
+
} else {
|
|
953
|
+
if (await checkFile(checkpoint)) {
|
|
954
|
+
if (await fs4.readFile(checkpoint, "utf8") !== identity)
|
|
955
|
+
throw new Error("Artifact changed during download");
|
|
956
|
+
} else await fs4.writeFile(checkpoint, identity, { flag: "wx" });
|
|
957
|
+
}
|
|
958
|
+
let offset = stat?.size ?? 0;
|
|
959
|
+
if (!stat) await fs4.writeFile(partial, Buffer.alloc(0), { flag: "wx" });
|
|
960
|
+
while (offset < expected.bytes) {
|
|
961
|
+
const request = {
|
|
962
|
+
artifactId: descriptor.id,
|
|
963
|
+
file: name,
|
|
964
|
+
offset,
|
|
965
|
+
length: MCP_FEEDBACK_CONTRACT.maximumChunkBytes
|
|
966
|
+
};
|
|
967
|
+
let response;
|
|
968
|
+
for (let attempt = 0; ; attempt++) {
|
|
969
|
+
try {
|
|
970
|
+
response = await readChunk(request);
|
|
971
|
+
break;
|
|
972
|
+
} catch (error) {
|
|
973
|
+
if (!(error instanceof McpReadTransportError) || attempt >= 3)
|
|
974
|
+
throw error;
|
|
975
|
+
await wait(
|
|
976
|
+
Math.max([2e3, 5e3, 1e4][attempt], error.retryAfterMs ?? 0)
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
if (!record(response) || response.id !== descriptor.id || response.file !== name || response.offset !== offset || response.bytes !== expected.bytes || response.sha256 !== expected.sha256 || response.encoding !== "base64" || typeof response.data !== "string" || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(
|
|
981
|
+
response.data
|
|
982
|
+
))
|
|
983
|
+
throw new Error("Invalid artifact chunk identity");
|
|
984
|
+
const bytes = Buffer.from(response.data, "base64");
|
|
985
|
+
const next = offset + bytes.length;
|
|
986
|
+
if (!bytes.length || bytes.length > request.length || next > expected.bytes || response.nextOffset !== (next === expected.bytes ? null : next))
|
|
987
|
+
throw new Error("Invalid artifact chunk range");
|
|
988
|
+
await fs4.appendFile(partial, bytes);
|
|
989
|
+
offset = next;
|
|
990
|
+
}
|
|
991
|
+
if (digest(await fs4.readFile(partial)) !== expected.sha256)
|
|
992
|
+
throw new Error("Downloaded bundle checksum mismatch");
|
|
993
|
+
await fs4.rename(partial, target);
|
|
994
|
+
await fs4.unlink(checkpoint);
|
|
995
|
+
}
|
|
996
|
+
const bundle = descriptor.kind === "runtime-evidence" ? await verifyRuntimeEvidenceBundle(bundleDir) : await verifyRuntimeFeedbackReplayBundle(bundleDir);
|
|
997
|
+
return bundle;
|
|
998
|
+
} finally {
|
|
999
|
+
await fs4.rmdir(lock);
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
843
1002
|
export {
|
|
1003
|
+
MCP_FEEDBACK_CONTRACT,
|
|
1004
|
+
McpReadTransportError,
|
|
844
1005
|
RUNTIME_EVIDENCE_COMPLETE_FILE,
|
|
845
1006
|
RUNTIME_EVIDENCE_MANIFEST_FILE,
|
|
846
1007
|
RUNTIME_EVIDENCE_PAYLOAD_FILE,
|
|
@@ -850,8 +1011,10 @@ export {
|
|
|
850
1011
|
RUNTIME_FEEDBACK_REPLAY_FILE,
|
|
851
1012
|
activeRuntimeEvidenceStrategies,
|
|
852
1013
|
assertCurrentRuntimeEvidenceArtifact,
|
|
1014
|
+
assertMcpFeedbackContract,
|
|
853
1015
|
discoverRuntimeEvidenceBundles,
|
|
854
1016
|
discoverRuntimeFeedbackReplayBundles,
|
|
1017
|
+
downloadMcpBundle,
|
|
855
1018
|
parseRuntimeEvidenceDeploymentSnapshot,
|
|
856
1019
|
parseRuntimeEvidenceProducer,
|
|
857
1020
|
publishRuntimeEvidenceBundle,
|
package/dist/mcp.d.mts
CHANGED
|
@@ -12,7 +12,7 @@ declare const redactMcpJobLog: (line: string) => string;
|
|
|
12
12
|
/** Offsets refer to the retained worker tail, which may shift while running. */
|
|
13
13
|
declare const readMcpJobLogs: (principal: McpPrincipal, id: string, offset?: number, limit?: number) => Promise<{
|
|
14
14
|
jobId: string;
|
|
15
|
-
status: "completed" | "failed" | "
|
|
15
|
+
status: "running" | "completed" | "failed" | "queued" | "waiting_resources" | "waiting_executor" | "cancelled";
|
|
16
16
|
updatedAt: number;
|
|
17
17
|
offset: number;
|
|
18
18
|
nextOffset: number | null;
|
|
@@ -30,7 +30,7 @@ declare const publicMcpJob: (job: McpJob) => {
|
|
|
30
30
|
userName: string;
|
|
31
31
|
clientId: string;
|
|
32
32
|
kind: "backtest" | "runtime-evidence" | "runtime-feedback-replay";
|
|
33
|
-
status: "queued" | "running" | "completed" | "failed" | "cancelled";
|
|
33
|
+
status: "queued" | "waiting_resources" | "waiting_executor" | "running" | "completed" | "failed" | "cancelled";
|
|
34
34
|
createdAt: number;
|
|
35
35
|
updatedAt: number;
|
|
36
36
|
startedAt?: number;
|
package/dist/mcp.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ declare const redactMcpJobLog: (line: string) => string;
|
|
|
12
12
|
/** Offsets refer to the retained worker tail, which may shift while running. */
|
|
13
13
|
declare const readMcpJobLogs: (principal: McpPrincipal, id: string, offset?: number, limit?: number) => Promise<{
|
|
14
14
|
jobId: string;
|
|
15
|
-
status: "completed" | "failed" | "
|
|
15
|
+
status: "running" | "completed" | "failed" | "queued" | "waiting_resources" | "waiting_executor" | "cancelled";
|
|
16
16
|
updatedAt: number;
|
|
17
17
|
offset: number;
|
|
18
18
|
nextOffset: number | null;
|
|
@@ -30,7 +30,7 @@ declare const publicMcpJob: (job: McpJob) => {
|
|
|
30
30
|
userName: string;
|
|
31
31
|
clientId: string;
|
|
32
32
|
kind: "backtest" | "runtime-evidence" | "runtime-feedback-replay";
|
|
33
|
-
status: "queued" | "running" | "completed" | "failed" | "cancelled";
|
|
33
|
+
status: "queued" | "waiting_resources" | "waiting_executor" | "running" | "completed" | "failed" | "cancelled";
|
|
34
34
|
createdAt: number;
|
|
35
35
|
updatedAt: number;
|
|
36
36
|
startedAt?: number;
|
package/dist/mcp.js
CHANGED
|
@@ -6211,6 +6211,112 @@ var buildAiHookContext = ({
|
|
|
6211
6211
|
};
|
|
6212
6212
|
};
|
|
6213
6213
|
|
|
6214
|
+
// src/researchEntryPolicy.ts
|
|
6215
|
+
var forbidden = /(?:^|\.)(?:__proto__|prototype|constructor|profit|pnl|outcome|tradeResult|backtestExecution|quality|deterministicQuality|approvalAllowedNow|timestamp|knownAt|source|coverage|points|rows|length)(?:\.|$)/i;
|
|
6216
|
+
var buildResearchEntryPayload = (signal, payload = buildAiPayload(signal)) => {
|
|
6217
|
+
const additional = payload.additionalIndicators && typeof payload.additionalIndicators === "object" ? payload.additionalIndicators : {};
|
|
6218
|
+
const raw = signal.additionalIndicators?.baseContext;
|
|
6219
|
+
const adapted = additional.baseContext;
|
|
6220
|
+
return {
|
|
6221
|
+
...payload,
|
|
6222
|
+
additionalIndicators: {
|
|
6223
|
+
...additional,
|
|
6224
|
+
...signal.additionalIndicators?.jev && typeof signal.additionalIndicators.jev === "object" ? { jev: signal.additionalIndicators.jev } : {},
|
|
6225
|
+
baseContext: {
|
|
6226
|
+
...raw && typeof raw === "object" ? raw : {},
|
|
6227
|
+
...adapted && typeof adapted === "object" ? adapted : {}
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6230
|
+
};
|
|
6231
|
+
};
|
|
6232
|
+
var parseResearchEntryPolicy = (value) => {
|
|
6233
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
6234
|
+
throw new Error("Invalid research entry policy");
|
|
6235
|
+
const policy = value;
|
|
6236
|
+
if (policy.schema !== "tradejs-research-entry-policy/v1" || typeof policy.strategy !== "string" || !policy.strategy.trim() || !["current-gate", "rules"].includes(policy.mode) || !Number.isInteger(policy.minQuality) || policy.minQuality < 1 || policy.minQuality > 5 || !policy.rules || typeof policy.rules !== "object")
|
|
6237
|
+
throw new Error("Invalid research entry policy contract");
|
|
6238
|
+
if (policy.directions !== void 0 && (!Array.isArray(policy.directions) || policy.directions.length > 2 || new Set(policy.directions).size !== policy.directions.length || policy.directions.some((side) => !["LONG", "SHORT"].includes(side))))
|
|
6239
|
+
throw new Error("Invalid research policy directions");
|
|
6240
|
+
for (const side of ["LONG", "SHORT"]) {
|
|
6241
|
+
const predicates = policy.rules[side];
|
|
6242
|
+
if (predicates === null) continue;
|
|
6243
|
+
if (!Array.isArray(predicates) || predicates.length > 4)
|
|
6244
|
+
throw new Error(
|
|
6245
|
+
"Research policy needs bounded rules for both directions"
|
|
6246
|
+
);
|
|
6247
|
+
for (const predicate of predicates) {
|
|
6248
|
+
if (!predicate || typeof predicate.path !== "string" || !/^additionalIndicators\.[a-zA-Z0-9_.]+$/.test(predicate.path) || forbidden.test(predicate.path) || !["<=", ">=", "=="].includes(predicate.op) || (predicate.op === "==" ? !["string", "boolean"].includes(typeof predicate.value) : typeof predicate.value !== "number" || !Number.isFinite(predicate.value)))
|
|
6249
|
+
throw new Error(
|
|
6250
|
+
"Research policy contains an unsupported or non-causal predicate"
|
|
6251
|
+
);
|
|
6252
|
+
}
|
|
6253
|
+
}
|
|
6254
|
+
return policy;
|
|
6255
|
+
};
|
|
6256
|
+
var matchesResearchEntryRules = (payload, predicates) => predicates.every((predicate) => {
|
|
6257
|
+
const value = predicate.path.split(".").reduce(
|
|
6258
|
+
(current, key) => current != null && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6259
|
+
payload
|
|
6260
|
+
);
|
|
6261
|
+
if (value == null) return false;
|
|
6262
|
+
if (predicate.op === "==") return value === predicate.value;
|
|
6263
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return false;
|
|
6264
|
+
return predicate.op === "<=" ? value <= predicate.value : value >= predicate.value;
|
|
6265
|
+
});
|
|
6266
|
+
var evaluateResearchEntryPolicy = async (policy, signal, onDecision) => {
|
|
6267
|
+
if (signal.strategy !== policy.strategy)
|
|
6268
|
+
throw new Error("Research policy strategy mismatch");
|
|
6269
|
+
if (policy.directions && !policy.directions.includes(signal.direction) || policy.mode === "rules" && policy.rules[signal.direction] === null) {
|
|
6270
|
+
onDecision?.({
|
|
6271
|
+
mode: policy.mode,
|
|
6272
|
+
allowed: false,
|
|
6273
|
+
reason: "DIRECTION_BLOCKED"
|
|
6274
|
+
});
|
|
6275
|
+
return false;
|
|
6276
|
+
}
|
|
6277
|
+
if (policy.mode === "current-gate") {
|
|
6278
|
+
const analysis = await runAiPromptLocal(signal);
|
|
6279
|
+
const allowed2 = analysis.direction === signal.direction && typeof analysis.quality === "number" && analysis.quality >= policy.minQuality;
|
|
6280
|
+
onDecision?.({
|
|
6281
|
+
mode: policy.mode,
|
|
6282
|
+
allowed: allowed2,
|
|
6283
|
+
quality: analysis.quality ?? null,
|
|
6284
|
+
reason: allowed2 ? "CURRENT_GATE_APPROVED" : "CURRENT_GATE_REJECTED"
|
|
6285
|
+
});
|
|
6286
|
+
return allowed2;
|
|
6287
|
+
}
|
|
6288
|
+
const original = buildAiPayload(signal);
|
|
6289
|
+
const gateContext = getDeterministicAiGateContext(original);
|
|
6290
|
+
if (Array.isArray(gateContext?.structuralHardBlockReasons) && gateContext.structuralHardBlockReasons.length) {
|
|
6291
|
+
onDecision?.({
|
|
6292
|
+
mode: policy.mode,
|
|
6293
|
+
allowed: false,
|
|
6294
|
+
reason: "STRUCTURAL_HARD_BLOCK"
|
|
6295
|
+
});
|
|
6296
|
+
return false;
|
|
6297
|
+
}
|
|
6298
|
+
const rules = policy.rules[signal.direction];
|
|
6299
|
+
const payload = buildResearchEntryPayload(signal, original);
|
|
6300
|
+
const allowed = rules !== null && matchesResearchEntryRules(payload, rules);
|
|
6301
|
+
onDecision?.({
|
|
6302
|
+
mode: policy.mode,
|
|
6303
|
+
allowed,
|
|
6304
|
+
reason: allowed ? "RULES_APPROVED" : "PREDICATE_REJECTED",
|
|
6305
|
+
predicates: rules?.map((rule) => {
|
|
6306
|
+
const value = rule.path.split(".").reduce(
|
|
6307
|
+
(current, key) => current && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6308
|
+
payload
|
|
6309
|
+
);
|
|
6310
|
+
return {
|
|
6311
|
+
path: rule.path,
|
|
6312
|
+
actual: typeof value === "number" && Number.isFinite(value) || typeof value === "string" || typeof value === "boolean" ? value : null,
|
|
6313
|
+
passed: matchesResearchEntryRules(payload, [rule])
|
|
6314
|
+
};
|
|
6315
|
+
})
|
|
6316
|
+
});
|
|
6317
|
+
return allowed;
|
|
6318
|
+
};
|
|
6319
|
+
|
|
6214
6320
|
// src/strategy/runtimeDecisionTelemetry.ts
|
|
6215
6321
|
var finitePositive = (value) => {
|
|
6216
6322
|
const parsed = Number(value);
|
|
@@ -6749,6 +6855,12 @@ var createStrategyRuntime = ({
|
|
|
6749
6855
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
6750
6856
|
const projectHooks = projectConfig.hooks;
|
|
6751
6857
|
const env = String(config.ENV ?? "BACKTEST");
|
|
6858
|
+
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
6859
|
+
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
6860
|
+
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
6861
|
+
throw new Error(
|
|
6862
|
+
"Research entry policies require a BACKTEST simulation connector"
|
|
6863
|
+
);
|
|
6752
6864
|
const jev = (0, import_jev6.parseJevConfig)(config.JEV);
|
|
6753
6865
|
if (jev && env !== "BACKTEST" && jev.source === "provider" && !jev.provider)
|
|
6754
6866
|
throw new Error(
|
|
@@ -7459,7 +7571,8 @@ var createStrategyRuntime = ({
|
|
|
7459
7571
|
await enrichSignalWithMarketContextStages({
|
|
7460
7572
|
signal,
|
|
7461
7573
|
env,
|
|
7462
|
-
includeHyperliquidWhales:
|
|
7574
|
+
includeHyperliquidWhales: Boolean(researchEntryPolicy),
|
|
7575
|
+
...researchEntryPolicy ? { coinMarketCapEnabled: true } : {}
|
|
7463
7576
|
});
|
|
7464
7577
|
if (jev) {
|
|
7465
7578
|
await assessSignalWithJev({
|
|
@@ -7557,6 +7670,22 @@ var createStrategyRuntime = ({
|
|
|
7557
7670
|
}
|
|
7558
7671
|
return signal ?? decision.code;
|
|
7559
7672
|
}
|
|
7673
|
+
if (researchEntryPolicy && (!signal || !await evaluateResearchEntryPolicy(
|
|
7674
|
+
researchEntryPolicy,
|
|
7675
|
+
signal,
|
|
7676
|
+
(decision2) => {
|
|
7677
|
+
signal.additionalIndicators = {
|
|
7678
|
+
...signal.additionalIndicators,
|
|
7679
|
+
researchEntryDecision: decision2
|
|
7680
|
+
};
|
|
7681
|
+
}
|
|
7682
|
+
))) {
|
|
7683
|
+
if (signal) {
|
|
7684
|
+
signal.orderStatus = "skipped";
|
|
7685
|
+
signal.orderSkipReason = "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7686
|
+
}
|
|
7687
|
+
return signal ?? "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7688
|
+
}
|
|
7560
7689
|
const entryGate = await invokeGateHooks(
|
|
7561
7690
|
"beforeEntryGate",
|
|
7562
7691
|
decisionManifest?.hooks?.beforeEntryGate,
|
|
@@ -8912,7 +9041,10 @@ var cancelMcpJob = async (principal, id) => {
|
|
|
8912
9041
|
const job = await readMcpJob(principal.userName, id);
|
|
8913
9042
|
if (job.clientId !== principal.clientId)
|
|
8914
9043
|
throw new Error("Only the submitting client can cancel this job");
|
|
8915
|
-
if (!["queued", "running"].includes(
|
|
9044
|
+
if (!["queued", "waiting_resources", "waiting_executor", "running"].includes(
|
|
9045
|
+
job.status
|
|
9046
|
+
))
|
|
9047
|
+
return job;
|
|
8916
9048
|
const next = {
|
|
8917
9049
|
...job,
|
|
8918
9050
|
cancelRequested: true,
|
package/dist/mcp.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
listRuntimeDeployments,
|
|
8
8
|
loadResolvedRuntimeStrategies
|
|
9
9
|
} from "./chunk-BO42QWAB.mjs";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-K56EW2BB.mjs";
|
|
11
11
|
import {
|
|
12
12
|
ensureIndicatorPluginsLoaded
|
|
13
13
|
} from "./chunk-UL5Z46JL.mjs";
|
|
@@ -236,7 +236,10 @@ var cancelMcpJob = async (principal, id) => {
|
|
|
236
236
|
const job = await readMcpJob(principal.userName, id);
|
|
237
237
|
if (job.clientId !== principal.clientId)
|
|
238
238
|
throw new Error("Only the submitting client can cancel this job");
|
|
239
|
-
if (!["queued", "running"].includes(
|
|
239
|
+
if (!["queued", "waiting_resources", "waiting_executor", "running"].includes(
|
|
240
|
+
job.status
|
|
241
|
+
))
|
|
242
|
+
return job;
|
|
240
243
|
const next = {
|
|
241
244
|
...job,
|
|
242
245
|
cancelRequested: true,
|
package/dist/registry.js
CHANGED
|
@@ -6213,6 +6213,112 @@ var buildAiHookContext = ({
|
|
|
6213
6213
|
};
|
|
6214
6214
|
};
|
|
6215
6215
|
|
|
6216
|
+
// src/researchEntryPolicy.ts
|
|
6217
|
+
var forbidden = /(?:^|\.)(?:__proto__|prototype|constructor|profit|pnl|outcome|tradeResult|backtestExecution|quality|deterministicQuality|approvalAllowedNow|timestamp|knownAt|source|coverage|points|rows|length)(?:\.|$)/i;
|
|
6218
|
+
var buildResearchEntryPayload = (signal, payload = buildAiPayload(signal)) => {
|
|
6219
|
+
const additional = payload.additionalIndicators && typeof payload.additionalIndicators === "object" ? payload.additionalIndicators : {};
|
|
6220
|
+
const raw = signal.additionalIndicators?.baseContext;
|
|
6221
|
+
const adapted = additional.baseContext;
|
|
6222
|
+
return {
|
|
6223
|
+
...payload,
|
|
6224
|
+
additionalIndicators: {
|
|
6225
|
+
...additional,
|
|
6226
|
+
...signal.additionalIndicators?.jev && typeof signal.additionalIndicators.jev === "object" ? { jev: signal.additionalIndicators.jev } : {},
|
|
6227
|
+
baseContext: {
|
|
6228
|
+
...raw && typeof raw === "object" ? raw : {},
|
|
6229
|
+
...adapted && typeof adapted === "object" ? adapted : {}
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6232
|
+
};
|
|
6233
|
+
};
|
|
6234
|
+
var parseResearchEntryPolicy = (value) => {
|
|
6235
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
6236
|
+
throw new Error("Invalid research entry policy");
|
|
6237
|
+
const policy = value;
|
|
6238
|
+
if (policy.schema !== "tradejs-research-entry-policy/v1" || typeof policy.strategy !== "string" || !policy.strategy.trim() || !["current-gate", "rules"].includes(policy.mode) || !Number.isInteger(policy.minQuality) || policy.minQuality < 1 || policy.minQuality > 5 || !policy.rules || typeof policy.rules !== "object")
|
|
6239
|
+
throw new Error("Invalid research entry policy contract");
|
|
6240
|
+
if (policy.directions !== void 0 && (!Array.isArray(policy.directions) || policy.directions.length > 2 || new Set(policy.directions).size !== policy.directions.length || policy.directions.some((side) => !["LONG", "SHORT"].includes(side))))
|
|
6241
|
+
throw new Error("Invalid research policy directions");
|
|
6242
|
+
for (const side of ["LONG", "SHORT"]) {
|
|
6243
|
+
const predicates = policy.rules[side];
|
|
6244
|
+
if (predicates === null) continue;
|
|
6245
|
+
if (!Array.isArray(predicates) || predicates.length > 4)
|
|
6246
|
+
throw new Error(
|
|
6247
|
+
"Research policy needs bounded rules for both directions"
|
|
6248
|
+
);
|
|
6249
|
+
for (const predicate of predicates) {
|
|
6250
|
+
if (!predicate || typeof predicate.path !== "string" || !/^additionalIndicators\.[a-zA-Z0-9_.]+$/.test(predicate.path) || forbidden.test(predicate.path) || !["<=", ">=", "=="].includes(predicate.op) || (predicate.op === "==" ? !["string", "boolean"].includes(typeof predicate.value) : typeof predicate.value !== "number" || !Number.isFinite(predicate.value)))
|
|
6251
|
+
throw new Error(
|
|
6252
|
+
"Research policy contains an unsupported or non-causal predicate"
|
|
6253
|
+
);
|
|
6254
|
+
}
|
|
6255
|
+
}
|
|
6256
|
+
return policy;
|
|
6257
|
+
};
|
|
6258
|
+
var matchesResearchEntryRules = (payload, predicates) => predicates.every((predicate) => {
|
|
6259
|
+
const value = predicate.path.split(".").reduce(
|
|
6260
|
+
(current, key) => current != null && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6261
|
+
payload
|
|
6262
|
+
);
|
|
6263
|
+
if (value == null) return false;
|
|
6264
|
+
if (predicate.op === "==") return value === predicate.value;
|
|
6265
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return false;
|
|
6266
|
+
return predicate.op === "<=" ? value <= predicate.value : value >= predicate.value;
|
|
6267
|
+
});
|
|
6268
|
+
var evaluateResearchEntryPolicy = async (policy, signal, onDecision) => {
|
|
6269
|
+
if (signal.strategy !== policy.strategy)
|
|
6270
|
+
throw new Error("Research policy strategy mismatch");
|
|
6271
|
+
if (policy.directions && !policy.directions.includes(signal.direction) || policy.mode === "rules" && policy.rules[signal.direction] === null) {
|
|
6272
|
+
onDecision?.({
|
|
6273
|
+
mode: policy.mode,
|
|
6274
|
+
allowed: false,
|
|
6275
|
+
reason: "DIRECTION_BLOCKED"
|
|
6276
|
+
});
|
|
6277
|
+
return false;
|
|
6278
|
+
}
|
|
6279
|
+
if (policy.mode === "current-gate") {
|
|
6280
|
+
const analysis = await runAiPromptLocal(signal);
|
|
6281
|
+
const allowed2 = analysis.direction === signal.direction && typeof analysis.quality === "number" && analysis.quality >= policy.minQuality;
|
|
6282
|
+
onDecision?.({
|
|
6283
|
+
mode: policy.mode,
|
|
6284
|
+
allowed: allowed2,
|
|
6285
|
+
quality: analysis.quality ?? null,
|
|
6286
|
+
reason: allowed2 ? "CURRENT_GATE_APPROVED" : "CURRENT_GATE_REJECTED"
|
|
6287
|
+
});
|
|
6288
|
+
return allowed2;
|
|
6289
|
+
}
|
|
6290
|
+
const original = buildAiPayload(signal);
|
|
6291
|
+
const gateContext = getDeterministicAiGateContext(original);
|
|
6292
|
+
if (Array.isArray(gateContext?.structuralHardBlockReasons) && gateContext.structuralHardBlockReasons.length) {
|
|
6293
|
+
onDecision?.({
|
|
6294
|
+
mode: policy.mode,
|
|
6295
|
+
allowed: false,
|
|
6296
|
+
reason: "STRUCTURAL_HARD_BLOCK"
|
|
6297
|
+
});
|
|
6298
|
+
return false;
|
|
6299
|
+
}
|
|
6300
|
+
const rules = policy.rules[signal.direction];
|
|
6301
|
+
const payload = buildResearchEntryPayload(signal, original);
|
|
6302
|
+
const allowed = rules !== null && matchesResearchEntryRules(payload, rules);
|
|
6303
|
+
onDecision?.({
|
|
6304
|
+
mode: policy.mode,
|
|
6305
|
+
allowed,
|
|
6306
|
+
reason: allowed ? "RULES_APPROVED" : "PREDICATE_REJECTED",
|
|
6307
|
+
predicates: rules?.map((rule) => {
|
|
6308
|
+
const value = rule.path.split(".").reduce(
|
|
6309
|
+
(current, key) => current && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6310
|
+
payload
|
|
6311
|
+
);
|
|
6312
|
+
return {
|
|
6313
|
+
path: rule.path,
|
|
6314
|
+
actual: typeof value === "number" && Number.isFinite(value) || typeof value === "string" || typeof value === "boolean" ? value : null,
|
|
6315
|
+
passed: matchesResearchEntryRules(payload, [rule])
|
|
6316
|
+
};
|
|
6317
|
+
})
|
|
6318
|
+
});
|
|
6319
|
+
return allowed;
|
|
6320
|
+
};
|
|
6321
|
+
|
|
6216
6322
|
// src/strategy/runtimeDecisionTelemetry.ts
|
|
6217
6323
|
var finitePositive = (value) => {
|
|
6218
6324
|
const parsed = Number(value);
|
|
@@ -6751,6 +6857,12 @@ var createStrategyRuntime = ({
|
|
|
6751
6857
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
6752
6858
|
const projectHooks = projectConfig.hooks;
|
|
6753
6859
|
const env = String(config.ENV ?? "BACKTEST");
|
|
6860
|
+
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
6861
|
+
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
6862
|
+
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
6863
|
+
throw new Error(
|
|
6864
|
+
"Research entry policies require a BACKTEST simulation connector"
|
|
6865
|
+
);
|
|
6754
6866
|
const jev = (0, import_jev6.parseJevConfig)(config.JEV);
|
|
6755
6867
|
if (jev && env !== "BACKTEST" && jev.source === "provider" && !jev.provider)
|
|
6756
6868
|
throw new Error(
|
|
@@ -7461,7 +7573,8 @@ var createStrategyRuntime = ({
|
|
|
7461
7573
|
await enrichSignalWithMarketContextStages({
|
|
7462
7574
|
signal,
|
|
7463
7575
|
env,
|
|
7464
|
-
includeHyperliquidWhales:
|
|
7576
|
+
includeHyperliquidWhales: Boolean(researchEntryPolicy),
|
|
7577
|
+
...researchEntryPolicy ? { coinMarketCapEnabled: true } : {}
|
|
7465
7578
|
});
|
|
7466
7579
|
if (jev) {
|
|
7467
7580
|
await assessSignalWithJev({
|
|
@@ -7559,6 +7672,22 @@ var createStrategyRuntime = ({
|
|
|
7559
7672
|
}
|
|
7560
7673
|
return signal ?? decision.code;
|
|
7561
7674
|
}
|
|
7675
|
+
if (researchEntryPolicy && (!signal || !await evaluateResearchEntryPolicy(
|
|
7676
|
+
researchEntryPolicy,
|
|
7677
|
+
signal,
|
|
7678
|
+
(decision2) => {
|
|
7679
|
+
signal.additionalIndicators = {
|
|
7680
|
+
...signal.additionalIndicators,
|
|
7681
|
+
researchEntryDecision: decision2
|
|
7682
|
+
};
|
|
7683
|
+
}
|
|
7684
|
+
))) {
|
|
7685
|
+
if (signal) {
|
|
7686
|
+
signal.orderStatus = "skipped";
|
|
7687
|
+
signal.orderSkipReason = "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7688
|
+
}
|
|
7689
|
+
return signal ?? "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7690
|
+
}
|
|
7562
7691
|
const entryGate = await invokeGateHooks(
|
|
7563
7692
|
"beforeEntryGate",
|
|
7564
7693
|
decisionManifest?.hooks?.beforeEntryGate,
|
package/dist/registry.mjs
CHANGED