@tradejs/cli 3.1.19 → 3.1.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/cli.js +1782 -1580
- package/dist/scripts/replay.js +952 -849
- package/dist/scripts/replayRunner.js +952 -849
- package/dist/scripts/runtimeParity.js +630 -628
- package/package.json +7 -7
|
@@ -38,9 +38,9 @@ module.exports = __toCommonJS(runtimeParity_exports);
|
|
|
38
38
|
var import_config = require("dotenv/config");
|
|
39
39
|
var import_node_crypto = require("crypto");
|
|
40
40
|
var import_args = __toESM(require("args"));
|
|
41
|
-
var
|
|
41
|
+
var import_chalk7 = __toESM(require("chalk"));
|
|
42
42
|
var import_constants3 = require("@tradejs/core/constants");
|
|
43
|
-
var
|
|
43
|
+
var import_time5 = require("@tradejs/core/time");
|
|
44
44
|
var import_logger = require("@tradejs/infra/logger");
|
|
45
45
|
var import_redis3 = require("@tradejs/infra/redis");
|
|
46
46
|
var import_cli3 = require("@tradejs/node/cli");
|
|
@@ -332,51 +332,6 @@ var normalizeCliArgv = (argv, legacyFlagMap) => argv.map((arg) => {
|
|
|
332
332
|
return `${normalizedFlag}=${rest.join("=")}`;
|
|
333
333
|
});
|
|
334
334
|
|
|
335
|
-
// src/lib/paritySummary.ts
|
|
336
|
-
var summarizeTradeParityByStrategy = ({
|
|
337
|
-
runtimeEntries,
|
|
338
|
-
runtimeDuplicateEntries,
|
|
339
|
-
backtestEntries,
|
|
340
|
-
matchedEntries,
|
|
341
|
-
runtimeOnlyEntries,
|
|
342
|
-
backtestOnlyEntries
|
|
343
|
-
}) => {
|
|
344
|
-
const rows = /* @__PURE__ */ new Map();
|
|
345
|
-
const ensureRow = (strategyName) => {
|
|
346
|
-
const row = rows.get(strategyName) ?? {
|
|
347
|
-
runtime: 0,
|
|
348
|
-
runtimeDuplicates: 0,
|
|
349
|
-
backtest: 0,
|
|
350
|
-
matched: 0,
|
|
351
|
-
runtimeOnly: 0,
|
|
352
|
-
backtestOnly: 0
|
|
353
|
-
};
|
|
354
|
-
rows.set(strategyName, row);
|
|
355
|
-
return row;
|
|
356
|
-
};
|
|
357
|
-
for (const entry of runtimeEntries) {
|
|
358
|
-
ensureRow(entry.strategy).runtime += 1;
|
|
359
|
-
}
|
|
360
|
-
for (const entry of runtimeDuplicateEntries) {
|
|
361
|
-
ensureRow(entry.strategy).runtimeDuplicates += 1;
|
|
362
|
-
}
|
|
363
|
-
for (const entry of backtestEntries) {
|
|
364
|
-
ensureRow(entry.strategy).backtest += 1;
|
|
365
|
-
}
|
|
366
|
-
for (const entry of matchedEntries) {
|
|
367
|
-
ensureRow(entry.runtime.strategy).matched += 1;
|
|
368
|
-
}
|
|
369
|
-
for (const entry of runtimeOnlyEntries) {
|
|
370
|
-
ensureRow(entry.strategy).runtimeOnly += 1;
|
|
371
|
-
}
|
|
372
|
-
for (const entry of backtestOnlyEntries) {
|
|
373
|
-
ensureRow(entry.strategy).backtestOnly += 1;
|
|
374
|
-
}
|
|
375
|
-
return [...rows.entries()].sort(
|
|
376
|
-
([left], [right]) => left.localeCompare(right)
|
|
377
|
-
);
|
|
378
|
-
};
|
|
379
|
-
|
|
380
335
|
// src/lib/runtimeRedis.ts
|
|
381
336
|
var import_redis = require("@tradejs/infra/redis");
|
|
382
337
|
var import_time = require("@tradejs/core/time");
|
|
@@ -4460,6 +4415,76 @@ var buildReplayTargets = async ({
|
|
|
4460
4415
|
);
|
|
4461
4416
|
};
|
|
4462
4417
|
|
|
4418
|
+
// src/lib/runtimeParity/evidence.ts
|
|
4419
|
+
var matchesSelection = (context, artifact) => (!context.strategy || artifact.strategy === context.strategy) && (!context.symbols || context.symbols.has(artifact.symbol));
|
|
4420
|
+
var loadRuntimeParityEvidence = async (context, adapters) => {
|
|
4421
|
+
const range = {
|
|
4422
|
+
startTime: context.window.start,
|
|
4423
|
+
endTime: context.window.end
|
|
4424
|
+
};
|
|
4425
|
+
const [allRuntimeTrades, allRuntimeSignals, allRuntimeSignalEvaluations] = await Promise.all([
|
|
4426
|
+
adapters.loadRuntimeTrades(context.userName, range),
|
|
4427
|
+
adapters.loadRuntimeSignals(context.userName, range),
|
|
4428
|
+
adapters.loadRuntimeSignalEvaluations(context.userName, range)
|
|
4429
|
+
]);
|
|
4430
|
+
return {
|
|
4431
|
+
runtimeTrades: allRuntimeTrades.filter(
|
|
4432
|
+
(trade) => trade.entryTimestamp >= context.window.start && trade.entryTimestamp <= context.window.end && matchesSelection(context, trade)
|
|
4433
|
+
),
|
|
4434
|
+
runtimeSignals: allRuntimeSignals.filter(
|
|
4435
|
+
(signal) => signal.timestamp >= context.window.start && signal.timestamp <= context.window.end && matchesSelection(context, signal)
|
|
4436
|
+
),
|
|
4437
|
+
runtimeSignalEvaluations: allRuntimeSignalEvaluations.filter(
|
|
4438
|
+
(evaluation) => evaluation.timestamp >= context.window.start && evaluation.timestamp <= context.window.end && matchesSelection(context, evaluation)
|
|
4439
|
+
)
|
|
4440
|
+
};
|
|
4441
|
+
};
|
|
4442
|
+
|
|
4443
|
+
// src/lib/paritySummary.ts
|
|
4444
|
+
var summarizeTradeParityByStrategy = ({
|
|
4445
|
+
runtimeEntries,
|
|
4446
|
+
runtimeDuplicateEntries,
|
|
4447
|
+
backtestEntries,
|
|
4448
|
+
matchedEntries,
|
|
4449
|
+
runtimeOnlyEntries,
|
|
4450
|
+
backtestOnlyEntries
|
|
4451
|
+
}) => {
|
|
4452
|
+
const rows = /* @__PURE__ */ new Map();
|
|
4453
|
+
const ensureRow = (strategyName) => {
|
|
4454
|
+
const row = rows.get(strategyName) ?? {
|
|
4455
|
+
runtime: 0,
|
|
4456
|
+
runtimeDuplicates: 0,
|
|
4457
|
+
backtest: 0,
|
|
4458
|
+
matched: 0,
|
|
4459
|
+
runtimeOnly: 0,
|
|
4460
|
+
backtestOnly: 0
|
|
4461
|
+
};
|
|
4462
|
+
rows.set(strategyName, row);
|
|
4463
|
+
return row;
|
|
4464
|
+
};
|
|
4465
|
+
for (const entry of runtimeEntries) {
|
|
4466
|
+
ensureRow(entry.strategy).runtime += 1;
|
|
4467
|
+
}
|
|
4468
|
+
for (const entry of runtimeDuplicateEntries) {
|
|
4469
|
+
ensureRow(entry.strategy).runtimeDuplicates += 1;
|
|
4470
|
+
}
|
|
4471
|
+
for (const entry of backtestEntries) {
|
|
4472
|
+
ensureRow(entry.strategy).backtest += 1;
|
|
4473
|
+
}
|
|
4474
|
+
for (const entry of matchedEntries) {
|
|
4475
|
+
ensureRow(entry.runtime.strategy).matched += 1;
|
|
4476
|
+
}
|
|
4477
|
+
for (const entry of runtimeOnlyEntries) {
|
|
4478
|
+
ensureRow(entry.strategy).runtimeOnly += 1;
|
|
4479
|
+
}
|
|
4480
|
+
for (const entry of backtestOnlyEntries) {
|
|
4481
|
+
ensureRow(entry.strategy).backtestOnly += 1;
|
|
4482
|
+
}
|
|
4483
|
+
return [...rows.entries()].sort(
|
|
4484
|
+
([left], [right]) => left.localeCompare(right)
|
|
4485
|
+
);
|
|
4486
|
+
};
|
|
4487
|
+
|
|
4463
4488
|
// src/lib/runtimeParity/classification.ts
|
|
4464
4489
|
var BACKTEST_ONLY_CLASSIFICATIONS = [
|
|
4465
4490
|
"gated_out",
|
|
@@ -4789,250 +4814,117 @@ var summarizeRuntimeOnlyClassifications = (classifiedEntries) => {
|
|
|
4789
4814
|
return nonZero.join(", ");
|
|
4790
4815
|
};
|
|
4791
4816
|
|
|
4792
|
-
// src/lib/
|
|
4793
|
-
var
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
});
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
);
|
|
4821
|
-
import_args.default.option(["d", "days"], "Replay window in days", 3);
|
|
4822
|
-
import_args.default.option(
|
|
4823
|
-
["b", "startTime"],
|
|
4824
|
-
"Explicit replay start timestamp (ms or seconds)"
|
|
4825
|
-
);
|
|
4826
|
-
import_args.default.option(["e", "endTime"], "Explicit replay end timestamp (ms or seconds)");
|
|
4827
|
-
import_args.default.option(["s", "strategy"], "Only compare one strategy");
|
|
4828
|
-
import_args.default.option(
|
|
4829
|
-
["t", "tickers"],
|
|
4830
|
-
"Replay comma-separated symbols for all configured strategies"
|
|
4831
|
-
);
|
|
4832
|
-
import_args.default.option(
|
|
4833
|
-
["C", "cacheOnly"],
|
|
4834
|
-
"Do not refresh market history before replay",
|
|
4835
|
-
false
|
|
4836
|
-
);
|
|
4837
|
-
import_args.default.option(
|
|
4838
|
-
["a", "toleranceBars"],
|
|
4839
|
-
"Allowed entry timestamp drift in bars when matching runtime vs backtest",
|
|
4840
|
-
1
|
|
4841
|
-
);
|
|
4842
|
-
import_args.default.option(
|
|
4843
|
-
"fullUniverse",
|
|
4844
|
-
"Replay every configured strategy across the full connector universe. By default parity replays only runtime-traded and strategy-results symbols unless --tickers is provided.",
|
|
4845
|
-
false
|
|
4846
|
-
);
|
|
4847
|
-
import_args.default.option(
|
|
4848
|
-
"runtimeGates",
|
|
4849
|
-
"Force runtime AI/ML gates for all replay targets. Runtime-gated configs are replayed with gates by default.",
|
|
4850
|
-
false
|
|
4851
|
-
);
|
|
4852
|
-
import_args.default.option(["N", "notify"], "Send parity summary to Telegram", false);
|
|
4853
|
-
import_args.default.option(["D", "details"], "Print unmatched entry details (capped)", false);
|
|
4854
|
-
process.argv = normalizeCliArgv(process.argv, {
|
|
4855
|
-
"-C": "--cacheOnly",
|
|
4856
|
-
"-D": "--details",
|
|
4857
|
-
"-E": "--endTime",
|
|
4858
|
-
"-N": "--notify",
|
|
4859
|
-
"-S": "--startTime",
|
|
4860
|
-
"-T": "--toleranceBars"
|
|
4861
|
-
});
|
|
4862
|
-
var flags = import_args.default.parse(process.argv);
|
|
4863
|
-
var interval = "15";
|
|
4864
|
-
var projectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
|
|
4865
|
-
var DEFAULT_LOOKBACK_DAYS = 3;
|
|
4866
|
-
var DETAIL_LIMIT = 10;
|
|
4867
|
-
var TELEGRAM_DETAIL_LIMIT = 5;
|
|
4868
|
-
var SUMMARY_TIMEZONE = "Europe/Moscow";
|
|
4869
|
-
var SUMMARY_TIMEZONE_LABEL = "MSK";
|
|
4870
|
-
var DEFAULT_REPLAY_ENV = flags.runtimeGates ? "PARITY" : "BACKTEST";
|
|
4871
|
-
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
4872
|
-
var formatMskDateTime = (timestamp) => new Intl.DateTimeFormat("ru-RU", {
|
|
4873
|
-
timeZone: SUMMARY_TIMEZONE,
|
|
4874
|
-
year: "numeric",
|
|
4875
|
-
month: "2-digit",
|
|
4876
|
-
day: "2-digit",
|
|
4877
|
-
hour: "2-digit",
|
|
4878
|
-
minute: "2-digit",
|
|
4879
|
-
hour12: false
|
|
4880
|
-
}).format(new Date(timestamp));
|
|
4881
|
-
var resolveParityConnectorName = async (value) => {
|
|
4882
|
-
const connectorName = await (0, import_connectors5.resolveConnectorName)(value, projectRoot);
|
|
4883
|
-
if (connectorName) {
|
|
4884
|
-
return connectorName;
|
|
4817
|
+
// src/lib/runtimeParity/analysis.ts
|
|
4818
|
+
var summarizeByStrategy = ({
|
|
4819
|
+
targets,
|
|
4820
|
+
successfulTargetKeys,
|
|
4821
|
+
replayErrors,
|
|
4822
|
+
runtimeEntries,
|
|
4823
|
+
runtimeDuplicateEntries,
|
|
4824
|
+
backtestEntries,
|
|
4825
|
+
matchedEntries,
|
|
4826
|
+
runtimeOnlyEntries,
|
|
4827
|
+
backtestOnlyEntries
|
|
4828
|
+
}) => {
|
|
4829
|
+
const rows = /* @__PURE__ */ new Map();
|
|
4830
|
+
const baseRows = summarizeTradeParityByStrategy({
|
|
4831
|
+
runtimeEntries,
|
|
4832
|
+
runtimeDuplicateEntries,
|
|
4833
|
+
backtestEntries,
|
|
4834
|
+
matchedEntries,
|
|
4835
|
+
runtimeOnlyEntries,
|
|
4836
|
+
backtestOnlyEntries
|
|
4837
|
+
});
|
|
4838
|
+
for (const [strategy, baseRow] of baseRows) {
|
|
4839
|
+
rows.set(strategy, {
|
|
4840
|
+
...baseRow,
|
|
4841
|
+
targets: 0,
|
|
4842
|
+
compared: 0,
|
|
4843
|
+
errors: 0
|
|
4844
|
+
});
|
|
4885
4845
|
}
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4846
|
+
const ensureRow = (strategy) => {
|
|
4847
|
+
const row = rows.get(strategy) ?? {
|
|
4848
|
+
runtime: 0,
|
|
4849
|
+
runtimeDuplicates: 0,
|
|
4850
|
+
backtest: 0,
|
|
4851
|
+
matched: 0,
|
|
4852
|
+
runtimeOnly: 0,
|
|
4853
|
+
backtestOnly: 0,
|
|
4854
|
+
targets: 0,
|
|
4855
|
+
compared: 0,
|
|
4856
|
+
errors: 0
|
|
4857
|
+
};
|
|
4858
|
+
rows.set(strategy, row);
|
|
4859
|
+
return row;
|
|
4860
|
+
};
|
|
4861
|
+
for (const target of targets) {
|
|
4862
|
+
const row = ensureRow(target.strategy);
|
|
4863
|
+
row.targets += 1;
|
|
4864
|
+
if (successfulTargetKeys.has(toTargetKey(target))) {
|
|
4865
|
+
row.compared += 1;
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
for (const error of replayErrors) {
|
|
4869
|
+
ensureRow(error.strategy).errors += 1;
|
|
4870
|
+
}
|
|
4871
|
+
return [...rows.entries()].sort(
|
|
4872
|
+
([left], [right]) => left.localeCompare(right)
|
|
4890
4873
|
);
|
|
4891
|
-
return import_connectors5.DEFAULT_CONNECTOR_NAME;
|
|
4892
4874
|
};
|
|
4893
|
-
var
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
}) => {
|
|
4897
|
-
const connectorFactory = await (0, import_connectors5.getConnectorCreatorByName)(
|
|
4898
|
-
connectorName,
|
|
4899
|
-
projectRoot
|
|
4875
|
+
var analyzeRuntimeParity = (context) => {
|
|
4876
|
+
const comparableRuntimeTrades = context.runtimeTrades.filter(
|
|
4877
|
+
(trade) => context.successfulTargetKeys.has(toTargetKey(trade))
|
|
4900
4878
|
);
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
}) => {
|
|
4911
|
-
const snapshots = [
|
|
4912
|
-
...runtimeTrades.filter(
|
|
4913
|
-
(trade) => trade.aiAnalysis && typeof trade.entryTimestamp === "number" && Number.isFinite(trade.entryTimestamp)
|
|
4914
|
-
).map((trade) => ({
|
|
4915
|
-
strategy: trade.strategy,
|
|
4916
|
-
symbol: trade.symbol,
|
|
4917
|
-
direction: trade.direction,
|
|
4918
|
-
timestamp: trade.entryTimestamp,
|
|
4919
|
-
toleranceMs,
|
|
4920
|
-
analysis: trade.aiAnalysis
|
|
4921
|
-
})),
|
|
4922
|
-
...runtimeSignalEvaluations.filter(
|
|
4923
|
-
(evaluation) => evaluation.aiAnalysis && evaluation.direction && typeof evaluation.timestamp === "number" && Number.isFinite(evaluation.timestamp)
|
|
4924
|
-
).map((evaluation) => ({
|
|
4925
|
-
strategy: evaluation.strategy,
|
|
4926
|
-
symbol: evaluation.symbol,
|
|
4927
|
-
direction: evaluation.direction,
|
|
4928
|
-
timestamp: evaluation.timestamp,
|
|
4929
|
-
toleranceMs,
|
|
4930
|
-
analysis: evaluation.aiAnalysis
|
|
4931
|
-
}))
|
|
4932
|
-
];
|
|
4933
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4934
|
-
return snapshots.filter((snapshot) => {
|
|
4935
|
-
const key = `${snapshot.strategy}:${snapshot.symbol}:${snapshot.direction}:${snapshot.timestamp}`;
|
|
4936
|
-
if (seen.has(key)) {
|
|
4937
|
-
return false;
|
|
4938
|
-
}
|
|
4939
|
-
seen.add(key);
|
|
4940
|
-
return true;
|
|
4941
|
-
});
|
|
4942
|
-
};
|
|
4943
|
-
var formatReplayEnvSummary = (configs) => {
|
|
4944
|
-
let backtest = 0;
|
|
4945
|
-
let parity = 0;
|
|
4946
|
-
for (const config of configs) {
|
|
4947
|
-
if (config.ENV === "PARITY") {
|
|
4948
|
-
parity += 1;
|
|
4949
|
-
} else {
|
|
4950
|
-
backtest += 1;
|
|
4951
|
-
}
|
|
4952
|
-
}
|
|
4953
|
-
if (parity > 0 && backtest > 0) {
|
|
4954
|
-
return `MIXED (PARITY=${parity}, BACKTEST=${backtest})`;
|
|
4955
|
-
}
|
|
4956
|
-
if (parity > 0) {
|
|
4957
|
-
return "PARITY";
|
|
4958
|
-
}
|
|
4959
|
-
return "BACKTEST";
|
|
4960
|
-
};
|
|
4961
|
-
var buildReplayConfig = async ({
|
|
4962
|
-
userName,
|
|
4963
|
-
strategy,
|
|
4964
|
-
symbol,
|
|
4965
|
-
replayInputsIndex,
|
|
4966
|
-
toleranceMs
|
|
4967
|
-
}) => {
|
|
4968
|
-
const [userConfig, strategyResults] = await Promise.all([
|
|
4969
|
-
(0, import_redis3.getData)(import_redis3.redisKeys.strategyConfig(userName, strategy), {}),
|
|
4970
|
-
(0, import_redis3.getData)(import_redis3.redisKeys.strategyResults(userName, strategy), {})
|
|
4971
|
-
]);
|
|
4972
|
-
const typedResults = strategyResults ?? {};
|
|
4973
|
-
const symbolResult = typedResults?.[symbol];
|
|
4974
|
-
const symbolConfig = symbolResult && symbolResult.config && typeof symbolResult.config === "object" ? symbolResult.config : {};
|
|
4975
|
-
const replayInputs = replayInputsIndex.get(toTargetKey({ strategy, symbol }));
|
|
4976
|
-
const aiReplayAnalyses = buildReplayAiAnalyses({
|
|
4977
|
-
runtimeTrades: replayInputs?.runtimeTrades ?? [],
|
|
4978
|
-
runtimeSignalEvaluations: replayInputs?.runtimeSignalEvaluations ?? [],
|
|
4979
|
-
strategy,
|
|
4980
|
-
symbol,
|
|
4981
|
-
toleranceMs
|
|
4879
|
+
const rawRuntimeEntries = extractRuntimeParityEntries(
|
|
4880
|
+
comparableRuntimeTrades
|
|
4881
|
+
);
|
|
4882
|
+
const runtimeDedupe = dedupeRuntimeParityEntries(rawRuntimeEntries);
|
|
4883
|
+
const runtimeEntries = runtimeDedupe.entries;
|
|
4884
|
+
const comparison = compareTradeParityEntries({
|
|
4885
|
+
runtimeEntries,
|
|
4886
|
+
backtestEntries: context.backtestEntries,
|
|
4887
|
+
toleranceMs: context.toleranceMs
|
|
4982
4888
|
});
|
|
4983
|
-
const
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
strategyConfig,
|
|
4989
|
-
env: resolveReplayStrategyEnv({
|
|
4990
|
-
strategyConfig,
|
|
4991
|
-
forceRuntimeGates: Boolean(flags.runtimeGates)
|
|
4992
|
-
}),
|
|
4993
|
-
interval,
|
|
4994
|
-
makeOrders: true,
|
|
4995
|
-
recordRuntimeTrades: false,
|
|
4996
|
-
aiReplayAnalyses
|
|
4889
|
+
const classifiedBacktestOnly = classifyBacktestOnlyEntries({
|
|
4890
|
+
entries: comparison.backtestOnly,
|
|
4891
|
+
runtimeSignals: context.runtimeSignals,
|
|
4892
|
+
runtimeSignalEvaluations: context.runtimeSignalEvaluations,
|
|
4893
|
+
toleranceMs: context.toleranceMs
|
|
4997
4894
|
});
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
preloadStart,
|
|
5004
|
-
preloadEnd
|
|
5005
|
-
}) => {
|
|
5006
|
-
const connector = await createConnector({ userName, connectorName });
|
|
5007
|
-
const symbols = [...new Set(targets.map((target) => target.symbol))];
|
|
5008
|
-
const warmStartedAt = Date.now();
|
|
5009
|
-
console.log(
|
|
5010
|
-
import_chalk6.default.gray(
|
|
5011
|
-
`Warm history: ${symbols.length} symbol(s) on ${connectorName}, preload ${(0, import_time3.formatUnix)(preloadStart)} -> ${(0, import_time3.formatUnix)(preloadEnd)}`
|
|
5012
|
-
)
|
|
5013
|
-
);
|
|
5014
|
-
const btcReferences = await loadBtcReferenceConnectors({
|
|
5015
|
-
connectorName,
|
|
5016
|
-
marketConnector: connector,
|
|
5017
|
-
userName,
|
|
5018
|
-
projectRoot,
|
|
5019
|
-
shouldUseDedicatedReferences: true,
|
|
5020
|
-
warn: (message) => import_logger.logger.warn(message)
|
|
4895
|
+
const classifiedRuntimeOnly = classifyRuntimeOnlyEntries({
|
|
4896
|
+
entries: comparison.runtimeOnly,
|
|
4897
|
+
replaySignalEvaluations: context.replaySignalEvaluations,
|
|
4898
|
+
backtestEntries: context.backtestEntries,
|
|
4899
|
+
toleranceMs: context.toleranceMs
|
|
5021
4900
|
});
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
4901
|
+
const matchedSummary = summarizeMatchedParity(comparison.matched);
|
|
4902
|
+
const strategyRows = summarizeByStrategy({
|
|
4903
|
+
targets: context.replayTargets,
|
|
4904
|
+
successfulTargetKeys: context.successfulTargetKeys,
|
|
4905
|
+
replayErrors: context.replayErrors,
|
|
4906
|
+
runtimeEntries,
|
|
4907
|
+
runtimeDuplicateEntries: runtimeDedupe.duplicateEntries,
|
|
4908
|
+
backtestEntries: context.backtestEntries,
|
|
4909
|
+
matchedEntries: comparison.matched,
|
|
4910
|
+
runtimeOnlyEntries: comparison.runtimeOnly,
|
|
4911
|
+
backtestOnlyEntries: comparison.backtestOnly
|
|
5031
4912
|
});
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
4913
|
+
return {
|
|
4914
|
+
comparableRuntimeTrades,
|
|
4915
|
+
rawRuntimeEntries,
|
|
4916
|
+
runtimeDedupe,
|
|
4917
|
+
runtimeEntries,
|
|
4918
|
+
comparison,
|
|
4919
|
+
classifiedBacktestOnly,
|
|
4920
|
+
classifiedRuntimeOnly,
|
|
4921
|
+
matchedSummary,
|
|
4922
|
+
strategyRows
|
|
4923
|
+
};
|
|
5035
4924
|
};
|
|
4925
|
+
|
|
4926
|
+
// src/lib/runtimeParity/reportingShared.ts
|
|
4927
|
+
var import_time3 = require("@tradejs/core/time");
|
|
5036
4928
|
var formatPercent = (value) => value == null || !Number.isFinite(value) ? "n/a" : `${value.toFixed(2)}%`;
|
|
5037
4929
|
var formatMinutes2 = (value) => value == null || !Number.isFinite(value) ? "n/a" : `${(value / 6e4).toFixed(2)}m`;
|
|
5038
4930
|
var formatEntryLabel = (entry) => `${entry.strategy} ${entry.symbol} ${entry.direction} ${(0, import_time3.formatUnix)(entry.timestamp)}`;
|
|
@@ -5052,16 +4944,11 @@ var formatSourceCountsSummary = (sourceCounts) => {
|
|
|
5052
4944
|
};
|
|
5053
4945
|
var normalizeSummaryText = (value, maxLength = 160) => {
|
|
5054
4946
|
const normalized = value.replace(/\s+/g, " ").trim();
|
|
5055
|
-
|
|
5056
|
-
return normalized;
|
|
5057
|
-
}
|
|
5058
|
-
return `${normalized.slice(0, maxLength - 3)}...`;
|
|
4947
|
+
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 3)}...`;
|
|
5059
4948
|
};
|
|
5060
4949
|
var pickFirstText = (...values) => {
|
|
5061
4950
|
for (const value of values) {
|
|
5062
|
-
if (typeof value === "string" && value.trim())
|
|
5063
|
-
return value.trim();
|
|
5064
|
-
}
|
|
4951
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
5065
4952
|
}
|
|
5066
4953
|
return null;
|
|
5067
4954
|
};
|
|
@@ -5081,9 +4968,7 @@ var buildMismatchSummaryRows = ({
|
|
|
5081
4968
|
item.nearestBacktestEntry?.id
|
|
5082
4969
|
);
|
|
5083
4970
|
const refs = [`signalId=${signalId ?? "n/a"}`];
|
|
5084
|
-
if (orderId && orderId !== signalId) {
|
|
5085
|
-
refs.push(`orderId=${orderId}`);
|
|
5086
|
-
}
|
|
4971
|
+
if (orderId && orderId !== signalId) refs.push(`orderId=${orderId}`);
|
|
5087
4972
|
if (item.evaluation?.evaluationId) {
|
|
5088
4973
|
refs.push(`evaluationId=${item.evaluation.evaluationId}`);
|
|
5089
4974
|
}
|
|
@@ -5145,20 +5030,16 @@ var buildMismatchSummaryRows = ({
|
|
|
5145
5030
|
};
|
|
5146
5031
|
var buildStrategyIssueRows = (strategyRows) => strategyRows.map(([strategy, row]) => {
|
|
5147
5032
|
const issues = [];
|
|
5148
|
-
if (row.runtimeOnly) {
|
|
5149
|
-
|
|
5150
|
-
}
|
|
5151
|
-
if (row.backtestOnly) {
|
|
5152
|
-
issues.push(`backtestOnly=${row.backtestOnly}`);
|
|
5153
|
-
}
|
|
5033
|
+
if (row.runtimeOnly) issues.push(`runtimeOnly=${row.runtimeOnly}`);
|
|
5034
|
+
if (row.backtestOnly) issues.push(`backtestOnly=${row.backtestOnly}`);
|
|
5154
5035
|
if (row.runtimeDuplicates) {
|
|
5155
5036
|
issues.push(`runtimeDuplicates=${row.runtimeDuplicates}`);
|
|
5156
5037
|
}
|
|
5157
|
-
if (row.errors) {
|
|
5158
|
-
issues.push(`errors=${row.errors}`);
|
|
5159
|
-
}
|
|
5038
|
+
if (row.errors) issues.push(`errors=${row.errors}`);
|
|
5160
5039
|
return issues.length ? `- ${strategy}: ${issues.join(", ")}` : null;
|
|
5161
5040
|
}).filter((line) => Boolean(line));
|
|
5041
|
+
|
|
5042
|
+
// src/lib/runtimeParity/reportingJson.ts
|
|
5162
5043
|
var toSerializableTradeParityEntry = (entry) => entry ? {
|
|
5163
5044
|
id: entry.id,
|
|
5164
5045
|
source: entry.source,
|
|
@@ -5301,34 +5182,12 @@ var getBacktestOnlyRecommendedChecks = (classification) => {
|
|
|
5301
5182
|
];
|
|
5302
5183
|
}
|
|
5303
5184
|
};
|
|
5304
|
-
var buildRuntimeParityMismatchAttachment = ({
|
|
5305
|
-
|
|
5306
|
-
connectorName,
|
|
5307
|
-
replayEnv,
|
|
5308
|
-
toleranceBars,
|
|
5309
|
-
toleranceMs,
|
|
5310
|
-
replayTargetsCount,
|
|
5311
|
-
comparedTargetsCount,
|
|
5312
|
-
replayErrors,
|
|
5313
|
-
sourceCounts,
|
|
5314
|
-
rawRuntimeEntriesCount,
|
|
5315
|
-
runtimeEntriesCount,
|
|
5316
|
-
runtimeDuplicateEntriesCount,
|
|
5317
|
-
backtestEntriesCount,
|
|
5318
|
-
matchedCount,
|
|
5319
|
-
runtimeOnlyCount,
|
|
5320
|
-
backtestOnlyCount,
|
|
5321
|
-
matchedSummary,
|
|
5322
|
-
classifiedRuntimeOnly,
|
|
5323
|
-
classifiedBacktestOnly,
|
|
5324
|
-
runtimeSignalEvaluationsCount,
|
|
5325
|
-
strategyRows
|
|
5326
|
-
}) => {
|
|
5327
|
-
if (!classifiedRuntimeOnly.length && !classifiedBacktestOnly.length) {
|
|
5185
|
+
var buildRuntimeParityMismatchAttachment = (context) => {
|
|
5186
|
+
if (!context.classifiedRuntimeOnly.length && !context.classifiedBacktestOnly.length) {
|
|
5328
5187
|
return null;
|
|
5329
5188
|
}
|
|
5330
5189
|
const cases = [
|
|
5331
|
-
...classifiedRuntimeOnly.map((item) => ({
|
|
5190
|
+
...context.classifiedRuntimeOnly.map((item) => ({
|
|
5332
5191
|
kind: "runtimeOnly",
|
|
5333
5192
|
strategy: item.entry.strategy,
|
|
5334
5193
|
symbol: item.entry.symbol,
|
|
@@ -5367,7 +5226,7 @@ var buildRuntimeParityMismatchAttachment = ({
|
|
|
5367
5226
|
)
|
|
5368
5227
|
}
|
|
5369
5228
|
})),
|
|
5370
|
-
...classifiedBacktestOnly.map((item) => ({
|
|
5229
|
+
...context.classifiedBacktestOnly.map((item) => ({
|
|
5371
5230
|
kind: "backtestOnly",
|
|
5372
5231
|
strategy: item.entry.strategy,
|
|
5373
5232
|
symbol: item.entry.symbol,
|
|
@@ -5413,40 +5272,37 @@ var buildRuntimeParityMismatchAttachment = ({
|
|
|
5413
5272
|
generatedAt: Date.now(),
|
|
5414
5273
|
codexQuestion: "For each mismatch case, explain why runtime and replay/backtest diverged. Use why.classification first, then confirm with decisionTrace, timing, and artifacts.",
|
|
5415
5274
|
window: {
|
|
5416
|
-
start: window.start,
|
|
5417
|
-
end: window.end,
|
|
5418
|
-
source: window.source
|
|
5419
|
-
},
|
|
5420
|
-
connectorName,
|
|
5421
|
-
replayEnv,
|
|
5422
|
-
tolerance: {
|
|
5423
|
-
bars: toleranceBars,
|
|
5424
|
-
ms: toleranceMs
|
|
5275
|
+
start: context.window.start,
|
|
5276
|
+
end: context.window.end,
|
|
5277
|
+
source: context.window.source
|
|
5425
5278
|
},
|
|
5279
|
+
connectorName: context.connectorName,
|
|
5280
|
+
replayEnv: context.replayEnv,
|
|
5281
|
+
tolerance: { bars: context.toleranceBars, ms: context.toleranceMs },
|
|
5426
5282
|
summary: {
|
|
5427
|
-
replayTargets: replayTargetsCount,
|
|
5428
|
-
comparedTargets: comparedTargetsCount,
|
|
5429
|
-
replayErrors: replayErrors.length,
|
|
5430
|
-
sourceCounts,
|
|
5431
|
-
runtimeEntriesRaw: rawRuntimeEntriesCount,
|
|
5432
|
-
runtimeEntries: runtimeEntriesCount,
|
|
5433
|
-
runtimeDuplicateEntries: runtimeDuplicateEntriesCount,
|
|
5434
|
-
backtestEntries: backtestEntriesCount,
|
|
5435
|
-
matchedEntries: matchedCount,
|
|
5436
|
-
runtimeOnlyEntries: runtimeOnlyCount,
|
|
5437
|
-
backtestOnlyEntries: backtestOnlyCount,
|
|
5438
|
-
runtimeSignalEvaluations: runtimeSignalEvaluationsCount,
|
|
5283
|
+
replayTargets: context.replayTargetsCount,
|
|
5284
|
+
comparedTargets: context.comparedTargetsCount,
|
|
5285
|
+
replayErrors: context.replayErrors.length,
|
|
5286
|
+
sourceCounts: context.sourceCounts,
|
|
5287
|
+
runtimeEntriesRaw: context.rawRuntimeEntriesCount,
|
|
5288
|
+
runtimeEntries: context.runtimeEntriesCount,
|
|
5289
|
+
runtimeDuplicateEntries: context.runtimeDuplicateEntriesCount,
|
|
5290
|
+
backtestEntries: context.backtestEntriesCount,
|
|
5291
|
+
matchedEntries: context.matchedCount,
|
|
5292
|
+
runtimeOnlyEntries: context.runtimeOnlyCount,
|
|
5293
|
+
backtestOnlyEntries: context.backtestOnlyCount,
|
|
5294
|
+
runtimeSignalEvaluations: context.runtimeSignalEvaluationsCount,
|
|
5439
5295
|
matchedDeltas: {
|
|
5440
|
-
priceAvgPct: matchedSummary.avgPriceDeltaPct,
|
|
5441
|
-
priceMaxPct: matchedSummary.maxPriceDeltaPct,
|
|
5442
|
-
timeAvgMs: matchedSummary.avgTimestampDiffMs,
|
|
5443
|
-
timeMaxMs: matchedSummary.maxTimestampDiffMs
|
|
5296
|
+
priceAvgPct: context.matchedSummary.avgPriceDeltaPct,
|
|
5297
|
+
priceMaxPct: context.matchedSummary.maxPriceDeltaPct,
|
|
5298
|
+
timeAvgMs: context.matchedSummary.avgTimestampDiffMs,
|
|
5299
|
+
timeMaxMs: context.matchedSummary.maxTimestampDiffMs
|
|
5444
5300
|
},
|
|
5445
|
-
strategyIssues: buildStrategyIssueRows(strategyRows).map(
|
|
5301
|
+
strategyIssues: buildStrategyIssueRows(context.strategyRows).map(
|
|
5446
5302
|
(line) => line.slice(2)
|
|
5447
5303
|
)
|
|
5448
5304
|
},
|
|
5449
|
-
replayErrors: replayErrors.map((error) => ({
|
|
5305
|
+
replayErrors: context.replayErrors.map((error) => ({
|
|
5450
5306
|
strategy: error.strategy,
|
|
5451
5307
|
symbol: error.symbol,
|
|
5452
5308
|
sources: error.sources,
|
|
@@ -5454,7 +5310,7 @@ var buildRuntimeParityMismatchAttachment = ({
|
|
|
5454
5310
|
})),
|
|
5455
5311
|
cases,
|
|
5456
5312
|
mismatches: {
|
|
5457
|
-
runtimeOnly: classifiedRuntimeOnly.map((item) => ({
|
|
5313
|
+
runtimeOnly: context.classifiedRuntimeOnly.map((item) => ({
|
|
5458
5314
|
classification: item.classification,
|
|
5459
5315
|
reason: item.reason,
|
|
5460
5316
|
runtimeEntry: toSerializableTradeParityEntry(item.entry),
|
|
@@ -5467,7 +5323,7 @@ var buildRuntimeParityMismatchAttachment = ({
|
|
|
5467
5323
|
),
|
|
5468
5324
|
nearestBacktestDriftMs: item.nearestBacktestEntryTimestampDiffMs
|
|
5469
5325
|
})),
|
|
5470
|
-
backtestOnly: classifiedBacktestOnly.map((item) => ({
|
|
5326
|
+
backtestOnly: context.classifiedBacktestOnly.map((item) => ({
|
|
5471
5327
|
classification: item.classification,
|
|
5472
5328
|
reason: item.reason,
|
|
5473
5329
|
backtestEntry: toSerializableTradeParityEntry(item.entry),
|
|
@@ -5481,15 +5337,93 @@ var buildRuntimeParityMismatchAttachment = ({
|
|
|
5481
5337
|
}
|
|
5482
5338
|
};
|
|
5483
5339
|
return {
|
|
5484
|
-
filename: `runtime-parity-mismatches-${connectorName}-${window.start}-${window.end}.json`,
|
|
5340
|
+
filename: `runtime-parity-mismatches-${context.connectorName}-${context.window.start}-${context.window.end}.json`,
|
|
5485
5341
|
content: JSON.stringify(payload, null, 2),
|
|
5486
5342
|
caption: "Runtime parity mismatch JSON"
|
|
5487
5343
|
};
|
|
5488
5344
|
};
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5345
|
+
|
|
5346
|
+
// src/lib/runtimeParity/reportingTerminal.ts
|
|
5347
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
5348
|
+
var import_time4 = require("@tradejs/core/time");
|
|
5349
|
+
var DETAIL_LIMIT = 10;
|
|
5350
|
+
var buildRuntimeParityTerminalReport = (context) => {
|
|
5351
|
+
const mismatchRows = buildMismatchSummaryRows(context);
|
|
5352
|
+
const strategyIssueRows = buildStrategyIssueRows(context.strategyRows);
|
|
5353
|
+
const lines = [
|
|
5354
|
+
import_chalk6.default.cyan("TradeJS runtime parity"),
|
|
5355
|
+
`Window: ${(0, import_time4.formatUnix)(context.window.start)} -> ${(0, import_time4.formatUnix)(context.window.end)} (${context.window.source})`,
|
|
5356
|
+
`Connector: ${context.connectorName}`,
|
|
5357
|
+
`Replay env: ${context.replayEnv}${context.runtimeGatesEnabled ? " (runtime AI/ML gates enabled)" : " (core/backtest gates only)"}`,
|
|
5358
|
+
`Tolerance: ${context.toleranceBars} bar(s) / ${(context.toleranceMs / 6e4).toFixed(0)}m`,
|
|
5359
|
+
`Summary: targets=${context.replayTargetsCount}, compared=${context.comparedTargetsCount}, errors=${context.replayErrors.length}, runtime=${context.runtimeEntriesCount}, backtest=${context.backtestEntriesCount}, runtimeOnly=${context.runtimeOnlyCount}, backtestOnly=${context.backtestOnlyCount}, evaluations=${context.runtimeSignalEvaluationsCount}`,
|
|
5360
|
+
`Sources: ${formatSourceCountsSummary(context.sourceCounts)}`,
|
|
5361
|
+
`Runtime entries: ${formatRuntimeEntriesSummary(context)}`,
|
|
5362
|
+
`Matched deltas: price avg/max=${formatPercent(context.matchedSummary.avgPriceDeltaPct)} / ${formatPercent(context.matchedSummary.maxPriceDeltaPct)}, time avg/max=${formatMinutes2(context.matchedSummary.avgTimestampDiffMs)} / ${formatMinutes2(context.matchedSummary.maxTimestampDiffMs)}`
|
|
5363
|
+
];
|
|
5364
|
+
if (context.runtimeDuplicateGroupsCount) {
|
|
5365
|
+
lines.push(
|
|
5366
|
+
`Runtime duplicate groups: ${context.runtimeDuplicateGroupsCount}, duplicate entries: ${context.runtimeDuplicateEntriesCount}`
|
|
5367
|
+
);
|
|
5368
|
+
}
|
|
5369
|
+
if (context.classifiedRuntimeOnly.length) {
|
|
5370
|
+
lines.push(
|
|
5371
|
+
`Runtime only classifications: ${summarizeRuntimeOnlyClassifications(context.classifiedRuntimeOnly)}`
|
|
5372
|
+
);
|
|
5373
|
+
}
|
|
5374
|
+
if (context.classifiedBacktestOnly.length) {
|
|
5375
|
+
lines.push(
|
|
5376
|
+
`Backtest only classifications: ${summarizeBacktestOnlyClassifications(context.classifiedBacktestOnly)}`
|
|
5377
|
+
);
|
|
5378
|
+
}
|
|
5379
|
+
if (context.runtimeSignalEvaluationsCount) {
|
|
5380
|
+
lines.push(`Runtime evaluations: ${context.runtimeSignalEvaluationsCount}`);
|
|
5381
|
+
}
|
|
5382
|
+
if (mismatchRows.length) {
|
|
5383
|
+
lines.push("", import_chalk6.default.yellow("Signal mismatches"));
|
|
5384
|
+
for (const item of mismatchRows.slice(0, context.detailLimit)) {
|
|
5385
|
+
lines.push(`- ${item.line}`);
|
|
5386
|
+
}
|
|
5387
|
+
if (mismatchRows.length > context.detailLimit) {
|
|
5388
|
+
lines.push(`- ... ${mismatchRows.length - context.detailLimit} more`);
|
|
5389
|
+
}
|
|
5390
|
+
}
|
|
5391
|
+
lines.push("");
|
|
5392
|
+
if (strategyIssueRows.length) {
|
|
5393
|
+
lines.push(import_chalk6.default.cyan("Strategy issues"), ...strategyIssueRows);
|
|
5394
|
+
} else if (context.strategyRows.length) {
|
|
5395
|
+
lines.push(
|
|
5396
|
+
`Strategies: clean ${context.strategyRows.length}/${context.strategyRows.length}`
|
|
5397
|
+
);
|
|
5398
|
+
}
|
|
5399
|
+
if (context.runtimeGateWarningCounts.size && !context.runtimeGatesRequested) {
|
|
5400
|
+
lines.push("", import_chalk6.default.yellow("Warnings"));
|
|
5401
|
+
for (const [strategy, count] of [
|
|
5402
|
+
...context.runtimeGateWarningCounts.entries()
|
|
5403
|
+
].sort(([left], [right]) => left.localeCompare(right))) {
|
|
5404
|
+
lines.push(
|
|
5405
|
+
`- ${strategy} uses AI/ML runtime gates on ${count} replay target(s); BACKTEST replay covers core execution, not live gating.`
|
|
5406
|
+
);
|
|
5407
|
+
}
|
|
5408
|
+
}
|
|
5409
|
+
if (context.replayErrors.length) {
|
|
5410
|
+
lines.push("", import_chalk6.default.red("Replay errors"));
|
|
5411
|
+
for (const error of context.replayErrors.slice(0, context.detailLimit)) {
|
|
5412
|
+
lines.push(`- ${error.strategy} ${error.symbol}: ${error.message}`);
|
|
5413
|
+
}
|
|
5414
|
+
if (context.replayErrors.length > context.detailLimit) {
|
|
5415
|
+
lines.push(
|
|
5416
|
+
`- ... ${context.replayErrors.length - context.detailLimit} more`
|
|
5417
|
+
);
|
|
5418
|
+
}
|
|
5492
5419
|
}
|
|
5420
|
+
return lines;
|
|
5421
|
+
};
|
|
5422
|
+
var writeRuntimeParityTerminalReport = (lines) => {
|
|
5423
|
+
for (const line of lines) console.log(line);
|
|
5424
|
+
};
|
|
5425
|
+
var printRuntimeDuplicateDetails = (groups) => {
|
|
5426
|
+
if (!groups.length) return;
|
|
5493
5427
|
console.log("");
|
|
5494
5428
|
console.log(import_chalk6.default.yellow("Runtime duplicates"));
|
|
5495
5429
|
for (const group of groups.slice(0, DETAIL_LIMIT)) {
|
|
@@ -5503,9 +5437,7 @@ var printRuntimeDuplicateDetails = (groups) => {
|
|
|
5503
5437
|
}
|
|
5504
5438
|
};
|
|
5505
5439
|
var printClassifiedBacktestOnlyDetails = (classifiedEntries) => {
|
|
5506
|
-
if (!classifiedEntries.length)
|
|
5507
|
-
return;
|
|
5508
|
-
}
|
|
5440
|
+
if (!classifiedEntries.length) return;
|
|
5509
5441
|
console.log("");
|
|
5510
5442
|
console.log(import_chalk6.default.yellow("Backtest only"));
|
|
5511
5443
|
for (const item of classifiedEntries.slice(0, DETAIL_LIMIT)) {
|
|
@@ -5520,9 +5452,7 @@ var printClassifiedBacktestOnlyDetails = (classifiedEntries) => {
|
|
|
5520
5452
|
}
|
|
5521
5453
|
};
|
|
5522
5454
|
var printClassifiedRuntimeOnlyDetails = (classifiedEntries) => {
|
|
5523
|
-
if (!classifiedEntries.length)
|
|
5524
|
-
return;
|
|
5525
|
-
}
|
|
5455
|
+
if (!classifiedEntries.length) return;
|
|
5526
5456
|
console.log("");
|
|
5527
5457
|
console.log(import_chalk6.default.yellow("Runtime only"));
|
|
5528
5458
|
for (const item of classifiedEntries.slice(0, DETAIL_LIMIT)) {
|
|
@@ -5536,149 +5466,78 @@ var printClassifiedRuntimeOnlyDetails = (classifiedEntries) => {
|
|
|
5536
5466
|
console.log(`- ... ${classifiedEntries.length - DETAIL_LIMIT} more`);
|
|
5537
5467
|
}
|
|
5538
5468
|
};
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
});
|
|
5559
|
-
for (const [strategy, baseRow] of baseRows) {
|
|
5560
|
-
rows.set(strategy, {
|
|
5561
|
-
...baseRow,
|
|
5562
|
-
targets: 0,
|
|
5563
|
-
compared: 0,
|
|
5564
|
-
errors: 0
|
|
5565
|
-
});
|
|
5566
|
-
}
|
|
5567
|
-
const ensureRow = (strategy) => {
|
|
5568
|
-
const row = rows.get(strategy) ?? {
|
|
5569
|
-
runtime: 0,
|
|
5570
|
-
runtimeDuplicates: 0,
|
|
5571
|
-
backtest: 0,
|
|
5572
|
-
matched: 0,
|
|
5573
|
-
runtimeOnly: 0,
|
|
5574
|
-
backtestOnly: 0,
|
|
5575
|
-
targets: 0,
|
|
5576
|
-
compared: 0,
|
|
5577
|
-
errors: 0
|
|
5578
|
-
};
|
|
5579
|
-
rows.set(strategy, row);
|
|
5580
|
-
return row;
|
|
5581
|
-
};
|
|
5582
|
-
for (const target of targets) {
|
|
5583
|
-
const row = ensureRow(target.strategy);
|
|
5584
|
-
row.targets += 1;
|
|
5585
|
-
if (successfulTargetKeys.has(toTargetKey(target))) {
|
|
5586
|
-
row.compared += 1;
|
|
5587
|
-
}
|
|
5588
|
-
}
|
|
5589
|
-
for (const error of replayErrors) {
|
|
5590
|
-
ensureRow(error.strategy).errors += 1;
|
|
5591
|
-
}
|
|
5592
|
-
return [...rows.entries()].sort(
|
|
5593
|
-
([left], [right]) => left.localeCompare(right)
|
|
5594
|
-
);
|
|
5595
|
-
};
|
|
5596
|
-
var buildRuntimeParityMessage = ({
|
|
5597
|
-
window,
|
|
5598
|
-
connectorName,
|
|
5599
|
-
replayEnv,
|
|
5600
|
-
runtimeGatesEnabled,
|
|
5601
|
-
toleranceBars,
|
|
5602
|
-
toleranceMs,
|
|
5603
|
-
replayTargetsCount,
|
|
5604
|
-
comparedTargetsCount,
|
|
5605
|
-
replayErrors,
|
|
5606
|
-
sourceCounts,
|
|
5607
|
-
rawRuntimeEntriesCount,
|
|
5608
|
-
runtimeEntriesCount,
|
|
5609
|
-
runtimeDuplicateEntriesCount,
|
|
5610
|
-
backtestEntriesCount,
|
|
5611
|
-
matchedCount: _matchedCount,
|
|
5612
|
-
runtimeOnlyCount,
|
|
5613
|
-
backtestOnlyCount,
|
|
5614
|
-
matchedSummary,
|
|
5615
|
-
classifiedRuntimeOnly,
|
|
5616
|
-
classifiedBacktestOnly,
|
|
5617
|
-
runtimeSignalEvaluationsCount,
|
|
5618
|
-
strategyRows,
|
|
5619
|
-
runtimeGateWarningCounts
|
|
5469
|
+
|
|
5470
|
+
// src/lib/runtimeParity/reportingTelegram.ts
|
|
5471
|
+
var TELEGRAM_DETAIL_LIMIT = 5;
|
|
5472
|
+
var SUMMARY_TIMEZONE = "Europe/Moscow";
|
|
5473
|
+
var SUMMARY_TIMEZONE_LABEL = "MSK";
|
|
5474
|
+
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
5475
|
+
var formatMskDateTime = (timestamp) => new Intl.DateTimeFormat("ru-RU", {
|
|
5476
|
+
timeZone: SUMMARY_TIMEZONE,
|
|
5477
|
+
year: "numeric",
|
|
5478
|
+
month: "2-digit",
|
|
5479
|
+
day: "2-digit",
|
|
5480
|
+
hour: "2-digit",
|
|
5481
|
+
minute: "2-digit",
|
|
5482
|
+
hour12: false
|
|
5483
|
+
}).format(new Date(timestamp));
|
|
5484
|
+
var buildRuntimeParityMessage = ({
|
|
5485
|
+
runtimeGatesEnabled,
|
|
5486
|
+
runtimeGateWarningCounts,
|
|
5487
|
+
...context
|
|
5620
5488
|
}) => {
|
|
5621
5489
|
const lines = [];
|
|
5622
5490
|
lines.push("\u{1F9EA} <b>TradeJS runtime parity</b>");
|
|
5623
5491
|
lines.push("");
|
|
5624
5492
|
lines.push("\u{1F552} <b>Window</b>");
|
|
5625
5493
|
lines.push(
|
|
5626
|
-
`<b>${escapeHtml(formatMskDateTime(window.start))} - ${escapeHtml(formatMskDateTime(window.end))} ${SUMMARY_TIMEZONE_LABEL}</b>`
|
|
5494
|
+
`<b>${escapeHtml(formatMskDateTime(context.window.start))} - ${escapeHtml(formatMskDateTime(context.window.end))} ${SUMMARY_TIMEZONE_LABEL}</b>`
|
|
5627
5495
|
);
|
|
5628
5496
|
lines.push("");
|
|
5629
|
-
lines.push(`\u{1F50C} Connector: <b>${escapeHtml(connectorName)}</b>`);
|
|
5630
|
-
lines.push(`\u{1F9EC} Replay env: <b>${escapeHtml(replayEnv)}</b>`);
|
|
5497
|
+
lines.push(`\u{1F50C} Connector: <b>${escapeHtml(context.connectorName)}</b>`);
|
|
5498
|
+
lines.push(`\u{1F9EC} Replay env: <b>${escapeHtml(context.replayEnv)}</b>`);
|
|
5631
5499
|
lines.push(
|
|
5632
|
-
`\u{1F3AF} Tolerance: <b>${toleranceBars} bar(s) / ${(toleranceMs / 6e4).toFixed(0)}m</b>`
|
|
5500
|
+
`\u{1F3AF} Tolerance: <b>${context.toleranceBars} bar(s) / ${(context.toleranceMs / 6e4).toFixed(0)}m</b>`
|
|
5633
5501
|
);
|
|
5634
5502
|
lines.push("");
|
|
5635
5503
|
lines.push("\u{1F4CC} <b>Overview</b>");
|
|
5636
5504
|
lines.push(
|
|
5637
|
-
`\u2022 Targets: <b>${replayTargetsCount}</b> / compared <b>${comparedTargetsCount}</b> / errors <b>${replayErrors.length}</b>`
|
|
5505
|
+
`\u2022 Targets: <b>${context.replayTargetsCount}</b> / compared <b>${context.comparedTargetsCount}</b> / errors <b>${context.replayErrors.length}</b>`
|
|
5638
5506
|
);
|
|
5639
5507
|
lines.push(
|
|
5640
|
-
`\u2022 Sources: <b>${escapeHtml(formatSourceCountsSummary(sourceCounts))}</b>`
|
|
5508
|
+
`\u2022 Sources: <b>${escapeHtml(formatSourceCountsSummary(context.sourceCounts))}</b>`
|
|
5641
5509
|
);
|
|
5642
5510
|
lines.push("");
|
|
5643
5511
|
lines.push("\u{1F4C8} <b>Entries</b>");
|
|
5644
5512
|
lines.push(
|
|
5645
|
-
`\u2022 Runtime: <b>${escapeHtml(
|
|
5646
|
-
formatRuntimeEntriesSummary({
|
|
5647
|
-
rawRuntimeEntriesCount,
|
|
5648
|
-
runtimeEntriesCount,
|
|
5649
|
-
runtimeDuplicateEntriesCount
|
|
5650
|
-
})
|
|
5651
|
-
)}</b>`
|
|
5513
|
+
`\u2022 Runtime: <b>${escapeHtml(formatRuntimeEntriesSummary(context))}</b>`
|
|
5652
5514
|
);
|
|
5653
|
-
lines.push(`\u2022 Backtest: <b>${backtestEntriesCount}</b>`);
|
|
5515
|
+
lines.push(`\u2022 Backtest: <b>${context.backtestEntriesCount}</b>`);
|
|
5654
5516
|
lines.push(
|
|
5655
|
-
`\u2022 Runtime only: <b>${runtimeOnlyCount}</b> / Backtest only: <b>${backtestOnlyCount}</b>`
|
|
5517
|
+
`\u2022 Runtime only: <b>${context.runtimeOnlyCount}</b> / Backtest only: <b>${context.backtestOnlyCount}</b>`
|
|
5656
5518
|
);
|
|
5657
5519
|
lines.push(
|
|
5658
|
-
`\u2022 Matched deltas: price avg/max=<b>${escapeHtml(formatPercent(matchedSummary.avgPriceDeltaPct))} / ${escapeHtml(formatPercent(matchedSummary.maxPriceDeltaPct))}</b>, time avg/max=<b>${escapeHtml(formatMinutes2(matchedSummary.avgTimestampDiffMs))} / ${escapeHtml(formatMinutes2(matchedSummary.maxTimestampDiffMs))}</b>`
|
|
5520
|
+
`\u2022 Matched deltas: price avg/max=<b>${escapeHtml(formatPercent(context.matchedSummary.avgPriceDeltaPct))} / ${escapeHtml(formatPercent(context.matchedSummary.maxPriceDeltaPct))}</b>, time avg/max=<b>${escapeHtml(formatMinutes2(context.matchedSummary.avgTimestampDiffMs))} / ${escapeHtml(formatMinutes2(context.matchedSummary.maxTimestampDiffMs))}</b>`
|
|
5659
5521
|
);
|
|
5660
|
-
if (classifiedRuntimeOnly.length) {
|
|
5522
|
+
if (context.classifiedRuntimeOnly.length) {
|
|
5661
5523
|
lines.push(
|
|
5662
|
-
`\u2022 Runtime-only classes: <code>${escapeHtml(summarizeRuntimeOnlyClassifications(classifiedRuntimeOnly))}</code>`
|
|
5524
|
+
`\u2022 Runtime-only classes: <code>${escapeHtml(summarizeRuntimeOnlyClassifications(context.classifiedRuntimeOnly))}</code>`
|
|
5663
5525
|
);
|
|
5664
5526
|
}
|
|
5665
|
-
if (classifiedBacktestOnly.length) {
|
|
5527
|
+
if (context.classifiedBacktestOnly.length) {
|
|
5666
5528
|
lines.push(
|
|
5667
|
-
`\u2022 Backtest-only classes: <code>${escapeHtml(summarizeBacktestOnlyClassifications(classifiedBacktestOnly))}</code>`
|
|
5529
|
+
`\u2022 Backtest-only classes: <code>${escapeHtml(summarizeBacktestOnlyClassifications(context.classifiedBacktestOnly))}</code>`
|
|
5668
5530
|
);
|
|
5669
5531
|
}
|
|
5670
|
-
if (runtimeSignalEvaluationsCount) {
|
|
5532
|
+
if (context.runtimeSignalEvaluationsCount) {
|
|
5671
5533
|
lines.push(
|
|
5672
|
-
`\u2022 Runtime evaluations: <b>${runtimeSignalEvaluationsCount}</b>`
|
|
5534
|
+
`\u2022 Runtime evaluations: <b>${context.runtimeSignalEvaluationsCount}</b>`
|
|
5673
5535
|
);
|
|
5674
5536
|
}
|
|
5675
|
-
const mismatchRows = buildMismatchSummaryRows(
|
|
5676
|
-
classifiedRuntimeOnly,
|
|
5677
|
-
classifiedBacktestOnly
|
|
5678
|
-
});
|
|
5537
|
+
const mismatchRows = buildMismatchSummaryRows(context);
|
|
5679
5538
|
if (mismatchRows.length) {
|
|
5680
5539
|
lines.push("");
|
|
5681
|
-
lines.push(
|
|
5540
|
+
lines.push("\u{1F50E} <b>Mismatches</b>");
|
|
5682
5541
|
for (const item of mismatchRows.slice(0, TELEGRAM_DETAIL_LIMIT)) {
|
|
5683
5542
|
lines.push(`\u2022 <code>${escapeHtml(item.line)}</code>`);
|
|
5684
5543
|
}
|
|
@@ -5688,17 +5547,17 @@ var buildRuntimeParityMessage = ({
|
|
|
5688
5547
|
);
|
|
5689
5548
|
}
|
|
5690
5549
|
}
|
|
5691
|
-
const strategyIssueRows = buildStrategyIssueRows(strategyRows);
|
|
5550
|
+
const strategyIssueRows = buildStrategyIssueRows(context.strategyRows);
|
|
5692
5551
|
if (strategyIssueRows.length) {
|
|
5693
5552
|
lines.push("");
|
|
5694
5553
|
lines.push("\u{1F4CA} <b>Strategy issues</b>");
|
|
5695
5554
|
for (const line of strategyIssueRows) {
|
|
5696
5555
|
lines.push(`\u2022 ${escapeHtml(line.slice(2))}`);
|
|
5697
5556
|
}
|
|
5698
|
-
} else if (strategyRows.length) {
|
|
5557
|
+
} else if (context.strategyRows.length) {
|
|
5699
5558
|
lines.push("");
|
|
5700
5559
|
lines.push(
|
|
5701
|
-
`\u{1F4CA} <b>Strategies</b>: clean <b>${strategyRows.length}</b> / total <b>${strategyRows.length}</b>`
|
|
5560
|
+
`\u{1F4CA} <b>Strategies</b>: clean <b>${context.strategyRows.length}</b> / total <b>${context.strategyRows.length}</b>`
|
|
5702
5561
|
);
|
|
5703
5562
|
}
|
|
5704
5563
|
if (runtimeGateWarningCounts.size && !runtimeGatesEnabled) {
|
|
@@ -5712,17 +5571,17 @@ var buildRuntimeParityMessage = ({
|
|
|
5712
5571
|
);
|
|
5713
5572
|
}
|
|
5714
5573
|
}
|
|
5715
|
-
if (replayErrors.length) {
|
|
5574
|
+
if (context.replayErrors.length) {
|
|
5716
5575
|
lines.push("");
|
|
5717
5576
|
lines.push("\u274C <b>Replay errors</b>");
|
|
5718
|
-
for (const error of replayErrors.slice(0, TELEGRAM_DETAIL_LIMIT)) {
|
|
5577
|
+
for (const error of context.replayErrors.slice(0, TELEGRAM_DETAIL_LIMIT)) {
|
|
5719
5578
|
lines.push(
|
|
5720
5579
|
`\u2022 <b>${escapeHtml(error.strategy)}</b> ${escapeHtml(error.symbol)}: <code>${escapeHtml(error.message)}</code>`
|
|
5721
5580
|
);
|
|
5722
5581
|
}
|
|
5723
|
-
if (replayErrors.length > TELEGRAM_DETAIL_LIMIT) {
|
|
5582
|
+
if (context.replayErrors.length > TELEGRAM_DETAIL_LIMIT) {
|
|
5724
5583
|
lines.push(
|
|
5725
|
-
`... <b>${replayErrors.length - TELEGRAM_DETAIL_LIMIT}</b> more`
|
|
5584
|
+
`... <b>${context.replayErrors.length - TELEGRAM_DETAIL_LIMIT}</b> more`
|
|
5726
5585
|
);
|
|
5727
5586
|
}
|
|
5728
5587
|
}
|
|
@@ -5732,7 +5591,6 @@ var buildRuntimeParityNoTargetsMessage = ({
|
|
|
5732
5591
|
window,
|
|
5733
5592
|
connectorName,
|
|
5734
5593
|
replayEnv,
|
|
5735
|
-
runtimeGatesEnabled: _runtimeGatesEnabled,
|
|
5736
5594
|
userName
|
|
5737
5595
|
}) => [
|
|
5738
5596
|
"\u{1F9EA} <b>TradeJS runtime parity</b>",
|
|
@@ -5745,6 +5603,238 @@ var buildRuntimeParityNoTargetsMessage = ({
|
|
|
5745
5603
|
"",
|
|
5746
5604
|
`\u26A0\uFE0F No replay targets found for user <b>${escapeHtml(userName)}</b>.`
|
|
5747
5605
|
].join("\n");
|
|
5606
|
+
|
|
5607
|
+
// src/lib/runtimeModeConfig.ts
|
|
5608
|
+
var hasRuntimeEntryGateEnabled = (strategyConfig) => strategyConfig.AI_ENABLED === true || strategyConfig.ML_ENABLED === true;
|
|
5609
|
+
var resolveReplayStrategyEnv = ({
|
|
5610
|
+
strategyConfig,
|
|
5611
|
+
forceRuntimeGates = false
|
|
5612
|
+
}) => forceRuntimeGates || hasRuntimeEntryGateEnabled(strategyConfig) ? "PARITY" : "BACKTEST";
|
|
5613
|
+
var buildRuntimeModeStrategyConfig = ({
|
|
5614
|
+
strategyConfig,
|
|
5615
|
+
env,
|
|
5616
|
+
interval: interval2,
|
|
5617
|
+
makeOrders,
|
|
5618
|
+
recordRuntimeTrades,
|
|
5619
|
+
aiReplayAnalyses
|
|
5620
|
+
}) => ({
|
|
5621
|
+
...strategyConfig,
|
|
5622
|
+
ENV: env,
|
|
5623
|
+
MAKE_ORDERS: makeOrders,
|
|
5624
|
+
INTERVAL: strategyConfig.INTERVAL ?? interval2,
|
|
5625
|
+
...typeof recordRuntimeTrades === "boolean" ? { RECORD_RUNTIME_TRADES: recordRuntimeTrades } : {},
|
|
5626
|
+
...aiReplayAnalyses?.length ? { AI_REPLAY_ANALYSES: aiReplayAnalyses } : {}
|
|
5627
|
+
});
|
|
5628
|
+
|
|
5629
|
+
// src/scripts/runtimeParity.ts
|
|
5630
|
+
import_args.default.option(["u", "user"], "Use user config", "root");
|
|
5631
|
+
import_args.default.option(
|
|
5632
|
+
["o", "connector"],
|
|
5633
|
+
"Connector provider or name for parity replay (e.g. bybit, binance, coinbase, custom)",
|
|
5634
|
+
"bybit"
|
|
5635
|
+
);
|
|
5636
|
+
import_args.default.option(["d", "days"], "Replay window in days", 3);
|
|
5637
|
+
import_args.default.option(
|
|
5638
|
+
["b", "startTime"],
|
|
5639
|
+
"Explicit replay start timestamp (ms or seconds)"
|
|
5640
|
+
);
|
|
5641
|
+
import_args.default.option(["e", "endTime"], "Explicit replay end timestamp (ms or seconds)");
|
|
5642
|
+
import_args.default.option(["s", "strategy"], "Only compare one strategy");
|
|
5643
|
+
import_args.default.option(
|
|
5644
|
+
["t", "tickers"],
|
|
5645
|
+
"Replay comma-separated symbols for all configured strategies"
|
|
5646
|
+
);
|
|
5647
|
+
import_args.default.option(
|
|
5648
|
+
["C", "cacheOnly"],
|
|
5649
|
+
"Do not refresh market history before replay",
|
|
5650
|
+
false
|
|
5651
|
+
);
|
|
5652
|
+
import_args.default.option(
|
|
5653
|
+
["a", "toleranceBars"],
|
|
5654
|
+
"Allowed entry timestamp drift in bars when matching runtime vs backtest",
|
|
5655
|
+
1
|
|
5656
|
+
);
|
|
5657
|
+
import_args.default.option(
|
|
5658
|
+
"fullUniverse",
|
|
5659
|
+
"Replay every configured strategy across the full connector universe. By default parity replays only runtime-traded and strategy-results symbols unless --tickers is provided.",
|
|
5660
|
+
false
|
|
5661
|
+
);
|
|
5662
|
+
import_args.default.option(
|
|
5663
|
+
"runtimeGates",
|
|
5664
|
+
"Force runtime AI/ML gates for all replay targets. Runtime-gated configs are replayed with gates by default.",
|
|
5665
|
+
false
|
|
5666
|
+
);
|
|
5667
|
+
import_args.default.option(["N", "notify"], "Send parity summary to Telegram", false);
|
|
5668
|
+
import_args.default.option(["D", "details"], "Print unmatched entry details (capped)", false);
|
|
5669
|
+
process.argv = normalizeCliArgv(process.argv, {
|
|
5670
|
+
"-C": "--cacheOnly",
|
|
5671
|
+
"-D": "--details",
|
|
5672
|
+
"-E": "--endTime",
|
|
5673
|
+
"-N": "--notify",
|
|
5674
|
+
"-S": "--startTime",
|
|
5675
|
+
"-T": "--toleranceBars"
|
|
5676
|
+
});
|
|
5677
|
+
var flags = import_args.default.parse(process.argv);
|
|
5678
|
+
var interval = "15";
|
|
5679
|
+
var projectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
|
|
5680
|
+
var DEFAULT_LOOKBACK_DAYS = 3;
|
|
5681
|
+
var DETAIL_LIMIT2 = 10;
|
|
5682
|
+
var DEFAULT_REPLAY_ENV = flags.runtimeGates ? "PARITY" : "BACKTEST";
|
|
5683
|
+
var resolveParityConnectorName = async (value) => {
|
|
5684
|
+
const connectorName = await (0, import_connectors5.resolveConnectorName)(value, projectRoot);
|
|
5685
|
+
if (connectorName) {
|
|
5686
|
+
return connectorName;
|
|
5687
|
+
}
|
|
5688
|
+
import_logger.logger.warn(
|
|
5689
|
+
'Unknown connector "%s". Fallback to %s.',
|
|
5690
|
+
String(value || "").trim() || String(value),
|
|
5691
|
+
import_connectors5.DEFAULT_CONNECTOR_NAME
|
|
5692
|
+
);
|
|
5693
|
+
return import_connectors5.DEFAULT_CONNECTOR_NAME;
|
|
5694
|
+
};
|
|
5695
|
+
var createConnector = async ({
|
|
5696
|
+
userName,
|
|
5697
|
+
connectorName
|
|
5698
|
+
}) => {
|
|
5699
|
+
const connectorFactory = await (0, import_connectors5.getConnectorCreatorByName)(
|
|
5700
|
+
connectorName,
|
|
5701
|
+
projectRoot
|
|
5702
|
+
);
|
|
5703
|
+
if (!connectorFactory) {
|
|
5704
|
+
throw new Error(`Connector "${connectorName}" is not registered`);
|
|
5705
|
+
}
|
|
5706
|
+
return await connectorFactory({ userName });
|
|
5707
|
+
};
|
|
5708
|
+
var buildReplayAiAnalyses = ({
|
|
5709
|
+
runtimeTrades,
|
|
5710
|
+
runtimeSignalEvaluations,
|
|
5711
|
+
toleranceMs
|
|
5712
|
+
}) => {
|
|
5713
|
+
const snapshots = [
|
|
5714
|
+
...runtimeTrades.filter(
|
|
5715
|
+
(trade) => trade.aiAnalysis && typeof trade.entryTimestamp === "number" && Number.isFinite(trade.entryTimestamp)
|
|
5716
|
+
).map((trade) => ({
|
|
5717
|
+
strategy: trade.strategy,
|
|
5718
|
+
symbol: trade.symbol,
|
|
5719
|
+
direction: trade.direction,
|
|
5720
|
+
timestamp: trade.entryTimestamp,
|
|
5721
|
+
toleranceMs,
|
|
5722
|
+
analysis: trade.aiAnalysis
|
|
5723
|
+
})),
|
|
5724
|
+
...runtimeSignalEvaluations.filter(
|
|
5725
|
+
(evaluation) => evaluation.aiAnalysis && evaluation.direction && typeof evaluation.timestamp === "number" && Number.isFinite(evaluation.timestamp)
|
|
5726
|
+
).map((evaluation) => ({
|
|
5727
|
+
strategy: evaluation.strategy,
|
|
5728
|
+
symbol: evaluation.symbol,
|
|
5729
|
+
direction: evaluation.direction,
|
|
5730
|
+
timestamp: evaluation.timestamp,
|
|
5731
|
+
toleranceMs,
|
|
5732
|
+
analysis: evaluation.aiAnalysis
|
|
5733
|
+
}))
|
|
5734
|
+
];
|
|
5735
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5736
|
+
return snapshots.filter((snapshot) => {
|
|
5737
|
+
const key = `${snapshot.strategy}:${snapshot.symbol}:${snapshot.direction}:${snapshot.timestamp}`;
|
|
5738
|
+
if (seen.has(key)) {
|
|
5739
|
+
return false;
|
|
5740
|
+
}
|
|
5741
|
+
seen.add(key);
|
|
5742
|
+
return true;
|
|
5743
|
+
});
|
|
5744
|
+
};
|
|
5745
|
+
var formatReplayEnvSummary = (configs) => {
|
|
5746
|
+
let backtest = 0;
|
|
5747
|
+
let parity = 0;
|
|
5748
|
+
for (const config of configs) {
|
|
5749
|
+
if (config.ENV === "PARITY") {
|
|
5750
|
+
parity += 1;
|
|
5751
|
+
} else {
|
|
5752
|
+
backtest += 1;
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
if (parity > 0 && backtest > 0) {
|
|
5756
|
+
return `MIXED (PARITY=${parity}, BACKTEST=${backtest})`;
|
|
5757
|
+
}
|
|
5758
|
+
if (parity > 0) {
|
|
5759
|
+
return "PARITY";
|
|
5760
|
+
}
|
|
5761
|
+
return "BACKTEST";
|
|
5762
|
+
};
|
|
5763
|
+
var buildReplayConfig = async ({
|
|
5764
|
+
userName,
|
|
5765
|
+
strategy,
|
|
5766
|
+
symbol,
|
|
5767
|
+
replayInputsIndex,
|
|
5768
|
+
toleranceMs
|
|
5769
|
+
}) => {
|
|
5770
|
+
const [userConfig, strategyResults] = await Promise.all([
|
|
5771
|
+
(0, import_redis3.getData)(import_redis3.redisKeys.strategyConfig(userName, strategy), {}),
|
|
5772
|
+
(0, import_redis3.getData)(import_redis3.redisKeys.strategyResults(userName, strategy), {})
|
|
5773
|
+
]);
|
|
5774
|
+
const typedResults = strategyResults ?? {};
|
|
5775
|
+
const symbolResult = typedResults?.[symbol];
|
|
5776
|
+
const symbolConfig = symbolResult && symbolResult.config && typeof symbolResult.config === "object" ? symbolResult.config : {};
|
|
5777
|
+
const replayInputs = replayInputsIndex.get(toTargetKey({ strategy, symbol }));
|
|
5778
|
+
const aiReplayAnalyses = buildReplayAiAnalyses({
|
|
5779
|
+
runtimeTrades: replayInputs?.runtimeTrades ?? [],
|
|
5780
|
+
runtimeSignalEvaluations: replayInputs?.runtimeSignalEvaluations ?? [],
|
|
5781
|
+
strategy,
|
|
5782
|
+
symbol,
|
|
5783
|
+
toleranceMs
|
|
5784
|
+
});
|
|
5785
|
+
const strategyConfig = {
|
|
5786
|
+
...userConfig,
|
|
5787
|
+
...symbolConfig
|
|
5788
|
+
};
|
|
5789
|
+
return buildRuntimeModeStrategyConfig({
|
|
5790
|
+
strategyConfig,
|
|
5791
|
+
env: resolveReplayStrategyEnv({
|
|
5792
|
+
strategyConfig,
|
|
5793
|
+
forceRuntimeGates: Boolean(flags.runtimeGates)
|
|
5794
|
+
}),
|
|
5795
|
+
interval,
|
|
5796
|
+
makeOrders: true,
|
|
5797
|
+
recordRuntimeTrades: false,
|
|
5798
|
+
aiReplayAnalyses
|
|
5799
|
+
});
|
|
5800
|
+
};
|
|
5801
|
+
var warmReplayHistory = async ({
|
|
5802
|
+
userName,
|
|
5803
|
+
connectorName,
|
|
5804
|
+
targets,
|
|
5805
|
+
preloadStart,
|
|
5806
|
+
preloadEnd
|
|
5807
|
+
}) => {
|
|
5808
|
+
const connector = await createConnector({ userName, connectorName });
|
|
5809
|
+
const symbols = [...new Set(targets.map((target) => target.symbol))];
|
|
5810
|
+
const warmStartedAt = Date.now();
|
|
5811
|
+
console.log(
|
|
5812
|
+
import_chalk7.default.gray(
|
|
5813
|
+
`Warm history: ${symbols.length} symbol(s) on ${connectorName}, preload ${(0, import_time5.formatUnix)(preloadStart)} -> ${(0, import_time5.formatUnix)(preloadEnd)}`
|
|
5814
|
+
)
|
|
5815
|
+
);
|
|
5816
|
+
const btcReferences = await loadBtcReferenceConnectors({
|
|
5817
|
+
connectorName,
|
|
5818
|
+
marketConnector: connector,
|
|
5819
|
+
userName,
|
|
5820
|
+
projectRoot,
|
|
5821
|
+
shouldUseDedicatedReferences: true,
|
|
5822
|
+
warn: (message) => import_logger.logger.warn(message)
|
|
5823
|
+
});
|
|
5824
|
+
await updateMarketHistoryWithBtcReferences({
|
|
5825
|
+
marketConnector: connector,
|
|
5826
|
+
connectorName,
|
|
5827
|
+
btcReferences,
|
|
5828
|
+
interval,
|
|
5829
|
+
symbols,
|
|
5830
|
+
preloadStart,
|
|
5831
|
+
preloadEnd,
|
|
5832
|
+
log: (message) => console.log(import_chalk7.default.gray(message))
|
|
5833
|
+
});
|
|
5834
|
+
console.log(
|
|
5835
|
+
import_chalk7.default.gray(`Warm history: done in ${formatDuration(warmStartedAt)}`)
|
|
5836
|
+
);
|
|
5837
|
+
};
|
|
5748
5838
|
var runtimeParity = async () => {
|
|
5749
5839
|
const window = resolveTimeWindow({
|
|
5750
5840
|
days: flags.days ?? DEFAULT_LOOKBACK_DAYS,
|
|
@@ -5767,28 +5857,18 @@ var runtimeParity = async () => {
|
|
|
5767
5857
|
userName: flags.user,
|
|
5768
5858
|
connectorName
|
|
5769
5859
|
});
|
|
5770
|
-
const
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
})
|
|
5783
|
-
]);
|
|
5784
|
-
const runtimeTrades = allRuntimeTrades.filter(
|
|
5785
|
-
(trade) => trade.entryTimestamp >= window.start && trade.entryTimestamp <= window.end && (!flags.strategy || trade.strategy === flags.strategy) && (!requestedSymbolSet || requestedSymbolSet.has(trade.symbol))
|
|
5786
|
-
);
|
|
5787
|
-
const runtimeSignals = allRuntimeSignals.filter(
|
|
5788
|
-
(signal) => signal.timestamp >= window.start && signal.timestamp <= window.end && (!flags.strategy || signal.strategy === flags.strategy) && (!requestedSymbolSet || requestedSymbolSet.has(signal.symbol))
|
|
5789
|
-
);
|
|
5790
|
-
const runtimeSignalEvaluations = allRuntimeSignalEvaluations.filter(
|
|
5791
|
-
(evaluation) => evaluation.timestamp >= window.start && evaluation.timestamp <= window.end && (!flags.strategy || evaluation.strategy === flags.strategy) && (!requestedSymbolSet || requestedSymbolSet.has(evaluation.symbol))
|
|
5860
|
+
const { runtimeTrades, runtimeSignals, runtimeSignalEvaluations } = await loadRuntimeParityEvidence(
|
|
5861
|
+
{
|
|
5862
|
+
userName: flags.user,
|
|
5863
|
+
window,
|
|
5864
|
+
strategy: flags.strategy,
|
|
5865
|
+
symbols: requestedSymbolSet
|
|
5866
|
+
},
|
|
5867
|
+
{
|
|
5868
|
+
loadRuntimeTrades,
|
|
5869
|
+
loadRuntimeSignals,
|
|
5870
|
+
loadRuntimeSignalEvaluations
|
|
5871
|
+
}
|
|
5792
5872
|
);
|
|
5793
5873
|
const connectorSymbols = requestedSymbols.length ? requestedSymbols : await (0, import_cli3.getTickers)(parityConnector);
|
|
5794
5874
|
const replayInputsIndex = buildReplayInputsIndex({
|
|
@@ -5805,8 +5885,8 @@ var runtimeParity = async () => {
|
|
|
5805
5885
|
});
|
|
5806
5886
|
if (!replayTargets.length) {
|
|
5807
5887
|
console.log(
|
|
5808
|
-
|
|
5809
|
-
`No replay targets found for ${flags.user} in ${(0,
|
|
5888
|
+
import_chalk7.default.yellow(
|
|
5889
|
+
`No replay targets found for ${flags.user} in ${(0, import_time5.formatUnix)(window.start)} -> ${(0, import_time5.formatUnix)(window.end)}.`
|
|
5810
5890
|
)
|
|
5811
5891
|
);
|
|
5812
5892
|
if (flags.notify) {
|
|
@@ -5824,7 +5904,7 @@ var runtimeParity = async () => {
|
|
|
5824
5904
|
return;
|
|
5825
5905
|
}
|
|
5826
5906
|
const sourceCounts = countReplayTargetSources(replayTargets);
|
|
5827
|
-
const preloadStart = (0,
|
|
5907
|
+
const preloadStart = (0, import_time5.getBacktestPreloadStart)(
|
|
5828
5908
|
window.start,
|
|
5829
5909
|
import_constants3.BACKTEST_PRELOAD_DAYS
|
|
5830
5910
|
);
|
|
@@ -5833,7 +5913,7 @@ var runtimeParity = async () => {
|
|
|
5833
5913
|
replayTargets.map((target) => target.symbol)
|
|
5834
5914
|
).size;
|
|
5835
5915
|
console.log(
|
|
5836
|
-
|
|
5916
|
+
import_chalk7.default.gray(
|
|
5837
5917
|
`Replay queue: ${replayTargets.length} target(s), ${replaySymbolsCount} symbol(s), mode=${flags.fullUniverse ? "full-universe" : requestedSymbols.length ? "explicit-tickers" : "runtime+results"}`
|
|
5838
5918
|
)
|
|
5839
5919
|
);
|
|
@@ -5868,7 +5948,7 @@ var runtimeParity = async () => {
|
|
|
5868
5948
|
aiEnabled: hasParityAiTargets,
|
|
5869
5949
|
mlEnabled: hasParityMlTargets,
|
|
5870
5950
|
strategyNames: replayTargets.map((target) => target.strategy),
|
|
5871
|
-
log: (message) => console.log(
|
|
5951
|
+
log: (message) => console.log(import_chalk7.default.gray(message))
|
|
5872
5952
|
});
|
|
5873
5953
|
if (!flags.cacheOnly) {
|
|
5874
5954
|
await warmReplayHistory({
|
|
@@ -5936,139 +6016,61 @@ var runtimeParity = async () => {
|
|
|
5936
6016
|
}
|
|
5937
6017
|
if (progressPosition === 1 || progressPosition === replayTargets.length || progressPosition % 25 === 0) {
|
|
5938
6018
|
console.log(
|
|
5939
|
-
|
|
6019
|
+
import_chalk7.default.gray(
|
|
5940
6020
|
`Replay progress: ${progressPosition}/${replayTargets.length}, ok=${successfulTargetKeys.size}, errors=${replayErrors.length}, current=${target.strategy} ${target.symbol}, elapsed=${formatDuration(replayStartedAt)}`
|
|
5941
6021
|
)
|
|
5942
6022
|
);
|
|
5943
6023
|
}
|
|
5944
6024
|
}
|
|
5945
|
-
const
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
const rawRuntimeEntries = extractRuntimeParityEntries(
|
|
5949
|
-
comparableRuntimeTrades
|
|
5950
|
-
);
|
|
5951
|
-
const runtimeDedupe = dedupeRuntimeParityEntries(rawRuntimeEntries);
|
|
5952
|
-
const runtimeEntries = runtimeDedupe.entries;
|
|
5953
|
-
const comparison = compareTradeParityEntries({
|
|
6025
|
+
const {
|
|
6026
|
+
rawRuntimeEntries,
|
|
6027
|
+
runtimeDedupe,
|
|
5954
6028
|
runtimeEntries,
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
6029
|
+
comparison,
|
|
6030
|
+
classifiedBacktestOnly,
|
|
6031
|
+
classifiedRuntimeOnly,
|
|
6032
|
+
matchedSummary: summary,
|
|
6033
|
+
strategyRows
|
|
6034
|
+
} = analyzeRuntimeParity({
|
|
6035
|
+
runtimeTrades,
|
|
5960
6036
|
runtimeSignals,
|
|
5961
6037
|
runtimeSignalEvaluations,
|
|
5962
|
-
toleranceMs
|
|
5963
|
-
});
|
|
5964
|
-
const classifiedRuntimeOnly = classifyRuntimeOnlyEntries({
|
|
5965
|
-
entries: comparison.runtimeOnly,
|
|
5966
|
-
replaySignalEvaluations,
|
|
5967
6038
|
backtestEntries,
|
|
6039
|
+
replaySignalEvaluations,
|
|
6040
|
+
replayTargets,
|
|
6041
|
+
successfulTargetKeys,
|
|
6042
|
+
replayErrors,
|
|
5968
6043
|
toleranceMs
|
|
5969
6044
|
});
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
`Summary: targets=${replayTargets.length}, compared=${successfulTargetKeys.size}, errors=${replayErrors.length}, runtime=${runtimeEntries.length}, backtest=${backtestEntries.length}, runtimeOnly=${comparison.runtimeOnly.length}, backtestOnly=${comparison.backtestOnly.length}, evaluations=${runtimeSignalEvaluations.length}`
|
|
5984
|
-
);
|
|
5985
|
-
console.log(`Sources: ${formatSourceCountsSummary(sourceCounts)}`);
|
|
5986
|
-
console.log(
|
|
5987
|
-
`Runtime entries: ${formatRuntimeEntriesSummary({
|
|
6045
|
+
writeRuntimeParityTerminalReport(
|
|
6046
|
+
buildRuntimeParityTerminalReport({
|
|
6047
|
+
window,
|
|
6048
|
+
connectorName,
|
|
6049
|
+
replayEnv: replayEnvSummary,
|
|
6050
|
+
runtimeGatesEnabled: Boolean(flags.runtimeGates) || replayEnvSummary.includes("PARITY"),
|
|
6051
|
+
runtimeGatesRequested: Boolean(flags.runtimeGates),
|
|
6052
|
+
toleranceBars,
|
|
6053
|
+
toleranceMs,
|
|
6054
|
+
replayTargetsCount: replayTargets.length,
|
|
6055
|
+
comparedTargetsCount: successfulTargetKeys.size,
|
|
6056
|
+
replayErrors,
|
|
6057
|
+
sourceCounts,
|
|
5988
6058
|
rawRuntimeEntriesCount: rawRuntimeEntries.length,
|
|
5989
6059
|
runtimeEntriesCount: runtimeEntries.length,
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
6060
|
+
runtimeDuplicateGroupsCount: runtimeDedupe.duplicateGroups.length,
|
|
6061
|
+
runtimeDuplicateEntriesCount: runtimeDedupe.duplicateEntries.length,
|
|
6062
|
+
backtestEntriesCount: backtestEntries.length,
|
|
6063
|
+
runtimeOnlyCount: comparison.runtimeOnly.length,
|
|
6064
|
+
backtestOnlyCount: comparison.backtestOnly.length,
|
|
6065
|
+
matchedSummary: summary,
|
|
6066
|
+
classifiedRuntimeOnly,
|
|
6067
|
+
classifiedBacktestOnly,
|
|
6068
|
+
runtimeSignalEvaluationsCount: runtimeSignalEvaluations.length,
|
|
6069
|
+
strategyRows,
|
|
6070
|
+
runtimeGateWarningCounts,
|
|
6071
|
+
detailLimit: DETAIL_LIMIT2
|
|
6072
|
+
})
|
|
5995
6073
|
);
|
|
5996
|
-
if (runtimeDedupe.duplicateGroups.length) {
|
|
5997
|
-
console.log(
|
|
5998
|
-
`Runtime duplicate groups: ${runtimeDedupe.duplicateGroups.length}, duplicate entries: ${runtimeDedupe.duplicateEntries.length}`
|
|
5999
|
-
);
|
|
6000
|
-
}
|
|
6001
|
-
if (classifiedRuntimeOnly.length) {
|
|
6002
|
-
console.log(
|
|
6003
|
-
`Runtime only classifications: ${summarizeRuntimeOnlyClassifications(classifiedRuntimeOnly)}`
|
|
6004
|
-
);
|
|
6005
|
-
}
|
|
6006
|
-
if (classifiedBacktestOnly.length) {
|
|
6007
|
-
console.log(
|
|
6008
|
-
`Backtest only classifications: ${summarizeBacktestOnlyClassifications(classifiedBacktestOnly)}`
|
|
6009
|
-
);
|
|
6010
|
-
}
|
|
6011
|
-
if (runtimeSignalEvaluations.length) {
|
|
6012
|
-
console.log(`Runtime evaluations: ${runtimeSignalEvaluations.length}`);
|
|
6013
|
-
}
|
|
6014
|
-
const strategyRows = summarizeByStrategy({
|
|
6015
|
-
targets: replayTargets,
|
|
6016
|
-
successfulTargetKeys,
|
|
6017
|
-
replayErrors,
|
|
6018
|
-
runtimeEntries,
|
|
6019
|
-
runtimeDuplicateEntries: runtimeDedupe.duplicateEntries,
|
|
6020
|
-
backtestEntries,
|
|
6021
|
-
matchedEntries: comparison.matched,
|
|
6022
|
-
runtimeOnlyEntries: comparison.runtimeOnly,
|
|
6023
|
-
backtestOnlyEntries: comparison.backtestOnly
|
|
6024
|
-
});
|
|
6025
|
-
const mismatchRows = buildMismatchSummaryRows({
|
|
6026
|
-
classifiedRuntimeOnly,
|
|
6027
|
-
classifiedBacktestOnly
|
|
6028
|
-
});
|
|
6029
|
-
const strategyIssueRows = buildStrategyIssueRows(strategyRows);
|
|
6030
|
-
if (mismatchRows.length) {
|
|
6031
|
-
console.log("");
|
|
6032
|
-
console.log(import_chalk6.default.yellow("Signal mismatches"));
|
|
6033
|
-
for (const item of mismatchRows.slice(0, DETAIL_LIMIT)) {
|
|
6034
|
-
console.log(`- ${item.line}`);
|
|
6035
|
-
}
|
|
6036
|
-
if (mismatchRows.length > DETAIL_LIMIT) {
|
|
6037
|
-
console.log(`- ... ${mismatchRows.length - DETAIL_LIMIT} more`);
|
|
6038
|
-
}
|
|
6039
|
-
}
|
|
6040
|
-
console.log("");
|
|
6041
|
-
if (strategyIssueRows.length) {
|
|
6042
|
-
console.log(import_chalk6.default.cyan("Strategy issues"));
|
|
6043
|
-
for (const line of strategyIssueRows) {
|
|
6044
|
-
console.log(line);
|
|
6045
|
-
}
|
|
6046
|
-
} else if (strategyRows.length) {
|
|
6047
|
-
console.log(
|
|
6048
|
-
`Strategies: clean ${strategyRows.length}/${strategyRows.length}`
|
|
6049
|
-
);
|
|
6050
|
-
}
|
|
6051
|
-
if (runtimeGateWarningCounts.size && !flags.runtimeGates) {
|
|
6052
|
-
console.log("");
|
|
6053
|
-
console.log(import_chalk6.default.yellow("Warnings"));
|
|
6054
|
-
for (const [strategy, count] of [
|
|
6055
|
-
...runtimeGateWarningCounts.entries()
|
|
6056
|
-
].sort(([left], [right]) => left.localeCompare(right))) {
|
|
6057
|
-
console.log(
|
|
6058
|
-
`- ${strategy} uses AI/ML runtime gates on ${count} replay target(s); BACKTEST replay covers core execution, not live gating.`
|
|
6059
|
-
);
|
|
6060
|
-
}
|
|
6061
|
-
}
|
|
6062
|
-
if (replayErrors.length) {
|
|
6063
|
-
console.log("");
|
|
6064
|
-
console.log(import_chalk6.default.red("Replay errors"));
|
|
6065
|
-
for (const error of replayErrors.slice(0, DETAIL_LIMIT)) {
|
|
6066
|
-
console.log(`- ${error.strategy} ${error.symbol}: ${error.message}`);
|
|
6067
|
-
}
|
|
6068
|
-
if (replayErrors.length > DETAIL_LIMIT) {
|
|
6069
|
-
console.log(`- ... ${replayErrors.length - DETAIL_LIMIT} more`);
|
|
6070
|
-
}
|
|
6071
|
-
}
|
|
6072
6074
|
if (flags.details) {
|
|
6073
6075
|
printRuntimeDuplicateDetails(runtimeDedupe.duplicateGroups);
|
|
6074
6076
|
printClassifiedRuntimeOnlyDetails(classifiedRuntimeOnly);
|