@tradejs/cli 1.0.9 → 1.0.10
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/cli.js +29497 -13537
- package/dist/lib/aiPocketSearch.js +1316 -0
- package/dist/lib/aiTrainCharts.js +711 -0
- package/dist/lib/aiTrainDataset.js +40 -0
- package/dist/lib/aiTrainEvaluationDump.js +76 -0
- package/dist/lib/aiTrainMetrics.js +148 -8
- package/dist/lib/aiTrainOptions.js +130 -0
- package/dist/lib/aiTrainQuarantine.js +199 -0
- package/dist/lib/aiTrainResearch.js +467 -0
- package/dist/lib/binanceMarketContextBackfill.js +841 -0
- package/dist/lib/binanceMarketData.js +392 -0
- package/dist/lib/coinMarketCapContextBackfill.js +1088 -0
- package/dist/lib/continuity.js +60 -0
- package/dist/lib/derivativesContextBackfill.js +490 -77
- package/dist/lib/executionCalibration.js +705 -0
- package/dist/lib/marketContextPrepare.js +4403 -0
- package/dist/lib/paritySummary.js +101 -0
- package/dist/lib/runBot.js +1 -0
- package/dist/lib/runEnvironment.js +2254 -0
- package/dist/lib/runFormatting.js +1657 -0
- package/dist/lib/runtimeDebugEvidence.js +526 -0
- package/dist/lib/runtimeModeConfig.js +53 -0
- package/dist/lib/runtimeParity.js +88 -11
- package/dist/lib/runtimeParityDetails.js +751 -0
- package/dist/lib/runtimeRedis.js +157 -0
- package/dist/lib/runtimeSignalsLoader.js +40 -6
- package/dist/lib/runtimeSignalsStorage.js +45 -22
- package/dist/lib/runtimeStrategyBacktest.js +134 -0
- package/dist/lib/runtimeTradeSync.js +263 -0
- package/dist/lib/telegramReports.js +9 -0
- package/dist/lib/tickerUniverseCache.js +116 -0
- package/dist/lib/timeWindow.js +3 -3
- package/dist/scripts/agentRun.js +14 -8
- package/dist/scripts/aiExport.js +1721 -7
- package/dist/scripts/aiExportSelect.js +16 -4
- package/dist/scripts/aiPocketSearch.js +4190 -0
- package/dist/scripts/aiTrain.js +1866 -87
- package/dist/scripts/backtest.js +4509 -818
- package/dist/scripts/binanceMarketIngest.js +565 -0
- package/dist/scripts/bot.js +16 -2
- package/dist/scripts/candlesMigrateProvider.js +32 -22
- package/dist/scripts/cleanDir.js +15 -2
- package/dist/scripts/cleanRedis.js +15 -2
- package/dist/scripts/cleanTests.js +38 -2
- package/dist/scripts/cleanupMarketContext.js +69 -0
- package/dist/scripts/continuity.js +148 -67
- package/dist/scripts/derivativesIngest.js +14 -4
- package/dist/scripts/derivativesIngestCoinalyzeAll.js +14 -4
- package/dist/scripts/doctor.js +14 -4
- package/dist/scripts/executionCalibration.js +792 -0
- package/dist/scripts/infraCommon.js +10 -0
- package/dist/scripts/infraDown.js +14 -4
- package/dist/scripts/infraInit.js +24 -4
- package/dist/scripts/infraUp.js +14 -4
- package/dist/scripts/marketWs.js +222 -0
- package/dist/scripts/migration.js +15 -2
- package/dist/scripts/mlExport.js +1698 -10
- package/dist/scripts/mlExportSelect.js +16 -4
- package/dist/scripts/mlInspect.js +13 -3
- package/dist/scripts/mlTrainLatestSelect.js +14 -4
- package/dist/scripts/replay.js +8824 -0
- package/dist/scripts/replayRunner.js +8827 -0
- package/dist/scripts/replayRuntimeEvidence.js +868 -0
- package/dist/scripts/researchAuto.js +286 -59
- package/dist/scripts/results.js +3 -3
- package/dist/scripts/runtimeEvidence.js +605 -0
- package/dist/scripts/runtimeParity.js +6362 -1043
- package/dist/scripts/serverHealth.js +588 -0
- package/dist/scripts/signals.js +5590 -511
- package/dist/scripts/signalsDaemon.js +6167 -0
- package/dist/scripts/signalsSummary.js +833 -248
- package/dist/scripts/test-ml.js +25 -3
- package/dist/scripts/test.js +26 -1
- package/dist/scripts/user-add.js +15 -2
- package/dist/workers/testerWorker.js +130 -21
- package/package.json +18 -12
|
@@ -0,0 +1,868 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/scripts/replayRuntimeEvidence.ts
|
|
31
|
+
var replayRuntimeEvidence_exports = {};
|
|
32
|
+
__export(replayRuntimeEvidence_exports, {
|
|
33
|
+
main: () => main,
|
|
34
|
+
replayRuntimeEvidence: () => replayRuntimeEvidence
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(replayRuntimeEvidence_exports);
|
|
37
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
38
|
+
var import_node_path = __toESM(require("path"));
|
|
39
|
+
var import_args = __toESM(require("args"));
|
|
40
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
41
|
+
var import_redis4 = require("@tradejs/infra/redis");
|
|
42
|
+
|
|
43
|
+
// src/lib/runtimeDebugEvidence.ts
|
|
44
|
+
var import_redis3 = require("@tradejs/infra/redis");
|
|
45
|
+
|
|
46
|
+
// src/lib/runtimeRedis.ts
|
|
47
|
+
var import_redis = require("@tradejs/infra/redis");
|
|
48
|
+
var import_time = require("@tradejs/core/time");
|
|
49
|
+
var RESERVED_STRATEGY_NAMES = /* @__PURE__ */ new Set(["charts"]);
|
|
50
|
+
var RESERVED_CONFIG_IDS = /* @__PURE__ */ new Set(["results"]);
|
|
51
|
+
var resolveStrategyConfigIdentityByKey = (userName, key) => {
|
|
52
|
+
const parts = key.split(":");
|
|
53
|
+
if (parts.length !== 5) return null;
|
|
54
|
+
const [users, keyUserName, strategiesKey, strategyName, configId] = parts;
|
|
55
|
+
if (users !== "users" || keyUserName !== userName || strategiesKey !== "strategies" || !strategyName || !configId || RESERVED_STRATEGY_NAMES.has(strategyName) || RESERVED_CONFIG_IDS.has(configId) || !/^[a-zA-Z0-9_-]+$/.test(configId)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return { strategyName, configId };
|
|
59
|
+
};
|
|
60
|
+
var isRuntimeTradeRecord = (value) => {
|
|
61
|
+
if (!value || typeof value !== "object") {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const record = value;
|
|
65
|
+
return typeof record.orderId === "string" && typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.entryTimestamp === "number" && typeof record.entryPrice === "number" && typeof record.qty === "number" && (record.direction === "LONG" || record.direction === "SHORT");
|
|
66
|
+
};
|
|
67
|
+
var resolveStrategyNameByConfigKey = (userName, key) => {
|
|
68
|
+
return resolveStrategyConfigIdentityByKey(userName, key)?.strategyName ?? null;
|
|
69
|
+
};
|
|
70
|
+
var getRuntimeStrategyConfigKeys = async (userName) => {
|
|
71
|
+
const keys = await (0, import_redis.getKeys)(`${import_redis.redisKeys.strategies(userName)}:`);
|
|
72
|
+
return keys.filter((key) => resolveStrategyConfigIdentityByKey(userName, key)).sort((left, right) => left.localeCompare(right));
|
|
73
|
+
};
|
|
74
|
+
var loadRuntimeStrategyConfigs = async (userName, {
|
|
75
|
+
onInvalidConfig
|
|
76
|
+
} = {}) => {
|
|
77
|
+
const configKeys = await getRuntimeStrategyConfigKeys(userName);
|
|
78
|
+
const strategyConfigs = await Promise.all(
|
|
79
|
+
configKeys.map(async (key) => {
|
|
80
|
+
const strategyName = resolveStrategyNameByConfigKey(userName, key);
|
|
81
|
+
const identity = resolveStrategyConfigIdentityByKey(userName, key);
|
|
82
|
+
if (!strategyName || !identity) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const strategyConfig = await (0, import_redis.getData)(
|
|
86
|
+
key,
|
|
87
|
+
null
|
|
88
|
+
);
|
|
89
|
+
if (!strategyConfig || typeof strategyConfig !== "object" || Array.isArray(strategyConfig)) {
|
|
90
|
+
onInvalidConfig?.(key);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
key,
|
|
95
|
+
strategyName,
|
|
96
|
+
configId: identity.configId,
|
|
97
|
+
strategyConfig
|
|
98
|
+
};
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
return strategyConfigs.filter(Boolean);
|
|
102
|
+
};
|
|
103
|
+
var loadRuntimeTrades = async (userName, {
|
|
104
|
+
startTime,
|
|
105
|
+
endTime
|
|
106
|
+
} = {}) => {
|
|
107
|
+
const hasWindow = Number.isFinite(startTime) && Number.isFinite(endTime) && startTime != null && endTime != null;
|
|
108
|
+
const filterByWindow = (trade) => !hasWindow || trade.entryTimestamp >= startTime && trade.entryTimestamp <= endTime;
|
|
109
|
+
if (hasWindow) {
|
|
110
|
+
const dayKeys = (0, import_time.getRuntimeStorageDayKeys)(startTime, endTime);
|
|
111
|
+
const bucketTrades = (await Promise.all(
|
|
112
|
+
dayKeys.map(
|
|
113
|
+
(dayKey) => (0, import_redis.getHashJsonValues)(
|
|
114
|
+
import_redis.redisKeys.runtimeTradeBucket(userName, dayKey)
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
)).flat();
|
|
118
|
+
const dedupedBucketTrades = /* @__PURE__ */ new Map();
|
|
119
|
+
for (const trade of bucketTrades) {
|
|
120
|
+
if (!isRuntimeTradeRecord(trade)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
dedupedBucketTrades.set(trade.orderId, trade);
|
|
124
|
+
}
|
|
125
|
+
const windowedTrades = [...dedupedBucketTrades.values()].filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
|
|
126
|
+
if (windowedTrades.length > 0 || dayKeys.length === 0) {
|
|
127
|
+
return windowedTrades;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const tradePrefix = import_redis.redisKeys.runtimeTrades(userName);
|
|
131
|
+
const bucketPrefix = import_redis.redisKeys.runtimeTradeBuckets(userName);
|
|
132
|
+
const keys = (await (0, import_redis.getKeys)(tradePrefix)).filter(
|
|
133
|
+
(key) => !key.startsWith(bucketPrefix)
|
|
134
|
+
);
|
|
135
|
+
const trades = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
|
|
136
|
+
return trades.filter(isRuntimeTradeRecord).filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// src/lib/runtimeSignalsLoader.ts
|
|
140
|
+
var import_redis2 = require("@tradejs/infra/redis");
|
|
141
|
+
|
|
142
|
+
// src/lib/runtimeSignalsStorage.ts
|
|
143
|
+
var import_constants = require("@tradejs/core/constants");
|
|
144
|
+
var import_time2 = require("@tradejs/core/time");
|
|
145
|
+
var isRuntimeSignalBucketRef = (value) => {
|
|
146
|
+
if (!value || typeof value !== "object") {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
const record = value;
|
|
150
|
+
return typeof record.signalId === "string" && typeof record.symbol === "string" && typeof record.strategy === "string" && typeof record.timestamp === "number";
|
|
151
|
+
};
|
|
152
|
+
var toCount = (value) => {
|
|
153
|
+
const parsed = Number.parseInt(String(value ?? "0"), 10);
|
|
154
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
155
|
+
};
|
|
156
|
+
var parseRuntimeSignalStatsBucket = (value) => {
|
|
157
|
+
const reasonGroups = /* @__PURE__ */ new Map();
|
|
158
|
+
for (const [field, rawCount] of Object.entries(value)) {
|
|
159
|
+
if (!field.startsWith("reason:")) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const [, source, ...reasonParts] = field.split(":");
|
|
163
|
+
const reason = reasonParts.join(":");
|
|
164
|
+
if (!source || !reason) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const reasons = reasonGroups.get(source) ?? /* @__PURE__ */ new Map();
|
|
168
|
+
reasons.set(reason, toCount(rawCount));
|
|
169
|
+
reasonGroups.set(source, reasons);
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
evaluated: toCount(value.evaluated),
|
|
173
|
+
signals: toCount(value.signals),
|
|
174
|
+
reasonGroups
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/lib/runtimeSignalsLoader.ts
|
|
179
|
+
var isSignalRecord = (value) => {
|
|
180
|
+
if (!value || typeof value !== "object") {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
const record = value;
|
|
184
|
+
return typeof record.signalId === "string" && typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.timestamp === "number";
|
|
185
|
+
};
|
|
186
|
+
var isRuntimeSignalEvaluationRecord = (value) => {
|
|
187
|
+
if (!value || typeof value !== "object") {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
const record = value;
|
|
191
|
+
return typeof record.evaluationId === "string" && typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.timestamp === "number" && (record.status === "signal" || record.status === "skip" || record.status === "error");
|
|
192
|
+
};
|
|
193
|
+
var sortByTimestamp = (records) => [...records].sort((left, right) => left.timestamp - right.timestamp);
|
|
194
|
+
var filterBucketKeysByWindow = (bucketKeys, prefix, startTime, endTime) => {
|
|
195
|
+
if (!Number.isFinite(startTime) || !Number.isFinite(endTime) || startTime == null || endTime == null) {
|
|
196
|
+
return bucketKeys;
|
|
197
|
+
}
|
|
198
|
+
const allowedDayKeys = new Set((0, import_time2.getRuntimeStorageDayKeys)(startTime, endTime));
|
|
199
|
+
if (!allowedDayKeys.size) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
return bucketKeys.filter((key) => {
|
|
203
|
+
const suffix = key.slice(prefix.length);
|
|
204
|
+
const firstColon = suffix.indexOf(":");
|
|
205
|
+
if (firstColon <= 0) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
return allowedDayKeys.has(suffix.slice(0, firstColon));
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
var loadRuntimeSignals = async (userName, {
|
|
212
|
+
startTime,
|
|
213
|
+
endTime
|
|
214
|
+
} = {}) => {
|
|
215
|
+
const bucketPrefix = import_redis2.redisKeys.runtimeSignalBuckets(userName);
|
|
216
|
+
const bucketKeys = filterBucketKeysByWindow(
|
|
217
|
+
await (0, import_redis2.getKeys)(bucketPrefix),
|
|
218
|
+
bucketPrefix,
|
|
219
|
+
startTime,
|
|
220
|
+
endTime
|
|
221
|
+
);
|
|
222
|
+
const refs = (await Promise.all(bucketKeys.map((key) => (0, import_redis2.getHashJsonValues)(key)))).flat().filter(isRuntimeSignalBucketRef);
|
|
223
|
+
const seen = /* @__PURE__ */ new Set();
|
|
224
|
+
const payloads = await Promise.all(
|
|
225
|
+
refs.map(async (ref) => {
|
|
226
|
+
const dedupeKey = `${ref.symbol}:${ref.signalId}`;
|
|
227
|
+
if (seen.has(dedupeKey)) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
seen.add(dedupeKey);
|
|
231
|
+
const signal = await (0, import_redis2.getData)(
|
|
232
|
+
import_redis2.redisKeys.storeSignal(ref.symbol, ref.signalId),
|
|
233
|
+
null
|
|
234
|
+
);
|
|
235
|
+
return isSignalRecord(signal) ? signal : null;
|
|
236
|
+
})
|
|
237
|
+
);
|
|
238
|
+
return sortByTimestamp(payloads.filter(isSignalRecord));
|
|
239
|
+
};
|
|
240
|
+
var loadRuntimeSignalEvaluations = async (userName, {
|
|
241
|
+
startTime,
|
|
242
|
+
endTime
|
|
243
|
+
} = {}) => {
|
|
244
|
+
const bucketPrefix = import_redis2.redisKeys.runtimeSignalEvaluationBuckets(userName);
|
|
245
|
+
const bucketKeys = filterBucketKeysByWindow(
|
|
246
|
+
await (0, import_redis2.getKeys)(bucketPrefix),
|
|
247
|
+
bucketPrefix,
|
|
248
|
+
startTime,
|
|
249
|
+
endTime
|
|
250
|
+
);
|
|
251
|
+
const evaluations = (await Promise.all(bucketKeys.map((key) => (0, import_redis2.getHashJsonValues)(key)))).flat().filter(isRuntimeSignalEvaluationRecord);
|
|
252
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
253
|
+
for (const evaluation of evaluations) {
|
|
254
|
+
deduped.set(evaluation.evaluationId, evaluation);
|
|
255
|
+
}
|
|
256
|
+
return sortByTimestamp([...deduped.values()]);
|
|
257
|
+
};
|
|
258
|
+
var parseBucketKey = (prefix, key) => {
|
|
259
|
+
if (!key.startsWith(prefix)) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
const suffix = key.slice(prefix.length);
|
|
263
|
+
const firstColon = suffix.indexOf(":");
|
|
264
|
+
if (firstColon <= 0 || firstColon >= suffix.length - 1) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
dayKey: suffix.slice(0, firstColon),
|
|
269
|
+
strategy: suffix.slice(firstColon + 1)
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
var loadRuntimeSignalEvaluationStatsBuckets = async (userName) => {
|
|
273
|
+
const prefix = import_redis2.redisKeys.runtimeSignalEvaluationStatsBuckets(userName);
|
|
274
|
+
const keys = await (0, import_redis2.getKeys)(prefix);
|
|
275
|
+
const entries = await Promise.all(
|
|
276
|
+
keys.map(async (key) => {
|
|
277
|
+
const parsedKey = parseBucketKey(prefix, key);
|
|
278
|
+
if (!parsedKey) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
key,
|
|
283
|
+
...parsedKey,
|
|
284
|
+
stats: parseRuntimeSignalStatsBucket(await (0, import_redis2.getHashData)(key))
|
|
285
|
+
};
|
|
286
|
+
})
|
|
287
|
+
);
|
|
288
|
+
return entries.filter(Boolean);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// src/lib/runtimeDebugEvidence.ts
|
|
292
|
+
var RUNTIME_DEBUG_TIMEZONE = "Europe/Moscow";
|
|
293
|
+
var formatRuntimeDebugDateTime = (timestamp) => new Intl.DateTimeFormat("ru-RU", {
|
|
294
|
+
timeZone: RUNTIME_DEBUG_TIMEZONE,
|
|
295
|
+
year: "numeric",
|
|
296
|
+
month: "2-digit",
|
|
297
|
+
day: "2-digit",
|
|
298
|
+
hour: "2-digit",
|
|
299
|
+
minute: "2-digit",
|
|
300
|
+
hour12: false
|
|
301
|
+
}).format(new Date(timestamp));
|
|
302
|
+
var buildRuntimeDebugEvaluationId = ({
|
|
303
|
+
strategy,
|
|
304
|
+
symbol,
|
|
305
|
+
timestamp
|
|
306
|
+
}) => `${strategy}:${symbol}:${timestamp}`;
|
|
307
|
+
var filterByStrategies = (values, strategies) => {
|
|
308
|
+
if (!strategies?.length) {
|
|
309
|
+
return values;
|
|
310
|
+
}
|
|
311
|
+
const strategySet = new Set(strategies);
|
|
312
|
+
return values.filter((value) => strategySet.has(value.strategy));
|
|
313
|
+
};
|
|
314
|
+
var collectRuntimeDebugEvidence = async ({
|
|
315
|
+
userName,
|
|
316
|
+
startTime,
|
|
317
|
+
endTime,
|
|
318
|
+
strategies
|
|
319
|
+
}) => {
|
|
320
|
+
const dayKeys = (0, import_time2.getRuntimeStorageDayKeys)(startTime, endTime);
|
|
321
|
+
const dayKeySet = new Set(dayKeys);
|
|
322
|
+
const [trades, signals, evaluations, evaluationStatsBuckets, configs] = await Promise.all([
|
|
323
|
+
loadRuntimeTrades(userName, { startTime, endTime }),
|
|
324
|
+
loadRuntimeSignals(userName, { startTime, endTime }),
|
|
325
|
+
loadRuntimeSignalEvaluations(userName, { startTime, endTime }),
|
|
326
|
+
loadRuntimeSignalEvaluationStatsBuckets(userName),
|
|
327
|
+
loadRuntimeStrategyConfigs(userName)
|
|
328
|
+
]);
|
|
329
|
+
const filteredStats = evaluationStatsBuckets.filter(
|
|
330
|
+
(entry) => dayKeySet.has(entry.dayKey) && (!strategies?.length || strategies.includes(entry.strategy))
|
|
331
|
+
);
|
|
332
|
+
return {
|
|
333
|
+
dayKeys,
|
|
334
|
+
trades: filterByStrategies(trades, strategies),
|
|
335
|
+
signals: filterByStrategies(signals, strategies),
|
|
336
|
+
evaluations: filterByStrategies(evaluations, strategies),
|
|
337
|
+
evaluationStatsBuckets: filteredStats,
|
|
338
|
+
strategyConfigs: configs.filter(
|
|
339
|
+
(config) => !strategies?.length || strategies.includes(config.strategyName)
|
|
340
|
+
)
|
|
341
|
+
};
|
|
342
|
+
};
|
|
343
|
+
var buildRuntimeDebugReportPayload = async ({
|
|
344
|
+
userName,
|
|
345
|
+
startTime,
|
|
346
|
+
endTime,
|
|
347
|
+
signals,
|
|
348
|
+
evaluations,
|
|
349
|
+
trades,
|
|
350
|
+
strategyConfigs,
|
|
351
|
+
evaluationStatsBuckets
|
|
352
|
+
}) => {
|
|
353
|
+
const signalById = new Map(
|
|
354
|
+
signals.map((signal) => [signal.signalId, signal])
|
|
355
|
+
);
|
|
356
|
+
const evaluationById = new Map(
|
|
357
|
+
evaluations.map((evaluation) => [evaluation.evaluationId, evaluation])
|
|
358
|
+
);
|
|
359
|
+
const evaluationBySignalShape = new Map(
|
|
360
|
+
evaluations.map((evaluation) => [
|
|
361
|
+
buildRuntimeDebugEvaluationId(evaluation),
|
|
362
|
+
evaluation
|
|
363
|
+
])
|
|
364
|
+
);
|
|
365
|
+
const dayKeys = (0, import_time2.getRuntimeStorageDayKeys)(startTime, endTime);
|
|
366
|
+
const debugTrades = await Promise.all(
|
|
367
|
+
trades.map(async (trade) => {
|
|
368
|
+
const signal = trade.signalId != null ? signalById.get(trade.signalId) : void 0;
|
|
369
|
+
const evaluationId = buildRuntimeDebugEvaluationId({
|
|
370
|
+
strategy: trade.strategy,
|
|
371
|
+
symbol: trade.symbol,
|
|
372
|
+
timestamp: signal?.timestamp ?? trade.entryTimestamp
|
|
373
|
+
});
|
|
374
|
+
const knownEvaluation = evaluationById.get(evaluationId) ?? evaluationBySignalShape.get(evaluationId);
|
|
375
|
+
const tradeDayKey = (0, import_time2.getRuntimeStorageDayKey)(trade.entryTimestamp);
|
|
376
|
+
const signalDayKey = signal != null ? (0, import_time2.getRuntimeStorageDayKey)(signal.timestamp) : tradeDayKey;
|
|
377
|
+
const evaluationDayKey = knownEvaluation != null ? (0, import_time2.getRuntimeStorageDayKey)(knownEvaluation.timestamp) : signalDayKey;
|
|
378
|
+
const tradeKey = import_redis3.redisKeys.runtimeTrade(userName, trade.orderId);
|
|
379
|
+
const tradeBucketKey = import_redis3.redisKeys.runtimeTradeBucket(
|
|
380
|
+
userName,
|
|
381
|
+
tradeDayKey
|
|
382
|
+
);
|
|
383
|
+
const activeTradeKey = import_redis3.redisKeys.runtimeActiveTrade(
|
|
384
|
+
userName,
|
|
385
|
+
trade.symbol
|
|
386
|
+
);
|
|
387
|
+
const signalKey = trade.signalId != null ? import_redis3.redisKeys.storeSignal(trade.symbol, trade.signalId) : null;
|
|
388
|
+
const evaluationBucketKey = import_redis3.redisKeys.runtimeSignalEvaluationBucket(
|
|
389
|
+
userName,
|
|
390
|
+
evaluationDayKey,
|
|
391
|
+
knownEvaluation?.strategy ?? trade.strategy
|
|
392
|
+
);
|
|
393
|
+
const evaluationField = knownEvaluation?.evaluationId ?? evaluationId;
|
|
394
|
+
const evaluationKey = import_redis3.redisKeys.runtimeSignalEvaluation(
|
|
395
|
+
userName,
|
|
396
|
+
evaluationField
|
|
397
|
+
);
|
|
398
|
+
const [
|
|
399
|
+
tradeValue,
|
|
400
|
+
activeTradeValue,
|
|
401
|
+
signalValue,
|
|
402
|
+
directEvaluationValue,
|
|
403
|
+
bucketEvaluationValue
|
|
404
|
+
] = await Promise.all([
|
|
405
|
+
(0, import_redis3.getData)(tradeKey, null),
|
|
406
|
+
(0, import_redis3.getData)(activeTradeKey, null),
|
|
407
|
+
signalKey ? (0, import_redis3.getData)(signalKey, null) : Promise.resolve(null),
|
|
408
|
+
(0, import_redis3.getData)(evaluationKey, null),
|
|
409
|
+
knownEvaluation ? Promise.resolve(knownEvaluation) : (0, import_redis3.getHashJsonField)(
|
|
410
|
+
evaluationBucketKey,
|
|
411
|
+
evaluationField,
|
|
412
|
+
null
|
|
413
|
+
)
|
|
414
|
+
]);
|
|
415
|
+
const evaluationValue = directEvaluationValue ?? bucketEvaluationValue ?? knownEvaluation ?? null;
|
|
416
|
+
return {
|
|
417
|
+
redisDebug: {
|
|
418
|
+
trade: tradeKey,
|
|
419
|
+
tradeBucket: {
|
|
420
|
+
key: tradeBucketKey,
|
|
421
|
+
field: trade.orderId
|
|
422
|
+
},
|
|
423
|
+
activeTrade: activeTradeKey,
|
|
424
|
+
signal: signalKey,
|
|
425
|
+
evaluation: {
|
|
426
|
+
key: evaluationBucketKey,
|
|
427
|
+
field: evaluationField,
|
|
428
|
+
directKey: evaluationKey
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
trade,
|
|
432
|
+
redisValues: {
|
|
433
|
+
trade: tradeValue,
|
|
434
|
+
tradeBucket: trade,
|
|
435
|
+
activeTrade: activeTradeValue,
|
|
436
|
+
signal: signalValue ?? signal ?? null,
|
|
437
|
+
evaluation: evaluationValue ?? knownEvaluation ?? null
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
})
|
|
441
|
+
);
|
|
442
|
+
const linkedEvaluations = /* @__PURE__ */ new Map();
|
|
443
|
+
for (const trade of debugTrades) {
|
|
444
|
+
const evaluation = trade.redisValues.evaluation;
|
|
445
|
+
if (evaluation && typeof evaluation === "object" && "evaluationId" in evaluation && typeof evaluation.evaluationId === "string") {
|
|
446
|
+
linkedEvaluations.set(
|
|
447
|
+
evaluation.evaluationId,
|
|
448
|
+
evaluation
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const reportEvaluations = evaluations.length > 0 ? evaluations : [...linkedEvaluations.values()];
|
|
453
|
+
return {
|
|
454
|
+
reportType: "runtime-daily-debug",
|
|
455
|
+
generatedAt: Date.now(),
|
|
456
|
+
generatedAtMsk: formatRuntimeDebugDateTime(Date.now()),
|
|
457
|
+
userName,
|
|
458
|
+
window: {
|
|
459
|
+
startTime,
|
|
460
|
+
endTime,
|
|
461
|
+
startMsk: formatRuntimeDebugDateTime(startTime),
|
|
462
|
+
endMsk: formatRuntimeDebugDateTime(endTime),
|
|
463
|
+
dayKeys
|
|
464
|
+
},
|
|
465
|
+
redisPrefixes: {
|
|
466
|
+
runtimeTrades: import_redis3.redisKeys.runtimeTrades(userName),
|
|
467
|
+
runtimeTradeBuckets: import_redis3.redisKeys.runtimeTradeBuckets(userName),
|
|
468
|
+
runtimeActiveTrades: import_redis3.redisKeys.runtimeActiveTrades(userName),
|
|
469
|
+
runtimeSignals: import_redis3.redisKeys.runtimeSignalBuckets(userName),
|
|
470
|
+
runtimeSignalEvaluations: import_redis3.redisKeys.runtimeSignalEvaluationBuckets(userName),
|
|
471
|
+
runtimeSignalEvaluationStats: import_redis3.redisKeys.runtimeSignalEvaluationStatsBuckets(userName)
|
|
472
|
+
},
|
|
473
|
+
counts: {
|
|
474
|
+
trades: trades.length,
|
|
475
|
+
signals: signals.length,
|
|
476
|
+
evaluations: reportEvaluations.length,
|
|
477
|
+
evaluationStatsBuckets: evaluationStatsBuckets?.length ?? 0,
|
|
478
|
+
strategyConfigs: strategyConfigs?.length ?? 0
|
|
479
|
+
},
|
|
480
|
+
trades: debugTrades,
|
|
481
|
+
signals: signals.map((signal) => ({
|
|
482
|
+
redisKey: import_redis3.redisKeys.storeSignal(signal.symbol, signal.signalId),
|
|
483
|
+
signal
|
|
484
|
+
})),
|
|
485
|
+
evaluations: reportEvaluations.map((evaluation) => ({
|
|
486
|
+
redisDebug: {
|
|
487
|
+
key: import_redis3.redisKeys.runtimeSignalEvaluationBucket(
|
|
488
|
+
userName,
|
|
489
|
+
(0, import_time2.getRuntimeStorageDayKey)(evaluation.timestamp),
|
|
490
|
+
evaluation.strategy
|
|
491
|
+
),
|
|
492
|
+
field: evaluation.evaluationId,
|
|
493
|
+
directKey: import_redis3.redisKeys.runtimeSignalEvaluation(
|
|
494
|
+
userName,
|
|
495
|
+
evaluation.evaluationId
|
|
496
|
+
)
|
|
497
|
+
},
|
|
498
|
+
evaluation
|
|
499
|
+
})),
|
|
500
|
+
evaluationStatsBuckets: evaluationStatsBuckets ?? [],
|
|
501
|
+
strategyConfigs: strategyConfigs ?? []
|
|
502
|
+
};
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// src/lib/replay/support.ts
|
|
506
|
+
var import_chalk = __toESM(require("chalk"));
|
|
507
|
+
var REPLAY_RESULTS_BY_STRATEGY_HEADERS = [
|
|
508
|
+
import_chalk.default.blue("STRATEGY"),
|
|
509
|
+
import_chalk.default.yellow("TICKERS"),
|
|
510
|
+
import_chalk.default.yellow("TRADE TICKERS"),
|
|
511
|
+
import_chalk.default.cyan("ORDERS"),
|
|
512
|
+
import_chalk.default.cyan("WIN/LOSS (%)"),
|
|
513
|
+
import_chalk.default.cyan("PROFIT"),
|
|
514
|
+
import_chalk.default.cyan("AVG TRADE")
|
|
515
|
+
];
|
|
516
|
+
var REPLAY_RUNTIME_COMPARISON_HEADERS = [
|
|
517
|
+
import_chalk.default.blue("STRATEGY"),
|
|
518
|
+
import_chalk.default.cyan("BT ENTRIES"),
|
|
519
|
+
import_chalk.default.cyan("BT PNL"),
|
|
520
|
+
import_chalk.default.yellow("RT TRADES"),
|
|
521
|
+
import_chalk.default.yellow("RT PNL"),
|
|
522
|
+
import_chalk.default.green("MATCHED"),
|
|
523
|
+
import_chalk.default.yellow("RT ONLY"),
|
|
524
|
+
import_chalk.default.magenta("BT ONLY")
|
|
525
|
+
];
|
|
526
|
+
var REPLAY_RESULTS_CONFIG = "replay";
|
|
527
|
+
var REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS = 1;
|
|
528
|
+
var REPLAY_RUNTIME_COMPARE_TOLERANCE_MS = REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS * 15 * 60 * 1e3;
|
|
529
|
+
|
|
530
|
+
// src/scripts/replayRuntimeEvidence.ts
|
|
531
|
+
import_args.default.option(["u", "user"], "Use user config", "root");
|
|
532
|
+
import_args.default.option(
|
|
533
|
+
["S", "startTime"],
|
|
534
|
+
"Window start timestamp, seconds/ms, or ISO date"
|
|
535
|
+
);
|
|
536
|
+
import_args.default.option(["E", "endTime"], "Window end timestamp, seconds/ms, or ISO date");
|
|
537
|
+
import_args.default.option(
|
|
538
|
+
["H", "hours"],
|
|
539
|
+
"Fallback window in hours when start/end are omitted",
|
|
540
|
+
24
|
|
541
|
+
);
|
|
542
|
+
import_args.default.option(["s", "strategy"], "Strategy filter, comma-separated");
|
|
543
|
+
import_args.default.option("replayKey", "Exact replay result Redis key");
|
|
544
|
+
import_args.default.option(
|
|
545
|
+
"runtimeEvidence",
|
|
546
|
+
"Runtime evidence JSON collected on the runtime server"
|
|
547
|
+
);
|
|
548
|
+
import_args.default.option(
|
|
549
|
+
["o", "out"],
|
|
550
|
+
"Output JSON path",
|
|
551
|
+
"output/replay-runtime-evidence.json"
|
|
552
|
+
);
|
|
553
|
+
var flags = import_args.default.parse(process.argv);
|
|
554
|
+
var projectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
|
|
555
|
+
var toEpochMs = (value) => {
|
|
556
|
+
if (value == null || value === "") {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
const raw = String(value).trim();
|
|
560
|
+
if (/^\d+$/.test(raw)) {
|
|
561
|
+
const parsed2 = Number(raw);
|
|
562
|
+
return parsed2 < 1e12 ? Math.trunc(parsed2 * 1e3) : Math.trunc(parsed2);
|
|
563
|
+
}
|
|
564
|
+
const parsed = Date.parse(raw);
|
|
565
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
566
|
+
};
|
|
567
|
+
var resolveWindow = () => {
|
|
568
|
+
const endTime = toEpochMs(flags.endTime) ?? Date.now();
|
|
569
|
+
const hours = Math.max(
|
|
570
|
+
1,
|
|
571
|
+
Number.parseInt(String(flags.hours ?? 24), 10) || 24
|
|
572
|
+
);
|
|
573
|
+
const startTime = toEpochMs(flags.startTime) ?? endTime - hours * 60 * 60 * 1e3;
|
|
574
|
+
if (startTime >= endTime) {
|
|
575
|
+
throw new Error(
|
|
576
|
+
`Invalid window: startTime (${startTime}) must be less than endTime (${endTime})`
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
return {
|
|
580
|
+
startTime,
|
|
581
|
+
endTime,
|
|
582
|
+
source: flags.startTime != null || flags.endTime != null ? "explicit" : "hours"
|
|
583
|
+
};
|
|
584
|
+
};
|
|
585
|
+
var parseStrategyFilter = (value) => String(value ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
586
|
+
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
587
|
+
var asArray = (value) => Array.isArray(value) ? value.filter(Boolean) : [];
|
|
588
|
+
var sum = (items, getter) => Number(
|
|
589
|
+
items.reduce((total, item) => {
|
|
590
|
+
const value = getter(item);
|
|
591
|
+
return total + (typeof value === "number" && Number.isFinite(value) ? value : 0);
|
|
592
|
+
}, 0).toFixed(12)
|
|
593
|
+
);
|
|
594
|
+
var groupCount = (items, getter) => {
|
|
595
|
+
const counts = {};
|
|
596
|
+
for (const item of items) {
|
|
597
|
+
const raw = getter(item);
|
|
598
|
+
const key = typeof raw === "string" && raw.trim() ? raw : "[unknown]";
|
|
599
|
+
counts[key] = (counts[key] ?? 0) + 1;
|
|
600
|
+
}
|
|
601
|
+
return counts;
|
|
602
|
+
};
|
|
603
|
+
var compactMatched = (item) => {
|
|
604
|
+
const runtime = asRecord(item.runtime);
|
|
605
|
+
const backtest = asRecord(item.backtest);
|
|
606
|
+
const pnl = asRecord(item.pnl);
|
|
607
|
+
const slippage = asRecord(item.slippage);
|
|
608
|
+
return {
|
|
609
|
+
strategy: backtest.strategy ?? runtime.inferredStrategy ?? null,
|
|
610
|
+
runtimeInferredStrategy: runtime.inferredStrategy ?? null,
|
|
611
|
+
symbol: backtest.symbol ?? runtime.symbol ?? null,
|
|
612
|
+
direction: backtest.direction ?? runtime.direction ?? null,
|
|
613
|
+
timestamp: backtest.timestamp ?? null,
|
|
614
|
+
comparisonTimestamp: backtest.comparisonTimestamp ?? null,
|
|
615
|
+
runtimeTimestamp: runtime.timestamp ?? null,
|
|
616
|
+
timestampDiffMs: item.timestampDiffMs ?? null,
|
|
617
|
+
backtestPrice: backtest.price ?? null,
|
|
618
|
+
runtimePrice: runtime.price ?? null,
|
|
619
|
+
backtestExitPrice: backtest.exitPrice ?? null,
|
|
620
|
+
runtimeExitPrice: runtime.exitPrice ?? null,
|
|
621
|
+
expectedPnl: pnl.expectedPnl ?? null,
|
|
622
|
+
realizedPnl: pnl.realizedPnl ?? null,
|
|
623
|
+
pnlDelta: pnl.delta ?? null,
|
|
624
|
+
entrySlippageCost: slippage.entryCost ?? null,
|
|
625
|
+
exitSlippageCost: slippage.exitCost ?? null,
|
|
626
|
+
totalSlippageCost: slippage.totalCost ?? null,
|
|
627
|
+
orderId: runtime.orderId ?? backtest.orderId ?? null,
|
|
628
|
+
orderLinkId: runtime.orderLinkId ?? null,
|
|
629
|
+
signalId: backtest.signalId ?? null
|
|
630
|
+
};
|
|
631
|
+
};
|
|
632
|
+
var resolveItemStrategy = (item) => asRecord(item.backtest).strategy ?? asRecord(item.runtime).inferredStrategy ?? item.strategy ?? item.inferredStrategy ?? null;
|
|
633
|
+
var summarizeReplayComparison = (runtimeComparison, strategies) => {
|
|
634
|
+
const comparison = asRecord(runtimeComparison);
|
|
635
|
+
const details = asRecord(comparison.details);
|
|
636
|
+
const strategySet = new Set(strategies);
|
|
637
|
+
const useFilter = strategySet.size > 0;
|
|
638
|
+
const hasStrategy = (item) => {
|
|
639
|
+
const strategy = resolveItemStrategy(item);
|
|
640
|
+
return !useFilter || typeof strategy === "string" && strategySet.has(strategy);
|
|
641
|
+
};
|
|
642
|
+
const matched = asArray(details.matched).filter(hasStrategy);
|
|
643
|
+
const backtestOnly = asArray(details.backtestOnly).filter(hasStrategy);
|
|
644
|
+
const runtimeOnly = asArray(details.runtimeOnly).filter(hasStrategy);
|
|
645
|
+
const drilldown = asRecord(details.mismatchDrilldown);
|
|
646
|
+
const drilldownBacktestOnly = asArray(drilldown.backtestOnly).filter(
|
|
647
|
+
(item) => hasStrategy(asRecord(item.entry))
|
|
648
|
+
);
|
|
649
|
+
const drilldownRuntimeOnly = asArray(drilldown.runtimeOnly).filter(
|
|
650
|
+
(item) => hasStrategy(asRecord(item.entry))
|
|
651
|
+
);
|
|
652
|
+
const strategyNames = useFilter ? strategies : [
|
|
653
|
+
...new Set(
|
|
654
|
+
[...matched, ...backtestOnly, ...runtimeOnly].map(resolveItemStrategy).filter((value) => typeof value === "string")
|
|
655
|
+
)
|
|
656
|
+
].sort((left, right) => left.localeCompare(right));
|
|
657
|
+
return {
|
|
658
|
+
mode: comparison.mode ?? null,
|
|
659
|
+
rows: asArray(comparison.rows).filter(
|
|
660
|
+
(row) => !useFilter || strategySet.has(String(row.strategyName ?? ""))
|
|
661
|
+
),
|
|
662
|
+
counts: {
|
|
663
|
+
matched: matched.length,
|
|
664
|
+
backtestOnly: backtestOnly.length,
|
|
665
|
+
runtimeOnly: runtimeOnly.length
|
|
666
|
+
},
|
|
667
|
+
pnl: {
|
|
668
|
+
matchedExpected: sum(matched, (item) => asRecord(item.pnl).expectedPnl),
|
|
669
|
+
matchedRealized: sum(matched, (item) => asRecord(item.pnl).realizedPnl),
|
|
670
|
+
matchedDelta: sum(matched, (item) => asRecord(item.pnl).delta),
|
|
671
|
+
backtestOnlyExpected: sum(backtestOnly, (item) => item.pnl),
|
|
672
|
+
runtimeOnlyRealized: sum(runtimeOnly, (item) => item.pnl)
|
|
673
|
+
},
|
|
674
|
+
byStrategy: Object.fromEntries(
|
|
675
|
+
strategyNames.map((strategy) => {
|
|
676
|
+
const strategyMatched = matched.filter(
|
|
677
|
+
(item) => resolveItemStrategy(item) === strategy
|
|
678
|
+
);
|
|
679
|
+
const strategyBacktestOnly = backtestOnly.filter(
|
|
680
|
+
(item) => resolveItemStrategy(item) === strategy
|
|
681
|
+
);
|
|
682
|
+
const strategyRuntimeOnly = runtimeOnly.filter(
|
|
683
|
+
(item) => resolveItemStrategy(item) === strategy
|
|
684
|
+
);
|
|
685
|
+
return [
|
|
686
|
+
strategy,
|
|
687
|
+
{
|
|
688
|
+
matched: strategyMatched.length,
|
|
689
|
+
backtestOnly: strategyBacktestOnly.length,
|
|
690
|
+
runtimeOnly: strategyRuntimeOnly.length,
|
|
691
|
+
matchedRuntimeInferredStrategy: groupCount(
|
|
692
|
+
strategyMatched,
|
|
693
|
+
(item) => asRecord(item.runtime).inferredStrategy
|
|
694
|
+
),
|
|
695
|
+
matchedExpectedPnl: sum(
|
|
696
|
+
strategyMatched,
|
|
697
|
+
(item) => asRecord(item.pnl).expectedPnl
|
|
698
|
+
),
|
|
699
|
+
matchedRealizedPnl: sum(
|
|
700
|
+
strategyMatched,
|
|
701
|
+
(item) => asRecord(item.pnl).realizedPnl
|
|
702
|
+
),
|
|
703
|
+
backtestOnlyExpectedPnl: sum(
|
|
704
|
+
strategyBacktestOnly,
|
|
705
|
+
(item) => item.pnl
|
|
706
|
+
),
|
|
707
|
+
runtimeOnlyRealizedPnl: sum(
|
|
708
|
+
strategyRuntimeOnly,
|
|
709
|
+
(item) => item.pnl
|
|
710
|
+
)
|
|
711
|
+
}
|
|
712
|
+
];
|
|
713
|
+
})
|
|
714
|
+
),
|
|
715
|
+
matched: matched.map(compactMatched),
|
|
716
|
+
backtestOnly,
|
|
717
|
+
runtimeOnly,
|
|
718
|
+
mismatchDrilldown: {
|
|
719
|
+
summary: drilldown.summary ?? null,
|
|
720
|
+
backtestOnly: drilldownBacktestOnly,
|
|
721
|
+
runtimeOnly: drilldownRuntimeOnly
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
};
|
|
725
|
+
var loadReplayResult = async ({
|
|
726
|
+
userName,
|
|
727
|
+
replayKey
|
|
728
|
+
}) => {
|
|
729
|
+
if (replayKey) {
|
|
730
|
+
return {
|
|
731
|
+
key: replayKey,
|
|
732
|
+
availableKeys: void 0,
|
|
733
|
+
value: await (0, import_redis4.getData)(replayKey, null)
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
const prefix = import_redis4.redisKeys.backtestResults(userName, REPLAY_RESULTS_CONFIG, "");
|
|
737
|
+
const keys = (await (0, import_redis4.getKeys)(prefix)).sort(
|
|
738
|
+
(left, right) => left.localeCompare(right)
|
|
739
|
+
);
|
|
740
|
+
const key = keys[keys.length - 1] ?? null;
|
|
741
|
+
return {
|
|
742
|
+
key,
|
|
743
|
+
availableKeys: keys.slice(-10),
|
|
744
|
+
value: key ? await (0, import_redis4.getData)(key, null) : null
|
|
745
|
+
};
|
|
746
|
+
};
|
|
747
|
+
var getReplayStrategies = (replayValue) => {
|
|
748
|
+
const replay = asRecord(replayValue);
|
|
749
|
+
const rows = [
|
|
750
|
+
...asArray(replay.resultsByStrategies).map((row) => row.strategyName),
|
|
751
|
+
...asArray(asRecord(replay.runtimeComparison).rows).map(
|
|
752
|
+
(row) => row.strategyName
|
|
753
|
+
)
|
|
754
|
+
];
|
|
755
|
+
return [
|
|
756
|
+
...new Set(
|
|
757
|
+
rows.filter((value) => typeof value === "string")
|
|
758
|
+
)
|
|
759
|
+
].sort((left, right) => left.localeCompare(right));
|
|
760
|
+
};
|
|
761
|
+
var loadRuntimePayload = async ({
|
|
762
|
+
startTime,
|
|
763
|
+
endTime,
|
|
764
|
+
strategies
|
|
765
|
+
}) => {
|
|
766
|
+
const runtimeEvidencePath = typeof flags.runtimeEvidence === "string" && flags.runtimeEvidence.trim() ? import_node_path.default.resolve(projectRoot, flags.runtimeEvidence.trim()) : null;
|
|
767
|
+
if (runtimeEvidencePath) {
|
|
768
|
+
const artifact = JSON.parse(
|
|
769
|
+
await import_promises.default.readFile(runtimeEvidencePath, "utf8")
|
|
770
|
+
);
|
|
771
|
+
return {
|
|
772
|
+
source: "file",
|
|
773
|
+
path: runtimeEvidencePath,
|
|
774
|
+
payload: artifact.runtime ?? artifact
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
const runtimeEvidence = await collectRuntimeDebugEvidence({
|
|
778
|
+
userName: flags.user,
|
|
779
|
+
startTime,
|
|
780
|
+
endTime,
|
|
781
|
+
strategies: strategies.length ? strategies : void 0
|
|
782
|
+
});
|
|
783
|
+
return {
|
|
784
|
+
source: "local-redis",
|
|
785
|
+
path: null,
|
|
786
|
+
payload: await buildRuntimeDebugReportPayload({
|
|
787
|
+
userName: flags.user,
|
|
788
|
+
startTime,
|
|
789
|
+
endTime,
|
|
790
|
+
signals: runtimeEvidence.signals,
|
|
791
|
+
evaluations: runtimeEvidence.evaluations,
|
|
792
|
+
trades: runtimeEvidence.trades,
|
|
793
|
+
strategyConfigs: runtimeEvidence.strategyConfigs,
|
|
794
|
+
evaluationStatsBuckets: runtimeEvidence.evaluationStatsBuckets
|
|
795
|
+
})
|
|
796
|
+
};
|
|
797
|
+
};
|
|
798
|
+
var replayRuntimeEvidence = async () => {
|
|
799
|
+
const { startTime, endTime, source } = resolveWindow();
|
|
800
|
+
const replay = await loadReplayResult({
|
|
801
|
+
userName: flags.user,
|
|
802
|
+
replayKey: typeof flags.replayKey === "string" && flags.replayKey.trim() ? flags.replayKey.trim() : void 0
|
|
803
|
+
});
|
|
804
|
+
const explicitStrategies = parseStrategyFilter(flags.strategy);
|
|
805
|
+
const strategies = explicitStrategies.length ? explicitStrategies : getReplayStrategies(replay.value);
|
|
806
|
+
const runtime = await loadRuntimePayload({
|
|
807
|
+
startTime,
|
|
808
|
+
endTime,
|
|
809
|
+
strategies
|
|
810
|
+
});
|
|
811
|
+
const replayValue = asRecord(replay.value);
|
|
812
|
+
const artifact = {
|
|
813
|
+
reportType: "replay-runtime-evidence",
|
|
814
|
+
generatedAt: Date.now(),
|
|
815
|
+
userName: flags.user,
|
|
816
|
+
window: {
|
|
817
|
+
startTime,
|
|
818
|
+
endTime,
|
|
819
|
+
startIso: new Date(startTime).toISOString(),
|
|
820
|
+
endIso: new Date(endTime).toISOString(),
|
|
821
|
+
source
|
|
822
|
+
},
|
|
823
|
+
strategies,
|
|
824
|
+
runtimeSource: {
|
|
825
|
+
type: runtime.source,
|
|
826
|
+
path: runtime.path
|
|
827
|
+
},
|
|
828
|
+
replay: {
|
|
829
|
+
key: replay.key,
|
|
830
|
+
availableKeys: replay.availableKeys,
|
|
831
|
+
startedAt: replayValue.startedAt ?? null,
|
|
832
|
+
finishedAt: replayValue.finishedAt ?? null,
|
|
833
|
+
resultsByStrategies: asArray(replayValue.resultsByStrategies).filter(
|
|
834
|
+
(row) => !strategies.length || strategies.includes(String(row.strategyName ?? ""))
|
|
835
|
+
),
|
|
836
|
+
runtimeComparison: summarizeReplayComparison(
|
|
837
|
+
replayValue.runtimeComparison,
|
|
838
|
+
strategies
|
|
839
|
+
)
|
|
840
|
+
},
|
|
841
|
+
runtime: runtime.payload
|
|
842
|
+
};
|
|
843
|
+
const outPath = import_node_path.default.resolve(projectRoot, String(flags.out));
|
|
844
|
+
await import_promises.default.mkdir(import_node_path.default.dirname(outPath), { recursive: true });
|
|
845
|
+
await import_promises.default.writeFile(outPath, `${JSON.stringify(artifact, null, 2)}
|
|
846
|
+
`, "utf8");
|
|
847
|
+
console.log(import_chalk2.default.green(`Wrote ${outPath}`));
|
|
848
|
+
console.log(
|
|
849
|
+
JSON.stringify(
|
|
850
|
+
{
|
|
851
|
+
replayKey: artifact.replay.key,
|
|
852
|
+
strategies: artifact.strategies,
|
|
853
|
+
replayRows: artifact.replay.runtimeComparison.rows,
|
|
854
|
+
replayPnl: artifact.replay.runtimeComparison.pnl,
|
|
855
|
+
runtimeSource: artifact.runtimeSource,
|
|
856
|
+
runtimeCounts: asRecord(artifact.runtime).counts ?? null
|
|
857
|
+
},
|
|
858
|
+
null,
|
|
859
|
+
2
|
|
860
|
+
)
|
|
861
|
+
);
|
|
862
|
+
};
|
|
863
|
+
var main = replayRuntimeEvidence;
|
|
864
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
865
|
+
0 && (module.exports = {
|
|
866
|
+
main,
|
|
867
|
+
replayRuntimeEvidence
|
|
868
|
+
});
|