@tradejs/node 3.1.29-beta.283 → 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.js +130 -1
- package/dist/diagnostics.mjs +1 -1
- package/dist/mcp.js +130 -1
- package/dist/mcp.mjs +1 -1
- 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.js
CHANGED
|
@@ -6217,6 +6217,112 @@ var buildAiHookContext = ({
|
|
|
6217
6217
|
};
|
|
6218
6218
|
};
|
|
6219
6219
|
|
|
6220
|
+
// src/researchEntryPolicy.ts
|
|
6221
|
+
var forbidden = /(?:^|\.)(?:__proto__|prototype|constructor|profit|pnl|outcome|tradeResult|backtestExecution|quality|deterministicQuality|approvalAllowedNow|timestamp|knownAt|source|coverage|points|rows|length)(?:\.|$)/i;
|
|
6222
|
+
var buildResearchEntryPayload = (signal, payload = buildAiPayload(signal)) => {
|
|
6223
|
+
const additional = payload.additionalIndicators && typeof payload.additionalIndicators === "object" ? payload.additionalIndicators : {};
|
|
6224
|
+
const raw = signal.additionalIndicators?.baseContext;
|
|
6225
|
+
const adapted = additional.baseContext;
|
|
6226
|
+
return {
|
|
6227
|
+
...payload,
|
|
6228
|
+
additionalIndicators: {
|
|
6229
|
+
...additional,
|
|
6230
|
+
...signal.additionalIndicators?.jev && typeof signal.additionalIndicators.jev === "object" ? { jev: signal.additionalIndicators.jev } : {},
|
|
6231
|
+
baseContext: {
|
|
6232
|
+
...raw && typeof raw === "object" ? raw : {},
|
|
6233
|
+
...adapted && typeof adapted === "object" ? adapted : {}
|
|
6234
|
+
}
|
|
6235
|
+
}
|
|
6236
|
+
};
|
|
6237
|
+
};
|
|
6238
|
+
var parseResearchEntryPolicy = (value) => {
|
|
6239
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
6240
|
+
throw new Error("Invalid research entry policy");
|
|
6241
|
+
const policy = value;
|
|
6242
|
+
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")
|
|
6243
|
+
throw new Error("Invalid research entry policy contract");
|
|
6244
|
+
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))))
|
|
6245
|
+
throw new Error("Invalid research policy directions");
|
|
6246
|
+
for (const side of ["LONG", "SHORT"]) {
|
|
6247
|
+
const predicates = policy.rules[side];
|
|
6248
|
+
if (predicates === null) continue;
|
|
6249
|
+
if (!Array.isArray(predicates) || predicates.length > 4)
|
|
6250
|
+
throw new Error(
|
|
6251
|
+
"Research policy needs bounded rules for both directions"
|
|
6252
|
+
);
|
|
6253
|
+
for (const predicate of predicates) {
|
|
6254
|
+
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)))
|
|
6255
|
+
throw new Error(
|
|
6256
|
+
"Research policy contains an unsupported or non-causal predicate"
|
|
6257
|
+
);
|
|
6258
|
+
}
|
|
6259
|
+
}
|
|
6260
|
+
return policy;
|
|
6261
|
+
};
|
|
6262
|
+
var matchesResearchEntryRules = (payload, predicates) => predicates.every((predicate) => {
|
|
6263
|
+
const value = predicate.path.split(".").reduce(
|
|
6264
|
+
(current, key) => current != null && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6265
|
+
payload
|
|
6266
|
+
);
|
|
6267
|
+
if (value == null) return false;
|
|
6268
|
+
if (predicate.op === "==") return value === predicate.value;
|
|
6269
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return false;
|
|
6270
|
+
return predicate.op === "<=" ? value <= predicate.value : value >= predicate.value;
|
|
6271
|
+
});
|
|
6272
|
+
var evaluateResearchEntryPolicy = async (policy, signal, onDecision) => {
|
|
6273
|
+
if (signal.strategy !== policy.strategy)
|
|
6274
|
+
throw new Error("Research policy strategy mismatch");
|
|
6275
|
+
if (policy.directions && !policy.directions.includes(signal.direction) || policy.mode === "rules" && policy.rules[signal.direction] === null) {
|
|
6276
|
+
onDecision?.({
|
|
6277
|
+
mode: policy.mode,
|
|
6278
|
+
allowed: false,
|
|
6279
|
+
reason: "DIRECTION_BLOCKED"
|
|
6280
|
+
});
|
|
6281
|
+
return false;
|
|
6282
|
+
}
|
|
6283
|
+
if (policy.mode === "current-gate") {
|
|
6284
|
+
const analysis = await runAiPromptLocal(signal);
|
|
6285
|
+
const allowed2 = analysis.direction === signal.direction && typeof analysis.quality === "number" && analysis.quality >= policy.minQuality;
|
|
6286
|
+
onDecision?.({
|
|
6287
|
+
mode: policy.mode,
|
|
6288
|
+
allowed: allowed2,
|
|
6289
|
+
quality: analysis.quality ?? null,
|
|
6290
|
+
reason: allowed2 ? "CURRENT_GATE_APPROVED" : "CURRENT_GATE_REJECTED"
|
|
6291
|
+
});
|
|
6292
|
+
return allowed2;
|
|
6293
|
+
}
|
|
6294
|
+
const original = buildAiPayload(signal);
|
|
6295
|
+
const gateContext = getDeterministicAiGateContext(original);
|
|
6296
|
+
if (Array.isArray(gateContext?.structuralHardBlockReasons) && gateContext.structuralHardBlockReasons.length) {
|
|
6297
|
+
onDecision?.({
|
|
6298
|
+
mode: policy.mode,
|
|
6299
|
+
allowed: false,
|
|
6300
|
+
reason: "STRUCTURAL_HARD_BLOCK"
|
|
6301
|
+
});
|
|
6302
|
+
return false;
|
|
6303
|
+
}
|
|
6304
|
+
const rules = policy.rules[signal.direction];
|
|
6305
|
+
const payload = buildResearchEntryPayload(signal, original);
|
|
6306
|
+
const allowed = rules !== null && matchesResearchEntryRules(payload, rules);
|
|
6307
|
+
onDecision?.({
|
|
6308
|
+
mode: policy.mode,
|
|
6309
|
+
allowed,
|
|
6310
|
+
reason: allowed ? "RULES_APPROVED" : "PREDICATE_REJECTED",
|
|
6311
|
+
predicates: rules?.map((rule) => {
|
|
6312
|
+
const value = rule.path.split(".").reduce(
|
|
6313
|
+
(current, key) => current && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6314
|
+
payload
|
|
6315
|
+
);
|
|
6316
|
+
return {
|
|
6317
|
+
path: rule.path,
|
|
6318
|
+
actual: typeof value === "number" && Number.isFinite(value) || typeof value === "string" || typeof value === "boolean" ? value : null,
|
|
6319
|
+
passed: matchesResearchEntryRules(payload, [rule])
|
|
6320
|
+
};
|
|
6321
|
+
})
|
|
6322
|
+
});
|
|
6323
|
+
return allowed;
|
|
6324
|
+
};
|
|
6325
|
+
|
|
6220
6326
|
// src/strategy/runtimeDecisionTelemetry.ts
|
|
6221
6327
|
var finitePositive = (value) => {
|
|
6222
6328
|
const parsed = Number(value);
|
|
@@ -6755,6 +6861,12 @@ var createStrategyRuntime = ({
|
|
|
6755
6861
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
6756
6862
|
const projectHooks = projectConfig.hooks;
|
|
6757
6863
|
const env = String(config.ENV ?? "BACKTEST");
|
|
6864
|
+
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
6865
|
+
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
6866
|
+
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
6867
|
+
throw new Error(
|
|
6868
|
+
"Research entry policies require a BACKTEST simulation connector"
|
|
6869
|
+
);
|
|
6758
6870
|
const jev = (0, import_jev6.parseJevConfig)(config.JEV);
|
|
6759
6871
|
if (jev && env !== "BACKTEST" && jev.source === "provider" && !jev.provider)
|
|
6760
6872
|
throw new Error(
|
|
@@ -7465,7 +7577,8 @@ var createStrategyRuntime = ({
|
|
|
7465
7577
|
await enrichSignalWithMarketContextStages({
|
|
7466
7578
|
signal,
|
|
7467
7579
|
env,
|
|
7468
|
-
includeHyperliquidWhales:
|
|
7580
|
+
includeHyperliquidWhales: Boolean(researchEntryPolicy),
|
|
7581
|
+
...researchEntryPolicy ? { coinMarketCapEnabled: true } : {}
|
|
7469
7582
|
});
|
|
7470
7583
|
if (jev) {
|
|
7471
7584
|
await assessSignalWithJev({
|
|
@@ -7563,6 +7676,22 @@ var createStrategyRuntime = ({
|
|
|
7563
7676
|
}
|
|
7564
7677
|
return signal ?? decision.code;
|
|
7565
7678
|
}
|
|
7679
|
+
if (researchEntryPolicy && (!signal || !await evaluateResearchEntryPolicy(
|
|
7680
|
+
researchEntryPolicy,
|
|
7681
|
+
signal,
|
|
7682
|
+
(decision2) => {
|
|
7683
|
+
signal.additionalIndicators = {
|
|
7684
|
+
...signal.additionalIndicators,
|
|
7685
|
+
researchEntryDecision: decision2
|
|
7686
|
+
};
|
|
7687
|
+
}
|
|
7688
|
+
))) {
|
|
7689
|
+
if (signal) {
|
|
7690
|
+
signal.orderStatus = "skipped";
|
|
7691
|
+
signal.orderSkipReason = "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7692
|
+
}
|
|
7693
|
+
return signal ?? "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7694
|
+
}
|
|
7566
7695
|
const entryGate = await invokeGateHooks(
|
|
7567
7696
|
"beforeEntryGate",
|
|
7568
7697
|
decisionManifest?.hooks?.beforeEntryGate,
|
package/dist/diagnostics.mjs
CHANGED
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,
|
package/dist/mcp.mjs
CHANGED
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
package/dist/runtimeDashboard.js
CHANGED
|
@@ -7195,6 +7195,112 @@ var buildAiHookContext = ({
|
|
|
7195
7195
|
};
|
|
7196
7196
|
};
|
|
7197
7197
|
|
|
7198
|
+
// src/researchEntryPolicy.ts
|
|
7199
|
+
var forbidden = /(?:^|\.)(?:__proto__|prototype|constructor|profit|pnl|outcome|tradeResult|backtestExecution|quality|deterministicQuality|approvalAllowedNow|timestamp|knownAt|source|coverage|points|rows|length)(?:\.|$)/i;
|
|
7200
|
+
var buildResearchEntryPayload = (signal, payload = buildAiPayload(signal)) => {
|
|
7201
|
+
const additional = payload.additionalIndicators && typeof payload.additionalIndicators === "object" ? payload.additionalIndicators : {};
|
|
7202
|
+
const raw = signal.additionalIndicators?.baseContext;
|
|
7203
|
+
const adapted = additional.baseContext;
|
|
7204
|
+
return {
|
|
7205
|
+
...payload,
|
|
7206
|
+
additionalIndicators: {
|
|
7207
|
+
...additional,
|
|
7208
|
+
...signal.additionalIndicators?.jev && typeof signal.additionalIndicators.jev === "object" ? { jev: signal.additionalIndicators.jev } : {},
|
|
7209
|
+
baseContext: {
|
|
7210
|
+
...raw && typeof raw === "object" ? raw : {},
|
|
7211
|
+
...adapted && typeof adapted === "object" ? adapted : {}
|
|
7212
|
+
}
|
|
7213
|
+
}
|
|
7214
|
+
};
|
|
7215
|
+
};
|
|
7216
|
+
var parseResearchEntryPolicy = (value) => {
|
|
7217
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
7218
|
+
throw new Error("Invalid research entry policy");
|
|
7219
|
+
const policy = value;
|
|
7220
|
+
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")
|
|
7221
|
+
throw new Error("Invalid research entry policy contract");
|
|
7222
|
+
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))))
|
|
7223
|
+
throw new Error("Invalid research policy directions");
|
|
7224
|
+
for (const side of ["LONG", "SHORT"]) {
|
|
7225
|
+
const predicates = policy.rules[side];
|
|
7226
|
+
if (predicates === null) continue;
|
|
7227
|
+
if (!Array.isArray(predicates) || predicates.length > 4)
|
|
7228
|
+
throw new Error(
|
|
7229
|
+
"Research policy needs bounded rules for both directions"
|
|
7230
|
+
);
|
|
7231
|
+
for (const predicate of predicates) {
|
|
7232
|
+
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)))
|
|
7233
|
+
throw new Error(
|
|
7234
|
+
"Research policy contains an unsupported or non-causal predicate"
|
|
7235
|
+
);
|
|
7236
|
+
}
|
|
7237
|
+
}
|
|
7238
|
+
return policy;
|
|
7239
|
+
};
|
|
7240
|
+
var matchesResearchEntryRules = (payload, predicates) => predicates.every((predicate) => {
|
|
7241
|
+
const value = predicate.path.split(".").reduce(
|
|
7242
|
+
(current, key) => current != null && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
7243
|
+
payload
|
|
7244
|
+
);
|
|
7245
|
+
if (value == null) return false;
|
|
7246
|
+
if (predicate.op === "==") return value === predicate.value;
|
|
7247
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return false;
|
|
7248
|
+
return predicate.op === "<=" ? value <= predicate.value : value >= predicate.value;
|
|
7249
|
+
});
|
|
7250
|
+
var evaluateResearchEntryPolicy = async (policy, signal, onDecision) => {
|
|
7251
|
+
if (signal.strategy !== policy.strategy)
|
|
7252
|
+
throw new Error("Research policy strategy mismatch");
|
|
7253
|
+
if (policy.directions && !policy.directions.includes(signal.direction) || policy.mode === "rules" && policy.rules[signal.direction] === null) {
|
|
7254
|
+
onDecision?.({
|
|
7255
|
+
mode: policy.mode,
|
|
7256
|
+
allowed: false,
|
|
7257
|
+
reason: "DIRECTION_BLOCKED"
|
|
7258
|
+
});
|
|
7259
|
+
return false;
|
|
7260
|
+
}
|
|
7261
|
+
if (policy.mode === "current-gate") {
|
|
7262
|
+
const analysis = await runAiPromptLocal(signal);
|
|
7263
|
+
const allowed2 = analysis.direction === signal.direction && typeof analysis.quality === "number" && analysis.quality >= policy.minQuality;
|
|
7264
|
+
onDecision?.({
|
|
7265
|
+
mode: policy.mode,
|
|
7266
|
+
allowed: allowed2,
|
|
7267
|
+
quality: analysis.quality ?? null,
|
|
7268
|
+
reason: allowed2 ? "CURRENT_GATE_APPROVED" : "CURRENT_GATE_REJECTED"
|
|
7269
|
+
});
|
|
7270
|
+
return allowed2;
|
|
7271
|
+
}
|
|
7272
|
+
const original = buildAiPayload(signal);
|
|
7273
|
+
const gateContext = getDeterministicAiGateContext(original);
|
|
7274
|
+
if (Array.isArray(gateContext?.structuralHardBlockReasons) && gateContext.structuralHardBlockReasons.length) {
|
|
7275
|
+
onDecision?.({
|
|
7276
|
+
mode: policy.mode,
|
|
7277
|
+
allowed: false,
|
|
7278
|
+
reason: "STRUCTURAL_HARD_BLOCK"
|
|
7279
|
+
});
|
|
7280
|
+
return false;
|
|
7281
|
+
}
|
|
7282
|
+
const rules = policy.rules[signal.direction];
|
|
7283
|
+
const payload = buildResearchEntryPayload(signal, original);
|
|
7284
|
+
const allowed = rules !== null && matchesResearchEntryRules(payload, rules);
|
|
7285
|
+
onDecision?.({
|
|
7286
|
+
mode: policy.mode,
|
|
7287
|
+
allowed,
|
|
7288
|
+
reason: allowed ? "RULES_APPROVED" : "PREDICATE_REJECTED",
|
|
7289
|
+
predicates: rules?.map((rule) => {
|
|
7290
|
+
const value = rule.path.split(".").reduce(
|
|
7291
|
+
(current, key) => current && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
7292
|
+
payload
|
|
7293
|
+
);
|
|
7294
|
+
return {
|
|
7295
|
+
path: rule.path,
|
|
7296
|
+
actual: typeof value === "number" && Number.isFinite(value) || typeof value === "string" || typeof value === "boolean" ? value : null,
|
|
7297
|
+
passed: matchesResearchEntryRules(payload, [rule])
|
|
7298
|
+
};
|
|
7299
|
+
})
|
|
7300
|
+
});
|
|
7301
|
+
return allowed;
|
|
7302
|
+
};
|
|
7303
|
+
|
|
7198
7304
|
// src/strategy/runtimeDecisionTelemetry.ts
|
|
7199
7305
|
var finitePositive = (value) => {
|
|
7200
7306
|
const parsed = Number(value);
|
|
@@ -7733,6 +7839,12 @@ var createStrategyRuntime = ({
|
|
|
7733
7839
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7734
7840
|
const projectHooks = projectConfig.hooks;
|
|
7735
7841
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7842
|
+
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7843
|
+
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7844
|
+
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
7845
|
+
throw new Error(
|
|
7846
|
+
"Research entry policies require a BACKTEST simulation connector"
|
|
7847
|
+
);
|
|
7736
7848
|
const jev = (0, import_jev6.parseJevConfig)(config.JEV);
|
|
7737
7849
|
if (jev && env !== "BACKTEST" && jev.source === "provider" && !jev.provider)
|
|
7738
7850
|
throw new Error(
|
|
@@ -8443,7 +8555,8 @@ var createStrategyRuntime = ({
|
|
|
8443
8555
|
await enrichSignalWithMarketContextStages({
|
|
8444
8556
|
signal,
|
|
8445
8557
|
env,
|
|
8446
|
-
includeHyperliquidWhales:
|
|
8558
|
+
includeHyperliquidWhales: Boolean(researchEntryPolicy),
|
|
8559
|
+
...researchEntryPolicy ? { coinMarketCapEnabled: true } : {}
|
|
8447
8560
|
});
|
|
8448
8561
|
if (jev) {
|
|
8449
8562
|
await assessSignalWithJev({
|
|
@@ -8541,6 +8654,22 @@ var createStrategyRuntime = ({
|
|
|
8541
8654
|
}
|
|
8542
8655
|
return signal ?? decision.code;
|
|
8543
8656
|
}
|
|
8657
|
+
if (researchEntryPolicy && (!signal || !await evaluateResearchEntryPolicy(
|
|
8658
|
+
researchEntryPolicy,
|
|
8659
|
+
signal,
|
|
8660
|
+
(decision2) => {
|
|
8661
|
+
signal.additionalIndicators = {
|
|
8662
|
+
...signal.additionalIndicators,
|
|
8663
|
+
researchEntryDecision: decision2
|
|
8664
|
+
};
|
|
8665
|
+
}
|
|
8666
|
+
))) {
|
|
8667
|
+
if (signal) {
|
|
8668
|
+
signal.orderStatus = "skipped";
|
|
8669
|
+
signal.orderSkipReason = "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
8670
|
+
}
|
|
8671
|
+
return signal ?? "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
8672
|
+
}
|
|
8544
8673
|
const entryGate = await invokeGateHooks(
|
|
8545
8674
|
"beforeEntryGate",
|
|
8546
8675
|
decisionManifest?.hooks?.beforeEntryGate,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
listRuntimeDeployments,
|
|
10
10
|
loadResolvedRuntimeStrategies
|
|
11
11
|
} from "./chunk-BO42QWAB.mjs";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-K56EW2BB.mjs";
|
|
13
13
|
import "./chunk-UL5Z46JL.mjs";
|
|
14
14
|
import "./chunk-LZDXRXIU.mjs";
|
|
15
15
|
import "./chunk-YR55Q355.mjs";
|