@tradejs/node 2.0.19 → 2.0.20
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.js +150 -5
- package/dist/backtest.mjs +150 -5
- package/package.json +5 -5
package/dist/backtest.js
CHANGED
|
@@ -6513,6 +6513,7 @@ var createBacktestProgress = ({
|
|
|
6513
6513
|
// src/backtest/session.ts
|
|
6514
6514
|
var import_types2 = require("@tradejs/types");
|
|
6515
6515
|
var import_ai3 = require("@tradejs/infra/ai");
|
|
6516
|
+
var import_coreResearch = require("@tradejs/infra/coreResearch");
|
|
6516
6517
|
var import_ml3 = require("@tradejs/infra/ml");
|
|
6517
6518
|
|
|
6518
6519
|
// src/executionCosts.ts
|
|
@@ -7611,6 +7612,37 @@ var createTestConnector = (connector, context) => {
|
|
|
7611
7612
|
};
|
|
7612
7613
|
};
|
|
7613
7614
|
|
|
7615
|
+
// src/backtest/researchTrace.ts
|
|
7616
|
+
var asRecord2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
7617
|
+
var findSetupKey = (value, depth = 0) => {
|
|
7618
|
+
if (depth > 5) return null;
|
|
7619
|
+
const record = asRecord2(value);
|
|
7620
|
+
if (!record) return null;
|
|
7621
|
+
for (const key of ["setupIdentity", "setupId", "patternId", "candidateId"]) {
|
|
7622
|
+
const candidate = record[key];
|
|
7623
|
+
if (typeof candidate === "string" && candidate.trim() || typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
7624
|
+
return `${key}:${String(candidate)}`;
|
|
7625
|
+
}
|
|
7626
|
+
}
|
|
7627
|
+
for (const [key, nested] of Object.entries(record)) {
|
|
7628
|
+
if (/context|signal|setup|pattern|candidate|strategy/i.test(key) && nested != null) {
|
|
7629
|
+
const found = findSetupKey(nested, depth + 1);
|
|
7630
|
+
if (found) return found;
|
|
7631
|
+
}
|
|
7632
|
+
}
|
|
7633
|
+
return null;
|
|
7634
|
+
};
|
|
7635
|
+
var resolveCoreResearchSetupIdentity = (signal) => {
|
|
7636
|
+
const strategyKey = findSetupKey(signal.additionalIndicators);
|
|
7637
|
+
return strategyKey ? {
|
|
7638
|
+
setupIdentity: `${signal.strategy}|${signal.symbol}|${signal.direction}|${strategyKey}`,
|
|
7639
|
+
setupIdentitySource: "strategy-context"
|
|
7640
|
+
} : {
|
|
7641
|
+
setupIdentity: `${signal.strategy}|${signal.symbol}|${signal.direction}|${signal.timestamp}`,
|
|
7642
|
+
setupIdentitySource: "signal-time-fallback"
|
|
7643
|
+
};
|
|
7644
|
+
};
|
|
7645
|
+
|
|
7614
7646
|
// src/backtest/session.ts
|
|
7615
7647
|
var createWarningCounts = () => ({
|
|
7616
7648
|
[import_types2.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY]: 0
|
|
@@ -7700,7 +7732,7 @@ var createBacktestSession = async ({
|
|
|
7700
7732
|
const testConnector = createTestConnector(connector, {
|
|
7701
7733
|
userName: test.userName,
|
|
7702
7734
|
mlEnabled: test.ml,
|
|
7703
|
-
aiEnabled: test.ai,
|
|
7735
|
+
aiEnabled: Boolean(test.ai || test.researchTrace),
|
|
7704
7736
|
fastMode: test.fast,
|
|
7705
7737
|
instrument,
|
|
7706
7738
|
executionCostModel,
|
|
@@ -7737,11 +7769,18 @@ var createBacktestSession = async ({
|
|
|
7737
7769
|
);
|
|
7738
7770
|
const pendingMlPayloadBySignalId = /* @__PURE__ */ new Map();
|
|
7739
7771
|
const pendingAiRowBySignalId = /* @__PURE__ */ new Map();
|
|
7772
|
+
const pendingResearchSetupBySignalId = /* @__PURE__ */ new Map();
|
|
7773
|
+
const researchSkipCounts = /* @__PURE__ */ new Map();
|
|
7774
|
+
const researchEventCounts = /* @__PURE__ */ new Map();
|
|
7740
7775
|
const warningCounts = createWarningCounts();
|
|
7741
7776
|
const replayEvaluations = test.collectReplaySignalEvaluations ? [] : null;
|
|
7742
7777
|
const chunkId = test.chunkId ?? "single";
|
|
7743
7778
|
const processSignal = async (signal, candle) => {
|
|
7744
7779
|
recordSignalWarning(warningCounts, signal);
|
|
7780
|
+
if (test.researchTrace && typeof signal === "string") {
|
|
7781
|
+
const reason = signal.trim() || "NO_SIGNAL";
|
|
7782
|
+
researchSkipCounts.set(reason, (researchSkipCounts.get(reason) ?? 0) + 1);
|
|
7783
|
+
}
|
|
7745
7784
|
if (typeof signal === "string" && signal.startsWith("BACKTEST_ENTRY_DELAY_")) {
|
|
7746
7785
|
return;
|
|
7747
7786
|
}
|
|
@@ -7758,6 +7797,32 @@ var createBacktestSession = async ({
|
|
|
7758
7797
|
onStageStart: monitor.contextStage
|
|
7759
7798
|
});
|
|
7760
7799
|
}
|
|
7800
|
+
if (test.researchTrace && signal && typeof signal !== "string" && signal.signalId) {
|
|
7801
|
+
const identity = resolveCoreResearchSetupIdentity(signal);
|
|
7802
|
+
pendingResearchSetupBySignalId.set(signal.signalId, identity);
|
|
7803
|
+
await (0, import_coreResearch.appendCoreResearchTraceEvent)({
|
|
7804
|
+
strategyName: test.strategyName,
|
|
7805
|
+
chunkId,
|
|
7806
|
+
event: {
|
|
7807
|
+
schema: "tradejs-core-research-trace/v1",
|
|
7808
|
+
event: signal.orderStatus === "failed" || signal.orderStatus === "skipped" ? "entry_rejected" : "signal_emitted",
|
|
7809
|
+
timestamp: signal.timestamp,
|
|
7810
|
+
strategy: signal.strategy || test.strategyName,
|
|
7811
|
+
symbol: signal.symbol || test.symbol,
|
|
7812
|
+
direction: signal.direction,
|
|
7813
|
+
signalId: signal.signalId,
|
|
7814
|
+
configId: test.configId,
|
|
7815
|
+
backtestRunId: test.backtestRunId,
|
|
7816
|
+
backtestTestKey: test.backtestTestKey,
|
|
7817
|
+
...identity
|
|
7818
|
+
}
|
|
7819
|
+
});
|
|
7820
|
+
const traceEvent = signal.orderStatus === "failed" || signal.orderStatus === "skipped" ? "entry_rejected" : "signal_emitted";
|
|
7821
|
+
researchEventCounts.set(
|
|
7822
|
+
traceEvent,
|
|
7823
|
+
(researchEventCounts.get(traceEvent) ?? 0) + 1
|
|
7824
|
+
);
|
|
7825
|
+
}
|
|
7761
7826
|
if (test.ml && signal && typeof signal !== "string" && signal.signalId) {
|
|
7762
7827
|
pendingMlPayloadBySignalId.set(
|
|
7763
7828
|
signal.signalId,
|
|
@@ -7795,7 +7860,7 @@ var createBacktestSession = async ({
|
|
|
7795
7860
|
}
|
|
7796
7861
|
};
|
|
7797
7862
|
const flush = async () => {
|
|
7798
|
-
if (!test.ml && !test.ai) return;
|
|
7863
|
+
if (!test.ml && !test.ai && !test.researchTrace) return;
|
|
7799
7864
|
const batch = await testConnector.drainMlResultsBatch();
|
|
7800
7865
|
for (const resultRecord of batch) {
|
|
7801
7866
|
const payload = pendingMlPayloadBySignalId.get(resultRecord.signalId);
|
|
@@ -7824,9 +7889,63 @@ var createBacktestSession = async ({
|
|
|
7824
7889
|
...rowBase,
|
|
7825
7890
|
payload: buildAiPayload(aiSignal),
|
|
7826
7891
|
profit: resultRecord.profit,
|
|
7827
|
-
tradeResult: resultRecord.tradeResult
|
|
7892
|
+
tradeResult: resultRecord.tradeResult,
|
|
7893
|
+
research: {
|
|
7894
|
+
schema: "tradejs-core-research-row/v1",
|
|
7895
|
+
...resolveCoreResearchSetupIdentity(aiSignal)
|
|
7896
|
+
}
|
|
7897
|
+
}
|
|
7898
|
+
});
|
|
7899
|
+
}
|
|
7900
|
+
const researchSetup = pendingResearchSetupBySignalId.get(
|
|
7901
|
+
resultRecord.signalId
|
|
7902
|
+
);
|
|
7903
|
+
if (test.researchTrace && researchSetup && resultRecord.tradeResult) {
|
|
7904
|
+
pendingResearchSetupBySignalId.delete(resultRecord.signalId);
|
|
7905
|
+
await (0, import_coreResearch.appendCoreResearchTraceEvent)({
|
|
7906
|
+
strategyName: test.strategyName,
|
|
7907
|
+
chunkId,
|
|
7908
|
+
event: {
|
|
7909
|
+
schema: "tradejs-core-research-trace/v1",
|
|
7910
|
+
event: "entry_executed",
|
|
7911
|
+
timestamp: resultRecord.tradeResult.entryTimestamp,
|
|
7912
|
+
strategy: test.strategyName,
|
|
7913
|
+
symbol: test.symbol,
|
|
7914
|
+
direction: resultRecord.tradeResult.direction,
|
|
7915
|
+
signalId: resultRecord.signalId,
|
|
7916
|
+
configId: test.configId,
|
|
7917
|
+
backtestRunId: test.backtestRunId,
|
|
7918
|
+
backtestTestKey: test.backtestTestKey,
|
|
7919
|
+
...researchSetup
|
|
7828
7920
|
}
|
|
7829
7921
|
});
|
|
7922
|
+
researchEventCounts.set(
|
|
7923
|
+
"entry_executed",
|
|
7924
|
+
(researchEventCounts.get("entry_executed") ?? 0) + 1
|
|
7925
|
+
);
|
|
7926
|
+
await (0, import_coreResearch.appendCoreResearchTraceEvent)({
|
|
7927
|
+
strategyName: test.strategyName,
|
|
7928
|
+
chunkId,
|
|
7929
|
+
event: {
|
|
7930
|
+
schema: "tradejs-core-research-trace/v1",
|
|
7931
|
+
event: "position_exited",
|
|
7932
|
+
timestamp: resultRecord.tradeResult.exitTimestamp,
|
|
7933
|
+
strategy: test.strategyName,
|
|
7934
|
+
symbol: test.symbol,
|
|
7935
|
+
direction: resultRecord.tradeResult.direction,
|
|
7936
|
+
signalId: resultRecord.signalId,
|
|
7937
|
+
configId: test.configId,
|
|
7938
|
+
backtestRunId: test.backtestRunId,
|
|
7939
|
+
backtestTestKey: test.backtestTestKey,
|
|
7940
|
+
netProfit: resultRecord.tradeResult.netProfit,
|
|
7941
|
+
exitReason: resultRecord.tradeResult.exitReason,
|
|
7942
|
+
...researchSetup
|
|
7943
|
+
}
|
|
7944
|
+
});
|
|
7945
|
+
researchEventCounts.set(
|
|
7946
|
+
"position_exited",
|
|
7947
|
+
(researchEventCounts.get("position_exited") ?? 0) + 1
|
|
7948
|
+
);
|
|
7830
7949
|
}
|
|
7831
7950
|
}
|
|
7832
7951
|
};
|
|
@@ -7860,15 +7979,41 @@ var createBacktestSession = async ({
|
|
|
7860
7979
|
flush: () => monitor.run("flush closed results", flush),
|
|
7861
7980
|
result: async () => {
|
|
7862
7981
|
await monitor.run("flush closed results", flush);
|
|
7982
|
+
if (test.researchTrace) {
|
|
7983
|
+
await (0, import_coreResearch.appendCoreResearchTraceEvent)({
|
|
7984
|
+
strategyName: test.strategyName,
|
|
7985
|
+
chunkId,
|
|
7986
|
+
event: {
|
|
7987
|
+
schema: "tradejs-core-research-trace/v1",
|
|
7988
|
+
event: "skip_summary",
|
|
7989
|
+
timestamp: test.options.end ?? test.options.start ?? 0,
|
|
7990
|
+
strategy: test.strategyName,
|
|
7991
|
+
symbol: test.symbol,
|
|
7992
|
+
configId: test.configId,
|
|
7993
|
+
backtestRunId: test.backtestRunId,
|
|
7994
|
+
backtestTestKey: test.backtestTestKey,
|
|
7995
|
+
skipCounts: Object.fromEntries(
|
|
7996
|
+
[...researchSkipCounts.entries()].sort(
|
|
7997
|
+
([left], [right]) => left < right ? -1 : left > right ? 1 : 0
|
|
7998
|
+
)
|
|
7999
|
+
)
|
|
8000
|
+
}
|
|
8001
|
+
});
|
|
8002
|
+
}
|
|
7863
8003
|
const result = await monitor.run(
|
|
7864
8004
|
"collect result",
|
|
7865
8005
|
() => testConnector.getResult()
|
|
7866
8006
|
);
|
|
8007
|
+
const researchTraceSummary = test.researchTrace ? {
|
|
8008
|
+
events: Object.fromEntries(researchEventCounts),
|
|
8009
|
+
skipCounts: Object.fromEntries(researchSkipCounts)
|
|
8010
|
+
} : void 0;
|
|
7867
8011
|
return replayEvaluations ? {
|
|
7868
8012
|
...result,
|
|
7869
8013
|
warningCounts,
|
|
7870
|
-
inlineReplaySignalEvaluations: replayEvaluations
|
|
7871
|
-
|
|
8014
|
+
inlineReplaySignalEvaluations: replayEvaluations,
|
|
8015
|
+
researchTraceSummary
|
|
8016
|
+
} : { ...result, warningCounts, researchTraceSummary };
|
|
7872
8017
|
}
|
|
7873
8018
|
};
|
|
7874
8019
|
};
|
package/dist/backtest.mjs
CHANGED
|
@@ -142,6 +142,7 @@ import {
|
|
|
142
142
|
BACKTEST_WARNING_CODES
|
|
143
143
|
} from "@tradejs/types";
|
|
144
144
|
import { appendAiDatasetRow } from "@tradejs/infra/ai";
|
|
145
|
+
import { appendCoreResearchTraceEvent } from "@tradejs/infra/coreResearch";
|
|
145
146
|
import {
|
|
146
147
|
appendMlDatasetRow,
|
|
147
148
|
buildMlTrainingRow,
|
|
@@ -1256,6 +1257,37 @@ var createTestConnector = (connector, context) => {
|
|
|
1256
1257
|
};
|
|
1257
1258
|
};
|
|
1258
1259
|
|
|
1260
|
+
// src/backtest/researchTrace.ts
|
|
1261
|
+
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1262
|
+
var findSetupKey = (value, depth = 0) => {
|
|
1263
|
+
if (depth > 5) return null;
|
|
1264
|
+
const record = asRecord(value);
|
|
1265
|
+
if (!record) return null;
|
|
1266
|
+
for (const key of ["setupIdentity", "setupId", "patternId", "candidateId"]) {
|
|
1267
|
+
const candidate = record[key];
|
|
1268
|
+
if (typeof candidate === "string" && candidate.trim() || typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
1269
|
+
return `${key}:${String(candidate)}`;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
for (const [key, nested] of Object.entries(record)) {
|
|
1273
|
+
if (/context|signal|setup|pattern|candidate|strategy/i.test(key) && nested != null) {
|
|
1274
|
+
const found = findSetupKey(nested, depth + 1);
|
|
1275
|
+
if (found) return found;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
return null;
|
|
1279
|
+
};
|
|
1280
|
+
var resolveCoreResearchSetupIdentity = (signal) => {
|
|
1281
|
+
const strategyKey = findSetupKey(signal.additionalIndicators);
|
|
1282
|
+
return strategyKey ? {
|
|
1283
|
+
setupIdentity: `${signal.strategy}|${signal.symbol}|${signal.direction}|${strategyKey}`,
|
|
1284
|
+
setupIdentitySource: "strategy-context"
|
|
1285
|
+
} : {
|
|
1286
|
+
setupIdentity: `${signal.strategy}|${signal.symbol}|${signal.direction}|${signal.timestamp}`,
|
|
1287
|
+
setupIdentitySource: "signal-time-fallback"
|
|
1288
|
+
};
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1259
1291
|
// src/backtest/session.ts
|
|
1260
1292
|
var createWarningCounts = () => ({
|
|
1261
1293
|
[BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY]: 0
|
|
@@ -1345,7 +1377,7 @@ var createBacktestSession = async ({
|
|
|
1345
1377
|
const testConnector = createTestConnector(connector, {
|
|
1346
1378
|
userName: test.userName,
|
|
1347
1379
|
mlEnabled: test.ml,
|
|
1348
|
-
aiEnabled: test.ai,
|
|
1380
|
+
aiEnabled: Boolean(test.ai || test.researchTrace),
|
|
1349
1381
|
fastMode: test.fast,
|
|
1350
1382
|
instrument,
|
|
1351
1383
|
executionCostModel,
|
|
@@ -1382,11 +1414,18 @@ var createBacktestSession = async ({
|
|
|
1382
1414
|
);
|
|
1383
1415
|
const pendingMlPayloadBySignalId = /* @__PURE__ */ new Map();
|
|
1384
1416
|
const pendingAiRowBySignalId = /* @__PURE__ */ new Map();
|
|
1417
|
+
const pendingResearchSetupBySignalId = /* @__PURE__ */ new Map();
|
|
1418
|
+
const researchSkipCounts = /* @__PURE__ */ new Map();
|
|
1419
|
+
const researchEventCounts = /* @__PURE__ */ new Map();
|
|
1385
1420
|
const warningCounts = createWarningCounts();
|
|
1386
1421
|
const replayEvaluations = test.collectReplaySignalEvaluations ? [] : null;
|
|
1387
1422
|
const chunkId = test.chunkId ?? "single";
|
|
1388
1423
|
const processSignal = async (signal, candle) => {
|
|
1389
1424
|
recordSignalWarning(warningCounts, signal);
|
|
1425
|
+
if (test.researchTrace && typeof signal === "string") {
|
|
1426
|
+
const reason = signal.trim() || "NO_SIGNAL";
|
|
1427
|
+
researchSkipCounts.set(reason, (researchSkipCounts.get(reason) ?? 0) + 1);
|
|
1428
|
+
}
|
|
1390
1429
|
if (typeof signal === "string" && signal.startsWith("BACKTEST_ENTRY_DELAY_")) {
|
|
1391
1430
|
return;
|
|
1392
1431
|
}
|
|
@@ -1403,6 +1442,32 @@ var createBacktestSession = async ({
|
|
|
1403
1442
|
onStageStart: monitor.contextStage
|
|
1404
1443
|
});
|
|
1405
1444
|
}
|
|
1445
|
+
if (test.researchTrace && signal && typeof signal !== "string" && signal.signalId) {
|
|
1446
|
+
const identity = resolveCoreResearchSetupIdentity(signal);
|
|
1447
|
+
pendingResearchSetupBySignalId.set(signal.signalId, identity);
|
|
1448
|
+
await appendCoreResearchTraceEvent({
|
|
1449
|
+
strategyName: test.strategyName,
|
|
1450
|
+
chunkId,
|
|
1451
|
+
event: {
|
|
1452
|
+
schema: "tradejs-core-research-trace/v1",
|
|
1453
|
+
event: signal.orderStatus === "failed" || signal.orderStatus === "skipped" ? "entry_rejected" : "signal_emitted",
|
|
1454
|
+
timestamp: signal.timestamp,
|
|
1455
|
+
strategy: signal.strategy || test.strategyName,
|
|
1456
|
+
symbol: signal.symbol || test.symbol,
|
|
1457
|
+
direction: signal.direction,
|
|
1458
|
+
signalId: signal.signalId,
|
|
1459
|
+
configId: test.configId,
|
|
1460
|
+
backtestRunId: test.backtestRunId,
|
|
1461
|
+
backtestTestKey: test.backtestTestKey,
|
|
1462
|
+
...identity
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
const traceEvent = signal.orderStatus === "failed" || signal.orderStatus === "skipped" ? "entry_rejected" : "signal_emitted";
|
|
1466
|
+
researchEventCounts.set(
|
|
1467
|
+
traceEvent,
|
|
1468
|
+
(researchEventCounts.get(traceEvent) ?? 0) + 1
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1406
1471
|
if (test.ml && signal && typeof signal !== "string" && signal.signalId) {
|
|
1407
1472
|
pendingMlPayloadBySignalId.set(
|
|
1408
1473
|
signal.signalId,
|
|
@@ -1440,7 +1505,7 @@ var createBacktestSession = async ({
|
|
|
1440
1505
|
}
|
|
1441
1506
|
};
|
|
1442
1507
|
const flush = async () => {
|
|
1443
|
-
if (!test.ml && !test.ai) return;
|
|
1508
|
+
if (!test.ml && !test.ai && !test.researchTrace) return;
|
|
1444
1509
|
const batch = await testConnector.drainMlResultsBatch();
|
|
1445
1510
|
for (const resultRecord of batch) {
|
|
1446
1511
|
const payload = pendingMlPayloadBySignalId.get(resultRecord.signalId);
|
|
@@ -1469,10 +1534,64 @@ var createBacktestSession = async ({
|
|
|
1469
1534
|
...rowBase,
|
|
1470
1535
|
payload: buildAiPayload(aiSignal),
|
|
1471
1536
|
profit: resultRecord.profit,
|
|
1472
|
-
tradeResult: resultRecord.tradeResult
|
|
1537
|
+
tradeResult: resultRecord.tradeResult,
|
|
1538
|
+
research: {
|
|
1539
|
+
schema: "tradejs-core-research-row/v1",
|
|
1540
|
+
...resolveCoreResearchSetupIdentity(aiSignal)
|
|
1541
|
+
}
|
|
1473
1542
|
}
|
|
1474
1543
|
});
|
|
1475
1544
|
}
|
|
1545
|
+
const researchSetup = pendingResearchSetupBySignalId.get(
|
|
1546
|
+
resultRecord.signalId
|
|
1547
|
+
);
|
|
1548
|
+
if (test.researchTrace && researchSetup && resultRecord.tradeResult) {
|
|
1549
|
+
pendingResearchSetupBySignalId.delete(resultRecord.signalId);
|
|
1550
|
+
await appendCoreResearchTraceEvent({
|
|
1551
|
+
strategyName: test.strategyName,
|
|
1552
|
+
chunkId,
|
|
1553
|
+
event: {
|
|
1554
|
+
schema: "tradejs-core-research-trace/v1",
|
|
1555
|
+
event: "entry_executed",
|
|
1556
|
+
timestamp: resultRecord.tradeResult.entryTimestamp,
|
|
1557
|
+
strategy: test.strategyName,
|
|
1558
|
+
symbol: test.symbol,
|
|
1559
|
+
direction: resultRecord.tradeResult.direction,
|
|
1560
|
+
signalId: resultRecord.signalId,
|
|
1561
|
+
configId: test.configId,
|
|
1562
|
+
backtestRunId: test.backtestRunId,
|
|
1563
|
+
backtestTestKey: test.backtestTestKey,
|
|
1564
|
+
...researchSetup
|
|
1565
|
+
}
|
|
1566
|
+
});
|
|
1567
|
+
researchEventCounts.set(
|
|
1568
|
+
"entry_executed",
|
|
1569
|
+
(researchEventCounts.get("entry_executed") ?? 0) + 1
|
|
1570
|
+
);
|
|
1571
|
+
await appendCoreResearchTraceEvent({
|
|
1572
|
+
strategyName: test.strategyName,
|
|
1573
|
+
chunkId,
|
|
1574
|
+
event: {
|
|
1575
|
+
schema: "tradejs-core-research-trace/v1",
|
|
1576
|
+
event: "position_exited",
|
|
1577
|
+
timestamp: resultRecord.tradeResult.exitTimestamp,
|
|
1578
|
+
strategy: test.strategyName,
|
|
1579
|
+
symbol: test.symbol,
|
|
1580
|
+
direction: resultRecord.tradeResult.direction,
|
|
1581
|
+
signalId: resultRecord.signalId,
|
|
1582
|
+
configId: test.configId,
|
|
1583
|
+
backtestRunId: test.backtestRunId,
|
|
1584
|
+
backtestTestKey: test.backtestTestKey,
|
|
1585
|
+
netProfit: resultRecord.tradeResult.netProfit,
|
|
1586
|
+
exitReason: resultRecord.tradeResult.exitReason,
|
|
1587
|
+
...researchSetup
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
researchEventCounts.set(
|
|
1591
|
+
"position_exited",
|
|
1592
|
+
(researchEventCounts.get("position_exited") ?? 0) + 1
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1476
1595
|
}
|
|
1477
1596
|
};
|
|
1478
1597
|
return {
|
|
@@ -1505,15 +1624,41 @@ var createBacktestSession = async ({
|
|
|
1505
1624
|
flush: () => monitor.run("flush closed results", flush),
|
|
1506
1625
|
result: async () => {
|
|
1507
1626
|
await monitor.run("flush closed results", flush);
|
|
1627
|
+
if (test.researchTrace) {
|
|
1628
|
+
await appendCoreResearchTraceEvent({
|
|
1629
|
+
strategyName: test.strategyName,
|
|
1630
|
+
chunkId,
|
|
1631
|
+
event: {
|
|
1632
|
+
schema: "tradejs-core-research-trace/v1",
|
|
1633
|
+
event: "skip_summary",
|
|
1634
|
+
timestamp: test.options.end ?? test.options.start ?? 0,
|
|
1635
|
+
strategy: test.strategyName,
|
|
1636
|
+
symbol: test.symbol,
|
|
1637
|
+
configId: test.configId,
|
|
1638
|
+
backtestRunId: test.backtestRunId,
|
|
1639
|
+
backtestTestKey: test.backtestTestKey,
|
|
1640
|
+
skipCounts: Object.fromEntries(
|
|
1641
|
+
[...researchSkipCounts.entries()].sort(
|
|
1642
|
+
([left], [right]) => left < right ? -1 : left > right ? 1 : 0
|
|
1643
|
+
)
|
|
1644
|
+
)
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1508
1648
|
const result = await monitor.run(
|
|
1509
1649
|
"collect result",
|
|
1510
1650
|
() => testConnector.getResult()
|
|
1511
1651
|
);
|
|
1652
|
+
const researchTraceSummary = test.researchTrace ? {
|
|
1653
|
+
events: Object.fromEntries(researchEventCounts),
|
|
1654
|
+
skipCounts: Object.fromEntries(researchSkipCounts)
|
|
1655
|
+
} : void 0;
|
|
1512
1656
|
return replayEvaluations ? {
|
|
1513
1657
|
...result,
|
|
1514
1658
|
warningCounts,
|
|
1515
|
-
inlineReplaySignalEvaluations: replayEvaluations
|
|
1516
|
-
|
|
1659
|
+
inlineReplaySignalEvaluations: replayEvaluations,
|
|
1660
|
+
researchTraceSummary
|
|
1661
|
+
} : { ...result, warningCounts, researchTraceSummary };
|
|
1517
1662
|
}
|
|
1518
1663
|
};
|
|
1519
1664
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradejs/node",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.20",
|
|
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",
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@langchain/core": "^1.2.3",
|
|
69
69
|
"@langchain/openai": "^1.5.5",
|
|
70
|
-
"@tradejs/core": "^2.0.
|
|
71
|
-
"@tradejs/infra": "^2.0.
|
|
72
|
-
"@tradejs/types": "^2.0.
|
|
70
|
+
"@tradejs/core": "^2.0.20",
|
|
71
|
+
"@tradejs/infra": "^2.0.20",
|
|
72
|
+
"@tradejs/types": "^2.0.20",
|
|
73
73
|
"chalk": "4.1.2",
|
|
74
74
|
"ioredis": "5.11.1",
|
|
75
75
|
"lodash": "^4.18.1",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"tsconfig-paths": "^4.2.0"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@tradejs/strategies": "^2.0.
|
|
83
|
+
"@tradejs/strategies": "^2.0.20",
|
|
84
84
|
"@types/node": "^24",
|
|
85
85
|
"tsup": "^8.5.1",
|
|
86
86
|
"typescript": "^5.9"
|