@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/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";
|
|
@@ -6192,6 +6192,112 @@ var buildAiHookContext = ({
|
|
|
6192
6192
|
};
|
|
6193
6193
|
};
|
|
6194
6194
|
|
|
6195
|
+
// src/researchEntryPolicy.ts
|
|
6196
|
+
var forbidden = /(?:^|\.)(?:__proto__|prototype|constructor|profit|pnl|outcome|tradeResult|backtestExecution|quality|deterministicQuality|approvalAllowedNow|timestamp|knownAt|source|coverage|points|rows|length)(?:\.|$)/i;
|
|
6197
|
+
var buildResearchEntryPayload = (signal, payload = buildAiPayload(signal)) => {
|
|
6198
|
+
const additional = payload.additionalIndicators && typeof payload.additionalIndicators === "object" ? payload.additionalIndicators : {};
|
|
6199
|
+
const raw = signal.additionalIndicators?.baseContext;
|
|
6200
|
+
const adapted = additional.baseContext;
|
|
6201
|
+
return {
|
|
6202
|
+
...payload,
|
|
6203
|
+
additionalIndicators: {
|
|
6204
|
+
...additional,
|
|
6205
|
+
...signal.additionalIndicators?.jev && typeof signal.additionalIndicators.jev === "object" ? { jev: signal.additionalIndicators.jev } : {},
|
|
6206
|
+
baseContext: {
|
|
6207
|
+
...raw && typeof raw === "object" ? raw : {},
|
|
6208
|
+
...adapted && typeof adapted === "object" ? adapted : {}
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
6211
|
+
};
|
|
6212
|
+
};
|
|
6213
|
+
var parseResearchEntryPolicy = (value) => {
|
|
6214
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
6215
|
+
throw new Error("Invalid research entry policy");
|
|
6216
|
+
const policy = value;
|
|
6217
|
+
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")
|
|
6218
|
+
throw new Error("Invalid research entry policy contract");
|
|
6219
|
+
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))))
|
|
6220
|
+
throw new Error("Invalid research policy directions");
|
|
6221
|
+
for (const side of ["LONG", "SHORT"]) {
|
|
6222
|
+
const predicates = policy.rules[side];
|
|
6223
|
+
if (predicates === null) continue;
|
|
6224
|
+
if (!Array.isArray(predicates) || predicates.length > 4)
|
|
6225
|
+
throw new Error(
|
|
6226
|
+
"Research policy needs bounded rules for both directions"
|
|
6227
|
+
);
|
|
6228
|
+
for (const predicate of predicates) {
|
|
6229
|
+
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)))
|
|
6230
|
+
throw new Error(
|
|
6231
|
+
"Research policy contains an unsupported or non-causal predicate"
|
|
6232
|
+
);
|
|
6233
|
+
}
|
|
6234
|
+
}
|
|
6235
|
+
return policy;
|
|
6236
|
+
};
|
|
6237
|
+
var matchesResearchEntryRules = (payload, predicates) => predicates.every((predicate) => {
|
|
6238
|
+
const value = predicate.path.split(".").reduce(
|
|
6239
|
+
(current, key) => current != null && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6240
|
+
payload
|
|
6241
|
+
);
|
|
6242
|
+
if (value == null) return false;
|
|
6243
|
+
if (predicate.op === "==") return value === predicate.value;
|
|
6244
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return false;
|
|
6245
|
+
return predicate.op === "<=" ? value <= predicate.value : value >= predicate.value;
|
|
6246
|
+
});
|
|
6247
|
+
var evaluateResearchEntryPolicy = async (policy, signal, onDecision) => {
|
|
6248
|
+
if (signal.strategy !== policy.strategy)
|
|
6249
|
+
throw new Error("Research policy strategy mismatch");
|
|
6250
|
+
if (policy.directions && !policy.directions.includes(signal.direction) || policy.mode === "rules" && policy.rules[signal.direction] === null) {
|
|
6251
|
+
onDecision?.({
|
|
6252
|
+
mode: policy.mode,
|
|
6253
|
+
allowed: false,
|
|
6254
|
+
reason: "DIRECTION_BLOCKED"
|
|
6255
|
+
});
|
|
6256
|
+
return false;
|
|
6257
|
+
}
|
|
6258
|
+
if (policy.mode === "current-gate") {
|
|
6259
|
+
const analysis = await runAiPromptLocal(signal);
|
|
6260
|
+
const allowed2 = analysis.direction === signal.direction && typeof analysis.quality === "number" && analysis.quality >= policy.minQuality;
|
|
6261
|
+
onDecision?.({
|
|
6262
|
+
mode: policy.mode,
|
|
6263
|
+
allowed: allowed2,
|
|
6264
|
+
quality: analysis.quality ?? null,
|
|
6265
|
+
reason: allowed2 ? "CURRENT_GATE_APPROVED" : "CURRENT_GATE_REJECTED"
|
|
6266
|
+
});
|
|
6267
|
+
return allowed2;
|
|
6268
|
+
}
|
|
6269
|
+
const original = buildAiPayload(signal);
|
|
6270
|
+
const gateContext = getDeterministicAiGateContext(original);
|
|
6271
|
+
if (Array.isArray(gateContext?.structuralHardBlockReasons) && gateContext.structuralHardBlockReasons.length) {
|
|
6272
|
+
onDecision?.({
|
|
6273
|
+
mode: policy.mode,
|
|
6274
|
+
allowed: false,
|
|
6275
|
+
reason: "STRUCTURAL_HARD_BLOCK"
|
|
6276
|
+
});
|
|
6277
|
+
return false;
|
|
6278
|
+
}
|
|
6279
|
+
const rules = policy.rules[signal.direction];
|
|
6280
|
+
const payload = buildResearchEntryPayload(signal, original);
|
|
6281
|
+
const allowed = rules !== null && matchesResearchEntryRules(payload, rules);
|
|
6282
|
+
onDecision?.({
|
|
6283
|
+
mode: policy.mode,
|
|
6284
|
+
allowed,
|
|
6285
|
+
reason: allowed ? "RULES_APPROVED" : "PREDICATE_REJECTED",
|
|
6286
|
+
predicates: rules?.map((rule) => {
|
|
6287
|
+
const value = rule.path.split(".").reduce(
|
|
6288
|
+
(current, key) => current && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6289
|
+
payload
|
|
6290
|
+
);
|
|
6291
|
+
return {
|
|
6292
|
+
path: rule.path,
|
|
6293
|
+
actual: typeof value === "number" && Number.isFinite(value) || typeof value === "string" || typeof value === "boolean" ? value : null,
|
|
6294
|
+
passed: matchesResearchEntryRules(payload, [rule])
|
|
6295
|
+
};
|
|
6296
|
+
})
|
|
6297
|
+
});
|
|
6298
|
+
return allowed;
|
|
6299
|
+
};
|
|
6300
|
+
|
|
6195
6301
|
// src/strategy/runtimeDecisionTelemetry.ts
|
|
6196
6302
|
var finitePositive = (value) => {
|
|
6197
6303
|
const parsed = Number(value);
|
|
@@ -6730,6 +6836,12 @@ var createStrategyRuntime = ({
|
|
|
6730
6836
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
6731
6837
|
const projectHooks = projectConfig.hooks;
|
|
6732
6838
|
const env = String(config.ENV ?? "BACKTEST");
|
|
6839
|
+
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
6840
|
+
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
6841
|
+
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
6842
|
+
throw new Error(
|
|
6843
|
+
"Research entry policies require a BACKTEST simulation connector"
|
|
6844
|
+
);
|
|
6733
6845
|
const jev = (0, import_jev6.parseJevConfig)(config.JEV);
|
|
6734
6846
|
if (jev && env !== "BACKTEST" && jev.source === "provider" && !jev.provider)
|
|
6735
6847
|
throw new Error(
|
|
@@ -7440,7 +7552,8 @@ var createStrategyRuntime = ({
|
|
|
7440
7552
|
await enrichSignalWithMarketContextStages({
|
|
7441
7553
|
signal,
|
|
7442
7554
|
env,
|
|
7443
|
-
includeHyperliquidWhales:
|
|
7555
|
+
includeHyperliquidWhales: Boolean(researchEntryPolicy),
|
|
7556
|
+
...researchEntryPolicy ? { coinMarketCapEnabled: true } : {}
|
|
7444
7557
|
});
|
|
7445
7558
|
if (jev) {
|
|
7446
7559
|
await assessSignalWithJev({
|
|
@@ -7538,6 +7651,22 @@ var createStrategyRuntime = ({
|
|
|
7538
7651
|
}
|
|
7539
7652
|
return signal ?? decision.code;
|
|
7540
7653
|
}
|
|
7654
|
+
if (researchEntryPolicy && (!signal || !await evaluateResearchEntryPolicy(
|
|
7655
|
+
researchEntryPolicy,
|
|
7656
|
+
signal,
|
|
7657
|
+
(decision2) => {
|
|
7658
|
+
signal.additionalIndicators = {
|
|
7659
|
+
...signal.additionalIndicators,
|
|
7660
|
+
researchEntryDecision: decision2
|
|
7661
|
+
};
|
|
7662
|
+
}
|
|
7663
|
+
))) {
|
|
7664
|
+
if (signal) {
|
|
7665
|
+
signal.orderStatus = "skipped";
|
|
7666
|
+
signal.orderSkipReason = "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7667
|
+
}
|
|
7668
|
+
return signal ?? "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7669
|
+
}
|
|
7541
7670
|
const entryGate = await invokeGateHooks(
|
|
7542
7671
|
"beforeEntryGate",
|
|
7543
7672
|
decisionManifest?.hooks?.beforeEntryGate,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
resolveRuntimeComposition,
|
|
10
10
|
verifyRuntimeDeclaration
|
|
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";
|
package/dist/strategies.js
CHANGED
|
@@ -6454,6 +6454,112 @@ var buildAiHookContext = ({
|
|
|
6454
6454
|
};
|
|
6455
6455
|
};
|
|
6456
6456
|
|
|
6457
|
+
// src/researchEntryPolicy.ts
|
|
6458
|
+
var forbidden = /(?:^|\.)(?:__proto__|prototype|constructor|profit|pnl|outcome|tradeResult|backtestExecution|quality|deterministicQuality|approvalAllowedNow|timestamp|knownAt|source|coverage|points|rows|length)(?:\.|$)/i;
|
|
6459
|
+
var buildResearchEntryPayload = (signal, payload = buildAiPayload(signal)) => {
|
|
6460
|
+
const additional = payload.additionalIndicators && typeof payload.additionalIndicators === "object" ? payload.additionalIndicators : {};
|
|
6461
|
+
const raw = signal.additionalIndicators?.baseContext;
|
|
6462
|
+
const adapted = additional.baseContext;
|
|
6463
|
+
return {
|
|
6464
|
+
...payload,
|
|
6465
|
+
additionalIndicators: {
|
|
6466
|
+
...additional,
|
|
6467
|
+
...signal.additionalIndicators?.jev && typeof signal.additionalIndicators.jev === "object" ? { jev: signal.additionalIndicators.jev } : {},
|
|
6468
|
+
baseContext: {
|
|
6469
|
+
...raw && typeof raw === "object" ? raw : {},
|
|
6470
|
+
...adapted && typeof adapted === "object" ? adapted : {}
|
|
6471
|
+
}
|
|
6472
|
+
}
|
|
6473
|
+
};
|
|
6474
|
+
};
|
|
6475
|
+
var parseResearchEntryPolicy = (value) => {
|
|
6476
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
6477
|
+
throw new Error("Invalid research entry policy");
|
|
6478
|
+
const policy = value;
|
|
6479
|
+
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")
|
|
6480
|
+
throw new Error("Invalid research entry policy contract");
|
|
6481
|
+
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))))
|
|
6482
|
+
throw new Error("Invalid research policy directions");
|
|
6483
|
+
for (const side of ["LONG", "SHORT"]) {
|
|
6484
|
+
const predicates = policy.rules[side];
|
|
6485
|
+
if (predicates === null) continue;
|
|
6486
|
+
if (!Array.isArray(predicates) || predicates.length > 4)
|
|
6487
|
+
throw new Error(
|
|
6488
|
+
"Research policy needs bounded rules for both directions"
|
|
6489
|
+
);
|
|
6490
|
+
for (const predicate of predicates) {
|
|
6491
|
+
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)))
|
|
6492
|
+
throw new Error(
|
|
6493
|
+
"Research policy contains an unsupported or non-causal predicate"
|
|
6494
|
+
);
|
|
6495
|
+
}
|
|
6496
|
+
}
|
|
6497
|
+
return policy;
|
|
6498
|
+
};
|
|
6499
|
+
var matchesResearchEntryRules = (payload, predicates) => predicates.every((predicate) => {
|
|
6500
|
+
const value = predicate.path.split(".").reduce(
|
|
6501
|
+
(current, key) => current != null && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6502
|
+
payload
|
|
6503
|
+
);
|
|
6504
|
+
if (value == null) return false;
|
|
6505
|
+
if (predicate.op === "==") return value === predicate.value;
|
|
6506
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return false;
|
|
6507
|
+
return predicate.op === "<=" ? value <= predicate.value : value >= predicate.value;
|
|
6508
|
+
});
|
|
6509
|
+
var evaluateResearchEntryPolicy = async (policy, signal, onDecision) => {
|
|
6510
|
+
if (signal.strategy !== policy.strategy)
|
|
6511
|
+
throw new Error("Research policy strategy mismatch");
|
|
6512
|
+
if (policy.directions && !policy.directions.includes(signal.direction) || policy.mode === "rules" && policy.rules[signal.direction] === null) {
|
|
6513
|
+
onDecision?.({
|
|
6514
|
+
mode: policy.mode,
|
|
6515
|
+
allowed: false,
|
|
6516
|
+
reason: "DIRECTION_BLOCKED"
|
|
6517
|
+
});
|
|
6518
|
+
return false;
|
|
6519
|
+
}
|
|
6520
|
+
if (policy.mode === "current-gate") {
|
|
6521
|
+
const analysis = await runAiPromptLocal(signal);
|
|
6522
|
+
const allowed2 = analysis.direction === signal.direction && typeof analysis.quality === "number" && analysis.quality >= policy.minQuality;
|
|
6523
|
+
onDecision?.({
|
|
6524
|
+
mode: policy.mode,
|
|
6525
|
+
allowed: allowed2,
|
|
6526
|
+
quality: analysis.quality ?? null,
|
|
6527
|
+
reason: allowed2 ? "CURRENT_GATE_APPROVED" : "CURRENT_GATE_REJECTED"
|
|
6528
|
+
});
|
|
6529
|
+
return allowed2;
|
|
6530
|
+
}
|
|
6531
|
+
const original = buildAiPayload(signal);
|
|
6532
|
+
const gateContext = getDeterministicAiGateContext(original);
|
|
6533
|
+
if (Array.isArray(gateContext?.structuralHardBlockReasons) && gateContext.structuralHardBlockReasons.length) {
|
|
6534
|
+
onDecision?.({
|
|
6535
|
+
mode: policy.mode,
|
|
6536
|
+
allowed: false,
|
|
6537
|
+
reason: "STRUCTURAL_HARD_BLOCK"
|
|
6538
|
+
});
|
|
6539
|
+
return false;
|
|
6540
|
+
}
|
|
6541
|
+
const rules = policy.rules[signal.direction];
|
|
6542
|
+
const payload = buildResearchEntryPayload(signal, original);
|
|
6543
|
+
const allowed = rules !== null && matchesResearchEntryRules(payload, rules);
|
|
6544
|
+
onDecision?.({
|
|
6545
|
+
mode: policy.mode,
|
|
6546
|
+
allowed,
|
|
6547
|
+
reason: allowed ? "RULES_APPROVED" : "PREDICATE_REJECTED",
|
|
6548
|
+
predicates: rules?.map((rule) => {
|
|
6549
|
+
const value = rule.path.split(".").reduce(
|
|
6550
|
+
(current, key) => current && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6551
|
+
payload
|
|
6552
|
+
);
|
|
6553
|
+
return {
|
|
6554
|
+
path: rule.path,
|
|
6555
|
+
actual: typeof value === "number" && Number.isFinite(value) || typeof value === "string" || typeof value === "boolean" ? value : null,
|
|
6556
|
+
passed: matchesResearchEntryRules(payload, [rule])
|
|
6557
|
+
};
|
|
6558
|
+
})
|
|
6559
|
+
});
|
|
6560
|
+
return allowed;
|
|
6561
|
+
};
|
|
6562
|
+
|
|
6457
6563
|
// src/strategy/runtimeDecisionTelemetry.ts
|
|
6458
6564
|
var finitePositive = (value) => {
|
|
6459
6565
|
const parsed = Number(value);
|
|
@@ -6992,6 +7098,12 @@ var createStrategyRuntime = ({
|
|
|
6992
7098
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
6993
7099
|
const projectHooks = projectConfig.hooks;
|
|
6994
7100
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7101
|
+
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7102
|
+
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7103
|
+
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
7104
|
+
throw new Error(
|
|
7105
|
+
"Research entry policies require a BACKTEST simulation connector"
|
|
7106
|
+
);
|
|
6995
7107
|
const jev = (0, import_jev6.parseJevConfig)(config.JEV);
|
|
6996
7108
|
if (jev && env !== "BACKTEST" && jev.source === "provider" && !jev.provider)
|
|
6997
7109
|
throw new Error(
|
|
@@ -7702,7 +7814,8 @@ var createStrategyRuntime = ({
|
|
|
7702
7814
|
await enrichSignalWithMarketContextStages({
|
|
7703
7815
|
signal,
|
|
7704
7816
|
env,
|
|
7705
|
-
includeHyperliquidWhales:
|
|
7817
|
+
includeHyperliquidWhales: Boolean(researchEntryPolicy),
|
|
7818
|
+
...researchEntryPolicy ? { coinMarketCapEnabled: true } : {}
|
|
7706
7819
|
});
|
|
7707
7820
|
if (jev) {
|
|
7708
7821
|
await assessSignalWithJev({
|
|
@@ -7800,6 +7913,22 @@ var createStrategyRuntime = ({
|
|
|
7800
7913
|
}
|
|
7801
7914
|
return signal ?? decision.code;
|
|
7802
7915
|
}
|
|
7916
|
+
if (researchEntryPolicy && (!signal || !await evaluateResearchEntryPolicy(
|
|
7917
|
+
researchEntryPolicy,
|
|
7918
|
+
signal,
|
|
7919
|
+
(decision2) => {
|
|
7920
|
+
signal.additionalIndicators = {
|
|
7921
|
+
...signal.additionalIndicators,
|
|
7922
|
+
researchEntryDecision: decision2
|
|
7923
|
+
};
|
|
7924
|
+
}
|
|
7925
|
+
))) {
|
|
7926
|
+
if (signal) {
|
|
7927
|
+
signal.orderStatus = "skipped";
|
|
7928
|
+
signal.orderSkipReason = "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7929
|
+
}
|
|
7930
|
+
return signal ?? "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7931
|
+
}
|
|
7803
7932
|
const entryGate = await invokeGateHooks(
|
|
7804
7933
|
"beforeEntryGate",
|
|
7805
7934
|
decisionManifest?.hooks?.beforeEntryGate,
|
package/dist/strategies.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradejs/node",
|
|
3
|
-
"version": "3.1.29-beta.
|
|
3
|
+
"version": "3.1.29-beta.284",
|
|
4
4
|
"description": "Node-only runtime for the TradeJS TypeScript framework: strategies, backtests, Pine strategy loading, and plugin registries.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tradejs",
|
|
@@ -105,9 +105,9 @@
|
|
|
105
105
|
"dependencies": {
|
|
106
106
|
"@langchain/core": "^1.2.3",
|
|
107
107
|
"@langchain/openai": "^1.5.5",
|
|
108
|
-
"@tradejs/core": "^3.1.29-beta.
|
|
109
|
-
"@tradejs/infra": "^3.1.29-beta.
|
|
110
|
-
"@tradejs/types": "^3.1.29-beta.
|
|
108
|
+
"@tradejs/core": "^3.1.29-beta.284",
|
|
109
|
+
"@tradejs/infra": "^3.1.29-beta.284",
|
|
110
|
+
"@tradejs/types": "^3.1.29-beta.284",
|
|
111
111
|
"@types/node": "^24",
|
|
112
112
|
"chalk": "4.1.2",
|
|
113
113
|
"ioredis": "5.11.1",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"typescript": "^5.9"
|
|
121
121
|
},
|
|
122
122
|
"devDependencies": {
|
|
123
|
-
"@tradejs/indicators": "^3.1.29-beta.
|
|
123
|
+
"@tradejs/indicators": "^3.1.29-beta.284",
|
|
124
124
|
"@tradejs/strategy-adaptive-momentum-ribbon": "3.0.5",
|
|
125
125
|
"@tradejs/strategy-hyperliquid-consensus": "3.0.5",
|
|
126
126
|
"@tradejs/strategy-kit": "3.0.6",
|