@tradejs/node 3.1.29-beta.283 → 3.1.29-beta.285
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 +164 -7
- package/dist/backtest.mjs +28 -6
- package/dist/{chunk-SKY2HNZU.mjs → chunk-KOV3U6ZZ.mjs} +143 -1
- package/dist/diagnostics.js +133 -1
- package/dist/diagnostics.mjs +1 -1
- package/dist/mcp.js +133 -1
- package/dist/mcp.mjs +1 -1
- package/dist/registry.js +133 -1
- package/dist/registry.mjs +1 -1
- package/dist/runtimeDashboard.js +133 -1
- package/dist/runtimeDashboard.mjs +1 -1
- package/dist/runtimeStrategies.js +133 -1
- package/dist/runtimeStrategies.mjs +1 -1
- package/dist/strategies.js +133 -1
- package/dist/strategies.mjs +1 -1
- package/package.json +5 -5
package/dist/runtimeDashboard.js
CHANGED
|
@@ -3978,6 +3978,7 @@ var recordRuntimeTradeIncrease = async (params) => {
|
|
|
3978
3978
|
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
3979
3979
|
const next = {
|
|
3980
3980
|
...existing,
|
|
3981
|
+
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
3981
3982
|
qty: resultingQty,
|
|
3982
3983
|
entryPrice: resultingEntryPrice,
|
|
3983
3984
|
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
@@ -6706,6 +6707,7 @@ var executeEntryOrder = async ({
|
|
|
6706
6707
|
openFee: estimatedOpenFee,
|
|
6707
6708
|
totalFee: estimatedOpenFee,
|
|
6708
6709
|
entryTimestamp: timestamp,
|
|
6710
|
+
...signal.orderLeverage ? { orderLeverage: signal.orderLeverage } : {},
|
|
6709
6711
|
universe: signal.universe,
|
|
6710
6712
|
assetClass: signal.assetClass,
|
|
6711
6713
|
accountId: signal.accountId,
|
|
@@ -6729,6 +6731,7 @@ var executeEntryOrder = async ({
|
|
|
6729
6731
|
addedQty: filledQty,
|
|
6730
6732
|
addedEntryPrice: fillPrice,
|
|
6731
6733
|
entryTimestamp: timestamp,
|
|
6734
|
+
...signal.orderLeverage ? { orderLeverage: signal.orderLeverage } : {},
|
|
6732
6735
|
fee: estimatedOpenFee,
|
|
6733
6736
|
accountId: signal.accountId,
|
|
6734
6737
|
deploymentId: signal.deploymentId
|
|
@@ -7195,6 +7198,112 @@ var buildAiHookContext = ({
|
|
|
7195
7198
|
};
|
|
7196
7199
|
};
|
|
7197
7200
|
|
|
7201
|
+
// src/researchEntryPolicy.ts
|
|
7202
|
+
var forbidden = /(?:^|\.)(?:__proto__|prototype|constructor|profit|pnl|outcome|tradeResult|backtestExecution|quality|deterministicQuality|approvalAllowedNow|timestamp|knownAt|source|coverage|points|rows|length)(?:\.|$)/i;
|
|
7203
|
+
var buildResearchEntryPayload = (signal, payload = buildAiPayload(signal)) => {
|
|
7204
|
+
const additional = payload.additionalIndicators && typeof payload.additionalIndicators === "object" ? payload.additionalIndicators : {};
|
|
7205
|
+
const raw = signal.additionalIndicators?.baseContext;
|
|
7206
|
+
const adapted = additional.baseContext;
|
|
7207
|
+
return {
|
|
7208
|
+
...payload,
|
|
7209
|
+
additionalIndicators: {
|
|
7210
|
+
...additional,
|
|
7211
|
+
...signal.additionalIndicators?.jev && typeof signal.additionalIndicators.jev === "object" ? { jev: signal.additionalIndicators.jev } : {},
|
|
7212
|
+
baseContext: {
|
|
7213
|
+
...raw && typeof raw === "object" ? raw : {},
|
|
7214
|
+
...adapted && typeof adapted === "object" ? adapted : {}
|
|
7215
|
+
}
|
|
7216
|
+
}
|
|
7217
|
+
};
|
|
7218
|
+
};
|
|
7219
|
+
var parseResearchEntryPolicy = (value) => {
|
|
7220
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
7221
|
+
throw new Error("Invalid research entry policy");
|
|
7222
|
+
const policy = value;
|
|
7223
|
+
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")
|
|
7224
|
+
throw new Error("Invalid research entry policy contract");
|
|
7225
|
+
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))))
|
|
7226
|
+
throw new Error("Invalid research policy directions");
|
|
7227
|
+
for (const side of ["LONG", "SHORT"]) {
|
|
7228
|
+
const predicates = policy.rules[side];
|
|
7229
|
+
if (predicates === null) continue;
|
|
7230
|
+
if (!Array.isArray(predicates) || predicates.length > 4)
|
|
7231
|
+
throw new Error(
|
|
7232
|
+
"Research policy needs bounded rules for both directions"
|
|
7233
|
+
);
|
|
7234
|
+
for (const predicate of predicates) {
|
|
7235
|
+
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)))
|
|
7236
|
+
throw new Error(
|
|
7237
|
+
"Research policy contains an unsupported or non-causal predicate"
|
|
7238
|
+
);
|
|
7239
|
+
}
|
|
7240
|
+
}
|
|
7241
|
+
return policy;
|
|
7242
|
+
};
|
|
7243
|
+
var matchesResearchEntryRules = (payload, predicates) => predicates.every((predicate) => {
|
|
7244
|
+
const value = predicate.path.split(".").reduce(
|
|
7245
|
+
(current, key) => current != null && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
7246
|
+
payload
|
|
7247
|
+
);
|
|
7248
|
+
if (value == null) return false;
|
|
7249
|
+
if (predicate.op === "==") return value === predicate.value;
|
|
7250
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return false;
|
|
7251
|
+
return predicate.op === "<=" ? value <= predicate.value : value >= predicate.value;
|
|
7252
|
+
});
|
|
7253
|
+
var evaluateResearchEntryPolicy = async (policy, signal, onDecision) => {
|
|
7254
|
+
if (signal.strategy !== policy.strategy)
|
|
7255
|
+
throw new Error("Research policy strategy mismatch");
|
|
7256
|
+
if (policy.directions && !policy.directions.includes(signal.direction) || policy.mode === "rules" && policy.rules[signal.direction] === null) {
|
|
7257
|
+
onDecision?.({
|
|
7258
|
+
mode: policy.mode,
|
|
7259
|
+
allowed: false,
|
|
7260
|
+
reason: "DIRECTION_BLOCKED"
|
|
7261
|
+
});
|
|
7262
|
+
return false;
|
|
7263
|
+
}
|
|
7264
|
+
if (policy.mode === "current-gate") {
|
|
7265
|
+
const analysis = await runAiPromptLocal(signal);
|
|
7266
|
+
const allowed2 = analysis.direction === signal.direction && typeof analysis.quality === "number" && analysis.quality >= policy.minQuality;
|
|
7267
|
+
onDecision?.({
|
|
7268
|
+
mode: policy.mode,
|
|
7269
|
+
allowed: allowed2,
|
|
7270
|
+
quality: analysis.quality ?? null,
|
|
7271
|
+
reason: allowed2 ? "CURRENT_GATE_APPROVED" : "CURRENT_GATE_REJECTED"
|
|
7272
|
+
});
|
|
7273
|
+
return allowed2;
|
|
7274
|
+
}
|
|
7275
|
+
const original = buildAiPayload(signal);
|
|
7276
|
+
const gateContext = getDeterministicAiGateContext(original);
|
|
7277
|
+
if (Array.isArray(gateContext?.structuralHardBlockReasons) && gateContext.structuralHardBlockReasons.length) {
|
|
7278
|
+
onDecision?.({
|
|
7279
|
+
mode: policy.mode,
|
|
7280
|
+
allowed: false,
|
|
7281
|
+
reason: "STRUCTURAL_HARD_BLOCK"
|
|
7282
|
+
});
|
|
7283
|
+
return false;
|
|
7284
|
+
}
|
|
7285
|
+
const rules = policy.rules[signal.direction];
|
|
7286
|
+
const payload = buildResearchEntryPayload(signal, original);
|
|
7287
|
+
const allowed = rules !== null && matchesResearchEntryRules(payload, rules);
|
|
7288
|
+
onDecision?.({
|
|
7289
|
+
mode: policy.mode,
|
|
7290
|
+
allowed,
|
|
7291
|
+
reason: allowed ? "RULES_APPROVED" : "PREDICATE_REJECTED",
|
|
7292
|
+
predicates: rules?.map((rule) => {
|
|
7293
|
+
const value = rule.path.split(".").reduce(
|
|
7294
|
+
(current, key) => current && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
7295
|
+
payload
|
|
7296
|
+
);
|
|
7297
|
+
return {
|
|
7298
|
+
path: rule.path,
|
|
7299
|
+
actual: typeof value === "number" && Number.isFinite(value) || typeof value === "string" || typeof value === "boolean" ? value : null,
|
|
7300
|
+
passed: matchesResearchEntryRules(payload, [rule])
|
|
7301
|
+
};
|
|
7302
|
+
})
|
|
7303
|
+
});
|
|
7304
|
+
return allowed;
|
|
7305
|
+
};
|
|
7306
|
+
|
|
7198
7307
|
// src/strategy/runtimeDecisionTelemetry.ts
|
|
7199
7308
|
var finitePositive = (value) => {
|
|
7200
7309
|
const parsed = Number(value);
|
|
@@ -7733,6 +7842,12 @@ var createStrategyRuntime = ({
|
|
|
7733
7842
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
7734
7843
|
const projectHooks = projectConfig.hooks;
|
|
7735
7844
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7845
|
+
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7846
|
+
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7847
|
+
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
7848
|
+
throw new Error(
|
|
7849
|
+
"Research entry policies require a BACKTEST simulation connector"
|
|
7850
|
+
);
|
|
7736
7851
|
const jev = (0, import_jev6.parseJevConfig)(config.JEV);
|
|
7737
7852
|
if (jev && env !== "BACKTEST" && jev.source === "provider" && !jev.provider)
|
|
7738
7853
|
throw new Error(
|
|
@@ -8443,7 +8558,8 @@ var createStrategyRuntime = ({
|
|
|
8443
8558
|
await enrichSignalWithMarketContextStages({
|
|
8444
8559
|
signal,
|
|
8445
8560
|
env,
|
|
8446
|
-
includeHyperliquidWhales:
|
|
8561
|
+
includeHyperliquidWhales: Boolean(researchEntryPolicy),
|
|
8562
|
+
...researchEntryPolicy ? { coinMarketCapEnabled: true } : {}
|
|
8447
8563
|
});
|
|
8448
8564
|
if (jev) {
|
|
8449
8565
|
await assessSignalWithJev({
|
|
@@ -8541,6 +8657,22 @@ var createStrategyRuntime = ({
|
|
|
8541
8657
|
}
|
|
8542
8658
|
return signal ?? decision.code;
|
|
8543
8659
|
}
|
|
8660
|
+
if (researchEntryPolicy && (!signal || !await evaluateResearchEntryPolicy(
|
|
8661
|
+
researchEntryPolicy,
|
|
8662
|
+
signal,
|
|
8663
|
+
(decision2) => {
|
|
8664
|
+
signal.additionalIndicators = {
|
|
8665
|
+
...signal.additionalIndicators,
|
|
8666
|
+
researchEntryDecision: decision2
|
|
8667
|
+
};
|
|
8668
|
+
}
|
|
8669
|
+
))) {
|
|
8670
|
+
if (signal) {
|
|
8671
|
+
signal.orderStatus = "skipped";
|
|
8672
|
+
signal.orderSkipReason = "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
8673
|
+
}
|
|
8674
|
+
return signal ?? "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
8675
|
+
}
|
|
8544
8676
|
const entryGate = await invokeGateHooks(
|
|
8545
8677
|
"beforeEntryGate",
|
|
8546
8678
|
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-KOV3U6ZZ.mjs";
|
|
13
13
|
import "./chunk-UL5Z46JL.mjs";
|
|
14
14
|
import "./chunk-LZDXRXIU.mjs";
|
|
15
15
|
import "./chunk-YR55Q355.mjs";
|
|
@@ -2975,6 +2975,7 @@ var recordRuntimeTradeIncrease = async (params) => {
|
|
|
2975
2975
|
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
2976
2976
|
const next = {
|
|
2977
2977
|
...existing,
|
|
2978
|
+
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
2978
2979
|
qty: resultingQty,
|
|
2979
2980
|
entryPrice: resultingEntryPrice,
|
|
2980
2981
|
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
@@ -5703,6 +5704,7 @@ var executeEntryOrder = async ({
|
|
|
5703
5704
|
openFee: estimatedOpenFee,
|
|
5704
5705
|
totalFee: estimatedOpenFee,
|
|
5705
5706
|
entryTimestamp: timestamp,
|
|
5707
|
+
...signal.orderLeverage ? { orderLeverage: signal.orderLeverage } : {},
|
|
5706
5708
|
universe: signal.universe,
|
|
5707
5709
|
assetClass: signal.assetClass,
|
|
5708
5710
|
accountId: signal.accountId,
|
|
@@ -5726,6 +5728,7 @@ var executeEntryOrder = async ({
|
|
|
5726
5728
|
addedQty: filledQty,
|
|
5727
5729
|
addedEntryPrice: fillPrice,
|
|
5728
5730
|
entryTimestamp: timestamp,
|
|
5731
|
+
...signal.orderLeverage ? { orderLeverage: signal.orderLeverage } : {},
|
|
5729
5732
|
fee: estimatedOpenFee,
|
|
5730
5733
|
accountId: signal.accountId,
|
|
5731
5734
|
deploymentId: signal.deploymentId
|
|
@@ -6192,6 +6195,112 @@ var buildAiHookContext = ({
|
|
|
6192
6195
|
};
|
|
6193
6196
|
};
|
|
6194
6197
|
|
|
6198
|
+
// src/researchEntryPolicy.ts
|
|
6199
|
+
var forbidden = /(?:^|\.)(?:__proto__|prototype|constructor|profit|pnl|outcome|tradeResult|backtestExecution|quality|deterministicQuality|approvalAllowedNow|timestamp|knownAt|source|coverage|points|rows|length)(?:\.|$)/i;
|
|
6200
|
+
var buildResearchEntryPayload = (signal, payload = buildAiPayload(signal)) => {
|
|
6201
|
+
const additional = payload.additionalIndicators && typeof payload.additionalIndicators === "object" ? payload.additionalIndicators : {};
|
|
6202
|
+
const raw = signal.additionalIndicators?.baseContext;
|
|
6203
|
+
const adapted = additional.baseContext;
|
|
6204
|
+
return {
|
|
6205
|
+
...payload,
|
|
6206
|
+
additionalIndicators: {
|
|
6207
|
+
...additional,
|
|
6208
|
+
...signal.additionalIndicators?.jev && typeof signal.additionalIndicators.jev === "object" ? { jev: signal.additionalIndicators.jev } : {},
|
|
6209
|
+
baseContext: {
|
|
6210
|
+
...raw && typeof raw === "object" ? raw : {},
|
|
6211
|
+
...adapted && typeof adapted === "object" ? adapted : {}
|
|
6212
|
+
}
|
|
6213
|
+
}
|
|
6214
|
+
};
|
|
6215
|
+
};
|
|
6216
|
+
var parseResearchEntryPolicy = (value) => {
|
|
6217
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
6218
|
+
throw new Error("Invalid research entry policy");
|
|
6219
|
+
const policy = value;
|
|
6220
|
+
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")
|
|
6221
|
+
throw new Error("Invalid research entry policy contract");
|
|
6222
|
+
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))))
|
|
6223
|
+
throw new Error("Invalid research policy directions");
|
|
6224
|
+
for (const side of ["LONG", "SHORT"]) {
|
|
6225
|
+
const predicates = policy.rules[side];
|
|
6226
|
+
if (predicates === null) continue;
|
|
6227
|
+
if (!Array.isArray(predicates) || predicates.length > 4)
|
|
6228
|
+
throw new Error(
|
|
6229
|
+
"Research policy needs bounded rules for both directions"
|
|
6230
|
+
);
|
|
6231
|
+
for (const predicate of predicates) {
|
|
6232
|
+
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)))
|
|
6233
|
+
throw new Error(
|
|
6234
|
+
"Research policy contains an unsupported or non-causal predicate"
|
|
6235
|
+
);
|
|
6236
|
+
}
|
|
6237
|
+
}
|
|
6238
|
+
return policy;
|
|
6239
|
+
};
|
|
6240
|
+
var matchesResearchEntryRules = (payload, predicates) => predicates.every((predicate) => {
|
|
6241
|
+
const value = predicate.path.split(".").reduce(
|
|
6242
|
+
(current, key) => current != null && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6243
|
+
payload
|
|
6244
|
+
);
|
|
6245
|
+
if (value == null) return false;
|
|
6246
|
+
if (predicate.op === "==") return value === predicate.value;
|
|
6247
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return false;
|
|
6248
|
+
return predicate.op === "<=" ? value <= predicate.value : value >= predicate.value;
|
|
6249
|
+
});
|
|
6250
|
+
var evaluateResearchEntryPolicy = async (policy, signal, onDecision) => {
|
|
6251
|
+
if (signal.strategy !== policy.strategy)
|
|
6252
|
+
throw new Error("Research policy strategy mismatch");
|
|
6253
|
+
if (policy.directions && !policy.directions.includes(signal.direction) || policy.mode === "rules" && policy.rules[signal.direction] === null) {
|
|
6254
|
+
onDecision?.({
|
|
6255
|
+
mode: policy.mode,
|
|
6256
|
+
allowed: false,
|
|
6257
|
+
reason: "DIRECTION_BLOCKED"
|
|
6258
|
+
});
|
|
6259
|
+
return false;
|
|
6260
|
+
}
|
|
6261
|
+
if (policy.mode === "current-gate") {
|
|
6262
|
+
const analysis = await runAiPromptLocal(signal);
|
|
6263
|
+
const allowed2 = analysis.direction === signal.direction && typeof analysis.quality === "number" && analysis.quality >= policy.minQuality;
|
|
6264
|
+
onDecision?.({
|
|
6265
|
+
mode: policy.mode,
|
|
6266
|
+
allowed: allowed2,
|
|
6267
|
+
quality: analysis.quality ?? null,
|
|
6268
|
+
reason: allowed2 ? "CURRENT_GATE_APPROVED" : "CURRENT_GATE_REJECTED"
|
|
6269
|
+
});
|
|
6270
|
+
return allowed2;
|
|
6271
|
+
}
|
|
6272
|
+
const original = buildAiPayload(signal);
|
|
6273
|
+
const gateContext = getDeterministicAiGateContext(original);
|
|
6274
|
+
if (Array.isArray(gateContext?.structuralHardBlockReasons) && gateContext.structuralHardBlockReasons.length) {
|
|
6275
|
+
onDecision?.({
|
|
6276
|
+
mode: policy.mode,
|
|
6277
|
+
allowed: false,
|
|
6278
|
+
reason: "STRUCTURAL_HARD_BLOCK"
|
|
6279
|
+
});
|
|
6280
|
+
return false;
|
|
6281
|
+
}
|
|
6282
|
+
const rules = policy.rules[signal.direction];
|
|
6283
|
+
const payload = buildResearchEntryPayload(signal, original);
|
|
6284
|
+
const allowed = rules !== null && matchesResearchEntryRules(payload, rules);
|
|
6285
|
+
onDecision?.({
|
|
6286
|
+
mode: policy.mode,
|
|
6287
|
+
allowed,
|
|
6288
|
+
reason: allowed ? "RULES_APPROVED" : "PREDICATE_REJECTED",
|
|
6289
|
+
predicates: rules?.map((rule) => {
|
|
6290
|
+
const value = rule.path.split(".").reduce(
|
|
6291
|
+
(current, key) => current && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6292
|
+
payload
|
|
6293
|
+
);
|
|
6294
|
+
return {
|
|
6295
|
+
path: rule.path,
|
|
6296
|
+
actual: typeof value === "number" && Number.isFinite(value) || typeof value === "string" || typeof value === "boolean" ? value : null,
|
|
6297
|
+
passed: matchesResearchEntryRules(payload, [rule])
|
|
6298
|
+
};
|
|
6299
|
+
})
|
|
6300
|
+
});
|
|
6301
|
+
return allowed;
|
|
6302
|
+
};
|
|
6303
|
+
|
|
6195
6304
|
// src/strategy/runtimeDecisionTelemetry.ts
|
|
6196
6305
|
var finitePositive = (value) => {
|
|
6197
6306
|
const parsed = Number(value);
|
|
@@ -6730,6 +6839,12 @@ var createStrategyRuntime = ({
|
|
|
6730
6839
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
6731
6840
|
const projectHooks = projectConfig.hooks;
|
|
6732
6841
|
const env = String(config.ENV ?? "BACKTEST");
|
|
6842
|
+
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
6843
|
+
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
6844
|
+
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
6845
|
+
throw new Error(
|
|
6846
|
+
"Research entry policies require a BACKTEST simulation connector"
|
|
6847
|
+
);
|
|
6733
6848
|
const jev = (0, import_jev6.parseJevConfig)(config.JEV);
|
|
6734
6849
|
if (jev && env !== "BACKTEST" && jev.source === "provider" && !jev.provider)
|
|
6735
6850
|
throw new Error(
|
|
@@ -7440,7 +7555,8 @@ var createStrategyRuntime = ({
|
|
|
7440
7555
|
await enrichSignalWithMarketContextStages({
|
|
7441
7556
|
signal,
|
|
7442
7557
|
env,
|
|
7443
|
-
includeHyperliquidWhales:
|
|
7558
|
+
includeHyperliquidWhales: Boolean(researchEntryPolicy),
|
|
7559
|
+
...researchEntryPolicy ? { coinMarketCapEnabled: true } : {}
|
|
7444
7560
|
});
|
|
7445
7561
|
if (jev) {
|
|
7446
7562
|
await assessSignalWithJev({
|
|
@@ -7538,6 +7654,22 @@ var createStrategyRuntime = ({
|
|
|
7538
7654
|
}
|
|
7539
7655
|
return signal ?? decision.code;
|
|
7540
7656
|
}
|
|
7657
|
+
if (researchEntryPolicy && (!signal || !await evaluateResearchEntryPolicy(
|
|
7658
|
+
researchEntryPolicy,
|
|
7659
|
+
signal,
|
|
7660
|
+
(decision2) => {
|
|
7661
|
+
signal.additionalIndicators = {
|
|
7662
|
+
...signal.additionalIndicators,
|
|
7663
|
+
researchEntryDecision: decision2
|
|
7664
|
+
};
|
|
7665
|
+
}
|
|
7666
|
+
))) {
|
|
7667
|
+
if (signal) {
|
|
7668
|
+
signal.orderStatus = "skipped";
|
|
7669
|
+
signal.orderSkipReason = "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7670
|
+
}
|
|
7671
|
+
return signal ?? "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7672
|
+
}
|
|
7541
7673
|
const entryGate = await invokeGateHooks(
|
|
7542
7674
|
"beforeEntryGate",
|
|
7543
7675
|
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-KOV3U6ZZ.mjs";
|
|
13
13
|
import "./chunk-UL5Z46JL.mjs";
|
|
14
14
|
import "./chunk-LZDXRXIU.mjs";
|
|
15
15
|
import "./chunk-YR55Q355.mjs";
|
package/dist/strategies.js
CHANGED
|
@@ -3068,6 +3068,7 @@ var recordRuntimeTradeIncrease = async (params) => {
|
|
|
3068
3068
|
const totalFee = (existing.totalFee ?? existing.fee ?? 0) + addedFee;
|
|
3069
3069
|
const next = {
|
|
3070
3070
|
...existing,
|
|
3071
|
+
...params.orderLeverage ? { orderLeverage: params.orderLeverage } : {},
|
|
3071
3072
|
qty: resultingQty,
|
|
3072
3073
|
entryPrice: resultingEntryPrice,
|
|
3073
3074
|
entryCount: Math.max(1, existing.entryCount ?? 1) + 1,
|
|
@@ -5965,6 +5966,7 @@ var executeEntryOrder = async ({
|
|
|
5965
5966
|
openFee: estimatedOpenFee,
|
|
5966
5967
|
totalFee: estimatedOpenFee,
|
|
5967
5968
|
entryTimestamp: timestamp,
|
|
5969
|
+
...signal.orderLeverage ? { orderLeverage: signal.orderLeverage } : {},
|
|
5968
5970
|
universe: signal.universe,
|
|
5969
5971
|
assetClass: signal.assetClass,
|
|
5970
5972
|
accountId: signal.accountId,
|
|
@@ -5988,6 +5990,7 @@ var executeEntryOrder = async ({
|
|
|
5988
5990
|
addedQty: filledQty,
|
|
5989
5991
|
addedEntryPrice: fillPrice,
|
|
5990
5992
|
entryTimestamp: timestamp,
|
|
5993
|
+
...signal.orderLeverage ? { orderLeverage: signal.orderLeverage } : {},
|
|
5991
5994
|
fee: estimatedOpenFee,
|
|
5992
5995
|
accountId: signal.accountId,
|
|
5993
5996
|
deploymentId: signal.deploymentId
|
|
@@ -6454,6 +6457,112 @@ var buildAiHookContext = ({
|
|
|
6454
6457
|
};
|
|
6455
6458
|
};
|
|
6456
6459
|
|
|
6460
|
+
// src/researchEntryPolicy.ts
|
|
6461
|
+
var forbidden = /(?:^|\.)(?:__proto__|prototype|constructor|profit|pnl|outcome|tradeResult|backtestExecution|quality|deterministicQuality|approvalAllowedNow|timestamp|knownAt|source|coverage|points|rows|length)(?:\.|$)/i;
|
|
6462
|
+
var buildResearchEntryPayload = (signal, payload = buildAiPayload(signal)) => {
|
|
6463
|
+
const additional = payload.additionalIndicators && typeof payload.additionalIndicators === "object" ? payload.additionalIndicators : {};
|
|
6464
|
+
const raw = signal.additionalIndicators?.baseContext;
|
|
6465
|
+
const adapted = additional.baseContext;
|
|
6466
|
+
return {
|
|
6467
|
+
...payload,
|
|
6468
|
+
additionalIndicators: {
|
|
6469
|
+
...additional,
|
|
6470
|
+
...signal.additionalIndicators?.jev && typeof signal.additionalIndicators.jev === "object" ? { jev: signal.additionalIndicators.jev } : {},
|
|
6471
|
+
baseContext: {
|
|
6472
|
+
...raw && typeof raw === "object" ? raw : {},
|
|
6473
|
+
...adapted && typeof adapted === "object" ? adapted : {}
|
|
6474
|
+
}
|
|
6475
|
+
}
|
|
6476
|
+
};
|
|
6477
|
+
};
|
|
6478
|
+
var parseResearchEntryPolicy = (value) => {
|
|
6479
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
6480
|
+
throw new Error("Invalid research entry policy");
|
|
6481
|
+
const policy = value;
|
|
6482
|
+
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")
|
|
6483
|
+
throw new Error("Invalid research entry policy contract");
|
|
6484
|
+
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))))
|
|
6485
|
+
throw new Error("Invalid research policy directions");
|
|
6486
|
+
for (const side of ["LONG", "SHORT"]) {
|
|
6487
|
+
const predicates = policy.rules[side];
|
|
6488
|
+
if (predicates === null) continue;
|
|
6489
|
+
if (!Array.isArray(predicates) || predicates.length > 4)
|
|
6490
|
+
throw new Error(
|
|
6491
|
+
"Research policy needs bounded rules for both directions"
|
|
6492
|
+
);
|
|
6493
|
+
for (const predicate of predicates) {
|
|
6494
|
+
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)))
|
|
6495
|
+
throw new Error(
|
|
6496
|
+
"Research policy contains an unsupported or non-causal predicate"
|
|
6497
|
+
);
|
|
6498
|
+
}
|
|
6499
|
+
}
|
|
6500
|
+
return policy;
|
|
6501
|
+
};
|
|
6502
|
+
var matchesResearchEntryRules = (payload, predicates) => predicates.every((predicate) => {
|
|
6503
|
+
const value = predicate.path.split(".").reduce(
|
|
6504
|
+
(current, key) => current != null && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6505
|
+
payload
|
|
6506
|
+
);
|
|
6507
|
+
if (value == null) return false;
|
|
6508
|
+
if (predicate.op === "==") return value === predicate.value;
|
|
6509
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return false;
|
|
6510
|
+
return predicate.op === "<=" ? value <= predicate.value : value >= predicate.value;
|
|
6511
|
+
});
|
|
6512
|
+
var evaluateResearchEntryPolicy = async (policy, signal, onDecision) => {
|
|
6513
|
+
if (signal.strategy !== policy.strategy)
|
|
6514
|
+
throw new Error("Research policy strategy mismatch");
|
|
6515
|
+
if (policy.directions && !policy.directions.includes(signal.direction) || policy.mode === "rules" && policy.rules[signal.direction] === null) {
|
|
6516
|
+
onDecision?.({
|
|
6517
|
+
mode: policy.mode,
|
|
6518
|
+
allowed: false,
|
|
6519
|
+
reason: "DIRECTION_BLOCKED"
|
|
6520
|
+
});
|
|
6521
|
+
return false;
|
|
6522
|
+
}
|
|
6523
|
+
if (policy.mode === "current-gate") {
|
|
6524
|
+
const analysis = await runAiPromptLocal(signal);
|
|
6525
|
+
const allowed2 = analysis.direction === signal.direction && typeof analysis.quality === "number" && analysis.quality >= policy.minQuality;
|
|
6526
|
+
onDecision?.({
|
|
6527
|
+
mode: policy.mode,
|
|
6528
|
+
allowed: allowed2,
|
|
6529
|
+
quality: analysis.quality ?? null,
|
|
6530
|
+
reason: allowed2 ? "CURRENT_GATE_APPROVED" : "CURRENT_GATE_REJECTED"
|
|
6531
|
+
});
|
|
6532
|
+
return allowed2;
|
|
6533
|
+
}
|
|
6534
|
+
const original = buildAiPayload(signal);
|
|
6535
|
+
const gateContext = getDeterministicAiGateContext(original);
|
|
6536
|
+
if (Array.isArray(gateContext?.structuralHardBlockReasons) && gateContext.structuralHardBlockReasons.length) {
|
|
6537
|
+
onDecision?.({
|
|
6538
|
+
mode: policy.mode,
|
|
6539
|
+
allowed: false,
|
|
6540
|
+
reason: "STRUCTURAL_HARD_BLOCK"
|
|
6541
|
+
});
|
|
6542
|
+
return false;
|
|
6543
|
+
}
|
|
6544
|
+
const rules = policy.rules[signal.direction];
|
|
6545
|
+
const payload = buildResearchEntryPayload(signal, original);
|
|
6546
|
+
const allowed = rules !== null && matchesResearchEntryRules(payload, rules);
|
|
6547
|
+
onDecision?.({
|
|
6548
|
+
mode: policy.mode,
|
|
6549
|
+
allowed,
|
|
6550
|
+
reason: allowed ? "RULES_APPROVED" : "PREDICATE_REJECTED",
|
|
6551
|
+
predicates: rules?.map((rule) => {
|
|
6552
|
+
const value = rule.path.split(".").reduce(
|
|
6553
|
+
(current, key) => current && typeof current === "object" && Object.hasOwn(current, key) ? Reflect.get(current, key) : void 0,
|
|
6554
|
+
payload
|
|
6555
|
+
);
|
|
6556
|
+
return {
|
|
6557
|
+
path: rule.path,
|
|
6558
|
+
actual: typeof value === "number" && Number.isFinite(value) || typeof value === "string" || typeof value === "boolean" ? value : null,
|
|
6559
|
+
passed: matchesResearchEntryRules(payload, [rule])
|
|
6560
|
+
};
|
|
6561
|
+
})
|
|
6562
|
+
});
|
|
6563
|
+
return allowed;
|
|
6564
|
+
};
|
|
6565
|
+
|
|
6457
6566
|
// src/strategy/runtimeDecisionTelemetry.ts
|
|
6458
6567
|
var finitePositive = (value) => {
|
|
6459
6568
|
const parsed = Number(value);
|
|
@@ -6992,6 +7101,12 @@ var createStrategyRuntime = ({
|
|
|
6992
7101
|
const projectConfig = await loadTradejsConfig(projectRoot);
|
|
6993
7102
|
const projectHooks = projectConfig.hooks;
|
|
6994
7103
|
const env = String(config.ENV ?? "BACKTEST");
|
|
7104
|
+
const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
|
|
7105
|
+
const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
|
|
7106
|
+
if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
|
|
7107
|
+
throw new Error(
|
|
7108
|
+
"Research entry policies require a BACKTEST simulation connector"
|
|
7109
|
+
);
|
|
6995
7110
|
const jev = (0, import_jev6.parseJevConfig)(config.JEV);
|
|
6996
7111
|
if (jev && env !== "BACKTEST" && jev.source === "provider" && !jev.provider)
|
|
6997
7112
|
throw new Error(
|
|
@@ -7702,7 +7817,8 @@ var createStrategyRuntime = ({
|
|
|
7702
7817
|
await enrichSignalWithMarketContextStages({
|
|
7703
7818
|
signal,
|
|
7704
7819
|
env,
|
|
7705
|
-
includeHyperliquidWhales:
|
|
7820
|
+
includeHyperliquidWhales: Boolean(researchEntryPolicy),
|
|
7821
|
+
...researchEntryPolicy ? { coinMarketCapEnabled: true } : {}
|
|
7706
7822
|
});
|
|
7707
7823
|
if (jev) {
|
|
7708
7824
|
await assessSignalWithJev({
|
|
@@ -7800,6 +7916,22 @@ var createStrategyRuntime = ({
|
|
|
7800
7916
|
}
|
|
7801
7917
|
return signal ?? decision.code;
|
|
7802
7918
|
}
|
|
7919
|
+
if (researchEntryPolicy && (!signal || !await evaluateResearchEntryPolicy(
|
|
7920
|
+
researchEntryPolicy,
|
|
7921
|
+
signal,
|
|
7922
|
+
(decision2) => {
|
|
7923
|
+
signal.additionalIndicators = {
|
|
7924
|
+
...signal.additionalIndicators,
|
|
7925
|
+
researchEntryDecision: decision2
|
|
7926
|
+
};
|
|
7927
|
+
}
|
|
7928
|
+
))) {
|
|
7929
|
+
if (signal) {
|
|
7930
|
+
signal.orderStatus = "skipped";
|
|
7931
|
+
signal.orderSkipReason = "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7932
|
+
}
|
|
7933
|
+
return signal ?? "RESEARCH_ENTRY_POLICY_REJECTED";
|
|
7934
|
+
}
|
|
7803
7935
|
const entryGate = await invokeGateHooks(
|
|
7804
7936
|
"beforeEntryGate",
|
|
7805
7937
|
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.285",
|
|
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.285",
|
|
109
|
+
"@tradejs/infra": "^3.1.29-beta.285",
|
|
110
|
+
"@tradejs/types": "^3.1.29-beta.285",
|
|
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.285",
|
|
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",
|