@tradejs/cli 3.1.19 → 3.1.20-beta.236
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
package/dist/cli.js
CHANGED
|
@@ -29649,84 +29649,6 @@ var init_runtimeParity = __esm({
|
|
|
29649
29649
|
}
|
|
29650
29650
|
});
|
|
29651
29651
|
|
|
29652
|
-
// src/lib/paritySummary.ts
|
|
29653
|
-
var summarizeRuntimeTradesByStrategy, summarizeTradeParityByStrategy;
|
|
29654
|
-
var init_paritySummary = __esm({
|
|
29655
|
-
"src/lib/paritySummary.ts"() {
|
|
29656
|
-
"use strict";
|
|
29657
|
-
summarizeRuntimeTradesByStrategy = (trades) => {
|
|
29658
|
-
const summaryByStrategy = /* @__PURE__ */ new Map();
|
|
29659
|
-
for (const trade of trades) {
|
|
29660
|
-
const summary = summaryByStrategy.get(trade.strategy) ?? {
|
|
29661
|
-
strategyName: trade.strategy,
|
|
29662
|
-
trades: 0,
|
|
29663
|
-
activeTrades: 0,
|
|
29664
|
-
closedTrades: 0,
|
|
29665
|
-
totalPnl: 0
|
|
29666
|
-
};
|
|
29667
|
-
summary.trades += 1;
|
|
29668
|
-
if (trade.status === "active") {
|
|
29669
|
-
summary.activeTrades += 1;
|
|
29670
|
-
} else {
|
|
29671
|
-
summary.closedTrades += 1;
|
|
29672
|
-
}
|
|
29673
|
-
const pnl = trade.status === "active" ? trade.currentPnl : trade.closedPnl ?? trade.currentPnl;
|
|
29674
|
-
if (typeof pnl === "number" && Number.isFinite(pnl)) {
|
|
29675
|
-
summary.totalPnl += pnl;
|
|
29676
|
-
}
|
|
29677
|
-
summaryByStrategy.set(trade.strategy, summary);
|
|
29678
|
-
}
|
|
29679
|
-
return [...summaryByStrategy.values()].map((summary) => ({
|
|
29680
|
-
...summary,
|
|
29681
|
-
totalPnl: Number(summary.totalPnl.toFixed(2))
|
|
29682
|
-
})).sort((left, right) => left.strategyName.localeCompare(right.strategyName));
|
|
29683
|
-
};
|
|
29684
|
-
summarizeTradeParityByStrategy = ({
|
|
29685
|
-
runtimeEntries,
|
|
29686
|
-
runtimeDuplicateEntries,
|
|
29687
|
-
backtestEntries,
|
|
29688
|
-
matchedEntries,
|
|
29689
|
-
runtimeOnlyEntries,
|
|
29690
|
-
backtestOnlyEntries
|
|
29691
|
-
}) => {
|
|
29692
|
-
const rows = /* @__PURE__ */ new Map();
|
|
29693
|
-
const ensureRow = (strategyName) => {
|
|
29694
|
-
const row = rows.get(strategyName) ?? {
|
|
29695
|
-
runtime: 0,
|
|
29696
|
-
runtimeDuplicates: 0,
|
|
29697
|
-
backtest: 0,
|
|
29698
|
-
matched: 0,
|
|
29699
|
-
runtimeOnly: 0,
|
|
29700
|
-
backtestOnly: 0
|
|
29701
|
-
};
|
|
29702
|
-
rows.set(strategyName, row);
|
|
29703
|
-
return row;
|
|
29704
|
-
};
|
|
29705
|
-
for (const entry of runtimeEntries) {
|
|
29706
|
-
ensureRow(entry.strategy).runtime += 1;
|
|
29707
|
-
}
|
|
29708
|
-
for (const entry of runtimeDuplicateEntries) {
|
|
29709
|
-
ensureRow(entry.strategy).runtimeDuplicates += 1;
|
|
29710
|
-
}
|
|
29711
|
-
for (const entry of backtestEntries) {
|
|
29712
|
-
ensureRow(entry.strategy).backtest += 1;
|
|
29713
|
-
}
|
|
29714
|
-
for (const entry of matchedEntries) {
|
|
29715
|
-
ensureRow(entry.runtime.strategy).matched += 1;
|
|
29716
|
-
}
|
|
29717
|
-
for (const entry of runtimeOnlyEntries) {
|
|
29718
|
-
ensureRow(entry.strategy).runtimeOnly += 1;
|
|
29719
|
-
}
|
|
29720
|
-
for (const entry of backtestOnlyEntries) {
|
|
29721
|
-
ensureRow(entry.strategy).backtestOnly += 1;
|
|
29722
|
-
}
|
|
29723
|
-
return [...rows.entries()].sort(
|
|
29724
|
-
([left], [right]) => left.localeCompare(right)
|
|
29725
|
-
);
|
|
29726
|
-
};
|
|
29727
|
-
}
|
|
29728
|
-
});
|
|
29729
|
-
|
|
29730
29652
|
// src/lib/runtimeParity/targets.ts
|
|
29731
29653
|
var parseSymbolsFromCLI2, toTargetKey, buildReplayInputsIndex, addReplayTarget, countReplayTargetSources, buildReplayTargets;
|
|
29732
29654
|
var init_targets = __esm({
|
|
@@ -29848,6 +29770,115 @@ var init_targets = __esm({
|
|
|
29848
29770
|
}
|
|
29849
29771
|
});
|
|
29850
29772
|
|
|
29773
|
+
// src/lib/runtimeParity/evidence.ts
|
|
29774
|
+
var matchesSelection, loadRuntimeParityEvidence;
|
|
29775
|
+
var init_evidence = __esm({
|
|
29776
|
+
"src/lib/runtimeParity/evidence.ts"() {
|
|
29777
|
+
"use strict";
|
|
29778
|
+
matchesSelection = (context, artifact) => (!context.strategy || artifact.strategy === context.strategy) && (!context.symbols || context.symbols.has(artifact.symbol));
|
|
29779
|
+
loadRuntimeParityEvidence = async (context, adapters) => {
|
|
29780
|
+
const range = {
|
|
29781
|
+
startTime: context.window.start,
|
|
29782
|
+
endTime: context.window.end
|
|
29783
|
+
};
|
|
29784
|
+
const [allRuntimeTrades, allRuntimeSignals, allRuntimeSignalEvaluations] = await Promise.all([
|
|
29785
|
+
adapters.loadRuntimeTrades(context.userName, range),
|
|
29786
|
+
adapters.loadRuntimeSignals(context.userName, range),
|
|
29787
|
+
adapters.loadRuntimeSignalEvaluations(context.userName, range)
|
|
29788
|
+
]);
|
|
29789
|
+
return {
|
|
29790
|
+
runtimeTrades: allRuntimeTrades.filter(
|
|
29791
|
+
(trade) => trade.entryTimestamp >= context.window.start && trade.entryTimestamp <= context.window.end && matchesSelection(context, trade)
|
|
29792
|
+
),
|
|
29793
|
+
runtimeSignals: allRuntimeSignals.filter(
|
|
29794
|
+
(signal) => signal.timestamp >= context.window.start && signal.timestamp <= context.window.end && matchesSelection(context, signal)
|
|
29795
|
+
),
|
|
29796
|
+
runtimeSignalEvaluations: allRuntimeSignalEvaluations.filter(
|
|
29797
|
+
(evaluation) => evaluation.timestamp >= context.window.start && evaluation.timestamp <= context.window.end && matchesSelection(context, evaluation)
|
|
29798
|
+
)
|
|
29799
|
+
};
|
|
29800
|
+
};
|
|
29801
|
+
}
|
|
29802
|
+
});
|
|
29803
|
+
|
|
29804
|
+
// src/lib/paritySummary.ts
|
|
29805
|
+
var summarizeRuntimeTradesByStrategy, summarizeTradeParityByStrategy;
|
|
29806
|
+
var init_paritySummary = __esm({
|
|
29807
|
+
"src/lib/paritySummary.ts"() {
|
|
29808
|
+
"use strict";
|
|
29809
|
+
summarizeRuntimeTradesByStrategy = (trades) => {
|
|
29810
|
+
const summaryByStrategy = /* @__PURE__ */ new Map();
|
|
29811
|
+
for (const trade of trades) {
|
|
29812
|
+
const summary = summaryByStrategy.get(trade.strategy) ?? {
|
|
29813
|
+
strategyName: trade.strategy,
|
|
29814
|
+
trades: 0,
|
|
29815
|
+
activeTrades: 0,
|
|
29816
|
+
closedTrades: 0,
|
|
29817
|
+
totalPnl: 0
|
|
29818
|
+
};
|
|
29819
|
+
summary.trades += 1;
|
|
29820
|
+
if (trade.status === "active") {
|
|
29821
|
+
summary.activeTrades += 1;
|
|
29822
|
+
} else {
|
|
29823
|
+
summary.closedTrades += 1;
|
|
29824
|
+
}
|
|
29825
|
+
const pnl = trade.status === "active" ? trade.currentPnl : trade.closedPnl ?? trade.currentPnl;
|
|
29826
|
+
if (typeof pnl === "number" && Number.isFinite(pnl)) {
|
|
29827
|
+
summary.totalPnl += pnl;
|
|
29828
|
+
}
|
|
29829
|
+
summaryByStrategy.set(trade.strategy, summary);
|
|
29830
|
+
}
|
|
29831
|
+
return [...summaryByStrategy.values()].map((summary) => ({
|
|
29832
|
+
...summary,
|
|
29833
|
+
totalPnl: Number(summary.totalPnl.toFixed(2))
|
|
29834
|
+
})).sort((left, right) => left.strategyName.localeCompare(right.strategyName));
|
|
29835
|
+
};
|
|
29836
|
+
summarizeTradeParityByStrategy = ({
|
|
29837
|
+
runtimeEntries,
|
|
29838
|
+
runtimeDuplicateEntries,
|
|
29839
|
+
backtestEntries,
|
|
29840
|
+
matchedEntries,
|
|
29841
|
+
runtimeOnlyEntries,
|
|
29842
|
+
backtestOnlyEntries
|
|
29843
|
+
}) => {
|
|
29844
|
+
const rows = /* @__PURE__ */ new Map();
|
|
29845
|
+
const ensureRow = (strategyName) => {
|
|
29846
|
+
const row = rows.get(strategyName) ?? {
|
|
29847
|
+
runtime: 0,
|
|
29848
|
+
runtimeDuplicates: 0,
|
|
29849
|
+
backtest: 0,
|
|
29850
|
+
matched: 0,
|
|
29851
|
+
runtimeOnly: 0,
|
|
29852
|
+
backtestOnly: 0
|
|
29853
|
+
};
|
|
29854
|
+
rows.set(strategyName, row);
|
|
29855
|
+
return row;
|
|
29856
|
+
};
|
|
29857
|
+
for (const entry of runtimeEntries) {
|
|
29858
|
+
ensureRow(entry.strategy).runtime += 1;
|
|
29859
|
+
}
|
|
29860
|
+
for (const entry of runtimeDuplicateEntries) {
|
|
29861
|
+
ensureRow(entry.strategy).runtimeDuplicates += 1;
|
|
29862
|
+
}
|
|
29863
|
+
for (const entry of backtestEntries) {
|
|
29864
|
+
ensureRow(entry.strategy).backtest += 1;
|
|
29865
|
+
}
|
|
29866
|
+
for (const entry of matchedEntries) {
|
|
29867
|
+
ensureRow(entry.runtime.strategy).matched += 1;
|
|
29868
|
+
}
|
|
29869
|
+
for (const entry of runtimeOnlyEntries) {
|
|
29870
|
+
ensureRow(entry.strategy).runtimeOnly += 1;
|
|
29871
|
+
}
|
|
29872
|
+
for (const entry of backtestOnlyEntries) {
|
|
29873
|
+
ensureRow(entry.strategy).backtestOnly += 1;
|
|
29874
|
+
}
|
|
29875
|
+
return [...rows.entries()].sort(
|
|
29876
|
+
([left], [right]) => left.localeCompare(right)
|
|
29877
|
+
);
|
|
29878
|
+
};
|
|
29879
|
+
}
|
|
29880
|
+
});
|
|
29881
|
+
|
|
29851
29882
|
// src/lib/runtimeParity/classification.ts
|
|
29852
29883
|
var BACKTEST_ONLY_CLASSIFICATIONS, RUNTIME_ONLY_CLASSIFICATIONS, formatMinutes, normalizeSignalOrderStatus, findNearestRuntimeSignal, findNearestRuntimeSignalEvaluation, findNearestBacktestEntryForRuntimeOnly, buildSignalClassificationReason, buildEvaluationClassification, buildRuntimeOnlyEvaluationClassification, classifyBacktestOnlyEntries, classifyRuntimeOnlyEntries, summarizeBacktestOnlyClassifications, summarizeRuntimeOnlyClassifications;
|
|
29853
29884
|
var init_classification = __esm({
|
|
@@ -30183,262 +30214,131 @@ var init_classification = __esm({
|
|
|
30183
30214
|
}
|
|
30184
30215
|
});
|
|
30185
30216
|
|
|
30186
|
-
// src/
|
|
30187
|
-
var
|
|
30188
|
-
|
|
30189
|
-
|
|
30190
|
-
main: () => main40,
|
|
30191
|
-
runtimeParity: () => runtimeParity
|
|
30192
|
-
});
|
|
30193
|
-
var import_config11, import_node_crypto14, import_args30, import_chalk43, import_constants15, import_time7, import_logger6, import_redis17, import_cli11, import_backtest3, import_connectors14, flags28, interval3, projectRoot13, DEFAULT_LOOKBACK_DAYS, DETAIL_LIMIT, TELEGRAM_DETAIL_LIMIT, SUMMARY_TIMEZONE, SUMMARY_TIMEZONE_LABEL, DEFAULT_REPLAY_ENV, escapeHtml5, formatMskDateTime2, resolveParityConnectorName, createConnector, buildReplayAiAnalyses, formatReplayEnvSummary, buildReplayConfig, warmReplayHistory, formatPercent4, formatMinutes2, formatEntryLabel, formatRuntimeEntriesSummary, formatSourceCountsSummary, normalizeSummaryText, pickFirstText, buildMismatchSummaryRows, buildStrategyIssueRows, toSerializableTradeParityEntry, toSerializableSignal, toSerializableRuntimeSignalEvaluation, getRuntimeOnlyLikelyCause, getBacktestOnlyLikelyCause, getRuntimeOnlyRecommendedChecks, getBacktestOnlyRecommendedChecks, buildRuntimeParityMismatchAttachment, printRuntimeDuplicateDetails, printClassifiedBacktestOnlyDetails, printClassifiedRuntimeOnlyDetails, summarizeByStrategy, buildRuntimeParityMessage, buildRuntimeParityNoTargetsMessage, runtimeParity, main40;
|
|
30194
|
-
var init_runtimeParity2 = __esm({
|
|
30195
|
-
"src/scripts/runtimeParity.ts"() {
|
|
30217
|
+
// src/lib/runtimeParity/analysis.ts
|
|
30218
|
+
var summarizeByStrategy, analyzeRuntimeParity;
|
|
30219
|
+
var init_analysis = __esm({
|
|
30220
|
+
"src/lib/runtimeParity/analysis.ts"() {
|
|
30196
30221
|
"use strict";
|
|
30197
|
-
import_config11 = require("dotenv/config");
|
|
30198
|
-
import_node_crypto14 = require("crypto");
|
|
30199
|
-
import_args30 = __toESM(require("args"));
|
|
30200
|
-
import_chalk43 = __toESM(require("chalk"));
|
|
30201
|
-
import_constants15 = require("@tradejs/core/constants");
|
|
30202
|
-
import_time7 = require("@tradejs/core/time");
|
|
30203
|
-
import_logger6 = require("@tradejs/infra/logger");
|
|
30204
|
-
import_redis17 = require("@tradejs/infra/redis");
|
|
30205
|
-
import_cli11 = require("@tradejs/node/cli");
|
|
30206
|
-
import_backtest3 = require("@tradejs/node/backtest");
|
|
30207
|
-
import_connectors14 = require("@tradejs/node/connectors");
|
|
30208
30222
|
init_runtimeParity();
|
|
30209
|
-
init_cliArgs();
|
|
30210
30223
|
init_paritySummary();
|
|
30211
|
-
init_runtimeRedis();
|
|
30212
|
-
init_runtimeSignalsLoader();
|
|
30213
|
-
init_telegramReports();
|
|
30214
|
-
init_timeWindow();
|
|
30215
|
-
init_runFormatting();
|
|
30216
|
-
init_marketContextPrepare();
|
|
30217
|
-
init_historyPrepare();
|
|
30218
|
-
init_targets();
|
|
30219
30224
|
init_classification();
|
|
30220
|
-
|
|
30221
|
-
|
|
30222
|
-
|
|
30223
|
-
|
|
30224
|
-
|
|
30225
|
-
|
|
30226
|
-
|
|
30227
|
-
|
|
30228
|
-
|
|
30229
|
-
|
|
30230
|
-
|
|
30231
|
-
);
|
|
30232
|
-
import_args30.default.option(["e", "endTime"], "Explicit replay end timestamp (ms or seconds)");
|
|
30233
|
-
import_args30.default.option(["s", "strategy"], "Only compare one strategy");
|
|
30234
|
-
import_args30.default.option(
|
|
30235
|
-
["t", "tickers"],
|
|
30236
|
-
"Replay comma-separated symbols for all configured strategies"
|
|
30237
|
-
);
|
|
30238
|
-
import_args30.default.option(
|
|
30239
|
-
["C", "cacheOnly"],
|
|
30240
|
-
"Do not refresh market history before replay",
|
|
30241
|
-
false
|
|
30242
|
-
);
|
|
30243
|
-
import_args30.default.option(
|
|
30244
|
-
["a", "toleranceBars"],
|
|
30245
|
-
"Allowed entry timestamp drift in bars when matching runtime vs backtest",
|
|
30246
|
-
1
|
|
30247
|
-
);
|
|
30248
|
-
import_args30.default.option(
|
|
30249
|
-
"fullUniverse",
|
|
30250
|
-
"Replay every configured strategy across the full connector universe. By default parity replays only runtime-traded and strategy-results symbols unless --tickers is provided.",
|
|
30251
|
-
false
|
|
30252
|
-
);
|
|
30253
|
-
import_args30.default.option(
|
|
30254
|
-
"runtimeGates",
|
|
30255
|
-
"Force runtime AI/ML gates for all replay targets. Runtime-gated configs are replayed with gates by default.",
|
|
30256
|
-
false
|
|
30257
|
-
);
|
|
30258
|
-
import_args30.default.option(["N", "notify"], "Send parity summary to Telegram", false);
|
|
30259
|
-
import_args30.default.option(["D", "details"], "Print unmatched entry details (capped)", false);
|
|
30260
|
-
process.argv = normalizeCliArgv(process.argv, {
|
|
30261
|
-
"-C": "--cacheOnly",
|
|
30262
|
-
"-D": "--details",
|
|
30263
|
-
"-E": "--endTime",
|
|
30264
|
-
"-N": "--notify",
|
|
30265
|
-
"-S": "--startTime",
|
|
30266
|
-
"-T": "--toleranceBars"
|
|
30267
|
-
});
|
|
30268
|
-
flags28 = import_args30.default.parse(process.argv);
|
|
30269
|
-
interval3 = "15";
|
|
30270
|
-
projectRoot13 = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
|
|
30271
|
-
DEFAULT_LOOKBACK_DAYS = 3;
|
|
30272
|
-
DETAIL_LIMIT = 10;
|
|
30273
|
-
TELEGRAM_DETAIL_LIMIT = 5;
|
|
30274
|
-
SUMMARY_TIMEZONE = "Europe/Moscow";
|
|
30275
|
-
SUMMARY_TIMEZONE_LABEL = "MSK";
|
|
30276
|
-
DEFAULT_REPLAY_ENV = flags28.runtimeGates ? "PARITY" : "BACKTEST";
|
|
30277
|
-
escapeHtml5 = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
30278
|
-
formatMskDateTime2 = (timestamp) => new Intl.DateTimeFormat("ru-RU", {
|
|
30279
|
-
timeZone: SUMMARY_TIMEZONE,
|
|
30280
|
-
year: "numeric",
|
|
30281
|
-
month: "2-digit",
|
|
30282
|
-
day: "2-digit",
|
|
30283
|
-
hour: "2-digit",
|
|
30284
|
-
minute: "2-digit",
|
|
30285
|
-
hour12: false
|
|
30286
|
-
}).format(new Date(timestamp));
|
|
30287
|
-
resolveParityConnectorName = async (value) => {
|
|
30288
|
-
const connectorName = await (0, import_connectors14.resolveConnectorName)(value, projectRoot13);
|
|
30289
|
-
if (connectorName) {
|
|
30290
|
-
return connectorName;
|
|
30291
|
-
}
|
|
30292
|
-
import_logger6.logger.warn(
|
|
30293
|
-
'Unknown connector "%s". Fallback to %s.',
|
|
30294
|
-
String(value || "").trim() || String(value),
|
|
30295
|
-
import_connectors14.DEFAULT_CONNECTOR_NAME
|
|
30296
|
-
);
|
|
30297
|
-
return import_connectors14.DEFAULT_CONNECTOR_NAME;
|
|
30298
|
-
};
|
|
30299
|
-
createConnector = async ({
|
|
30300
|
-
userName: userName3,
|
|
30301
|
-
connectorName
|
|
30302
|
-
}) => {
|
|
30303
|
-
const connectorFactory = await (0, import_connectors14.getConnectorCreatorByName)(
|
|
30304
|
-
connectorName,
|
|
30305
|
-
projectRoot13
|
|
30306
|
-
);
|
|
30307
|
-
if (!connectorFactory) {
|
|
30308
|
-
throw new Error(`Connector "${connectorName}" is not registered`);
|
|
30309
|
-
}
|
|
30310
|
-
return await connectorFactory({ userName: userName3 });
|
|
30311
|
-
};
|
|
30312
|
-
buildReplayAiAnalyses = ({
|
|
30313
|
-
runtimeTrades,
|
|
30314
|
-
runtimeSignalEvaluations,
|
|
30315
|
-
toleranceMs
|
|
30225
|
+
init_targets();
|
|
30226
|
+
summarizeByStrategy = ({
|
|
30227
|
+
targets,
|
|
30228
|
+
successfulTargetKeys,
|
|
30229
|
+
replayErrors,
|
|
30230
|
+
runtimeEntries,
|
|
30231
|
+
runtimeDuplicateEntries,
|
|
30232
|
+
backtestEntries,
|
|
30233
|
+
matchedEntries,
|
|
30234
|
+
runtimeOnlyEntries,
|
|
30235
|
+
backtestOnlyEntries
|
|
30316
30236
|
}) => {
|
|
30317
|
-
const
|
|
30318
|
-
|
|
30319
|
-
|
|
30320
|
-
|
|
30321
|
-
|
|
30322
|
-
|
|
30323
|
-
|
|
30324
|
-
|
|
30325
|
-
toleranceMs,
|
|
30326
|
-
analysis: trade.aiAnalysis
|
|
30327
|
-
})),
|
|
30328
|
-
...runtimeSignalEvaluations.filter(
|
|
30329
|
-
(evaluation) => evaluation.aiAnalysis && evaluation.direction && typeof evaluation.timestamp === "number" && Number.isFinite(evaluation.timestamp)
|
|
30330
|
-
).map((evaluation) => ({
|
|
30331
|
-
strategy: evaluation.strategy,
|
|
30332
|
-
symbol: evaluation.symbol,
|
|
30333
|
-
direction: evaluation.direction,
|
|
30334
|
-
timestamp: evaluation.timestamp,
|
|
30335
|
-
toleranceMs,
|
|
30336
|
-
analysis: evaluation.aiAnalysis
|
|
30337
|
-
}))
|
|
30338
|
-
];
|
|
30339
|
-
const seen = /* @__PURE__ */ new Set();
|
|
30340
|
-
return snapshots.filter((snapshot) => {
|
|
30341
|
-
const key = `${snapshot.strategy}:${snapshot.symbol}:${snapshot.direction}:${snapshot.timestamp}`;
|
|
30342
|
-
if (seen.has(key)) {
|
|
30343
|
-
return false;
|
|
30344
|
-
}
|
|
30345
|
-
seen.add(key);
|
|
30346
|
-
return true;
|
|
30237
|
+
const rows = /* @__PURE__ */ new Map();
|
|
30238
|
+
const baseRows = summarizeTradeParityByStrategy({
|
|
30239
|
+
runtimeEntries,
|
|
30240
|
+
runtimeDuplicateEntries,
|
|
30241
|
+
backtestEntries,
|
|
30242
|
+
matchedEntries,
|
|
30243
|
+
runtimeOnlyEntries,
|
|
30244
|
+
backtestOnlyEntries
|
|
30347
30245
|
});
|
|
30348
|
-
|
|
30349
|
-
|
|
30350
|
-
|
|
30351
|
-
|
|
30352
|
-
|
|
30353
|
-
|
|
30354
|
-
|
|
30355
|
-
} else {
|
|
30356
|
-
backtest2 += 1;
|
|
30357
|
-
}
|
|
30246
|
+
for (const [strategy, baseRow] of baseRows) {
|
|
30247
|
+
rows.set(strategy, {
|
|
30248
|
+
...baseRow,
|
|
30249
|
+
targets: 0,
|
|
30250
|
+
compared: 0,
|
|
30251
|
+
errors: 0
|
|
30252
|
+
});
|
|
30358
30253
|
}
|
|
30359
|
-
|
|
30360
|
-
|
|
30254
|
+
const ensureRow = (strategy) => {
|
|
30255
|
+
const row = rows.get(strategy) ?? {
|
|
30256
|
+
runtime: 0,
|
|
30257
|
+
runtimeDuplicates: 0,
|
|
30258
|
+
backtest: 0,
|
|
30259
|
+
matched: 0,
|
|
30260
|
+
runtimeOnly: 0,
|
|
30261
|
+
backtestOnly: 0,
|
|
30262
|
+
targets: 0,
|
|
30263
|
+
compared: 0,
|
|
30264
|
+
errors: 0
|
|
30265
|
+
};
|
|
30266
|
+
rows.set(strategy, row);
|
|
30267
|
+
return row;
|
|
30268
|
+
};
|
|
30269
|
+
for (const target of targets) {
|
|
30270
|
+
const row = ensureRow(target.strategy);
|
|
30271
|
+
row.targets += 1;
|
|
30272
|
+
if (successfulTargetKeys.has(toTargetKey(target))) {
|
|
30273
|
+
row.compared += 1;
|
|
30274
|
+
}
|
|
30361
30275
|
}
|
|
30362
|
-
|
|
30363
|
-
|
|
30276
|
+
for (const error of replayErrors) {
|
|
30277
|
+
ensureRow(error.strategy).errors += 1;
|
|
30364
30278
|
}
|
|
30365
|
-
return
|
|
30366
|
-
|
|
30367
|
-
|
|
30368
|
-
userName: userName3,
|
|
30369
|
-
strategy,
|
|
30370
|
-
symbol,
|
|
30371
|
-
replayInputsIndex,
|
|
30372
|
-
toleranceMs
|
|
30373
|
-
}) => {
|
|
30374
|
-
const [userConfig, strategyResults] = await Promise.all([
|
|
30375
|
-
(0, import_redis17.getData)(import_redis17.redisKeys.strategyConfig(userName3, strategy), {}),
|
|
30376
|
-
(0, import_redis17.getData)(import_redis17.redisKeys.strategyResults(userName3, strategy), {})
|
|
30377
|
-
]);
|
|
30378
|
-
const typedResults = strategyResults ?? {};
|
|
30379
|
-
const symbolResult = typedResults?.[symbol];
|
|
30380
|
-
const symbolConfig = symbolResult && symbolResult.config && typeof symbolResult.config === "object" ? symbolResult.config : {};
|
|
30381
|
-
const replayInputs = replayInputsIndex.get(toTargetKey({ strategy, symbol }));
|
|
30382
|
-
const aiReplayAnalyses = buildReplayAiAnalyses({
|
|
30383
|
-
runtimeTrades: replayInputs?.runtimeTrades ?? [],
|
|
30384
|
-
runtimeSignalEvaluations: replayInputs?.runtimeSignalEvaluations ?? [],
|
|
30385
|
-
strategy,
|
|
30386
|
-
symbol,
|
|
30387
|
-
toleranceMs
|
|
30388
|
-
});
|
|
30389
|
-
const strategyConfig = {
|
|
30390
|
-
...userConfig,
|
|
30391
|
-
...symbolConfig
|
|
30392
|
-
};
|
|
30393
|
-
return buildRuntimeModeStrategyConfig({
|
|
30394
|
-
strategyConfig,
|
|
30395
|
-
env: resolveReplayStrategyEnv({
|
|
30396
|
-
strategyConfig,
|
|
30397
|
-
forceRuntimeGates: Boolean(flags28.runtimeGates)
|
|
30398
|
-
}),
|
|
30399
|
-
interval: interval3,
|
|
30400
|
-
makeOrders: true,
|
|
30401
|
-
recordRuntimeTrades: false,
|
|
30402
|
-
aiReplayAnalyses
|
|
30403
|
-
});
|
|
30279
|
+
return [...rows.entries()].sort(
|
|
30280
|
+
([left], [right]) => left.localeCompare(right)
|
|
30281
|
+
);
|
|
30404
30282
|
};
|
|
30405
|
-
|
|
30406
|
-
|
|
30407
|
-
|
|
30408
|
-
targets,
|
|
30409
|
-
preloadStart,
|
|
30410
|
-
preloadEnd
|
|
30411
|
-
}) => {
|
|
30412
|
-
const connector = await createConnector({ userName: userName3, connectorName });
|
|
30413
|
-
const symbols = [...new Set(targets.map((target) => target.symbol))];
|
|
30414
|
-
const warmStartedAt = Date.now();
|
|
30415
|
-
console.log(
|
|
30416
|
-
import_chalk43.default.gray(
|
|
30417
|
-
`Warm history: ${symbols.length} symbol(s) on ${connectorName}, preload ${(0, import_time7.formatUnix)(preloadStart)} -> ${(0, import_time7.formatUnix)(preloadEnd)}`
|
|
30418
|
-
)
|
|
30283
|
+
analyzeRuntimeParity = (context) => {
|
|
30284
|
+
const comparableRuntimeTrades = context.runtimeTrades.filter(
|
|
30285
|
+
(trade) => context.successfulTargetKeys.has(toTargetKey(trade))
|
|
30419
30286
|
);
|
|
30420
|
-
const
|
|
30421
|
-
|
|
30422
|
-
marketConnector: connector,
|
|
30423
|
-
userName: userName3,
|
|
30424
|
-
projectRoot: projectRoot13,
|
|
30425
|
-
shouldUseDedicatedReferences: true,
|
|
30426
|
-
warn: (message) => import_logger6.logger.warn(message)
|
|
30427
|
-
});
|
|
30428
|
-
await updateMarketHistoryWithBtcReferences({
|
|
30429
|
-
marketConnector: connector,
|
|
30430
|
-
connectorName,
|
|
30431
|
-
btcReferences,
|
|
30432
|
-
interval: interval3,
|
|
30433
|
-
symbols,
|
|
30434
|
-
preloadStart,
|
|
30435
|
-
preloadEnd,
|
|
30436
|
-
log: (message) => console.log(import_chalk43.default.gray(message))
|
|
30437
|
-
});
|
|
30438
|
-
console.log(
|
|
30439
|
-
import_chalk43.default.gray(`Warm history: done in ${formatDuration(warmStartedAt)}`)
|
|
30287
|
+
const rawRuntimeEntries = extractRuntimeParityEntries(
|
|
30288
|
+
comparableRuntimeTrades
|
|
30440
30289
|
);
|
|
30290
|
+
const runtimeDedupe = dedupeRuntimeParityEntries(rawRuntimeEntries);
|
|
30291
|
+
const runtimeEntries = runtimeDedupe.entries;
|
|
30292
|
+
const comparison = compareTradeParityEntries({
|
|
30293
|
+
runtimeEntries,
|
|
30294
|
+
backtestEntries: context.backtestEntries,
|
|
30295
|
+
toleranceMs: context.toleranceMs
|
|
30296
|
+
});
|
|
30297
|
+
const classifiedBacktestOnly = classifyBacktestOnlyEntries({
|
|
30298
|
+
entries: comparison.backtestOnly,
|
|
30299
|
+
runtimeSignals: context.runtimeSignals,
|
|
30300
|
+
runtimeSignalEvaluations: context.runtimeSignalEvaluations,
|
|
30301
|
+
toleranceMs: context.toleranceMs
|
|
30302
|
+
});
|
|
30303
|
+
const classifiedRuntimeOnly = classifyRuntimeOnlyEntries({
|
|
30304
|
+
entries: comparison.runtimeOnly,
|
|
30305
|
+
replaySignalEvaluations: context.replaySignalEvaluations,
|
|
30306
|
+
backtestEntries: context.backtestEntries,
|
|
30307
|
+
toleranceMs: context.toleranceMs
|
|
30308
|
+
});
|
|
30309
|
+
const matchedSummary = summarizeMatchedParity(comparison.matched);
|
|
30310
|
+
const strategyRows = summarizeByStrategy({
|
|
30311
|
+
targets: context.replayTargets,
|
|
30312
|
+
successfulTargetKeys: context.successfulTargetKeys,
|
|
30313
|
+
replayErrors: context.replayErrors,
|
|
30314
|
+
runtimeEntries,
|
|
30315
|
+
runtimeDuplicateEntries: runtimeDedupe.duplicateEntries,
|
|
30316
|
+
backtestEntries: context.backtestEntries,
|
|
30317
|
+
matchedEntries: comparison.matched,
|
|
30318
|
+
runtimeOnlyEntries: comparison.runtimeOnly,
|
|
30319
|
+
backtestOnlyEntries: comparison.backtestOnly
|
|
30320
|
+
});
|
|
30321
|
+
return {
|
|
30322
|
+
comparableRuntimeTrades,
|
|
30323
|
+
rawRuntimeEntries,
|
|
30324
|
+
runtimeDedupe,
|
|
30325
|
+
runtimeEntries,
|
|
30326
|
+
comparison,
|
|
30327
|
+
classifiedBacktestOnly,
|
|
30328
|
+
classifiedRuntimeOnly,
|
|
30329
|
+
matchedSummary,
|
|
30330
|
+
strategyRows
|
|
30331
|
+
};
|
|
30441
30332
|
};
|
|
30333
|
+
}
|
|
30334
|
+
});
|
|
30335
|
+
|
|
30336
|
+
// src/lib/runtimeParity/reportingShared.ts
|
|
30337
|
+
var import_time7, formatPercent4, formatMinutes2, formatEntryLabel, formatRuntimeEntriesSummary, formatSourceCountsSummary, normalizeSummaryText, pickFirstText, buildMismatchSummaryRows, buildStrategyIssueRows;
|
|
30338
|
+
var init_reportingShared = __esm({
|
|
30339
|
+
"src/lib/runtimeParity/reportingShared.ts"() {
|
|
30340
|
+
"use strict";
|
|
30341
|
+
import_time7 = require("@tradejs/core/time");
|
|
30442
30342
|
formatPercent4 = (value) => value == null || !Number.isFinite(value) ? "n/a" : `${value.toFixed(2)}%`;
|
|
30443
30343
|
formatMinutes2 = (value) => value == null || !Number.isFinite(value) ? "n/a" : `${(value / 6e4).toFixed(2)}m`;
|
|
30444
30344
|
formatEntryLabel = (entry) => `${entry.strategy} ${entry.symbol} ${entry.direction} ${(0, import_time7.formatUnix)(entry.timestamp)}`;
|
|
@@ -30458,16 +30358,11 @@ var init_runtimeParity2 = __esm({
|
|
|
30458
30358
|
};
|
|
30459
30359
|
normalizeSummaryText = (value, maxLength = 160) => {
|
|
30460
30360
|
const normalized = value.replace(/\s+/g, " ").trim();
|
|
30461
|
-
|
|
30462
|
-
return normalized;
|
|
30463
|
-
}
|
|
30464
|
-
return `${normalized.slice(0, maxLength - 3)}...`;
|
|
30361
|
+
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 3)}...`;
|
|
30465
30362
|
};
|
|
30466
30363
|
pickFirstText = (...values) => {
|
|
30467
30364
|
for (const value of values) {
|
|
30468
|
-
if (typeof value === "string" && value.trim())
|
|
30469
|
-
return value.trim();
|
|
30470
|
-
}
|
|
30365
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
30471
30366
|
}
|
|
30472
30367
|
return null;
|
|
30473
30368
|
};
|
|
@@ -30487,9 +30382,7 @@ var init_runtimeParity2 = __esm({
|
|
|
30487
30382
|
item.nearestBacktestEntry?.id
|
|
30488
30383
|
);
|
|
30489
30384
|
const refs = [`signalId=${signalId ?? "n/a"}`];
|
|
30490
|
-
if (orderId && orderId !== signalId) {
|
|
30491
|
-
refs.push(`orderId=${orderId}`);
|
|
30492
|
-
}
|
|
30385
|
+
if (orderId && orderId !== signalId) refs.push(`orderId=${orderId}`);
|
|
30493
30386
|
if (item.evaluation?.evaluationId) {
|
|
30494
30387
|
refs.push(`evaluationId=${item.evaluation.evaluationId}`);
|
|
30495
30388
|
}
|
|
@@ -30551,20 +30444,23 @@ var init_runtimeParity2 = __esm({
|
|
|
30551
30444
|
};
|
|
30552
30445
|
buildStrategyIssueRows = (strategyRows) => strategyRows.map(([strategy, row]) => {
|
|
30553
30446
|
const issues = [];
|
|
30554
|
-
if (row.runtimeOnly) {
|
|
30555
|
-
|
|
30556
|
-
}
|
|
30557
|
-
if (row.backtestOnly) {
|
|
30558
|
-
issues.push(`backtestOnly=${row.backtestOnly}`);
|
|
30559
|
-
}
|
|
30447
|
+
if (row.runtimeOnly) issues.push(`runtimeOnly=${row.runtimeOnly}`);
|
|
30448
|
+
if (row.backtestOnly) issues.push(`backtestOnly=${row.backtestOnly}`);
|
|
30560
30449
|
if (row.runtimeDuplicates) {
|
|
30561
30450
|
issues.push(`runtimeDuplicates=${row.runtimeDuplicates}`);
|
|
30562
30451
|
}
|
|
30563
|
-
if (row.errors) {
|
|
30564
|
-
issues.push(`errors=${row.errors}`);
|
|
30565
|
-
}
|
|
30452
|
+
if (row.errors) issues.push(`errors=${row.errors}`);
|
|
30566
30453
|
return issues.length ? `- ${strategy}: ${issues.join(", ")}` : null;
|
|
30567
30454
|
}).filter((line) => Boolean(line));
|
|
30455
|
+
}
|
|
30456
|
+
});
|
|
30457
|
+
|
|
30458
|
+
// src/lib/runtimeParity/reportingJson.ts
|
|
30459
|
+
var toSerializableTradeParityEntry, toSerializableSignal, toSerializableRuntimeSignalEvaluation, getRuntimeOnlyLikelyCause, getBacktestOnlyLikelyCause, getRuntimeOnlyRecommendedChecks, getBacktestOnlyRecommendedChecks, buildRuntimeParityMismatchAttachment;
|
|
30460
|
+
var init_reportingJson = __esm({
|
|
30461
|
+
"src/lib/runtimeParity/reportingJson.ts"() {
|
|
30462
|
+
"use strict";
|
|
30463
|
+
init_reportingShared();
|
|
30568
30464
|
toSerializableTradeParityEntry = (entry) => entry ? {
|
|
30569
30465
|
id: entry.id,
|
|
30570
30466
|
source: entry.source,
|
|
@@ -30707,34 +30603,12 @@ var init_runtimeParity2 = __esm({
|
|
|
30707
30603
|
];
|
|
30708
30604
|
}
|
|
30709
30605
|
};
|
|
30710
|
-
buildRuntimeParityMismatchAttachment = ({
|
|
30711
|
-
|
|
30712
|
-
connectorName,
|
|
30713
|
-
replayEnv,
|
|
30714
|
-
toleranceBars,
|
|
30715
|
-
toleranceMs,
|
|
30716
|
-
replayTargetsCount,
|
|
30717
|
-
comparedTargetsCount,
|
|
30718
|
-
replayErrors,
|
|
30719
|
-
sourceCounts,
|
|
30720
|
-
rawRuntimeEntriesCount,
|
|
30721
|
-
runtimeEntriesCount,
|
|
30722
|
-
runtimeDuplicateEntriesCount,
|
|
30723
|
-
backtestEntriesCount,
|
|
30724
|
-
matchedCount,
|
|
30725
|
-
runtimeOnlyCount,
|
|
30726
|
-
backtestOnlyCount,
|
|
30727
|
-
matchedSummary,
|
|
30728
|
-
classifiedRuntimeOnly,
|
|
30729
|
-
classifiedBacktestOnly,
|
|
30730
|
-
runtimeSignalEvaluationsCount,
|
|
30731
|
-
strategyRows
|
|
30732
|
-
}) => {
|
|
30733
|
-
if (!classifiedRuntimeOnly.length && !classifiedBacktestOnly.length) {
|
|
30606
|
+
buildRuntimeParityMismatchAttachment = (context) => {
|
|
30607
|
+
if (!context.classifiedRuntimeOnly.length && !context.classifiedBacktestOnly.length) {
|
|
30734
30608
|
return null;
|
|
30735
30609
|
}
|
|
30736
30610
|
const cases = [
|
|
30737
|
-
...classifiedRuntimeOnly.map((item) => ({
|
|
30611
|
+
...context.classifiedRuntimeOnly.map((item) => ({
|
|
30738
30612
|
kind: "runtimeOnly",
|
|
30739
30613
|
strategy: item.entry.strategy,
|
|
30740
30614
|
symbol: item.entry.symbol,
|
|
@@ -30773,7 +30647,7 @@ var init_runtimeParity2 = __esm({
|
|
|
30773
30647
|
)
|
|
30774
30648
|
}
|
|
30775
30649
|
})),
|
|
30776
|
-
...classifiedBacktestOnly.map((item) => ({
|
|
30650
|
+
...context.classifiedBacktestOnly.map((item) => ({
|
|
30777
30651
|
kind: "backtestOnly",
|
|
30778
30652
|
strategy: item.entry.strategy,
|
|
30779
30653
|
symbol: item.entry.symbol,
|
|
@@ -30819,40 +30693,37 @@ var init_runtimeParity2 = __esm({
|
|
|
30819
30693
|
generatedAt: Date.now(),
|
|
30820
30694
|
codexQuestion: "For each mismatch case, explain why runtime and replay/backtest diverged. Use why.classification first, then confirm with decisionTrace, timing, and artifacts.",
|
|
30821
30695
|
window: {
|
|
30822
|
-
start: window.start,
|
|
30823
|
-
end: window.end,
|
|
30824
|
-
source: window.source
|
|
30825
|
-
},
|
|
30826
|
-
connectorName,
|
|
30827
|
-
replayEnv,
|
|
30828
|
-
tolerance: {
|
|
30829
|
-
bars: toleranceBars,
|
|
30830
|
-
ms: toleranceMs
|
|
30696
|
+
start: context.window.start,
|
|
30697
|
+
end: context.window.end,
|
|
30698
|
+
source: context.window.source
|
|
30831
30699
|
},
|
|
30700
|
+
connectorName: context.connectorName,
|
|
30701
|
+
replayEnv: context.replayEnv,
|
|
30702
|
+
tolerance: { bars: context.toleranceBars, ms: context.toleranceMs },
|
|
30832
30703
|
summary: {
|
|
30833
|
-
replayTargets: replayTargetsCount,
|
|
30834
|
-
comparedTargets: comparedTargetsCount,
|
|
30835
|
-
replayErrors: replayErrors.length,
|
|
30836
|
-
sourceCounts,
|
|
30837
|
-
runtimeEntriesRaw: rawRuntimeEntriesCount,
|
|
30838
|
-
runtimeEntries: runtimeEntriesCount,
|
|
30839
|
-
runtimeDuplicateEntries: runtimeDuplicateEntriesCount,
|
|
30840
|
-
backtestEntries: backtestEntriesCount,
|
|
30841
|
-
matchedEntries: matchedCount,
|
|
30842
|
-
runtimeOnlyEntries: runtimeOnlyCount,
|
|
30843
|
-
backtestOnlyEntries: backtestOnlyCount,
|
|
30844
|
-
runtimeSignalEvaluations: runtimeSignalEvaluationsCount,
|
|
30704
|
+
replayTargets: context.replayTargetsCount,
|
|
30705
|
+
comparedTargets: context.comparedTargetsCount,
|
|
30706
|
+
replayErrors: context.replayErrors.length,
|
|
30707
|
+
sourceCounts: context.sourceCounts,
|
|
30708
|
+
runtimeEntriesRaw: context.rawRuntimeEntriesCount,
|
|
30709
|
+
runtimeEntries: context.runtimeEntriesCount,
|
|
30710
|
+
runtimeDuplicateEntries: context.runtimeDuplicateEntriesCount,
|
|
30711
|
+
backtestEntries: context.backtestEntriesCount,
|
|
30712
|
+
matchedEntries: context.matchedCount,
|
|
30713
|
+
runtimeOnlyEntries: context.runtimeOnlyCount,
|
|
30714
|
+
backtestOnlyEntries: context.backtestOnlyCount,
|
|
30715
|
+
runtimeSignalEvaluations: context.runtimeSignalEvaluationsCount,
|
|
30845
30716
|
matchedDeltas: {
|
|
30846
|
-
priceAvgPct: matchedSummary.avgPriceDeltaPct,
|
|
30847
|
-
priceMaxPct: matchedSummary.maxPriceDeltaPct,
|
|
30848
|
-
timeAvgMs: matchedSummary.avgTimestampDiffMs,
|
|
30849
|
-
timeMaxMs: matchedSummary.maxTimestampDiffMs
|
|
30717
|
+
priceAvgPct: context.matchedSummary.avgPriceDeltaPct,
|
|
30718
|
+
priceMaxPct: context.matchedSummary.maxPriceDeltaPct,
|
|
30719
|
+
timeAvgMs: context.matchedSummary.avgTimestampDiffMs,
|
|
30720
|
+
timeMaxMs: context.matchedSummary.maxTimestampDiffMs
|
|
30850
30721
|
},
|
|
30851
|
-
strategyIssues: buildStrategyIssueRows(strategyRows).map(
|
|
30722
|
+
strategyIssues: buildStrategyIssueRows(context.strategyRows).map(
|
|
30852
30723
|
(line) => line.slice(2)
|
|
30853
30724
|
)
|
|
30854
30725
|
},
|
|
30855
|
-
replayErrors: replayErrors.map((error) => ({
|
|
30726
|
+
replayErrors: context.replayErrors.map((error) => ({
|
|
30856
30727
|
strategy: error.strategy,
|
|
30857
30728
|
symbol: error.symbol,
|
|
30858
30729
|
sources: error.sources,
|
|
@@ -30860,7 +30731,7 @@ var init_runtimeParity2 = __esm({
|
|
|
30860
30731
|
})),
|
|
30861
30732
|
cases,
|
|
30862
30733
|
mismatches: {
|
|
30863
|
-
runtimeOnly: classifiedRuntimeOnly.map((item) => ({
|
|
30734
|
+
runtimeOnly: context.classifiedRuntimeOnly.map((item) => ({
|
|
30864
30735
|
classification: item.classification,
|
|
30865
30736
|
reason: item.reason,
|
|
30866
30737
|
runtimeEntry: toSerializableTradeParityEntry(item.entry),
|
|
@@ -30873,7 +30744,7 @@ var init_runtimeParity2 = __esm({
|
|
|
30873
30744
|
),
|
|
30874
30745
|
nearestBacktestDriftMs: item.nearestBacktestEntryTimestampDiffMs
|
|
30875
30746
|
})),
|
|
30876
|
-
backtestOnly: classifiedBacktestOnly.map((item) => ({
|
|
30747
|
+
backtestOnly: context.classifiedBacktestOnly.map((item) => ({
|
|
30877
30748
|
classification: item.classification,
|
|
30878
30749
|
reason: item.reason,
|
|
30879
30750
|
backtestEntry: toSerializableTradeParityEntry(item.entry),
|
|
@@ -30887,31 +30758,115 @@ var init_runtimeParity2 = __esm({
|
|
|
30887
30758
|
}
|
|
30888
30759
|
};
|
|
30889
30760
|
return {
|
|
30890
|
-
filename: `runtime-parity-mismatches-${connectorName}-${window.start}-${window.end}.json`,
|
|
30761
|
+
filename: `runtime-parity-mismatches-${context.connectorName}-${context.window.start}-${context.window.end}.json`,
|
|
30891
30762
|
content: JSON.stringify(payload, null, 2),
|
|
30892
30763
|
caption: "Runtime parity mismatch JSON"
|
|
30893
30764
|
};
|
|
30894
30765
|
};
|
|
30895
|
-
|
|
30896
|
-
|
|
30897
|
-
|
|
30898
|
-
|
|
30899
|
-
|
|
30900
|
-
|
|
30901
|
-
|
|
30902
|
-
|
|
30903
|
-
|
|
30904
|
-
|
|
30905
|
-
|
|
30906
|
-
|
|
30907
|
-
|
|
30766
|
+
}
|
|
30767
|
+
});
|
|
30768
|
+
|
|
30769
|
+
// src/lib/runtimeParity/reportingTerminal.ts
|
|
30770
|
+
var import_chalk43, import_time8, DETAIL_LIMIT, buildRuntimeParityTerminalReport, writeRuntimeParityTerminalReport, printRuntimeDuplicateDetails, printClassifiedBacktestOnlyDetails, printClassifiedRuntimeOnlyDetails;
|
|
30771
|
+
var init_reportingTerminal = __esm({
|
|
30772
|
+
"src/lib/runtimeParity/reportingTerminal.ts"() {
|
|
30773
|
+
"use strict";
|
|
30774
|
+
import_chalk43 = __toESM(require("chalk"));
|
|
30775
|
+
import_time8 = require("@tradejs/core/time");
|
|
30776
|
+
init_classification();
|
|
30777
|
+
init_reportingShared();
|
|
30778
|
+
DETAIL_LIMIT = 10;
|
|
30779
|
+
buildRuntimeParityTerminalReport = (context) => {
|
|
30780
|
+
const mismatchRows = buildMismatchSummaryRows(context);
|
|
30781
|
+
const strategyIssueRows = buildStrategyIssueRows(context.strategyRows);
|
|
30782
|
+
const lines = [
|
|
30783
|
+
import_chalk43.default.cyan("TradeJS runtime parity"),
|
|
30784
|
+
`Window: ${(0, import_time8.formatUnix)(context.window.start)} -> ${(0, import_time8.formatUnix)(context.window.end)} (${context.window.source})`,
|
|
30785
|
+
`Connector: ${context.connectorName}`,
|
|
30786
|
+
`Replay env: ${context.replayEnv}${context.runtimeGatesEnabled ? " (runtime AI/ML gates enabled)" : " (core/backtest gates only)"}`,
|
|
30787
|
+
`Tolerance: ${context.toleranceBars} bar(s) / ${(context.toleranceMs / 6e4).toFixed(0)}m`,
|
|
30788
|
+
`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}`,
|
|
30789
|
+
`Sources: ${formatSourceCountsSummary(context.sourceCounts)}`,
|
|
30790
|
+
`Runtime entries: ${formatRuntimeEntriesSummary(context)}`,
|
|
30791
|
+
`Matched deltas: price avg/max=${formatPercent4(context.matchedSummary.avgPriceDeltaPct)} / ${formatPercent4(context.matchedSummary.maxPriceDeltaPct)}, time avg/max=${formatMinutes2(context.matchedSummary.avgTimestampDiffMs)} / ${formatMinutes2(context.matchedSummary.maxTimestampDiffMs)}`
|
|
30792
|
+
];
|
|
30793
|
+
if (context.runtimeDuplicateGroupsCount) {
|
|
30794
|
+
lines.push(
|
|
30795
|
+
`Runtime duplicate groups: ${context.runtimeDuplicateGroupsCount}, duplicate entries: ${context.runtimeDuplicateEntriesCount}`
|
|
30796
|
+
);
|
|
30797
|
+
}
|
|
30798
|
+
if (context.classifiedRuntimeOnly.length) {
|
|
30799
|
+
lines.push(
|
|
30800
|
+
`Runtime only classifications: ${summarizeRuntimeOnlyClassifications(context.classifiedRuntimeOnly)}`
|
|
30801
|
+
);
|
|
30802
|
+
}
|
|
30803
|
+
if (context.classifiedBacktestOnly.length) {
|
|
30804
|
+
lines.push(
|
|
30805
|
+
`Backtest only classifications: ${summarizeBacktestOnlyClassifications(context.classifiedBacktestOnly)}`
|
|
30806
|
+
);
|
|
30807
|
+
}
|
|
30808
|
+
if (context.runtimeSignalEvaluationsCount) {
|
|
30809
|
+
lines.push(`Runtime evaluations: ${context.runtimeSignalEvaluationsCount}`);
|
|
30810
|
+
}
|
|
30811
|
+
if (mismatchRows.length) {
|
|
30812
|
+
lines.push("", import_chalk43.default.yellow("Signal mismatches"));
|
|
30813
|
+
for (const item of mismatchRows.slice(0, context.detailLimit)) {
|
|
30814
|
+
lines.push(`- ${item.line}`);
|
|
30815
|
+
}
|
|
30816
|
+
if (mismatchRows.length > context.detailLimit) {
|
|
30817
|
+
lines.push(`- ... ${mismatchRows.length - context.detailLimit} more`);
|
|
30818
|
+
}
|
|
30819
|
+
}
|
|
30820
|
+
lines.push("");
|
|
30821
|
+
if (strategyIssueRows.length) {
|
|
30822
|
+
lines.push(import_chalk43.default.cyan("Strategy issues"), ...strategyIssueRows);
|
|
30823
|
+
} else if (context.strategyRows.length) {
|
|
30824
|
+
lines.push(
|
|
30825
|
+
`Strategies: clean ${context.strategyRows.length}/${context.strategyRows.length}`
|
|
30826
|
+
);
|
|
30827
|
+
}
|
|
30828
|
+
if (context.runtimeGateWarningCounts.size && !context.runtimeGatesRequested) {
|
|
30829
|
+
lines.push("", import_chalk43.default.yellow("Warnings"));
|
|
30830
|
+
for (const [strategy, count] of [
|
|
30831
|
+
...context.runtimeGateWarningCounts.entries()
|
|
30832
|
+
].sort(([left], [right]) => left.localeCompare(right))) {
|
|
30833
|
+
lines.push(
|
|
30834
|
+
`- ${strategy} uses AI/ML runtime gates on ${count} replay target(s); BACKTEST replay covers core execution, not live gating.`
|
|
30835
|
+
);
|
|
30836
|
+
}
|
|
30837
|
+
}
|
|
30838
|
+
if (context.replayErrors.length) {
|
|
30839
|
+
lines.push("", import_chalk43.default.red("Replay errors"));
|
|
30840
|
+
for (const error of context.replayErrors.slice(0, context.detailLimit)) {
|
|
30841
|
+
lines.push(`- ${error.strategy} ${error.symbol}: ${error.message}`);
|
|
30842
|
+
}
|
|
30843
|
+
if (context.replayErrors.length > context.detailLimit) {
|
|
30844
|
+
lines.push(
|
|
30845
|
+
`- ... ${context.replayErrors.length - context.detailLimit} more`
|
|
30846
|
+
);
|
|
30847
|
+
}
|
|
30848
|
+
}
|
|
30849
|
+
return lines;
|
|
30850
|
+
};
|
|
30851
|
+
writeRuntimeParityTerminalReport = (lines) => {
|
|
30852
|
+
for (const line of lines) console.log(line);
|
|
30853
|
+
};
|
|
30854
|
+
printRuntimeDuplicateDetails = (groups) => {
|
|
30855
|
+
if (!groups.length) return;
|
|
30856
|
+
console.log("");
|
|
30857
|
+
console.log(import_chalk43.default.yellow("Runtime duplicates"));
|
|
30858
|
+
for (const group of groups.slice(0, DETAIL_LIMIT)) {
|
|
30859
|
+
const ids = group.entries.map((entry) => entry.orderId ?? entry.id);
|
|
30860
|
+
console.log(
|
|
30861
|
+
`- ${formatEntryLabel(group.entries[0])} count=${group.entries.length}, duplicateEntries=${group.entries.length - 1}, ids=${ids.join(",")}`
|
|
30862
|
+
);
|
|
30863
|
+
}
|
|
30864
|
+
if (groups.length > DETAIL_LIMIT) {
|
|
30908
30865
|
console.log(`- ... ${groups.length - DETAIL_LIMIT} more`);
|
|
30909
30866
|
}
|
|
30910
30867
|
};
|
|
30911
30868
|
printClassifiedBacktestOnlyDetails = (classifiedEntries) => {
|
|
30912
|
-
if (!classifiedEntries.length)
|
|
30913
|
-
return;
|
|
30914
|
-
}
|
|
30869
|
+
if (!classifiedEntries.length) return;
|
|
30915
30870
|
console.log("");
|
|
30916
30871
|
console.log(import_chalk43.default.yellow("Backtest only"));
|
|
30917
30872
|
for (const item of classifiedEntries.slice(0, DETAIL_LIMIT)) {
|
|
@@ -30926,9 +30881,7 @@ var init_runtimeParity2 = __esm({
|
|
|
30926
30881
|
}
|
|
30927
30882
|
};
|
|
30928
30883
|
printClassifiedRuntimeOnlyDetails = (classifiedEntries) => {
|
|
30929
|
-
if (!classifiedEntries.length)
|
|
30930
|
-
return;
|
|
30931
|
-
}
|
|
30884
|
+
if (!classifiedEntries.length) return;
|
|
30932
30885
|
console.log("");
|
|
30933
30886
|
console.log(import_chalk43.default.yellow("Runtime only"));
|
|
30934
30887
|
for (const item of classifiedEntries.slice(0, DETAIL_LIMIT)) {
|
|
@@ -30942,149 +30895,86 @@ var init_runtimeParity2 = __esm({
|
|
|
30942
30895
|
console.log(`- ... ${classifiedEntries.length - DETAIL_LIMIT} more`);
|
|
30943
30896
|
}
|
|
30944
30897
|
};
|
|
30945
|
-
|
|
30946
|
-
|
|
30947
|
-
|
|
30948
|
-
|
|
30949
|
-
|
|
30950
|
-
|
|
30951
|
-
|
|
30952
|
-
|
|
30953
|
-
|
|
30954
|
-
|
|
30955
|
-
|
|
30956
|
-
|
|
30957
|
-
|
|
30958
|
-
|
|
30959
|
-
|
|
30960
|
-
|
|
30961
|
-
|
|
30962
|
-
|
|
30963
|
-
|
|
30964
|
-
|
|
30965
|
-
|
|
30966
|
-
|
|
30967
|
-
|
|
30968
|
-
targets: 0,
|
|
30969
|
-
compared: 0,
|
|
30970
|
-
errors: 0
|
|
30971
|
-
});
|
|
30972
|
-
}
|
|
30973
|
-
const ensureRow = (strategy) => {
|
|
30974
|
-
const row = rows.get(strategy) ?? {
|
|
30975
|
-
runtime: 0,
|
|
30976
|
-
runtimeDuplicates: 0,
|
|
30977
|
-
backtest: 0,
|
|
30978
|
-
matched: 0,
|
|
30979
|
-
runtimeOnly: 0,
|
|
30980
|
-
backtestOnly: 0,
|
|
30981
|
-
targets: 0,
|
|
30982
|
-
compared: 0,
|
|
30983
|
-
errors: 0
|
|
30984
|
-
};
|
|
30985
|
-
rows.set(strategy, row);
|
|
30986
|
-
return row;
|
|
30987
|
-
};
|
|
30988
|
-
for (const target of targets) {
|
|
30989
|
-
const row = ensureRow(target.strategy);
|
|
30990
|
-
row.targets += 1;
|
|
30991
|
-
if (successfulTargetKeys.has(toTargetKey(target))) {
|
|
30992
|
-
row.compared += 1;
|
|
30993
|
-
}
|
|
30994
|
-
}
|
|
30995
|
-
for (const error of replayErrors) {
|
|
30996
|
-
ensureRow(error.strategy).errors += 1;
|
|
30997
|
-
}
|
|
30998
|
-
return [...rows.entries()].sort(
|
|
30999
|
-
([left], [right]) => left.localeCompare(right)
|
|
31000
|
-
);
|
|
31001
|
-
};
|
|
30898
|
+
}
|
|
30899
|
+
});
|
|
30900
|
+
|
|
30901
|
+
// src/lib/runtimeParity/reportingTelegram.ts
|
|
30902
|
+
var TELEGRAM_DETAIL_LIMIT, SUMMARY_TIMEZONE, SUMMARY_TIMEZONE_LABEL, escapeHtml5, formatMskDateTime2, buildRuntimeParityMessage, buildRuntimeParityNoTargetsMessage;
|
|
30903
|
+
var init_reportingTelegram = __esm({
|
|
30904
|
+
"src/lib/runtimeParity/reportingTelegram.ts"() {
|
|
30905
|
+
"use strict";
|
|
30906
|
+
init_classification();
|
|
30907
|
+
init_reportingShared();
|
|
30908
|
+
TELEGRAM_DETAIL_LIMIT = 5;
|
|
30909
|
+
SUMMARY_TIMEZONE = "Europe/Moscow";
|
|
30910
|
+
SUMMARY_TIMEZONE_LABEL = "MSK";
|
|
30911
|
+
escapeHtml5 = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
30912
|
+
formatMskDateTime2 = (timestamp) => new Intl.DateTimeFormat("ru-RU", {
|
|
30913
|
+
timeZone: SUMMARY_TIMEZONE,
|
|
30914
|
+
year: "numeric",
|
|
30915
|
+
month: "2-digit",
|
|
30916
|
+
day: "2-digit",
|
|
30917
|
+
hour: "2-digit",
|
|
30918
|
+
minute: "2-digit",
|
|
30919
|
+
hour12: false
|
|
30920
|
+
}).format(new Date(timestamp));
|
|
31002
30921
|
buildRuntimeParityMessage = ({
|
|
31003
|
-
window,
|
|
31004
|
-
connectorName,
|
|
31005
|
-
replayEnv,
|
|
31006
30922
|
runtimeGatesEnabled,
|
|
31007
|
-
|
|
31008
|
-
|
|
31009
|
-
replayTargetsCount,
|
|
31010
|
-
comparedTargetsCount,
|
|
31011
|
-
replayErrors,
|
|
31012
|
-
sourceCounts,
|
|
31013
|
-
rawRuntimeEntriesCount,
|
|
31014
|
-
runtimeEntriesCount,
|
|
31015
|
-
runtimeDuplicateEntriesCount,
|
|
31016
|
-
backtestEntriesCount,
|
|
31017
|
-
matchedCount: _matchedCount,
|
|
31018
|
-
runtimeOnlyCount,
|
|
31019
|
-
backtestOnlyCount,
|
|
31020
|
-
matchedSummary,
|
|
31021
|
-
classifiedRuntimeOnly,
|
|
31022
|
-
classifiedBacktestOnly,
|
|
31023
|
-
runtimeSignalEvaluationsCount,
|
|
31024
|
-
strategyRows,
|
|
31025
|
-
runtimeGateWarningCounts
|
|
30923
|
+
runtimeGateWarningCounts,
|
|
30924
|
+
...context
|
|
31026
30925
|
}) => {
|
|
31027
30926
|
const lines = [];
|
|
31028
30927
|
lines.push("\u{1F9EA} <b>TradeJS runtime parity</b>");
|
|
31029
30928
|
lines.push("");
|
|
31030
30929
|
lines.push("\u{1F552} <b>Window</b>");
|
|
31031
30930
|
lines.push(
|
|
31032
|
-
`<b>${escapeHtml5(formatMskDateTime2(window.start))} - ${escapeHtml5(formatMskDateTime2(window.end))} ${SUMMARY_TIMEZONE_LABEL}</b>`
|
|
30931
|
+
`<b>${escapeHtml5(formatMskDateTime2(context.window.start))} - ${escapeHtml5(formatMskDateTime2(context.window.end))} ${SUMMARY_TIMEZONE_LABEL}</b>`
|
|
31033
30932
|
);
|
|
31034
30933
|
lines.push("");
|
|
31035
|
-
lines.push(`\u{1F50C} Connector: <b>${escapeHtml5(connectorName)}</b>`);
|
|
31036
|
-
lines.push(`\u{1F9EC} Replay env: <b>${escapeHtml5(replayEnv)}</b>`);
|
|
30934
|
+
lines.push(`\u{1F50C} Connector: <b>${escapeHtml5(context.connectorName)}</b>`);
|
|
30935
|
+
lines.push(`\u{1F9EC} Replay env: <b>${escapeHtml5(context.replayEnv)}</b>`);
|
|
31037
30936
|
lines.push(
|
|
31038
|
-
`\u{1F3AF} Tolerance: <b>${toleranceBars} bar(s) / ${(toleranceMs / 6e4).toFixed(0)}m</b>`
|
|
30937
|
+
`\u{1F3AF} Tolerance: <b>${context.toleranceBars} bar(s) / ${(context.toleranceMs / 6e4).toFixed(0)}m</b>`
|
|
31039
30938
|
);
|
|
31040
30939
|
lines.push("");
|
|
31041
30940
|
lines.push("\u{1F4CC} <b>Overview</b>");
|
|
31042
30941
|
lines.push(
|
|
31043
|
-
`\u2022 Targets: <b>${replayTargetsCount}</b> / compared <b>${comparedTargetsCount}</b> / errors <b>${replayErrors.length}</b>`
|
|
30942
|
+
`\u2022 Targets: <b>${context.replayTargetsCount}</b> / compared <b>${context.comparedTargetsCount}</b> / errors <b>${context.replayErrors.length}</b>`
|
|
31044
30943
|
);
|
|
31045
30944
|
lines.push(
|
|
31046
|
-
`\u2022 Sources: <b>${escapeHtml5(formatSourceCountsSummary(sourceCounts))}</b>`
|
|
30945
|
+
`\u2022 Sources: <b>${escapeHtml5(formatSourceCountsSummary(context.sourceCounts))}</b>`
|
|
31047
30946
|
);
|
|
31048
30947
|
lines.push("");
|
|
31049
30948
|
lines.push("\u{1F4C8} <b>Entries</b>");
|
|
31050
30949
|
lines.push(
|
|
31051
|
-
`\u2022 Runtime: <b>${escapeHtml5(
|
|
31052
|
-
formatRuntimeEntriesSummary({
|
|
31053
|
-
rawRuntimeEntriesCount,
|
|
31054
|
-
runtimeEntriesCount,
|
|
31055
|
-
runtimeDuplicateEntriesCount
|
|
31056
|
-
})
|
|
31057
|
-
)}</b>`
|
|
30950
|
+
`\u2022 Runtime: <b>${escapeHtml5(formatRuntimeEntriesSummary(context))}</b>`
|
|
31058
30951
|
);
|
|
31059
|
-
lines.push(`\u2022 Backtest: <b>${backtestEntriesCount}</b>`);
|
|
30952
|
+
lines.push(`\u2022 Backtest: <b>${context.backtestEntriesCount}</b>`);
|
|
31060
30953
|
lines.push(
|
|
31061
|
-
`\u2022 Runtime only: <b>${runtimeOnlyCount}</b> / Backtest only: <b>${backtestOnlyCount}</b>`
|
|
30954
|
+
`\u2022 Runtime only: <b>${context.runtimeOnlyCount}</b> / Backtest only: <b>${context.backtestOnlyCount}</b>`
|
|
31062
30955
|
);
|
|
31063
30956
|
lines.push(
|
|
31064
|
-
`\u2022 Matched deltas: price avg/max=<b>${escapeHtml5(formatPercent4(matchedSummary.avgPriceDeltaPct))} / ${escapeHtml5(formatPercent4(matchedSummary.maxPriceDeltaPct))}</b>, time avg/max=<b>${escapeHtml5(formatMinutes2(matchedSummary.avgTimestampDiffMs))} / ${escapeHtml5(formatMinutes2(matchedSummary.maxTimestampDiffMs))}</b>`
|
|
30957
|
+
`\u2022 Matched deltas: price avg/max=<b>${escapeHtml5(formatPercent4(context.matchedSummary.avgPriceDeltaPct))} / ${escapeHtml5(formatPercent4(context.matchedSummary.maxPriceDeltaPct))}</b>, time avg/max=<b>${escapeHtml5(formatMinutes2(context.matchedSummary.avgTimestampDiffMs))} / ${escapeHtml5(formatMinutes2(context.matchedSummary.maxTimestampDiffMs))}</b>`
|
|
31065
30958
|
);
|
|
31066
|
-
if (classifiedRuntimeOnly.length) {
|
|
30959
|
+
if (context.classifiedRuntimeOnly.length) {
|
|
31067
30960
|
lines.push(
|
|
31068
|
-
`\u2022 Runtime-only classes: <code>${escapeHtml5(summarizeRuntimeOnlyClassifications(classifiedRuntimeOnly))}</code>`
|
|
30961
|
+
`\u2022 Runtime-only classes: <code>${escapeHtml5(summarizeRuntimeOnlyClassifications(context.classifiedRuntimeOnly))}</code>`
|
|
31069
30962
|
);
|
|
31070
30963
|
}
|
|
31071
|
-
if (classifiedBacktestOnly.length) {
|
|
30964
|
+
if (context.classifiedBacktestOnly.length) {
|
|
31072
30965
|
lines.push(
|
|
31073
|
-
`\u2022 Backtest-only classes: <code>${escapeHtml5(summarizeBacktestOnlyClassifications(classifiedBacktestOnly))}</code>`
|
|
30966
|
+
`\u2022 Backtest-only classes: <code>${escapeHtml5(summarizeBacktestOnlyClassifications(context.classifiedBacktestOnly))}</code>`
|
|
31074
30967
|
);
|
|
31075
30968
|
}
|
|
31076
|
-
if (runtimeSignalEvaluationsCount) {
|
|
30969
|
+
if (context.runtimeSignalEvaluationsCount) {
|
|
31077
30970
|
lines.push(
|
|
31078
|
-
`\u2022 Runtime evaluations: <b>${runtimeSignalEvaluationsCount}</b>`
|
|
30971
|
+
`\u2022 Runtime evaluations: <b>${context.runtimeSignalEvaluationsCount}</b>`
|
|
31079
30972
|
);
|
|
31080
30973
|
}
|
|
31081
|
-
const mismatchRows = buildMismatchSummaryRows(
|
|
31082
|
-
classifiedRuntimeOnly,
|
|
31083
|
-
classifiedBacktestOnly
|
|
31084
|
-
});
|
|
30974
|
+
const mismatchRows = buildMismatchSummaryRows(context);
|
|
31085
30975
|
if (mismatchRows.length) {
|
|
31086
30976
|
lines.push("");
|
|
31087
|
-
lines.push(
|
|
30977
|
+
lines.push("\u{1F50E} <b>Mismatches</b>");
|
|
31088
30978
|
for (const item of mismatchRows.slice(0, TELEGRAM_DETAIL_LIMIT)) {
|
|
31089
30979
|
lines.push(`\u2022 <code>${escapeHtml5(item.line)}</code>`);
|
|
31090
30980
|
}
|
|
@@ -31094,17 +30984,17 @@ var init_runtimeParity2 = __esm({
|
|
|
31094
30984
|
);
|
|
31095
30985
|
}
|
|
31096
30986
|
}
|
|
31097
|
-
const strategyIssueRows = buildStrategyIssueRows(strategyRows);
|
|
30987
|
+
const strategyIssueRows = buildStrategyIssueRows(context.strategyRows);
|
|
31098
30988
|
if (strategyIssueRows.length) {
|
|
31099
30989
|
lines.push("");
|
|
31100
30990
|
lines.push("\u{1F4CA} <b>Strategy issues</b>");
|
|
31101
30991
|
for (const line of strategyIssueRows) {
|
|
31102
30992
|
lines.push(`\u2022 ${escapeHtml5(line.slice(2))}`);
|
|
31103
30993
|
}
|
|
31104
|
-
} else if (strategyRows.length) {
|
|
30994
|
+
} else if (context.strategyRows.length) {
|
|
31105
30995
|
lines.push("");
|
|
31106
30996
|
lines.push(
|
|
31107
|
-
`\u{1F4CA} <b>Strategies</b>: clean <b>${strategyRows.length}</b> / total <b>${strategyRows.length}</b>`
|
|
30997
|
+
`\u{1F4CA} <b>Strategies</b>: clean <b>${context.strategyRows.length}</b> / total <b>${context.strategyRows.length}</b>`
|
|
31108
30998
|
);
|
|
31109
30999
|
}
|
|
31110
31000
|
if (runtimeGateWarningCounts.size && !runtimeGatesEnabled) {
|
|
@@ -31118,17 +31008,17 @@ var init_runtimeParity2 = __esm({
|
|
|
31118
31008
|
);
|
|
31119
31009
|
}
|
|
31120
31010
|
}
|
|
31121
|
-
if (replayErrors.length) {
|
|
31011
|
+
if (context.replayErrors.length) {
|
|
31122
31012
|
lines.push("");
|
|
31123
31013
|
lines.push("\u274C <b>Replay errors</b>");
|
|
31124
|
-
for (const error of replayErrors.slice(0, TELEGRAM_DETAIL_LIMIT)) {
|
|
31014
|
+
for (const error of context.replayErrors.slice(0, TELEGRAM_DETAIL_LIMIT)) {
|
|
31125
31015
|
lines.push(
|
|
31126
31016
|
`\u2022 <b>${escapeHtml5(error.strategy)}</b> ${escapeHtml5(error.symbol)}: <code>${escapeHtml5(error.message)}</code>`
|
|
31127
31017
|
);
|
|
31128
31018
|
}
|
|
31129
|
-
if (replayErrors.length > TELEGRAM_DETAIL_LIMIT) {
|
|
31019
|
+
if (context.replayErrors.length > TELEGRAM_DETAIL_LIMIT) {
|
|
31130
31020
|
lines.push(
|
|
31131
|
-
`... <b>${replayErrors.length - TELEGRAM_DETAIL_LIMIT}</b> more`
|
|
31021
|
+
`... <b>${context.replayErrors.length - TELEGRAM_DETAIL_LIMIT}</b> more`
|
|
31132
31022
|
);
|
|
31133
31023
|
}
|
|
31134
31024
|
}
|
|
@@ -31138,7 +31028,6 @@ var init_runtimeParity2 = __esm({
|
|
|
31138
31028
|
window,
|
|
31139
31029
|
connectorName,
|
|
31140
31030
|
replayEnv,
|
|
31141
|
-
runtimeGatesEnabled: _runtimeGatesEnabled,
|
|
31142
31031
|
userName: userName3
|
|
31143
31032
|
}) => [
|
|
31144
31033
|
"\u{1F9EA} <b>TradeJS runtime parity</b>",
|
|
@@ -31151,19 +31040,278 @@ var init_runtimeParity2 = __esm({
|
|
|
31151
31040
|
"",
|
|
31152
31041
|
`\u26A0\uFE0F No replay targets found for user <b>${escapeHtml5(userName3)}</b>.`
|
|
31153
31042
|
].join("\n");
|
|
31154
|
-
|
|
31155
|
-
|
|
31156
|
-
|
|
31157
|
-
|
|
31158
|
-
|
|
31159
|
-
|
|
31160
|
-
|
|
31161
|
-
|
|
31162
|
-
|
|
31163
|
-
|
|
31164
|
-
|
|
31043
|
+
}
|
|
31044
|
+
});
|
|
31045
|
+
|
|
31046
|
+
// src/lib/runtimeParity/reporting.ts
|
|
31047
|
+
var init_reporting = __esm({
|
|
31048
|
+
"src/lib/runtimeParity/reporting.ts"() {
|
|
31049
|
+
"use strict";
|
|
31050
|
+
init_reportingShared();
|
|
31051
|
+
init_reportingJson();
|
|
31052
|
+
init_reportingTerminal();
|
|
31053
|
+
init_reportingTelegram();
|
|
31054
|
+
}
|
|
31055
|
+
});
|
|
31056
|
+
|
|
31057
|
+
// src/scripts/runtimeParity.ts
|
|
31058
|
+
var runtimeParity_exports = {};
|
|
31059
|
+
__export(runtimeParity_exports, {
|
|
31060
|
+
buildRuntimeParityMessage: () => buildRuntimeParityMessage,
|
|
31061
|
+
main: () => main40,
|
|
31062
|
+
runtimeParity: () => runtimeParity
|
|
31063
|
+
});
|
|
31064
|
+
var import_config11, import_node_crypto14, import_args30, import_chalk44, import_constants15, import_time9, import_logger6, import_redis17, import_cli11, import_backtest3, import_connectors14, flags28, interval3, projectRoot13, DEFAULT_LOOKBACK_DAYS, DETAIL_LIMIT2, DEFAULT_REPLAY_ENV, resolveParityConnectorName, createConnector, buildReplayAiAnalyses, formatReplayEnvSummary, buildReplayConfig, warmReplayHistory, runtimeParity, main40;
|
|
31065
|
+
var init_runtimeParity2 = __esm({
|
|
31066
|
+
"src/scripts/runtimeParity.ts"() {
|
|
31067
|
+
"use strict";
|
|
31068
|
+
import_config11 = require("dotenv/config");
|
|
31069
|
+
import_node_crypto14 = require("crypto");
|
|
31070
|
+
import_args30 = __toESM(require("args"));
|
|
31071
|
+
import_chalk44 = __toESM(require("chalk"));
|
|
31072
|
+
import_constants15 = require("@tradejs/core/constants");
|
|
31073
|
+
import_time9 = require("@tradejs/core/time");
|
|
31074
|
+
import_logger6 = require("@tradejs/infra/logger");
|
|
31075
|
+
import_redis17 = require("@tradejs/infra/redis");
|
|
31076
|
+
import_cli11 = require("@tradejs/node/cli");
|
|
31077
|
+
import_backtest3 = require("@tradejs/node/backtest");
|
|
31078
|
+
import_connectors14 = require("@tradejs/node/connectors");
|
|
31079
|
+
init_runtimeParity();
|
|
31080
|
+
init_cliArgs();
|
|
31081
|
+
init_runtimeRedis();
|
|
31082
|
+
init_runtimeSignalsLoader();
|
|
31083
|
+
init_telegramReports();
|
|
31084
|
+
init_timeWindow();
|
|
31085
|
+
init_runFormatting();
|
|
31086
|
+
init_marketContextPrepare();
|
|
31087
|
+
init_historyPrepare();
|
|
31088
|
+
init_targets();
|
|
31089
|
+
init_evidence();
|
|
31090
|
+
init_analysis();
|
|
31091
|
+
init_reporting();
|
|
31092
|
+
init_reporting();
|
|
31093
|
+
init_runtimeModeConfig();
|
|
31094
|
+
import_args30.default.option(["u", "user"], "Use user config", "root");
|
|
31095
|
+
import_args30.default.option(
|
|
31096
|
+
["o", "connector"],
|
|
31097
|
+
"Connector provider or name for parity replay (e.g. bybit, binance, coinbase, custom)",
|
|
31098
|
+
"bybit"
|
|
31099
|
+
);
|
|
31100
|
+
import_args30.default.option(["d", "days"], "Replay window in days", 3);
|
|
31101
|
+
import_args30.default.option(
|
|
31102
|
+
["b", "startTime"],
|
|
31103
|
+
"Explicit replay start timestamp (ms or seconds)"
|
|
31104
|
+
);
|
|
31105
|
+
import_args30.default.option(["e", "endTime"], "Explicit replay end timestamp (ms or seconds)");
|
|
31106
|
+
import_args30.default.option(["s", "strategy"], "Only compare one strategy");
|
|
31107
|
+
import_args30.default.option(
|
|
31108
|
+
["t", "tickers"],
|
|
31109
|
+
"Replay comma-separated symbols for all configured strategies"
|
|
31110
|
+
);
|
|
31111
|
+
import_args30.default.option(
|
|
31112
|
+
["C", "cacheOnly"],
|
|
31113
|
+
"Do not refresh market history before replay",
|
|
31114
|
+
false
|
|
31115
|
+
);
|
|
31116
|
+
import_args30.default.option(
|
|
31117
|
+
["a", "toleranceBars"],
|
|
31118
|
+
"Allowed entry timestamp drift in bars when matching runtime vs backtest",
|
|
31119
|
+
1
|
|
31120
|
+
);
|
|
31121
|
+
import_args30.default.option(
|
|
31122
|
+
"fullUniverse",
|
|
31123
|
+
"Replay every configured strategy across the full connector universe. By default parity replays only runtime-traded and strategy-results symbols unless --tickers is provided.",
|
|
31124
|
+
false
|
|
31125
|
+
);
|
|
31126
|
+
import_args30.default.option(
|
|
31127
|
+
"runtimeGates",
|
|
31128
|
+
"Force runtime AI/ML gates for all replay targets. Runtime-gated configs are replayed with gates by default.",
|
|
31129
|
+
false
|
|
31130
|
+
);
|
|
31131
|
+
import_args30.default.option(["N", "notify"], "Send parity summary to Telegram", false);
|
|
31132
|
+
import_args30.default.option(["D", "details"], "Print unmatched entry details (capped)", false);
|
|
31133
|
+
process.argv = normalizeCliArgv(process.argv, {
|
|
31134
|
+
"-C": "--cacheOnly",
|
|
31135
|
+
"-D": "--details",
|
|
31136
|
+
"-E": "--endTime",
|
|
31137
|
+
"-N": "--notify",
|
|
31138
|
+
"-S": "--startTime",
|
|
31139
|
+
"-T": "--toleranceBars"
|
|
31140
|
+
});
|
|
31141
|
+
flags28 = import_args30.default.parse(process.argv);
|
|
31142
|
+
interval3 = "15";
|
|
31143
|
+
projectRoot13 = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
|
|
31144
|
+
DEFAULT_LOOKBACK_DAYS = 3;
|
|
31145
|
+
DETAIL_LIMIT2 = 10;
|
|
31146
|
+
DEFAULT_REPLAY_ENV = flags28.runtimeGates ? "PARITY" : "BACKTEST";
|
|
31147
|
+
resolveParityConnectorName = async (value) => {
|
|
31148
|
+
const connectorName = await (0, import_connectors14.resolveConnectorName)(value, projectRoot13);
|
|
31149
|
+
if (connectorName) {
|
|
31150
|
+
return connectorName;
|
|
31151
|
+
}
|
|
31152
|
+
import_logger6.logger.warn(
|
|
31153
|
+
'Unknown connector "%s". Fallback to %s.',
|
|
31154
|
+
String(value || "").trim() || String(value),
|
|
31155
|
+
import_connectors14.DEFAULT_CONNECTOR_NAME
|
|
31165
31156
|
);
|
|
31166
|
-
|
|
31157
|
+
return import_connectors14.DEFAULT_CONNECTOR_NAME;
|
|
31158
|
+
};
|
|
31159
|
+
createConnector = async ({
|
|
31160
|
+
userName: userName3,
|
|
31161
|
+
connectorName
|
|
31162
|
+
}) => {
|
|
31163
|
+
const connectorFactory = await (0, import_connectors14.getConnectorCreatorByName)(
|
|
31164
|
+
connectorName,
|
|
31165
|
+
projectRoot13
|
|
31166
|
+
);
|
|
31167
|
+
if (!connectorFactory) {
|
|
31168
|
+
throw new Error(`Connector "${connectorName}" is not registered`);
|
|
31169
|
+
}
|
|
31170
|
+
return await connectorFactory({ userName: userName3 });
|
|
31171
|
+
};
|
|
31172
|
+
buildReplayAiAnalyses = ({
|
|
31173
|
+
runtimeTrades,
|
|
31174
|
+
runtimeSignalEvaluations,
|
|
31175
|
+
toleranceMs
|
|
31176
|
+
}) => {
|
|
31177
|
+
const snapshots = [
|
|
31178
|
+
...runtimeTrades.filter(
|
|
31179
|
+
(trade) => trade.aiAnalysis && typeof trade.entryTimestamp === "number" && Number.isFinite(trade.entryTimestamp)
|
|
31180
|
+
).map((trade) => ({
|
|
31181
|
+
strategy: trade.strategy,
|
|
31182
|
+
symbol: trade.symbol,
|
|
31183
|
+
direction: trade.direction,
|
|
31184
|
+
timestamp: trade.entryTimestamp,
|
|
31185
|
+
toleranceMs,
|
|
31186
|
+
analysis: trade.aiAnalysis
|
|
31187
|
+
})),
|
|
31188
|
+
...runtimeSignalEvaluations.filter(
|
|
31189
|
+
(evaluation) => evaluation.aiAnalysis && evaluation.direction && typeof evaluation.timestamp === "number" && Number.isFinite(evaluation.timestamp)
|
|
31190
|
+
).map((evaluation) => ({
|
|
31191
|
+
strategy: evaluation.strategy,
|
|
31192
|
+
symbol: evaluation.symbol,
|
|
31193
|
+
direction: evaluation.direction,
|
|
31194
|
+
timestamp: evaluation.timestamp,
|
|
31195
|
+
toleranceMs,
|
|
31196
|
+
analysis: evaluation.aiAnalysis
|
|
31197
|
+
}))
|
|
31198
|
+
];
|
|
31199
|
+
const seen = /* @__PURE__ */ new Set();
|
|
31200
|
+
return snapshots.filter((snapshot) => {
|
|
31201
|
+
const key = `${snapshot.strategy}:${snapshot.symbol}:${snapshot.direction}:${snapshot.timestamp}`;
|
|
31202
|
+
if (seen.has(key)) {
|
|
31203
|
+
return false;
|
|
31204
|
+
}
|
|
31205
|
+
seen.add(key);
|
|
31206
|
+
return true;
|
|
31207
|
+
});
|
|
31208
|
+
};
|
|
31209
|
+
formatReplayEnvSummary = (configs) => {
|
|
31210
|
+
let backtest2 = 0;
|
|
31211
|
+
let parity = 0;
|
|
31212
|
+
for (const config2 of configs) {
|
|
31213
|
+
if (config2.ENV === "PARITY") {
|
|
31214
|
+
parity += 1;
|
|
31215
|
+
} else {
|
|
31216
|
+
backtest2 += 1;
|
|
31217
|
+
}
|
|
31218
|
+
}
|
|
31219
|
+
if (parity > 0 && backtest2 > 0) {
|
|
31220
|
+
return `MIXED (PARITY=${parity}, BACKTEST=${backtest2})`;
|
|
31221
|
+
}
|
|
31222
|
+
if (parity > 0) {
|
|
31223
|
+
return "PARITY";
|
|
31224
|
+
}
|
|
31225
|
+
return "BACKTEST";
|
|
31226
|
+
};
|
|
31227
|
+
buildReplayConfig = async ({
|
|
31228
|
+
userName: userName3,
|
|
31229
|
+
strategy,
|
|
31230
|
+
symbol,
|
|
31231
|
+
replayInputsIndex,
|
|
31232
|
+
toleranceMs
|
|
31233
|
+
}) => {
|
|
31234
|
+
const [userConfig, strategyResults] = await Promise.all([
|
|
31235
|
+
(0, import_redis17.getData)(import_redis17.redisKeys.strategyConfig(userName3, strategy), {}),
|
|
31236
|
+
(0, import_redis17.getData)(import_redis17.redisKeys.strategyResults(userName3, strategy), {})
|
|
31237
|
+
]);
|
|
31238
|
+
const typedResults = strategyResults ?? {};
|
|
31239
|
+
const symbolResult = typedResults?.[symbol];
|
|
31240
|
+
const symbolConfig = symbolResult && symbolResult.config && typeof symbolResult.config === "object" ? symbolResult.config : {};
|
|
31241
|
+
const replayInputs = replayInputsIndex.get(toTargetKey({ strategy, symbol }));
|
|
31242
|
+
const aiReplayAnalyses = buildReplayAiAnalyses({
|
|
31243
|
+
runtimeTrades: replayInputs?.runtimeTrades ?? [],
|
|
31244
|
+
runtimeSignalEvaluations: replayInputs?.runtimeSignalEvaluations ?? [],
|
|
31245
|
+
strategy,
|
|
31246
|
+
symbol,
|
|
31247
|
+
toleranceMs
|
|
31248
|
+
});
|
|
31249
|
+
const strategyConfig = {
|
|
31250
|
+
...userConfig,
|
|
31251
|
+
...symbolConfig
|
|
31252
|
+
};
|
|
31253
|
+
return buildRuntimeModeStrategyConfig({
|
|
31254
|
+
strategyConfig,
|
|
31255
|
+
env: resolveReplayStrategyEnv({
|
|
31256
|
+
strategyConfig,
|
|
31257
|
+
forceRuntimeGates: Boolean(flags28.runtimeGates)
|
|
31258
|
+
}),
|
|
31259
|
+
interval: interval3,
|
|
31260
|
+
makeOrders: true,
|
|
31261
|
+
recordRuntimeTrades: false,
|
|
31262
|
+
aiReplayAnalyses
|
|
31263
|
+
});
|
|
31264
|
+
};
|
|
31265
|
+
warmReplayHistory = async ({
|
|
31266
|
+
userName: userName3,
|
|
31267
|
+
connectorName,
|
|
31268
|
+
targets,
|
|
31269
|
+
preloadStart,
|
|
31270
|
+
preloadEnd
|
|
31271
|
+
}) => {
|
|
31272
|
+
const connector = await createConnector({ userName: userName3, connectorName });
|
|
31273
|
+
const symbols = [...new Set(targets.map((target) => target.symbol))];
|
|
31274
|
+
const warmStartedAt = Date.now();
|
|
31275
|
+
console.log(
|
|
31276
|
+
import_chalk44.default.gray(
|
|
31277
|
+
`Warm history: ${symbols.length} symbol(s) on ${connectorName}, preload ${(0, import_time9.formatUnix)(preloadStart)} -> ${(0, import_time9.formatUnix)(preloadEnd)}`
|
|
31278
|
+
)
|
|
31279
|
+
);
|
|
31280
|
+
const btcReferences = await loadBtcReferenceConnectors({
|
|
31281
|
+
connectorName,
|
|
31282
|
+
marketConnector: connector,
|
|
31283
|
+
userName: userName3,
|
|
31284
|
+
projectRoot: projectRoot13,
|
|
31285
|
+
shouldUseDedicatedReferences: true,
|
|
31286
|
+
warn: (message) => import_logger6.logger.warn(message)
|
|
31287
|
+
});
|
|
31288
|
+
await updateMarketHistoryWithBtcReferences({
|
|
31289
|
+
marketConnector: connector,
|
|
31290
|
+
connectorName,
|
|
31291
|
+
btcReferences,
|
|
31292
|
+
interval: interval3,
|
|
31293
|
+
symbols,
|
|
31294
|
+
preloadStart,
|
|
31295
|
+
preloadEnd,
|
|
31296
|
+
log: (message) => console.log(import_chalk44.default.gray(message))
|
|
31297
|
+
});
|
|
31298
|
+
console.log(
|
|
31299
|
+
import_chalk44.default.gray(`Warm history: done in ${formatDuration(warmStartedAt)}`)
|
|
31300
|
+
);
|
|
31301
|
+
};
|
|
31302
|
+
runtimeParity = async () => {
|
|
31303
|
+
const window = resolveTimeWindow({
|
|
31304
|
+
days: flags28.days ?? DEFAULT_LOOKBACK_DAYS,
|
|
31305
|
+
startTime: flags28.startTime,
|
|
31306
|
+
endTime: flags28.endTime,
|
|
31307
|
+
defaultStartMs: Date.now() - DEFAULT_LOOKBACK_DAYS * 24 * 60 * 60 * 1e3,
|
|
31308
|
+
defaultEndMs: Date.now()
|
|
31309
|
+
});
|
|
31310
|
+
const toleranceBars = Math.max(
|
|
31311
|
+
0,
|
|
31312
|
+
Number.parseInt(String(flags28.toleranceBars ?? 1), 10) || 0
|
|
31313
|
+
);
|
|
31314
|
+
const toleranceMs = toleranceBars * 15 * 60 * 1e3;
|
|
31167
31315
|
const connectorName = await resolveParityConnectorName(flags28.connector);
|
|
31168
31316
|
const requestedSymbols = parseSymbolsFromCLI2(String(flags28.tickers || ""));
|
|
31169
31317
|
const requestedSymbolSet = requestedSymbols.length ? new Set(requestedSymbols) : null;
|
|
@@ -31173,28 +31321,18 @@ var init_runtimeParity2 = __esm({
|
|
|
31173
31321
|
userName: flags28.user,
|
|
31174
31322
|
connectorName
|
|
31175
31323
|
});
|
|
31176
|
-
const
|
|
31177
|
-
|
|
31178
|
-
|
|
31179
|
-
|
|
31180
|
-
|
|
31181
|
-
|
|
31182
|
-
|
|
31183
|
-
|
|
31184
|
-
|
|
31185
|
-
|
|
31186
|
-
|
|
31187
|
-
|
|
31188
|
-
})
|
|
31189
|
-
]);
|
|
31190
|
-
const runtimeTrades = allRuntimeTrades.filter(
|
|
31191
|
-
(trade) => trade.entryTimestamp >= window.start && trade.entryTimestamp <= window.end && (!flags28.strategy || trade.strategy === flags28.strategy) && (!requestedSymbolSet || requestedSymbolSet.has(trade.symbol))
|
|
31192
|
-
);
|
|
31193
|
-
const runtimeSignals = allRuntimeSignals.filter(
|
|
31194
|
-
(signal) => signal.timestamp >= window.start && signal.timestamp <= window.end && (!flags28.strategy || signal.strategy === flags28.strategy) && (!requestedSymbolSet || requestedSymbolSet.has(signal.symbol))
|
|
31195
|
-
);
|
|
31196
|
-
const runtimeSignalEvaluations = allRuntimeSignalEvaluations.filter(
|
|
31197
|
-
(evaluation) => evaluation.timestamp >= window.start && evaluation.timestamp <= window.end && (!flags28.strategy || evaluation.strategy === flags28.strategy) && (!requestedSymbolSet || requestedSymbolSet.has(evaluation.symbol))
|
|
31324
|
+
const { runtimeTrades, runtimeSignals, runtimeSignalEvaluations } = await loadRuntimeParityEvidence(
|
|
31325
|
+
{
|
|
31326
|
+
userName: flags28.user,
|
|
31327
|
+
window,
|
|
31328
|
+
strategy: flags28.strategy,
|
|
31329
|
+
symbols: requestedSymbolSet
|
|
31330
|
+
},
|
|
31331
|
+
{
|
|
31332
|
+
loadRuntimeTrades,
|
|
31333
|
+
loadRuntimeSignals,
|
|
31334
|
+
loadRuntimeSignalEvaluations
|
|
31335
|
+
}
|
|
31198
31336
|
);
|
|
31199
31337
|
const connectorSymbols = requestedSymbols.length ? requestedSymbols : await (0, import_cli11.getTickers)(parityConnector);
|
|
31200
31338
|
const replayInputsIndex = buildReplayInputsIndex({
|
|
@@ -31211,8 +31349,8 @@ var init_runtimeParity2 = __esm({
|
|
|
31211
31349
|
});
|
|
31212
31350
|
if (!replayTargets.length) {
|
|
31213
31351
|
console.log(
|
|
31214
|
-
|
|
31215
|
-
`No replay targets found for ${flags28.user} in ${(0,
|
|
31352
|
+
import_chalk44.default.yellow(
|
|
31353
|
+
`No replay targets found for ${flags28.user} in ${(0, import_time9.formatUnix)(window.start)} -> ${(0, import_time9.formatUnix)(window.end)}.`
|
|
31216
31354
|
)
|
|
31217
31355
|
);
|
|
31218
31356
|
if (flags28.notify) {
|
|
@@ -31230,7 +31368,7 @@ var init_runtimeParity2 = __esm({
|
|
|
31230
31368
|
return;
|
|
31231
31369
|
}
|
|
31232
31370
|
const sourceCounts = countReplayTargetSources(replayTargets);
|
|
31233
|
-
const preloadStart = (0,
|
|
31371
|
+
const preloadStart = (0, import_time9.getBacktestPreloadStart)(
|
|
31234
31372
|
window.start,
|
|
31235
31373
|
import_constants15.BACKTEST_PRELOAD_DAYS
|
|
31236
31374
|
);
|
|
@@ -31239,7 +31377,7 @@ var init_runtimeParity2 = __esm({
|
|
|
31239
31377
|
replayTargets.map((target) => target.symbol)
|
|
31240
31378
|
).size;
|
|
31241
31379
|
console.log(
|
|
31242
|
-
|
|
31380
|
+
import_chalk44.default.gray(
|
|
31243
31381
|
`Replay queue: ${replayTargets.length} target(s), ${replaySymbolsCount} symbol(s), mode=${flags28.fullUniverse ? "full-universe" : requestedSymbols.length ? "explicit-tickers" : "runtime+results"}`
|
|
31244
31382
|
)
|
|
31245
31383
|
);
|
|
@@ -31274,7 +31412,7 @@ var init_runtimeParity2 = __esm({
|
|
|
31274
31412
|
aiEnabled: hasParityAiTargets,
|
|
31275
31413
|
mlEnabled: hasParityMlTargets,
|
|
31276
31414
|
strategyNames: replayTargets.map((target) => target.strategy),
|
|
31277
|
-
log: (message) => console.log(
|
|
31415
|
+
log: (message) => console.log(import_chalk44.default.gray(message))
|
|
31278
31416
|
});
|
|
31279
31417
|
if (!flags28.cacheOnly) {
|
|
31280
31418
|
await warmReplayHistory({
|
|
@@ -31342,139 +31480,61 @@ var init_runtimeParity2 = __esm({
|
|
|
31342
31480
|
}
|
|
31343
31481
|
if (progressPosition === 1 || progressPosition === replayTargets.length || progressPosition % 25 === 0) {
|
|
31344
31482
|
console.log(
|
|
31345
|
-
|
|
31483
|
+
import_chalk44.default.gray(
|
|
31346
31484
|
`Replay progress: ${progressPosition}/${replayTargets.length}, ok=${successfulTargetKeys.size}, errors=${replayErrors.length}, current=${target.strategy} ${target.symbol}, elapsed=${formatDuration(replayStartedAt)}`
|
|
31347
31485
|
)
|
|
31348
31486
|
);
|
|
31349
31487
|
}
|
|
31350
31488
|
}
|
|
31351
|
-
const
|
|
31352
|
-
|
|
31353
|
-
|
|
31354
|
-
const rawRuntimeEntries = extractRuntimeParityEntries(
|
|
31355
|
-
comparableRuntimeTrades
|
|
31356
|
-
);
|
|
31357
|
-
const runtimeDedupe = dedupeRuntimeParityEntries(rawRuntimeEntries);
|
|
31358
|
-
const runtimeEntries = runtimeDedupe.entries;
|
|
31359
|
-
const comparison = compareTradeParityEntries({
|
|
31489
|
+
const {
|
|
31490
|
+
rawRuntimeEntries,
|
|
31491
|
+
runtimeDedupe,
|
|
31360
31492
|
runtimeEntries,
|
|
31361
|
-
|
|
31362
|
-
|
|
31363
|
-
|
|
31364
|
-
|
|
31365
|
-
|
|
31493
|
+
comparison,
|
|
31494
|
+
classifiedBacktestOnly,
|
|
31495
|
+
classifiedRuntimeOnly,
|
|
31496
|
+
matchedSummary: summary,
|
|
31497
|
+
strategyRows
|
|
31498
|
+
} = analyzeRuntimeParity({
|
|
31499
|
+
runtimeTrades,
|
|
31366
31500
|
runtimeSignals,
|
|
31367
31501
|
runtimeSignalEvaluations,
|
|
31368
|
-
toleranceMs
|
|
31369
|
-
});
|
|
31370
|
-
const classifiedRuntimeOnly = classifyRuntimeOnlyEntries({
|
|
31371
|
-
entries: comparison.runtimeOnly,
|
|
31372
|
-
replaySignalEvaluations,
|
|
31373
31502
|
backtestEntries,
|
|
31503
|
+
replaySignalEvaluations,
|
|
31504
|
+
replayTargets,
|
|
31505
|
+
successfulTargetKeys,
|
|
31506
|
+
replayErrors,
|
|
31374
31507
|
toleranceMs
|
|
31375
31508
|
});
|
|
31376
|
-
|
|
31377
|
-
|
|
31378
|
-
|
|
31379
|
-
|
|
31380
|
-
|
|
31381
|
-
|
|
31382
|
-
|
|
31383
|
-
|
|
31384
|
-
|
|
31385
|
-
|
|
31386
|
-
|
|
31387
|
-
|
|
31388
|
-
|
|
31389
|
-
`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}`
|
|
31390
|
-
);
|
|
31391
|
-
console.log(`Sources: ${formatSourceCountsSummary(sourceCounts)}`);
|
|
31392
|
-
console.log(
|
|
31393
|
-
`Runtime entries: ${formatRuntimeEntriesSummary({
|
|
31509
|
+
writeRuntimeParityTerminalReport(
|
|
31510
|
+
buildRuntimeParityTerminalReport({
|
|
31511
|
+
window,
|
|
31512
|
+
connectorName,
|
|
31513
|
+
replayEnv: replayEnvSummary,
|
|
31514
|
+
runtimeGatesEnabled: Boolean(flags28.runtimeGates) || replayEnvSummary.includes("PARITY"),
|
|
31515
|
+
runtimeGatesRequested: Boolean(flags28.runtimeGates),
|
|
31516
|
+
toleranceBars,
|
|
31517
|
+
toleranceMs,
|
|
31518
|
+
replayTargetsCount: replayTargets.length,
|
|
31519
|
+
comparedTargetsCount: successfulTargetKeys.size,
|
|
31520
|
+
replayErrors,
|
|
31521
|
+
sourceCounts,
|
|
31394
31522
|
rawRuntimeEntriesCount: rawRuntimeEntries.length,
|
|
31395
31523
|
runtimeEntriesCount: runtimeEntries.length,
|
|
31396
|
-
|
|
31397
|
-
|
|
31398
|
-
|
|
31399
|
-
|
|
31400
|
-
|
|
31524
|
+
runtimeDuplicateGroupsCount: runtimeDedupe.duplicateGroups.length,
|
|
31525
|
+
runtimeDuplicateEntriesCount: runtimeDedupe.duplicateEntries.length,
|
|
31526
|
+
backtestEntriesCount: backtestEntries.length,
|
|
31527
|
+
runtimeOnlyCount: comparison.runtimeOnly.length,
|
|
31528
|
+
backtestOnlyCount: comparison.backtestOnly.length,
|
|
31529
|
+
matchedSummary: summary,
|
|
31530
|
+
classifiedRuntimeOnly,
|
|
31531
|
+
classifiedBacktestOnly,
|
|
31532
|
+
runtimeSignalEvaluationsCount: runtimeSignalEvaluations.length,
|
|
31533
|
+
strategyRows,
|
|
31534
|
+
runtimeGateWarningCounts,
|
|
31535
|
+
detailLimit: DETAIL_LIMIT2
|
|
31536
|
+
})
|
|
31401
31537
|
);
|
|
31402
|
-
if (runtimeDedupe.duplicateGroups.length) {
|
|
31403
|
-
console.log(
|
|
31404
|
-
`Runtime duplicate groups: ${runtimeDedupe.duplicateGroups.length}, duplicate entries: ${runtimeDedupe.duplicateEntries.length}`
|
|
31405
|
-
);
|
|
31406
|
-
}
|
|
31407
|
-
if (classifiedRuntimeOnly.length) {
|
|
31408
|
-
console.log(
|
|
31409
|
-
`Runtime only classifications: ${summarizeRuntimeOnlyClassifications(classifiedRuntimeOnly)}`
|
|
31410
|
-
);
|
|
31411
|
-
}
|
|
31412
|
-
if (classifiedBacktestOnly.length) {
|
|
31413
|
-
console.log(
|
|
31414
|
-
`Backtest only classifications: ${summarizeBacktestOnlyClassifications(classifiedBacktestOnly)}`
|
|
31415
|
-
);
|
|
31416
|
-
}
|
|
31417
|
-
if (runtimeSignalEvaluations.length) {
|
|
31418
|
-
console.log(`Runtime evaluations: ${runtimeSignalEvaluations.length}`);
|
|
31419
|
-
}
|
|
31420
|
-
const strategyRows = summarizeByStrategy({
|
|
31421
|
-
targets: replayTargets,
|
|
31422
|
-
successfulTargetKeys,
|
|
31423
|
-
replayErrors,
|
|
31424
|
-
runtimeEntries,
|
|
31425
|
-
runtimeDuplicateEntries: runtimeDedupe.duplicateEntries,
|
|
31426
|
-
backtestEntries,
|
|
31427
|
-
matchedEntries: comparison.matched,
|
|
31428
|
-
runtimeOnlyEntries: comparison.runtimeOnly,
|
|
31429
|
-
backtestOnlyEntries: comparison.backtestOnly
|
|
31430
|
-
});
|
|
31431
|
-
const mismatchRows = buildMismatchSummaryRows({
|
|
31432
|
-
classifiedRuntimeOnly,
|
|
31433
|
-
classifiedBacktestOnly
|
|
31434
|
-
});
|
|
31435
|
-
const strategyIssueRows = buildStrategyIssueRows(strategyRows);
|
|
31436
|
-
if (mismatchRows.length) {
|
|
31437
|
-
console.log("");
|
|
31438
|
-
console.log(import_chalk43.default.yellow("Signal mismatches"));
|
|
31439
|
-
for (const item of mismatchRows.slice(0, DETAIL_LIMIT)) {
|
|
31440
|
-
console.log(`- ${item.line}`);
|
|
31441
|
-
}
|
|
31442
|
-
if (mismatchRows.length > DETAIL_LIMIT) {
|
|
31443
|
-
console.log(`- ... ${mismatchRows.length - DETAIL_LIMIT} more`);
|
|
31444
|
-
}
|
|
31445
|
-
}
|
|
31446
|
-
console.log("");
|
|
31447
|
-
if (strategyIssueRows.length) {
|
|
31448
|
-
console.log(import_chalk43.default.cyan("Strategy issues"));
|
|
31449
|
-
for (const line of strategyIssueRows) {
|
|
31450
|
-
console.log(line);
|
|
31451
|
-
}
|
|
31452
|
-
} else if (strategyRows.length) {
|
|
31453
|
-
console.log(
|
|
31454
|
-
`Strategies: clean ${strategyRows.length}/${strategyRows.length}`
|
|
31455
|
-
);
|
|
31456
|
-
}
|
|
31457
|
-
if (runtimeGateWarningCounts.size && !flags28.runtimeGates) {
|
|
31458
|
-
console.log("");
|
|
31459
|
-
console.log(import_chalk43.default.yellow("Warnings"));
|
|
31460
|
-
for (const [strategy, count] of [
|
|
31461
|
-
...runtimeGateWarningCounts.entries()
|
|
31462
|
-
].sort(([left], [right]) => left.localeCompare(right))) {
|
|
31463
|
-
console.log(
|
|
31464
|
-
`- ${strategy} uses AI/ML runtime gates on ${count} replay target(s); BACKTEST replay covers core execution, not live gating.`
|
|
31465
|
-
);
|
|
31466
|
-
}
|
|
31467
|
-
}
|
|
31468
|
-
if (replayErrors.length) {
|
|
31469
|
-
console.log("");
|
|
31470
|
-
console.log(import_chalk43.default.red("Replay errors"));
|
|
31471
|
-
for (const error of replayErrors.slice(0, DETAIL_LIMIT)) {
|
|
31472
|
-
console.log(`- ${error.strategy} ${error.symbol}: ${error.message}`);
|
|
31473
|
-
}
|
|
31474
|
-
if (replayErrors.length > DETAIL_LIMIT) {
|
|
31475
|
-
console.log(`- ... ${replayErrors.length - DETAIL_LIMIT} more`);
|
|
31476
|
-
}
|
|
31477
|
-
}
|
|
31478
31538
|
if (flags28.details) {
|
|
31479
31539
|
printRuntimeDuplicateDetails(runtimeDedupe.duplicateGroups);
|
|
31480
31540
|
printClassifiedRuntimeOnlyDetails(classifiedRuntimeOnly);
|
|
@@ -31619,11 +31679,11 @@ var init_runtimeStrategies = __esm({
|
|
|
31619
31679
|
});
|
|
31620
31680
|
|
|
31621
31681
|
// src/lib/signals/skipStats.ts
|
|
31622
|
-
var
|
|
31682
|
+
var import_chalk45, import_logger7, createStrategySkipStats, recordStrategyReason, logStrategySkipStats;
|
|
31623
31683
|
var init_skipStats = __esm({
|
|
31624
31684
|
"src/lib/signals/skipStats.ts"() {
|
|
31625
31685
|
"use strict";
|
|
31626
|
-
|
|
31686
|
+
import_chalk45 = __toESM(require("chalk"));
|
|
31627
31687
|
import_logger7 = require("@tradejs/infra/logger");
|
|
31628
31688
|
init_runtimeSignalsStorage();
|
|
31629
31689
|
createStrategySkipStats = (runtimeStrategies) => new Map(
|
|
@@ -31649,7 +31709,7 @@ var init_skipStats = __esm({
|
|
|
31649
31709
|
);
|
|
31650
31710
|
};
|
|
31651
31711
|
logStrategySkipStats = (runtimeStrategies, strategyStats) => {
|
|
31652
|
-
import_logger7.logger.info(
|
|
31712
|
+
import_logger7.logger.info(import_chalk45.default.yellow("skip stats:"));
|
|
31653
31713
|
for (const { strategyName } of runtimeStrategies) {
|
|
31654
31714
|
const stats = strategyStats.get(strategyName);
|
|
31655
31715
|
if (!stats) {
|
|
@@ -32594,18 +32654,18 @@ var init_configuredScopes = __esm({
|
|
|
32594
32654
|
});
|
|
32595
32655
|
|
|
32596
32656
|
// src/lib/signals/runner.ts
|
|
32597
|
-
var import_progress11,
|
|
32657
|
+
var import_progress11, import_chalk46, import_connectors16, import_cli12, import_async5, import_strategies11, import_constants16, import_time10, import_logger10, import_runtimeHeartbeats, import_runtimeStrategies6, SLOW_SIGNALS_WARNING_MS, formatDuration3, resolvePositiveInteger2, resolveNonNegativeInteger, createSignalsRunner;
|
|
32598
32658
|
var init_runner = __esm({
|
|
32599
32659
|
"src/lib/signals/runner.ts"() {
|
|
32600
32660
|
"use strict";
|
|
32601
32661
|
import_progress11 = __toESM(require("progress"));
|
|
32602
|
-
|
|
32662
|
+
import_chalk46 = __toESM(require("chalk"));
|
|
32603
32663
|
import_connectors16 = require("@tradejs/node/connectors");
|
|
32604
32664
|
import_cli12 = require("@tradejs/node/cli");
|
|
32605
32665
|
import_async5 = require("@tradejs/core/async");
|
|
32606
32666
|
import_strategies11 = require("@tradejs/core/strategies");
|
|
32607
32667
|
import_constants16 = require("@tradejs/core/constants");
|
|
32608
|
-
|
|
32668
|
+
import_time10 = require("@tradejs/core/time");
|
|
32609
32669
|
import_logger10 = require("@tradejs/infra/logger");
|
|
32610
32670
|
import_runtimeHeartbeats = require("@tradejs/infra/runtimeHeartbeats");
|
|
32611
32671
|
import_runtimeStrategies6 = require("@tradejs/node/runtimeStrategies");
|
|
@@ -32666,7 +32726,7 @@ var init_runner = __esm({
|
|
|
32666
32726
|
const timeOperation3 = (label, operation) => timeOperation(
|
|
32667
32727
|
label,
|
|
32668
32728
|
operation,
|
|
32669
|
-
(message) => import_logger10.logger.info(
|
|
32729
|
+
(message) => import_logger10.logger.info(import_chalk46.default.gray(message))
|
|
32670
32730
|
);
|
|
32671
32731
|
const formatSlowSignalsWarning = (params) => [
|
|
32672
32732
|
"<b>Slow yarn signals run</b>",
|
|
@@ -32781,7 +32841,7 @@ var init_runner = __esm({
|
|
|
32781
32841
|
const selectedTickers = selection?.tickers ? [.../* @__PURE__ */ new Set([...selection.tickers, ...selectedActiveSymbols])] : void 0;
|
|
32782
32842
|
if (selectedActiveSymbols.length) {
|
|
32783
32843
|
import_logger10.logger.info(
|
|
32784
|
-
|
|
32844
|
+
import_chalk46.default.gray(
|
|
32785
32845
|
`strategy selection retained active symbols: ${selectedActiveSymbols.join(", ")}`
|
|
32786
32846
|
)
|
|
32787
32847
|
);
|
|
@@ -32811,13 +32871,13 @@ var init_runner = __esm({
|
|
|
32811
32871
|
instruments.map((instrument) => [instrument.symbol, instrument])
|
|
32812
32872
|
);
|
|
32813
32873
|
if (config2.showTickersList) {
|
|
32814
|
-
console.log(
|
|
32874
|
+
console.log(import_chalk46.default.gray(JSON.stringify(tickers.sort(), null, 2)));
|
|
32815
32875
|
return;
|
|
32816
32876
|
}
|
|
32817
32877
|
const projectConfig = await (0, import_cli12.loadTradejsConfig)(projectRoot15);
|
|
32818
32878
|
projectHooks = projectConfig.hooks;
|
|
32819
|
-
const currentTimestamp = (0,
|
|
32820
|
-
const preloadStart = (0,
|
|
32879
|
+
const currentTimestamp = (0, import_time10.getTimestamp)();
|
|
32880
|
+
const preloadStart = (0, import_time10.getTimestamp)(import_constants16.SIGNALS_CLI_PRELOAD_DAYS);
|
|
32821
32881
|
if (!config2.cacheOnly && session.klineFeed) {
|
|
32822
32882
|
const expectedSymbols = universe === "crypto" ? [.../* @__PURE__ */ new Set([...tickers, "BTCUSDT", "ETHUSDT"])] : tickers;
|
|
32823
32883
|
session.klineFeed.setSubscriptions(expectedSymbols);
|
|
@@ -32832,7 +32892,7 @@ var init_runner = __esm({
|
|
|
32832
32892
|
);
|
|
32833
32893
|
await session.klineFeed.flush();
|
|
32834
32894
|
import_logger10.logger.info(
|
|
32835
|
-
|
|
32895
|
+
import_chalk46.default.gray(
|
|
32836
32896
|
`websocket candles: ready=${expectedSymbols.length - missingSymbols.length}/${expectedSymbols.length} missing=${missingSymbols.length}`
|
|
32837
32897
|
)
|
|
32838
32898
|
);
|
|
@@ -32843,7 +32903,7 @@ var init_runner = __esm({
|
|
|
32843
32903
|
symbols: missingSymbols,
|
|
32844
32904
|
preloadDays: import_constants16.SIGNALS_CLI_PRELOAD_DAYS,
|
|
32845
32905
|
universe,
|
|
32846
|
-
log: (message) => import_logger10.logger.info(
|
|
32906
|
+
log: (message) => import_logger10.logger.info(import_chalk46.default.gray(message))
|
|
32847
32907
|
});
|
|
32848
32908
|
await updateBtcReferenceHistory({
|
|
32849
32909
|
marketConnector,
|
|
@@ -32851,7 +32911,7 @@ var init_runner = __esm({
|
|
|
32851
32911
|
interval: interval5,
|
|
32852
32912
|
preloadDays: import_constants16.SIGNALS_CLI_PRELOAD_DAYS,
|
|
32853
32913
|
universe,
|
|
32854
|
-
log: (message) => import_logger10.logger.info(
|
|
32914
|
+
log: (message) => import_logger10.logger.info(import_chalk46.default.gray(message))
|
|
32855
32915
|
});
|
|
32856
32916
|
} else if (!config2.cacheOnly) {
|
|
32857
32917
|
await updateMarketHistoryWithBtcReferences({
|
|
@@ -32862,7 +32922,7 @@ var init_runner = __esm({
|
|
|
32862
32922
|
symbols: tickers,
|
|
32863
32923
|
preloadDays: import_constants16.SIGNALS_CLI_PRELOAD_DAYS,
|
|
32864
32924
|
universe,
|
|
32865
|
-
log: (message) => import_logger10.logger.info(
|
|
32925
|
+
log: (message) => import_logger10.logger.info(import_chalk46.default.gray(message))
|
|
32866
32926
|
});
|
|
32867
32927
|
}
|
|
32868
32928
|
await prepareMarketContextForRun({
|
|
@@ -32876,7 +32936,7 @@ var init_runner = __esm({
|
|
|
32876
32936
|
endMs: currentTimestamp,
|
|
32877
32937
|
preloadStartMs: preloadStart,
|
|
32878
32938
|
cacheOnly: config2.cacheOnly,
|
|
32879
|
-
log: (message) => import_logger10.logger.info(
|
|
32939
|
+
log: (message) => import_logger10.logger.info(import_chalk46.default.gray(message))
|
|
32880
32940
|
});
|
|
32881
32941
|
if (config2.updateOnly) {
|
|
32882
32942
|
return;
|
|
@@ -32971,7 +33031,7 @@ var init_runner = __esm({
|
|
|
32971
33031
|
)
|
|
32972
33032
|
);
|
|
32973
33033
|
import_logger10.logger.info(
|
|
32974
|
-
|
|
33034
|
+
import_chalk46.default.yellow(
|
|
32975
33035
|
`loaded strategies (user=${config2.userName}): ${runtimeStrategies.map(formatConfiguredStrategyIdentity).join(", ")}`
|
|
32976
33036
|
)
|
|
32977
33037
|
);
|
|
@@ -33007,9 +33067,9 @@ var init_runner = __esm({
|
|
|
33007
33067
|
width: 30
|
|
33008
33068
|
}
|
|
33009
33069
|
);
|
|
33010
|
-
import_logger10.logger.info(
|
|
33070
|
+
import_logger10.logger.info(import_chalk46.default.yellow(`tickers: ${tickers.length}`));
|
|
33011
33071
|
const signalsParallel = resolvePositiveInteger2(config2.parallel, 4);
|
|
33012
|
-
import_logger10.logger.info(
|
|
33072
|
+
import_logger10.logger.info(import_chalk46.default.yellow(`signal workers: ${signalsParallel}`));
|
|
33013
33073
|
const evaluateTicker = createSignalsTickerEvaluator({
|
|
33014
33074
|
userName: config2.userName,
|
|
33015
33075
|
makeOrders: config2.makeOrders,
|
|
@@ -33048,8 +33108,8 @@ var init_runner = __esm({
|
|
|
33048
33108
|
signals3.push(...strategySignals);
|
|
33049
33109
|
}
|
|
33050
33110
|
bar.tick(1, {
|
|
33051
|
-
found:
|
|
33052
|
-
symbol:
|
|
33111
|
+
found: import_chalk46.default.cyan(signals3.length),
|
|
33112
|
+
symbol: import_chalk46.default.gray(symbol)
|
|
33053
33113
|
});
|
|
33054
33114
|
});
|
|
33055
33115
|
});
|
|
@@ -33153,7 +33213,7 @@ var init_runner = __esm({
|
|
|
33153
33213
|
}
|
|
33154
33214
|
}
|
|
33155
33215
|
import_logger10.logger.info(
|
|
33156
|
-
|
|
33216
|
+
import_chalk46.default.yellow(
|
|
33157
33217
|
`signals ${status} in ${formatDuration3(durationMs)} (found=${signals3.length})`
|
|
33158
33218
|
)
|
|
33159
33219
|
);
|
|
@@ -34148,91 +34208,13 @@ var init_portfolioReplayConnector = __esm({
|
|
|
34148
34208
|
}
|
|
34149
34209
|
});
|
|
34150
34210
|
|
|
34151
|
-
// src/lib/replay/
|
|
34152
|
-
var
|
|
34153
|
-
var
|
|
34154
|
-
"src/lib/replay/
|
|
34211
|
+
// src/lib/replay/historicalSignalsReplayPreparation.ts
|
|
34212
|
+
var buildPreparedData, strategyIncludesSymbol, buildSymbolSharedReplayKey, prepareHistoricalReplay;
|
|
34213
|
+
var init_historicalSignalsReplayPreparation = __esm({
|
|
34214
|
+
"src/lib/replay/historicalSignalsReplayPreparation.ts"() {
|
|
34155
34215
|
"use strict";
|
|
34156
|
-
import_chalk46 = __toESM(require("chalk"));
|
|
34157
|
-
import_progress12 = __toESM(require("progress"));
|
|
34158
|
-
import_backtest4 = require("@tradejs/core/backtest");
|
|
34159
|
-
import_strategies12 = require("@tradejs/core/strategies");
|
|
34160
|
-
import_time9 = require("@tradejs/core/time");
|
|
34161
|
-
import_logger11 = require("@tradejs/infra/logger");
|
|
34162
|
-
import_redis19 = require("@tradejs/infra/redis");
|
|
34163
|
-
import_connectors17 = require("@tradejs/node/connectors");
|
|
34164
|
-
import_cli13 = require("@tradejs/node/cli");
|
|
34165
|
-
import_strategies13 = require("@tradejs/node/strategies");
|
|
34166
|
-
import_runtimeStrategies8 = require("@tradejs/node/runtimeStrategies");
|
|
34167
|
-
init_cliConfig2();
|
|
34168
34216
|
init_support();
|
|
34169
|
-
init_portfolioReplayConnector();
|
|
34170
34217
|
init_windows();
|
|
34171
|
-
init_hooks();
|
|
34172
|
-
init_runtimeLineage();
|
|
34173
|
-
loadRuntimeStrategies = async (runtimeStrategies) => {
|
|
34174
|
-
const strategies = await Promise.all(
|
|
34175
|
-
runtimeStrategies.map(async (runtimeStrategy) => {
|
|
34176
|
-
const {
|
|
34177
|
-
strategyName,
|
|
34178
|
-
strategyRevision,
|
|
34179
|
-
deploymentCompositionId,
|
|
34180
|
-
strategyPackage,
|
|
34181
|
-
strategyPackageVersion,
|
|
34182
|
-
strategyDependencyVersions,
|
|
34183
|
-
runtimePackageVersion,
|
|
34184
|
-
strategyConfig,
|
|
34185
|
-
selection
|
|
34186
|
-
} = runtimeStrategy;
|
|
34187
|
-
const strategyCreator = await (0, import_strategies13.getStrategyCreator)(
|
|
34188
|
-
strategyName,
|
|
34189
|
-
replayProjectRoot
|
|
34190
|
-
);
|
|
34191
|
-
if (!strategyCreator) {
|
|
34192
|
-
throw new Error(`Unknown strategy: ${strategyName}`);
|
|
34193
|
-
}
|
|
34194
|
-
const installed = await (0, import_runtimeStrategies8.getRuntimeStrategyPackageMetadata)({
|
|
34195
|
-
strategyName,
|
|
34196
|
-
projectRoot: replayProjectRoot
|
|
34197
|
-
});
|
|
34198
|
-
if (installed.strategyPackage !== strategyPackage || installed.strategyPackageVersion !== strategyPackageVersion || JSON.stringify(installed.strategyDependencyVersions) !== JSON.stringify(strategyDependencyVersions) || installed.runtimePackageVersion !== runtimePackageVersion) {
|
|
34199
|
-
throw new Error(
|
|
34200
|
-
`Runtime evidence package mismatch for ${strategyName}: expected=${strategyPackage}@${strategyPackageVersion}/@tradejs/node@${runtimePackageVersion}, installed=${installed.strategyPackage ?? "missing"}@${installed.strategyPackageVersion ?? "missing"}/@tradejs/node@${installed.runtimePackageVersion ?? "missing"}`
|
|
34201
|
-
);
|
|
34202
|
-
}
|
|
34203
|
-
const strategyResults = await (0, import_redis19.getData)(
|
|
34204
|
-
import_redis19.redisKeys.strategyResults(replayUserName, strategyName),
|
|
34205
|
-
{}
|
|
34206
|
-
);
|
|
34207
|
-
return {
|
|
34208
|
-
strategyName,
|
|
34209
|
-
strategyRevision,
|
|
34210
|
-
deploymentCompositionId,
|
|
34211
|
-
strategyPackage,
|
|
34212
|
-
strategyPackageVersion,
|
|
34213
|
-
strategyDependencyVersions,
|
|
34214
|
-
runtimePackageVersion,
|
|
34215
|
-
strategyCreator,
|
|
34216
|
-
strategyConfig,
|
|
34217
|
-
strategyResults,
|
|
34218
|
-
...selection ? { selection } : {}
|
|
34219
|
-
};
|
|
34220
|
-
})
|
|
34221
|
-
);
|
|
34222
|
-
return strategies;
|
|
34223
|
-
};
|
|
34224
|
-
loadReferenceConnector = async (connectorName) => {
|
|
34225
|
-
const connectorFactory = await (0, import_connectors17.getConnectorCreatorByName)(
|
|
34226
|
-
connectorName,
|
|
34227
|
-
replayProjectRoot
|
|
34228
|
-
);
|
|
34229
|
-
if (!connectorFactory) {
|
|
34230
|
-
throw new Error(`Connector "${connectorName}" is not registered`);
|
|
34231
|
-
}
|
|
34232
|
-
return await connectorFactory({
|
|
34233
|
-
userName: replayUserName
|
|
34234
|
-
});
|
|
34235
|
-
};
|
|
34236
34218
|
buildPreparedData = ({
|
|
34237
34219
|
data,
|
|
34238
34220
|
btcData,
|
|
@@ -34281,25 +34263,6 @@ var init_historicalSignalsReplay = __esm({
|
|
|
34281
34263
|
btcCoinbasePrevData
|
|
34282
34264
|
};
|
|
34283
34265
|
};
|
|
34284
|
-
buildAfterSignalsContext = ({
|
|
34285
|
-
connector,
|
|
34286
|
-
connectorName,
|
|
34287
|
-
tickers,
|
|
34288
|
-
runtimeStrategies,
|
|
34289
|
-
interval: interval4
|
|
34290
|
-
}) => ({
|
|
34291
|
-
connector,
|
|
34292
|
-
connectorName,
|
|
34293
|
-
userName: replayUserName,
|
|
34294
|
-
interval: interval4,
|
|
34295
|
-
tickers: [...tickers],
|
|
34296
|
-
runtimeStrategies: runtimeStrategies.map(
|
|
34297
|
-
({ strategyName, strategyConfig }) => ({
|
|
34298
|
-
strategyName,
|
|
34299
|
-
strategyConfig
|
|
34300
|
-
})
|
|
34301
|
-
)
|
|
34302
|
-
});
|
|
34303
34266
|
strategyIncludesSymbol = (strategy, symbol) => {
|
|
34304
34267
|
const tickers = strategy.selection?.tickers;
|
|
34305
34268
|
if (!tickers) return true;
|
|
@@ -34309,73 +34272,28 @@ var init_historicalSignalsReplay = __esm({
|
|
|
34309
34272
|
);
|
|
34310
34273
|
};
|
|
34311
34274
|
buildSymbolSharedReplayKey = ({
|
|
34275
|
+
userName: userName3,
|
|
34312
34276
|
connectorName,
|
|
34313
34277
|
interval: interval4,
|
|
34314
34278
|
symbol,
|
|
34315
34279
|
start,
|
|
34316
34280
|
end
|
|
34317
|
-
}) => ["replay",
|
|
34318
|
-
|
|
34319
|
-
|
|
34320
|
-
|
|
34321
|
-
|
|
34322
|
-
|
|
34323
|
-
|
|
34324
|
-
|
|
34325
|
-
|
|
34326
|
-
|
|
34327
|
-
|
|
34328
|
-
|
|
34329
|
-
const projectHooks = projectConfig.hooks;
|
|
34330
|
-
const loadedStrategies = await loadRuntimeStrategies(runtimeStrategies);
|
|
34331
|
-
const replayConnector = createPortfolioReplayConnector(
|
|
34332
|
-
preparedRun.marketConnector
|
|
34333
|
-
);
|
|
34334
|
-
const connectorName = String(
|
|
34335
|
-
preparedRun.marketConnector?.name || import_connectors17.DEFAULT_CONNECTOR_NAME
|
|
34336
|
-
).trim() || import_connectors17.DEFAULT_CONNECTOR_NAME;
|
|
34337
|
-
const binanceConnector = connectorName.toLowerCase() === import_connectors17.DEFAULT_CONNECTOR_NAME.toLowerCase() ? await loadReferenceConnector("Binance") : preparedRun.marketConnector;
|
|
34338
|
-
const coinbaseConnector = connectorName.toLowerCase() === import_connectors17.DEFAULT_CONNECTOR_NAME.toLowerCase() ? await loadReferenceConnector("Coinbase") : preparedRun.marketConnector;
|
|
34339
|
-
const [btcBinanceData, btcCoinbaseData] = await Promise.all([
|
|
34340
|
-
binanceConnector.kline({
|
|
34341
|
-
symbol: "BTCUSDT",
|
|
34342
|
-
start: preparedRun.preloadStart,
|
|
34343
|
-
end: preparedRun.window.end,
|
|
34344
|
-
cacheOnly: true,
|
|
34345
|
-
interval: interval4
|
|
34346
|
-
}),
|
|
34347
|
-
coinbaseConnector.kline({
|
|
34348
|
-
symbol: "BTCUSDT",
|
|
34349
|
-
start: preparedRun.preloadStart,
|
|
34350
|
-
end: preparedRun.window.end,
|
|
34351
|
-
cacheOnly: true,
|
|
34352
|
-
interval: interval4
|
|
34353
|
-
})
|
|
34354
|
-
]);
|
|
34355
|
-
const btcMarketData = await preparedRun.marketConnector.kline({
|
|
34356
|
-
symbol: "BTCUSDT",
|
|
34357
|
-
start: preparedRun.preloadStart,
|
|
34358
|
-
end: preparedRun.window.end,
|
|
34359
|
-
cacheOnly: true,
|
|
34360
|
-
interval: interval4
|
|
34361
|
-
});
|
|
34362
|
-
const ethMarketData = await preparedRun.marketConnector.kline({
|
|
34363
|
-
symbol: "ETHUSDT",
|
|
34364
|
-
start: preparedRun.preloadStart,
|
|
34365
|
-
end: preparedRun.window.end,
|
|
34366
|
-
cacheOnly: true,
|
|
34367
|
-
interval: interval4
|
|
34368
|
-
});
|
|
34281
|
+
}) => ["replay", userName3, connectorName, symbol, interval4, start, end].join(":");
|
|
34282
|
+
prepareHistoricalReplay = async (context, adapters) => {
|
|
34283
|
+
const {
|
|
34284
|
+
userName: userName3,
|
|
34285
|
+
projectRoot: projectRoot15,
|
|
34286
|
+
preparedRun,
|
|
34287
|
+
interval: interval4,
|
|
34288
|
+
connectorName,
|
|
34289
|
+
replayConnector,
|
|
34290
|
+
strategies,
|
|
34291
|
+
references
|
|
34292
|
+
} = context;
|
|
34369
34293
|
const cycleSymbolsByTimestamp = /* @__PURE__ */ new Map();
|
|
34370
34294
|
const sharedReplayKeyPrefixes = [];
|
|
34295
|
+
const runtimeLineages = [];
|
|
34371
34296
|
let skippedSymbols = 0;
|
|
34372
|
-
const prepareBar = new import_progress12.default(
|
|
34373
|
-
"prepare :current/:total [:bar][:percent] skipped=:skipped :etas(s) :symbol",
|
|
34374
|
-
{
|
|
34375
|
-
total: preparedRun.tickers.length,
|
|
34376
|
-
width: 30
|
|
34377
|
-
}
|
|
34378
|
-
);
|
|
34379
34297
|
for (const symbol of preparedRun.tickers) {
|
|
34380
34298
|
const data = await preparedRun.marketConnector.kline({
|
|
34381
34299
|
symbol,
|
|
@@ -34386,22 +34304,23 @@ var init_historicalSignalsReplay = __esm({
|
|
|
34386
34304
|
});
|
|
34387
34305
|
const preparedData = buildPreparedData({
|
|
34388
34306
|
data,
|
|
34389
|
-
btcData: btcMarketData,
|
|
34390
|
-
ethData: ethMarketData,
|
|
34391
|
-
btcBinanceData,
|
|
34392
|
-
btcCoinbaseData,
|
|
34307
|
+
btcData: references.btcMarketData,
|
|
34308
|
+
ethData: references.ethMarketData,
|
|
34309
|
+
btcBinanceData: references.btcBinanceData,
|
|
34310
|
+
btcCoinbaseData: references.btcCoinbaseData,
|
|
34393
34311
|
start: preparedRun.window.start,
|
|
34394
34312
|
preloadStart: preparedRun.preloadStart
|
|
34395
34313
|
});
|
|
34396
34314
|
if (!preparedData.replayData.length || !preparedData.btcReplayData.length) {
|
|
34397
34315
|
skippedSymbols += 1;
|
|
34398
|
-
|
|
34399
|
-
skipped:
|
|
34400
|
-
symbol:
|
|
34316
|
+
adapters.progress.tick({
|
|
34317
|
+
skipped: adapters.display.skipped(skippedSymbols),
|
|
34318
|
+
symbol: adapters.display.symbol(symbol)
|
|
34401
34319
|
});
|
|
34402
34320
|
continue;
|
|
34403
34321
|
}
|
|
34404
34322
|
const sharedIndicatorsReplayKey = buildSymbolSharedReplayKey({
|
|
34323
|
+
userName: userName3,
|
|
34405
34324
|
connectorName,
|
|
34406
34325
|
interval: interval4,
|
|
34407
34326
|
symbol,
|
|
@@ -34410,66 +34329,61 @@ var init_historicalSignalsReplay = __esm({
|
|
|
34410
34329
|
});
|
|
34411
34330
|
sharedReplayKeyPrefixes.push(sharedIndicatorsReplayKey);
|
|
34412
34331
|
const strategiesForSymbol = await Promise.all(
|
|
34413
|
-
|
|
34414
|
-
|
|
34415
|
-
|
|
34416
|
-
|
|
34417
|
-
|
|
34418
|
-
|
|
34419
|
-
|
|
34420
|
-
|
|
34421
|
-
|
|
34422
|
-
|
|
34423
|
-
|
|
34424
|
-
|
|
34425
|
-
|
|
34426
|
-
|
|
34427
|
-
|
|
34428
|
-
|
|
34429
|
-
|
|
34430
|
-
|
|
34431
|
-
|
|
34432
|
-
|
|
34433
|
-
|
|
34434
|
-
|
|
34435
|
-
|
|
34436
|
-
}
|
|
34437
|
-
|
|
34438
|
-
|
|
34439
|
-
|
|
34440
|
-
|
|
34441
|
-
|
|
34442
|
-
|
|
34443
|
-
|
|
34444
|
-
|
|
34445
|
-
|
|
34446
|
-
|
|
34332
|
+
strategies.filter((strategy) => strategyIncludesSymbol(strategy, symbol)).map(async (strategy) => {
|
|
34333
|
+
const runtimeLineage = await adapters.buildLineage({
|
|
34334
|
+
projectRoot: projectRoot15,
|
|
34335
|
+
strategyName: strategy.strategyName,
|
|
34336
|
+
strategyRevision: strategy.strategyRevision,
|
|
34337
|
+
deploymentCompositionId: strategy.deploymentCompositionId,
|
|
34338
|
+
strategyPackageVersion: strategy.strategyPackageVersion,
|
|
34339
|
+
strategyDependencyVersions: strategy.strategyDependencyVersions,
|
|
34340
|
+
runtimePackageVersion: strategy.runtimePackageVersion,
|
|
34341
|
+
config: {
|
|
34342
|
+
configId: strategy.strategyRevision,
|
|
34343
|
+
strategyConfig: strategy.strategyConfig,
|
|
34344
|
+
symbolResultConfig: strategy.strategyResults?.[symbol]?.config ?? null
|
|
34345
|
+
},
|
|
34346
|
+
runContext: {
|
|
34347
|
+
connectorName: connectorName.toLowerCase(),
|
|
34348
|
+
interval: String(interval4),
|
|
34349
|
+
universe: preparedRun.universe ?? null
|
|
34350
|
+
}
|
|
34351
|
+
});
|
|
34352
|
+
runtimeLineages.push({
|
|
34353
|
+
strategy: strategy.strategyName,
|
|
34354
|
+
symbol,
|
|
34355
|
+
...preparedRun.deploymentId ? { deploymentId: preparedRun.deploymentId } : {},
|
|
34356
|
+
...preparedRun.accountId ? { accountId: preparedRun.accountId } : {},
|
|
34357
|
+
lineage: runtimeLineage
|
|
34358
|
+
});
|
|
34359
|
+
return {
|
|
34360
|
+
strategyName: strategy.strategyName,
|
|
34361
|
+
strategyConfig: strategy.strategyConfig,
|
|
34362
|
+
runtimeLineage,
|
|
34363
|
+
run: await strategy.strategyCreator({
|
|
34364
|
+
userName: userName3,
|
|
34365
|
+
connectorName,
|
|
34366
|
+
runtimeConfigId: strategy.strategyRevision,
|
|
34367
|
+
strategyRevision: strategy.strategyRevision,
|
|
34447
34368
|
runtimeLineage,
|
|
34448
|
-
|
|
34449
|
-
|
|
34450
|
-
|
|
34451
|
-
|
|
34452
|
-
|
|
34453
|
-
|
|
34454
|
-
|
|
34455
|
-
|
|
34456
|
-
|
|
34457
|
-
|
|
34458
|
-
|
|
34459
|
-
|
|
34460
|
-
|
|
34461
|
-
|
|
34462
|
-
|
|
34463
|
-
|
|
34464
|
-
|
|
34465
|
-
|
|
34466
|
-
btcCoinbaseData: preparedData.btcCoinbasePrevData,
|
|
34467
|
-
connector: replayConnector,
|
|
34468
|
-
sharedIndicatorsReplayKey
|
|
34469
|
-
})
|
|
34470
|
-
};
|
|
34471
|
-
}
|
|
34472
|
-
)
|
|
34369
|
+
universe: preparedRun.universe,
|
|
34370
|
+
accountId: preparedRun.accountId,
|
|
34371
|
+
deploymentId: preparedRun.deploymentId,
|
|
34372
|
+
config: buildReplayStrategyConfig({
|
|
34373
|
+
strategyConfig: strategy.strategyConfig,
|
|
34374
|
+
interval: interval4
|
|
34375
|
+
}),
|
|
34376
|
+
symbol,
|
|
34377
|
+
data: preparedData.prevData,
|
|
34378
|
+
btcData: preparedData.btcPrevData,
|
|
34379
|
+
ethData: preparedData.ethPrevData,
|
|
34380
|
+
btcBinanceData: preparedData.btcBinancePrevData,
|
|
34381
|
+
btcCoinbaseData: preparedData.btcCoinbasePrevData,
|
|
34382
|
+
connector: replayConnector,
|
|
34383
|
+
sharedIndicatorsReplayKey
|
|
34384
|
+
})
|
|
34385
|
+
};
|
|
34386
|
+
})
|
|
34473
34387
|
);
|
|
34474
34388
|
const symbolRuntime = {
|
|
34475
34389
|
symbol,
|
|
@@ -34479,9 +34393,9 @@ var init_historicalSignalsReplay = __esm({
|
|
|
34479
34393
|
currentIndex: 0,
|
|
34480
34394
|
strategies: strategiesForSymbol
|
|
34481
34395
|
};
|
|
34482
|
-
|
|
34483
|
-
skipped:
|
|
34484
|
-
symbol:
|
|
34396
|
+
adapters.progress.tick({
|
|
34397
|
+
skipped: adapters.display.skipped(skippedSymbols),
|
|
34398
|
+
symbol: adapters.display.symbol(symbol)
|
|
34485
34399
|
});
|
|
34486
34400
|
for (const candle of preparedData.replayData) {
|
|
34487
34401
|
const bucket = cycleSymbolsByTimestamp.get(candle.timestamp) ?? [];
|
|
@@ -34489,60 +34403,72 @@ var init_historicalSignalsReplay = __esm({
|
|
|
34489
34403
|
cycleSymbolsByTimestamp.set(candle.timestamp, bucket);
|
|
34490
34404
|
}
|
|
34491
34405
|
}
|
|
34492
|
-
|
|
34493
|
-
|
|
34494
|
-
|
|
34495
|
-
|
|
34496
|
-
|
|
34497
|
-
|
|
34498
|
-
|
|
34499
|
-
|
|
34500
|
-
|
|
34501
|
-
|
|
34502
|
-
|
|
34503
|
-
|
|
34504
|
-
|
|
34505
|
-
|
|
34506
|
-
|
|
34507
|
-
|
|
34406
|
+
return {
|
|
34407
|
+
cycleSymbolsByTimestamp,
|
|
34408
|
+
orderedTimestamps: [...cycleSymbolsByTimestamp.keys()].sort(
|
|
34409
|
+
(left, right) => left - right
|
|
34410
|
+
),
|
|
34411
|
+
sharedReplayKeyPrefixes,
|
|
34412
|
+
runtimeLineages
|
|
34413
|
+
};
|
|
34414
|
+
};
|
|
34415
|
+
}
|
|
34416
|
+
});
|
|
34417
|
+
|
|
34418
|
+
// src/lib/replay/historicalSignalsReplayExecution.ts
|
|
34419
|
+
var advanceCycleMarket, advanceAbortedCycle, executeHistoricalReplay;
|
|
34420
|
+
var init_historicalSignalsReplayExecution = __esm({
|
|
34421
|
+
"src/lib/replay/historicalSignalsReplayExecution.ts"() {
|
|
34422
|
+
"use strict";
|
|
34423
|
+
advanceCycleMarket = async (plan, connector, timestamp) => {
|
|
34424
|
+
const cycleSymbols = plan.cycleSymbolsByTimestamp.get(timestamp) ?? [];
|
|
34425
|
+
for (const symbolRuntime of cycleSymbols) {
|
|
34426
|
+
const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
|
|
34427
|
+
if (candle?.timestamp === timestamp) {
|
|
34428
|
+
await connector.advanceMarket({
|
|
34429
|
+
symbol: symbolRuntime.symbol,
|
|
34430
|
+
candle
|
|
34431
|
+
});
|
|
34508
34432
|
}
|
|
34509
|
-
|
|
34433
|
+
}
|
|
34434
|
+
return cycleSymbols;
|
|
34435
|
+
};
|
|
34436
|
+
advanceAbortedCycle = (cycleSymbols, timestamp) => {
|
|
34437
|
+
for (const symbolRuntime of cycleSymbols ?? []) {
|
|
34438
|
+
const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
|
|
34439
|
+
if (candle?.timestamp === timestamp) {
|
|
34440
|
+
symbolRuntime.currentIndex += 1;
|
|
34441
|
+
}
|
|
34442
|
+
}
|
|
34443
|
+
};
|
|
34444
|
+
executeHistoricalReplay = async (context, adapters) => {
|
|
34445
|
+
const signals2 = [];
|
|
34446
|
+
let abortedCycles = 0;
|
|
34510
34447
|
try {
|
|
34511
|
-
for (const timestamp of orderedTimestamps) {
|
|
34512
|
-
const cycleStartedAt =
|
|
34513
|
-
const cycleSymbols =
|
|
34514
|
-
|
|
34515
|
-
|
|
34516
|
-
|
|
34517
|
-
|
|
34518
|
-
|
|
34519
|
-
|
|
34520
|
-
|
|
34521
|
-
candle
|
|
34522
|
-
});
|
|
34523
|
-
}
|
|
34524
|
-
const beforeSignalsResult = await invokeBeforeSignalsHooks(
|
|
34525
|
-
projectHooks,
|
|
34526
|
-
afterSignalsContextBase
|
|
34448
|
+
for (const timestamp of context.plan.orderedTimestamps) {
|
|
34449
|
+
const cycleStartedAt = adapters.clock.now();
|
|
34450
|
+
const cycleSymbols = await advanceCycleMarket(
|
|
34451
|
+
context.plan,
|
|
34452
|
+
context.connector,
|
|
34453
|
+
timestamp
|
|
34454
|
+
);
|
|
34455
|
+
const beforeSignalsResult = await adapters.invokeBeforeSignals(
|
|
34456
|
+
context.hooks,
|
|
34457
|
+
context.hookContext
|
|
34527
34458
|
);
|
|
34528
34459
|
if (beforeSignalsResult?.abort === true) {
|
|
34529
34460
|
abortedCycles += 1;
|
|
34530
|
-
await
|
|
34531
|
-
...
|
|
34461
|
+
await adapters.invokeAfterSignals(context.hooks, {
|
|
34462
|
+
...context.hookContext,
|
|
34532
34463
|
signals: [],
|
|
34533
34464
|
status: "completed",
|
|
34534
|
-
durationMs:
|
|
34465
|
+
durationMs: adapters.clock.now() - cycleStartedAt
|
|
34535
34466
|
});
|
|
34536
|
-
|
|
34537
|
-
|
|
34538
|
-
|
|
34539
|
-
|
|
34540
|
-
|
|
34541
|
-
}
|
|
34542
|
-
cycleBar.tick(1, {
|
|
34543
|
-
signals: import_chalk46.default.cyan(signals2.length),
|
|
34544
|
-
aborted: import_chalk46.default.yellow(abortedCycles),
|
|
34545
|
-
ts: import_chalk46.default.gray((0, import_time9.formatUnix)(timestamp))
|
|
34467
|
+
advanceAbortedCycle(cycleSymbols, timestamp);
|
|
34468
|
+
adapters.progress.tick({
|
|
34469
|
+
signals: adapters.display.signals(signals2.length),
|
|
34470
|
+
aborted: adapters.display.aborted(abortedCycles),
|
|
34471
|
+
ts: adapters.display.timestamp(timestamp)
|
|
34546
34472
|
});
|
|
34547
34473
|
continue;
|
|
34548
34474
|
}
|
|
@@ -34562,81 +34488,323 @@ var init_historicalSignalsReplay = __esm({
|
|
|
34562
34488
|
);
|
|
34563
34489
|
if (result && typeof result !== "string") {
|
|
34564
34490
|
result.runtimeLineage = strategyRuntime.runtimeLineage;
|
|
34565
|
-
await (
|
|
34566
|
-
signal: result,
|
|
34567
|
-
env: "PARITY"
|
|
34568
|
-
});
|
|
34491
|
+
await adapters.enrichSignal(result);
|
|
34569
34492
|
cycleSignals.push(result);
|
|
34570
34493
|
signals2.push(result);
|
|
34571
34494
|
}
|
|
34572
34495
|
}
|
|
34573
34496
|
symbolRuntime.currentIndex += 1;
|
|
34574
34497
|
}
|
|
34575
|
-
await
|
|
34576
|
-
...
|
|
34498
|
+
await adapters.invokeAfterSignals(context.hooks, {
|
|
34499
|
+
...context.hookContext,
|
|
34577
34500
|
signals: cycleSignals,
|
|
34578
34501
|
status: "completed",
|
|
34579
|
-
durationMs:
|
|
34502
|
+
durationMs: adapters.clock.now() - cycleStartedAt
|
|
34580
34503
|
});
|
|
34581
|
-
|
|
34582
|
-
signals:
|
|
34583
|
-
aborted:
|
|
34584
|
-
ts:
|
|
34504
|
+
adapters.progress.tick({
|
|
34505
|
+
signals: adapters.display.signals(signals2.length),
|
|
34506
|
+
aborted: adapters.display.aborted(abortedCycles),
|
|
34507
|
+
ts: adapters.display.timestamp(timestamp)
|
|
34585
34508
|
});
|
|
34586
34509
|
}
|
|
34587
34510
|
} finally {
|
|
34588
|
-
for (const keyPrefix of sharedReplayKeyPrefixes) {
|
|
34589
|
-
|
|
34590
|
-
|
|
34511
|
+
for (const keyPrefix of context.plan.sharedReplayKeyPrefixes) {
|
|
34512
|
+
adapters.releaseIndicatorsCache(keyPrefix);
|
|
34513
|
+
adapters.releaseReplayCache(keyPrefix);
|
|
34591
34514
|
}
|
|
34592
34515
|
}
|
|
34593
|
-
|
|
34594
|
-
const strategies = loadedStrategies.map(
|
|
34595
|
-
({ strategyName, strategyConfig }) => {
|
|
34596
|
-
const orderLog = artifacts.orderLogByStrategy.get(strategyName) ?? [];
|
|
34597
|
-
const positionLog = artifacts.positionLogByStrategy.get(strategyName) ?? [];
|
|
34598
|
-
return {
|
|
34599
|
-
strategyName,
|
|
34600
|
-
strategyConfig,
|
|
34601
|
-
orderLog,
|
|
34602
|
-
positionLog,
|
|
34603
|
-
stat: positionLog.length ? (0, import_backtest4.calculateStatsFull)(positionLog) : {
|
|
34604
|
-
orders: 0,
|
|
34605
|
-
wins: 0,
|
|
34606
|
-
losses: 0,
|
|
34607
|
-
netProfit: 0,
|
|
34608
|
-
amount: 0
|
|
34609
|
-
}
|
|
34610
|
-
};
|
|
34611
|
-
}
|
|
34612
|
-
);
|
|
34613
|
-
import_logger11.logger.info(
|
|
34614
|
-
import_chalk46.default.gray(
|
|
34615
|
-
`signals replay historical cycles: ${orderedTimestamps.length} (aborted=${abortedCycles}) done in ${((Date.now() - startedAt) / 1e3).toFixed(1)}s`
|
|
34616
|
-
)
|
|
34617
|
-
);
|
|
34618
|
-
return {
|
|
34619
|
-
strategies,
|
|
34620
|
-
signals: signals2,
|
|
34621
|
-
orderLog: artifacts.orderLog,
|
|
34622
|
-
positionLog: artifacts.positionLog,
|
|
34623
|
-
cycleCount: orderedTimestamps.length,
|
|
34624
|
-
abortedCycles,
|
|
34625
|
-
runtimeLineages
|
|
34626
|
-
};
|
|
34516
|
+
return { signals: signals2, abortedCycles };
|
|
34627
34517
|
};
|
|
34628
34518
|
}
|
|
34629
34519
|
});
|
|
34630
34520
|
|
|
34631
|
-
// src/lib/replay/
|
|
34632
|
-
var
|
|
34633
|
-
var
|
|
34634
|
-
"src/lib/replay/
|
|
34521
|
+
// src/lib/replay/historicalSignalsReplayResults.ts
|
|
34522
|
+
var import_backtest4, emptyStat, collectHistoricalReplayResult;
|
|
34523
|
+
var init_historicalSignalsReplayResults = __esm({
|
|
34524
|
+
"src/lib/replay/historicalSignalsReplayResults.ts"() {
|
|
34635
34525
|
"use strict";
|
|
34636
|
-
|
|
34637
|
-
|
|
34638
|
-
|
|
34639
|
-
|
|
34526
|
+
import_backtest4 = require("@tradejs/core/backtest");
|
|
34527
|
+
emptyStat = () => ({
|
|
34528
|
+
orders: 0,
|
|
34529
|
+
wins: 0,
|
|
34530
|
+
losses: 0,
|
|
34531
|
+
netProfit: 0,
|
|
34532
|
+
amount: 0
|
|
34533
|
+
});
|
|
34534
|
+
collectHistoricalReplayResult = (context) => ({
|
|
34535
|
+
strategies: context.strategies.map(({ strategyName, strategyConfig }) => {
|
|
34536
|
+
const orderLog = context.artifacts.orderLogByStrategy.get(strategyName) ?? [];
|
|
34537
|
+
const positionLog = context.artifacts.positionLogByStrategy.get(strategyName) ?? [];
|
|
34538
|
+
return {
|
|
34539
|
+
strategyName,
|
|
34540
|
+
strategyConfig,
|
|
34541
|
+
orderLog,
|
|
34542
|
+
positionLog,
|
|
34543
|
+
stat: positionLog.length ? (0, import_backtest4.calculateStatsFull)(positionLog) : emptyStat()
|
|
34544
|
+
};
|
|
34545
|
+
}),
|
|
34546
|
+
signals: context.signals,
|
|
34547
|
+
orderLog: context.artifacts.orderLog,
|
|
34548
|
+
positionLog: context.artifacts.positionLog,
|
|
34549
|
+
cycleCount: context.cycleCount,
|
|
34550
|
+
abortedCycles: context.abortedCycles,
|
|
34551
|
+
runtimeLineages: context.runtimeLineages
|
|
34552
|
+
});
|
|
34553
|
+
}
|
|
34554
|
+
});
|
|
34555
|
+
|
|
34556
|
+
// src/lib/replay/historicalSignalsReplay.ts
|
|
34557
|
+
var import_chalk47, import_progress12, import_strategies12, import_time11, import_logger11, import_redis19, import_connectors17, import_cli13, import_strategies13, import_runtimeStrategies8, loadRuntimeStrategies, loadReferenceConnector, buildAfterSignalsContext, getConnectorName, runHistoricalSignalsReplay;
|
|
34558
|
+
var init_historicalSignalsReplay = __esm({
|
|
34559
|
+
"src/lib/replay/historicalSignalsReplay.ts"() {
|
|
34560
|
+
"use strict";
|
|
34561
|
+
import_chalk47 = __toESM(require("chalk"));
|
|
34562
|
+
import_progress12 = __toESM(require("progress"));
|
|
34563
|
+
import_strategies12 = require("@tradejs/core/strategies");
|
|
34564
|
+
import_time11 = require("@tradejs/core/time");
|
|
34565
|
+
import_logger11 = require("@tradejs/infra/logger");
|
|
34566
|
+
import_redis19 = require("@tradejs/infra/redis");
|
|
34567
|
+
import_connectors17 = require("@tradejs/node/connectors");
|
|
34568
|
+
import_cli13 = require("@tradejs/node/cli");
|
|
34569
|
+
import_strategies13 = require("@tradejs/node/strategies");
|
|
34570
|
+
import_runtimeStrategies8 = require("@tradejs/node/runtimeStrategies");
|
|
34571
|
+
init_cliConfig2();
|
|
34572
|
+
init_portfolioReplayConnector();
|
|
34573
|
+
init_hooks();
|
|
34574
|
+
init_runtimeLineage();
|
|
34575
|
+
init_historicalSignalsReplayPreparation();
|
|
34576
|
+
init_historicalSignalsReplayExecution();
|
|
34577
|
+
init_historicalSignalsReplayResults();
|
|
34578
|
+
loadRuntimeStrategies = async (runtimeStrategies) => {
|
|
34579
|
+
const strategies = await Promise.all(
|
|
34580
|
+
runtimeStrategies.map(async (runtimeStrategy) => {
|
|
34581
|
+
const {
|
|
34582
|
+
strategyName,
|
|
34583
|
+
strategyRevision,
|
|
34584
|
+
deploymentCompositionId,
|
|
34585
|
+
strategyPackage,
|
|
34586
|
+
strategyPackageVersion,
|
|
34587
|
+
strategyDependencyVersions,
|
|
34588
|
+
runtimePackageVersion,
|
|
34589
|
+
strategyConfig,
|
|
34590
|
+
selection
|
|
34591
|
+
} = runtimeStrategy;
|
|
34592
|
+
const strategyCreator = await (0, import_strategies13.getStrategyCreator)(
|
|
34593
|
+
strategyName,
|
|
34594
|
+
replayProjectRoot
|
|
34595
|
+
);
|
|
34596
|
+
if (!strategyCreator) {
|
|
34597
|
+
throw new Error(`Unknown strategy: ${strategyName}`);
|
|
34598
|
+
}
|
|
34599
|
+
const installed = await (0, import_runtimeStrategies8.getRuntimeStrategyPackageMetadata)({
|
|
34600
|
+
strategyName,
|
|
34601
|
+
projectRoot: replayProjectRoot
|
|
34602
|
+
});
|
|
34603
|
+
if (installed.strategyPackage !== strategyPackage || installed.strategyPackageVersion !== strategyPackageVersion || JSON.stringify(installed.strategyDependencyVersions) !== JSON.stringify(strategyDependencyVersions) || installed.runtimePackageVersion !== runtimePackageVersion) {
|
|
34604
|
+
throw new Error(
|
|
34605
|
+
`Runtime evidence package mismatch for ${strategyName}: expected=${strategyPackage}@${strategyPackageVersion}/@tradejs/node@${runtimePackageVersion}, installed=${installed.strategyPackage ?? "missing"}@${installed.strategyPackageVersion ?? "missing"}/@tradejs/node@${installed.runtimePackageVersion ?? "missing"}`
|
|
34606
|
+
);
|
|
34607
|
+
}
|
|
34608
|
+
const strategyResults = await (0, import_redis19.getData)(
|
|
34609
|
+
import_redis19.redisKeys.strategyResults(replayUserName, strategyName),
|
|
34610
|
+
{}
|
|
34611
|
+
);
|
|
34612
|
+
return {
|
|
34613
|
+
strategyName,
|
|
34614
|
+
strategyRevision,
|
|
34615
|
+
deploymentCompositionId,
|
|
34616
|
+
strategyPackage,
|
|
34617
|
+
strategyPackageVersion,
|
|
34618
|
+
strategyDependencyVersions,
|
|
34619
|
+
runtimePackageVersion,
|
|
34620
|
+
strategyCreator,
|
|
34621
|
+
strategyConfig,
|
|
34622
|
+
strategyResults,
|
|
34623
|
+
...selection ? { selection } : {}
|
|
34624
|
+
};
|
|
34625
|
+
})
|
|
34626
|
+
);
|
|
34627
|
+
return strategies;
|
|
34628
|
+
};
|
|
34629
|
+
loadReferenceConnector = async (connectorName) => {
|
|
34630
|
+
const connectorFactory = await (0, import_connectors17.getConnectorCreatorByName)(
|
|
34631
|
+
connectorName,
|
|
34632
|
+
replayProjectRoot
|
|
34633
|
+
);
|
|
34634
|
+
if (!connectorFactory) {
|
|
34635
|
+
throw new Error(`Connector "${connectorName}" is not registered`);
|
|
34636
|
+
}
|
|
34637
|
+
return await connectorFactory({
|
|
34638
|
+
userName: replayUserName
|
|
34639
|
+
});
|
|
34640
|
+
};
|
|
34641
|
+
buildAfterSignalsContext = ({
|
|
34642
|
+
connector,
|
|
34643
|
+
connectorName,
|
|
34644
|
+
tickers,
|
|
34645
|
+
runtimeStrategies,
|
|
34646
|
+
interval: interval4
|
|
34647
|
+
}) => ({
|
|
34648
|
+
connector,
|
|
34649
|
+
connectorName,
|
|
34650
|
+
userName: replayUserName,
|
|
34651
|
+
interval: interval4,
|
|
34652
|
+
tickers: [...tickers],
|
|
34653
|
+
runtimeStrategies: runtimeStrategies.map(
|
|
34654
|
+
({ strategyName, strategyConfig }) => ({
|
|
34655
|
+
strategyName,
|
|
34656
|
+
strategyConfig
|
|
34657
|
+
})
|
|
34658
|
+
)
|
|
34659
|
+
});
|
|
34660
|
+
getConnectorName = (connector) => {
|
|
34661
|
+
const name = "name" in connector ? connector.name : void 0;
|
|
34662
|
+
return String(name || import_connectors17.DEFAULT_CONNECTOR_NAME).trim() || import_connectors17.DEFAULT_CONNECTOR_NAME;
|
|
34663
|
+
};
|
|
34664
|
+
runHistoricalSignalsReplay = async ({
|
|
34665
|
+
preparedRun,
|
|
34666
|
+
interval: interval4,
|
|
34667
|
+
runtimeStrategies
|
|
34668
|
+
}) => {
|
|
34669
|
+
const startedAt = Date.now();
|
|
34670
|
+
const projectConfig = await (0, import_cli13.loadTradejsConfig)(replayProjectRoot);
|
|
34671
|
+
const projectHooks = projectConfig.hooks;
|
|
34672
|
+
const loadedStrategies = await loadRuntimeStrategies(runtimeStrategies);
|
|
34673
|
+
const replayConnector = createPortfolioReplayConnector(
|
|
34674
|
+
preparedRun.marketConnector
|
|
34675
|
+
);
|
|
34676
|
+
const connectorName = getConnectorName(preparedRun.marketConnector);
|
|
34677
|
+
const binanceConnector = connectorName.toLowerCase() === import_connectors17.DEFAULT_CONNECTOR_NAME.toLowerCase() ? await loadReferenceConnector("Binance") : preparedRun.marketConnector;
|
|
34678
|
+
const coinbaseConnector = connectorName.toLowerCase() === import_connectors17.DEFAULT_CONNECTOR_NAME.toLowerCase() ? await loadReferenceConnector("Coinbase") : preparedRun.marketConnector;
|
|
34679
|
+
const [btcBinanceData, btcCoinbaseData] = await Promise.all([
|
|
34680
|
+
binanceConnector.kline({
|
|
34681
|
+
symbol: "BTCUSDT",
|
|
34682
|
+
start: preparedRun.preloadStart,
|
|
34683
|
+
end: preparedRun.window.end,
|
|
34684
|
+
cacheOnly: true,
|
|
34685
|
+
interval: interval4
|
|
34686
|
+
}),
|
|
34687
|
+
coinbaseConnector.kline({
|
|
34688
|
+
symbol: "BTCUSDT",
|
|
34689
|
+
start: preparedRun.preloadStart,
|
|
34690
|
+
end: preparedRun.window.end,
|
|
34691
|
+
cacheOnly: true,
|
|
34692
|
+
interval: interval4
|
|
34693
|
+
})
|
|
34694
|
+
]);
|
|
34695
|
+
const btcMarketData = await preparedRun.marketConnector.kline({
|
|
34696
|
+
symbol: "BTCUSDT",
|
|
34697
|
+
start: preparedRun.preloadStart,
|
|
34698
|
+
end: preparedRun.window.end,
|
|
34699
|
+
cacheOnly: true,
|
|
34700
|
+
interval: interval4
|
|
34701
|
+
});
|
|
34702
|
+
const ethMarketData = await preparedRun.marketConnector.kline({
|
|
34703
|
+
symbol: "ETHUSDT",
|
|
34704
|
+
start: preparedRun.preloadStart,
|
|
34705
|
+
end: preparedRun.window.end,
|
|
34706
|
+
cacheOnly: true,
|
|
34707
|
+
interval: interval4
|
|
34708
|
+
});
|
|
34709
|
+
const prepareBar = new import_progress12.default(
|
|
34710
|
+
"prepare :current/:total [:bar][:percent] skipped=:skipped :etas(s) :symbol",
|
|
34711
|
+
{
|
|
34712
|
+
total: preparedRun.tickers.length,
|
|
34713
|
+
width: 30
|
|
34714
|
+
}
|
|
34715
|
+
);
|
|
34716
|
+
const plan = await prepareHistoricalReplay(
|
|
34717
|
+
{
|
|
34718
|
+
userName: replayUserName,
|
|
34719
|
+
projectRoot: replayProjectRoot,
|
|
34720
|
+
preparedRun,
|
|
34721
|
+
interval: interval4,
|
|
34722
|
+
connectorName,
|
|
34723
|
+
replayConnector,
|
|
34724
|
+
strategies: loadedStrategies,
|
|
34725
|
+
references: {
|
|
34726
|
+
btcMarketData,
|
|
34727
|
+
ethMarketData,
|
|
34728
|
+
btcBinanceData,
|
|
34729
|
+
btcCoinbaseData
|
|
34730
|
+
}
|
|
34731
|
+
},
|
|
34732
|
+
{
|
|
34733
|
+
progress: {
|
|
34734
|
+
tick: (tokens) => prepareBar.tick(1, tokens)
|
|
34735
|
+
},
|
|
34736
|
+
display: {
|
|
34737
|
+
skipped: (value) => import_chalk47.default.yellow(value),
|
|
34738
|
+
symbol: (value) => import_chalk47.default.gray(value)
|
|
34739
|
+
},
|
|
34740
|
+
buildLineage: buildRuntimeLineage
|
|
34741
|
+
}
|
|
34742
|
+
);
|
|
34743
|
+
const hookContext = buildAfterSignalsContext({
|
|
34744
|
+
connector: replayConnector,
|
|
34745
|
+
connectorName: preparedRun.connectorName,
|
|
34746
|
+
tickers: preparedRun.tickers,
|
|
34747
|
+
runtimeStrategies: loadedStrategies,
|
|
34748
|
+
interval: interval4
|
|
34749
|
+
});
|
|
34750
|
+
const cycleBar = new import_progress12.default(
|
|
34751
|
+
"cycles :current/:total [:bar][:percent] sig=:signals abort=:aborted :etas(s) :ts",
|
|
34752
|
+
{
|
|
34753
|
+
total: plan.orderedTimestamps.length,
|
|
34754
|
+
width: 30
|
|
34755
|
+
}
|
|
34756
|
+
);
|
|
34757
|
+
const execution = await executeHistoricalReplay(
|
|
34758
|
+
{
|
|
34759
|
+
plan,
|
|
34760
|
+
connector: replayConnector,
|
|
34761
|
+
hooks: projectHooks,
|
|
34762
|
+
hookContext
|
|
34763
|
+
},
|
|
34764
|
+
{
|
|
34765
|
+
clock: { now: Date.now },
|
|
34766
|
+
progress: {
|
|
34767
|
+
tick: (tokens) => cycleBar.tick(1, tokens)
|
|
34768
|
+
},
|
|
34769
|
+
display: {
|
|
34770
|
+
signals: (value) => import_chalk47.default.cyan(value),
|
|
34771
|
+
aborted: (value) => import_chalk47.default.yellow(value),
|
|
34772
|
+
timestamp: (value) => import_chalk47.default.gray((0, import_time11.formatUnix)(value))
|
|
34773
|
+
},
|
|
34774
|
+
invokeBeforeSignals: invokeBeforeSignalsHooks,
|
|
34775
|
+
invokeAfterSignals: invokeAfterSignalsHooks,
|
|
34776
|
+
enrichSignal: (signal) => (0, import_strategies13.enrichSignalWithBinanceMarketContext)({ signal, env: "PARITY" }),
|
|
34777
|
+
releaseIndicatorsCache: import_strategies12.releaseStrategyIndicatorsReplayCache,
|
|
34778
|
+
releaseReplayCache: import_strategies12.releaseStrategyReplayCache
|
|
34779
|
+
}
|
|
34780
|
+
);
|
|
34781
|
+
const artifacts = replayConnector.getReplayArtifacts();
|
|
34782
|
+
import_logger11.logger.info(
|
|
34783
|
+
import_chalk47.default.gray(
|
|
34784
|
+
`signals replay historical cycles: ${plan.orderedTimestamps.length} (aborted=${execution.abortedCycles}) done in ${((Date.now() - startedAt) / 1e3).toFixed(1)}s`
|
|
34785
|
+
)
|
|
34786
|
+
);
|
|
34787
|
+
return collectHistoricalReplayResult({
|
|
34788
|
+
strategies: loadedStrategies,
|
|
34789
|
+
artifacts,
|
|
34790
|
+
signals: execution.signals,
|
|
34791
|
+
cycleCount: plan.orderedTimestamps.length,
|
|
34792
|
+
abortedCycles: execution.abortedCycles,
|
|
34793
|
+
runtimeLineages: plan.runtimeLineages
|
|
34794
|
+
});
|
|
34795
|
+
};
|
|
34796
|
+
}
|
|
34797
|
+
});
|
|
34798
|
+
|
|
34799
|
+
// src/lib/replay/chartSnapshot.ts
|
|
34800
|
+
var formatPercent5, formatSigned2, toSimpleOrderLog, toReplayChartOrders, buildReplayChartSnapshot;
|
|
34801
|
+
var init_chartSnapshot = __esm({
|
|
34802
|
+
"src/lib/replay/chartSnapshot.ts"() {
|
|
34803
|
+
"use strict";
|
|
34804
|
+
formatPercent5 = (value) => typeof value === "number" && Number.isFinite(value) ? `${value.toFixed(1)}%` : "n/a";
|
|
34805
|
+
formatSigned2 = (value) => typeof value === "number" && Number.isFinite(value) ? `${value >= 0 ? "+" : ""}${value.toFixed(2)}` : "n/a";
|
|
34806
|
+
toSimpleOrderLog = (orderLog) => orderLog.map((entry) => [entry.timestamp, entry.amount]);
|
|
34807
|
+
toReplayChartOrders = (orderLog) => orderLog.map((entry, index) => {
|
|
34640
34808
|
const isOpenOrder = entry.type.startsWith("OPEN");
|
|
34641
34809
|
const equityBefore = orderLog[index - 1]?.amount ?? (Number.isFinite(entry.amount) && Number.isFinite(entry.profit) ? entry.amount - entry.profit : null);
|
|
34642
34810
|
return {
|
|
@@ -34725,11 +34893,11 @@ var init_chartSnapshot = __esm({
|
|
|
34725
34893
|
});
|
|
34726
34894
|
|
|
34727
34895
|
// src/lib/replay/resultsReporting.ts
|
|
34728
|
-
var
|
|
34896
|
+
var import_chalk48, saveAndPrintReplayResultsByStrategy;
|
|
34729
34897
|
var init_resultsReporting = __esm({
|
|
34730
34898
|
"src/lib/replay/resultsReporting.ts"() {
|
|
34731
34899
|
"use strict";
|
|
34732
|
-
|
|
34900
|
+
import_chalk48 = __toESM(require("chalk"));
|
|
34733
34901
|
init_runtimeParity();
|
|
34734
34902
|
init_runFormatting();
|
|
34735
34903
|
init_support();
|
|
@@ -34768,14 +34936,14 @@ var init_resultsReporting = __esm({
|
|
|
34768
34936
|
const rows = summaries.map((summary) => {
|
|
34769
34937
|
const profit = `${summary.netProfit.toFixed(2)}$`;
|
|
34770
34938
|
const avgTrade = `${summary.avgTradeProfit.toFixed(2)}$`;
|
|
34771
|
-
const profitColor = summary.netProfit > 0 ?
|
|
34772
|
-
const avgTradeColor = summary.avgTradeProfit > 0 ?
|
|
34939
|
+
const profitColor = summary.netProfit > 0 ? import_chalk48.default.green : summary.netProfit < 0 ? import_chalk48.default.red : import_chalk48.default.gray;
|
|
34940
|
+
const avgTradeColor = summary.avgTradeProfit > 0 ? import_chalk48.default.green : summary.avgTradeProfit < 0 ? import_chalk48.default.red : import_chalk48.default.gray;
|
|
34773
34941
|
return [
|
|
34774
|
-
|
|
34775
|
-
|
|
34776
|
-
|
|
34777
|
-
|
|
34778
|
-
|
|
34942
|
+
import_chalk48.default.blue(summary.strategyName),
|
|
34943
|
+
import_chalk48.default.yellow(String(summary.tickers)),
|
|
34944
|
+
import_chalk48.default.yellow(String(summary.tickersWithTrades)),
|
|
34945
|
+
import_chalk48.default.cyan(String(summary.orders)),
|
|
34946
|
+
import_chalk48.default.cyan(
|
|
34779
34947
|
`${summary.wins}/${summary.losses} (${summary.winRate.toFixed(2)}%)`
|
|
34780
34948
|
),
|
|
34781
34949
|
profitColor(profit),
|
|
@@ -34795,12 +34963,12 @@ var init_resultsReporting = __esm({
|
|
|
34795
34963
|
});
|
|
34796
34964
|
|
|
34797
34965
|
// src/lib/runtimeTradeSync.ts
|
|
34798
|
-
var import_constants18,
|
|
34966
|
+
var import_constants18, import_time12, import_redis20, CLOSED_PNL_LIMIT, CLOSED_PNL_RECONCILIATION_LOOKBACK_MS, EXCHANGE_HISTORY_MAX_RANGE_MS, isRuntimeTradeSyncFallbackClose, splitExchangeHistoryTimeRange, formatRuntimeTradeSyncError, loadClosedPnlRows, consumeClosedPnlMatch, isRuntimeTradeInConnectorScope, syncRuntimeTrades;
|
|
34799
34967
|
var init_runtimeTradeSync = __esm({
|
|
34800
34968
|
"src/lib/runtimeTradeSync.ts"() {
|
|
34801
34969
|
"use strict";
|
|
34802
34970
|
import_constants18 = require("@tradejs/core/constants");
|
|
34803
|
-
|
|
34971
|
+
import_time12 = require("@tradejs/core/time");
|
|
34804
34972
|
import_redis20 = require("@tradejs/infra/redis");
|
|
34805
34973
|
CLOSED_PNL_LIMIT = 100;
|
|
34806
34974
|
CLOSED_PNL_RECONCILIATION_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -35007,7 +35175,7 @@ var init_runtimeTradeSync = __esm({
|
|
|
35007
35175
|
await (0, import_redis20.setHashJsonField)(
|
|
35008
35176
|
import_redis20.redisKeys.runtimeTradeBucket(
|
|
35009
35177
|
userName3,
|
|
35010
|
-
(0,
|
|
35178
|
+
(0, import_time12.getRuntimeStorageDayKey)(trade.entryTimestamp)
|
|
35011
35179
|
),
|
|
35012
35180
|
trade.orderId,
|
|
35013
35181
|
nextTrade2,
|
|
@@ -35045,7 +35213,7 @@ var init_runtimeTradeSync = __esm({
|
|
|
35045
35213
|
(0, import_redis20.setHashJsonField)(
|
|
35046
35214
|
import_redis20.redisKeys.runtimeTradeBucket(
|
|
35047
35215
|
userName3,
|
|
35048
|
-
(0,
|
|
35216
|
+
(0, import_time12.getRuntimeStorageDayKey)(trade.entryTimestamp)
|
|
35049
35217
|
),
|
|
35050
35218
|
trade.orderId,
|
|
35051
35219
|
nextTrade,
|
|
@@ -35054,7 +35222,7 @@ var init_runtimeTradeSync = __esm({
|
|
|
35054
35222
|
(0, import_redis20.setHashJsonField)(
|
|
35055
35223
|
import_redis20.redisKeys.runtimeClosedTradeBucket(
|
|
35056
35224
|
userName3,
|
|
35057
|
-
(0,
|
|
35225
|
+
(0, import_time12.getRuntimeStorageDayKey)(nextTrade.exitTimestamp)
|
|
35058
35226
|
),
|
|
35059
35227
|
trade.orderId,
|
|
35060
35228
|
nextTrade,
|
|
@@ -35805,49 +35973,14 @@ var init_runtimeParityDetails = __esm({
|
|
|
35805
35973
|
}
|
|
35806
35974
|
});
|
|
35807
35975
|
|
|
35808
|
-
// src/lib/replay/
|
|
35809
|
-
var
|
|
35810
|
-
var
|
|
35811
|
-
"src/lib/replay/
|
|
35976
|
+
// src/lib/replay/runtimeComparisonCalculations.ts
|
|
35977
|
+
var lineageScopeKey, buildExpectedLineageByScope, hasExpectedLineage, filterReplayComparisonByLineage, buildPriceDeltaPct3, compareExchangeEntriesToBacktest, findExchangeRuntimeOutcome, hasLineageLinkedRuntimeOutcome, splitExchangeMatchesByRuntimeOrderStatus, summarizeBacktestPnlByStrategy, buildExchangeComparisonRows, buildRuntimeComparisonRows;
|
|
35978
|
+
var init_runtimeComparisonCalculations = __esm({
|
|
35979
|
+
"src/lib/replay/runtimeComparisonCalculations.ts"() {
|
|
35812
35980
|
"use strict";
|
|
35813
|
-
import_chalk48 = __toESM(require("chalk"));
|
|
35814
|
-
import_time11 = require("@tradejs/core/time");
|
|
35815
35981
|
init_runtimeParity();
|
|
35816
|
-
init_paritySummary();
|
|
35817
|
-
init_runtimeRedis();
|
|
35818
|
-
init_runtimeSignalsLoader();
|
|
35819
|
-
init_runtimeTradeSync();
|
|
35820
|
-
init_runFormatting();
|
|
35821
35982
|
init_runtimeParityDetails();
|
|
35822
|
-
init_runState();
|
|
35823
|
-
init_cliConfig2();
|
|
35824
|
-
init_runtimeEvidenceSource();
|
|
35825
|
-
init_support();
|
|
35826
35983
|
init_runtimeLineage();
|
|
35827
|
-
getReplayEntryTimestampCompareOffsetMs = () => {
|
|
35828
|
-
const intervalMinutes = Number(replayInterval);
|
|
35829
|
-
return Number.isFinite(intervalMinutes) && intervalMinutes > 0 ? intervalMinutes * 60 * 1e3 : 15 * 60 * 1e3;
|
|
35830
|
-
};
|
|
35831
|
-
buildReplaySignalEvaluations = (signals2) => signals2.map((signal) => ({
|
|
35832
|
-
evaluationId: `${signal.strategy}:${signal.symbol}:${signal.timestamp}`,
|
|
35833
|
-
userName: replayUserName,
|
|
35834
|
-
strategy: signal.strategy,
|
|
35835
|
-
symbol: signal.symbol,
|
|
35836
|
-
interval: signal.interval,
|
|
35837
|
-
timestamp: signal.timestamp,
|
|
35838
|
-
evaluatedAt: signal.timestamp,
|
|
35839
|
-
status: "signal",
|
|
35840
|
-
reason: signal.orderSkipReason || signal.orderStatus || "SIGNAL",
|
|
35841
|
-
signalId: signal.signalId,
|
|
35842
|
-
direction: signal.direction,
|
|
35843
|
-
accountId: signal.accountId,
|
|
35844
|
-
deploymentId: signal.deploymentId,
|
|
35845
|
-
runtimeLineage: signal.runtimeLineage,
|
|
35846
|
-
orderStatus: signal.orderStatus,
|
|
35847
|
-
orderSkipReason: signal.orderSkipReason,
|
|
35848
|
-
...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
|
|
35849
|
-
...signal.ml ? { ml: signal.ml } : {}
|
|
35850
|
-
}));
|
|
35851
35984
|
lineageScopeKey = ({
|
|
35852
35985
|
strategy,
|
|
35853
35986
|
symbol,
|
|
@@ -35867,12 +36000,7 @@ var init_runtimeComparison = __esm({
|
|
|
35867
36000
|
}) => runtimeLineagesComparable(
|
|
35868
36001
|
lineage,
|
|
35869
36002
|
expectedByScope.get(
|
|
35870
|
-
lineageScopeKey({
|
|
35871
|
-
strategy,
|
|
35872
|
-
symbol,
|
|
35873
|
-
deploymentId,
|
|
35874
|
-
accountId
|
|
35875
|
-
})
|
|
36003
|
+
lineageScopeKey({ strategy, symbol, deploymentId, accountId })
|
|
35876
36004
|
)
|
|
35877
36005
|
);
|
|
35878
36006
|
filterReplayComparisonByLineage = ({
|
|
@@ -35994,13 +36122,7 @@ var init_runtimeComparison = __esm({
|
|
|
35994
36122
|
reason: comparableScopeKeys.size > 0 ? null : expectedByScope.size === 0 ? "replay_produced_no_lineage_scopes" : "no_runtime_artifacts_with_matching_lineage",
|
|
35995
36123
|
replay: [...expectedByScope.entries()].map(([scope, lineage]) => {
|
|
35996
36124
|
const [deploymentId, accountId, strategy, symbol] = scope.split("::");
|
|
35997
|
-
return {
|
|
35998
|
-
deploymentId,
|
|
35999
|
-
accountId,
|
|
36000
|
-
strategy,
|
|
36001
|
-
symbol,
|
|
36002
|
-
lineage
|
|
36003
|
-
};
|
|
36125
|
+
return { deploymentId, accountId, strategy, symbol, lineage };
|
|
36004
36126
|
}).sort(
|
|
36005
36127
|
(left, right) => left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || runtimeLineageKey(left.lineage).localeCompare(
|
|
36006
36128
|
runtimeLineageKey(right.lineage)
|
|
@@ -36009,208 +36131,12 @@ var init_runtimeComparison = __esm({
|
|
|
36009
36131
|
}
|
|
36010
36132
|
};
|
|
36011
36133
|
};
|
|
36012
|
-
formatDrilldownSummary = (summary) => Object.entries(summary).filter(([, count]) => count > 0).map(([classification, count]) => `${classification}=${count}`).join(", ");
|
|
36013
|
-
toFiniteNumberOrNull4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
36014
|
-
sumOptionalNumbers = (values) => {
|
|
36015
|
-
let sum3 = 0;
|
|
36016
|
-
let hasValue = false;
|
|
36017
|
-
for (const value of values) {
|
|
36018
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
36019
|
-
continue;
|
|
36020
|
-
}
|
|
36021
|
-
sum3 += value;
|
|
36022
|
-
hasValue = true;
|
|
36023
|
-
}
|
|
36024
|
-
return hasValue ? Number(sum3.toFixed(12)) : null;
|
|
36025
|
-
};
|
|
36026
|
-
firstFiniteNumber = (values) => {
|
|
36027
|
-
for (const value of values) {
|
|
36028
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
36029
|
-
return value;
|
|
36030
|
-
}
|
|
36031
|
-
}
|
|
36032
|
-
return null;
|
|
36033
|
-
};
|
|
36034
|
-
firstString = (values) => values.find((value) => typeof value === "string" && value.trim());
|
|
36035
|
-
summarizeBacktestPnlByStrategy = (entries) => {
|
|
36036
|
-
const totals = /* @__PURE__ */ new Map();
|
|
36037
|
-
for (const entry of entries) {
|
|
36038
|
-
const pnl = typeof entry.expectedPnl === "number" && Number.isFinite(entry.expectedPnl) ? entry.expectedPnl : 0;
|
|
36039
|
-
totals.set(entry.strategy, (totals.get(entry.strategy) ?? 0) + pnl);
|
|
36040
|
-
}
|
|
36041
|
-
return totals;
|
|
36042
|
-
};
|
|
36043
|
-
getExchangeExecutionMergeKey = (entry) => {
|
|
36044
|
-
const orderId = firstString([entry.orderId, entry.orderLinkId]);
|
|
36045
|
-
return orderId ? `${entry.symbol}::${entry.direction}::${orderId}` : null;
|
|
36046
|
-
};
|
|
36047
|
-
mergeExchangeExecutionGroup = (group) => {
|
|
36048
|
-
const [first] = group;
|
|
36049
|
-
const qty = sumOptionalNumbers(group.map((entry) => entry.qty)) ?? first.qty;
|
|
36050
|
-
const weightedPriceSum = group.reduce((sum3, entry) => {
|
|
36051
|
-
const entryQty = toFiniteNumberOrNull4(entry.qty);
|
|
36052
|
-
const entryPrice2 = toFiniteNumberOrNull4(entry.entryPrice);
|
|
36053
|
-
return entryQty != null && entryPrice2 != null ? sum3 + entryQty * entryPrice2 : sum3;
|
|
36054
|
-
}, 0);
|
|
36055
|
-
const entryPrice = qty > 0 && weightedPriceSum > 0 ? Number((weightedPriceSum / qty).toFixed(12)) : firstFiniteNumber(group.map((entry) => entry.entryPrice));
|
|
36056
|
-
const openFee = sumOptionalNumbers(group.map((entry) => entry.openFee));
|
|
36057
|
-
const closeFee = sumOptionalNumbers(group.map((entry) => entry.closeFee));
|
|
36058
|
-
const fundingFee = firstFiniteNumber(group.map((entry) => entry.fundingFee));
|
|
36059
|
-
const totalFee = sumOptionalNumbers([openFee, closeFee, fundingFee]) ?? firstFiniteNumber(group.map((entry) => entry.totalFee));
|
|
36060
|
-
return {
|
|
36061
|
-
symbol: first.symbol,
|
|
36062
|
-
direction: first.direction,
|
|
36063
|
-
qty,
|
|
36064
|
-
entryPrice,
|
|
36065
|
-
entryTimestamp: Math.min(...group.map((entry) => entry.entryTimestamp)),
|
|
36066
|
-
...firstString(group.map((entry) => entry.orderId)) ? { orderId: firstString(group.map((entry) => entry.orderId)) } : {},
|
|
36067
|
-
...firstString(group.map((entry) => entry.orderLinkId)) ? { orderLinkId: firstString(group.map((entry) => entry.orderLinkId)) } : {},
|
|
36068
|
-
...firstFiniteNumber(group.map((entry) => entry.takeProfitPrice)) != null ? {
|
|
36069
|
-
takeProfitPrice: firstFiniteNumber(
|
|
36070
|
-
group.map((entry) => entry.takeProfitPrice)
|
|
36071
|
-
)
|
|
36072
|
-
} : {},
|
|
36073
|
-
...firstFiniteNumber(group.map((entry) => entry.stopLossPrice)) != null ? {
|
|
36074
|
-
stopLossPrice: firstFiniteNumber(
|
|
36075
|
-
group.map((entry) => entry.stopLossPrice)
|
|
36076
|
-
)
|
|
36077
|
-
} : {},
|
|
36078
|
-
...firstFiniteNumber(group.map((entry) => entry.exitPrice)) != null ? { exitPrice: firstFiniteNumber(group.map((entry) => entry.exitPrice)) } : {},
|
|
36079
|
-
...firstFiniteNumber(group.map((entry) => entry.exitTimestamp)) != null ? {
|
|
36080
|
-
exitTimestamp: firstFiniteNumber(
|
|
36081
|
-
group.map((entry) => entry.exitTimestamp)
|
|
36082
|
-
)
|
|
36083
|
-
} : {},
|
|
36084
|
-
...sumOptionalNumbers(group.map((entry) => entry.closedPnl)) != null ? { closedPnl: sumOptionalNumbers(group.map((entry) => entry.closedPnl)) } : {},
|
|
36085
|
-
...openFee != null ? { openFee } : {},
|
|
36086
|
-
...closeFee != null ? { closeFee } : {},
|
|
36087
|
-
...fundingFee != null ? { fundingFee } : {},
|
|
36088
|
-
...totalFee != null ? { totalFee } : {}
|
|
36089
|
-
};
|
|
36090
|
-
};
|
|
36091
|
-
mergeExchangeEntryExecutions = (rows) => {
|
|
36092
|
-
const mergedRows = [];
|
|
36093
|
-
const groups = /* @__PURE__ */ new Map();
|
|
36094
|
-
for (const row of rows) {
|
|
36095
|
-
const key = getExchangeExecutionMergeKey(row);
|
|
36096
|
-
if (!key) {
|
|
36097
|
-
mergedRows.push(row);
|
|
36098
|
-
continue;
|
|
36099
|
-
}
|
|
36100
|
-
const group = groups.get(key) ?? [];
|
|
36101
|
-
group.push(row);
|
|
36102
|
-
groups.set(key, group);
|
|
36103
|
-
}
|
|
36104
|
-
for (const group of groups.values()) {
|
|
36105
|
-
mergedRows.push(
|
|
36106
|
-
group.length === 1 ? group[0] : mergeExchangeExecutionGroup(group)
|
|
36107
|
-
);
|
|
36108
|
-
}
|
|
36109
|
-
return mergedRows.sort(
|
|
36110
|
-
(left, right) => left.entryTimestamp - right.entryTimestamp
|
|
36111
|
-
);
|
|
36112
|
-
};
|
|
36113
|
-
loadExchangeEntryRows = async ({
|
|
36114
|
-
connector,
|
|
36115
|
-
startTime,
|
|
36116
|
-
endTime
|
|
36117
|
-
}) => {
|
|
36118
|
-
if (typeof connector.getEntryExecutions !== "function") {
|
|
36119
|
-
console.log(
|
|
36120
|
-
import_chalk48.default.yellow(
|
|
36121
|
-
"runtime compare: connector does not support entry execution history"
|
|
36122
|
-
)
|
|
36123
|
-
);
|
|
36124
|
-
return [];
|
|
36125
|
-
}
|
|
36126
|
-
const rows = [];
|
|
36127
|
-
const chunks = splitExchangeHistoryTimeRange({ startTime, endTime });
|
|
36128
|
-
for (const chunk of chunks) {
|
|
36129
|
-
try {
|
|
36130
|
-
const chunkRows = await connector.getEntryExecutions({
|
|
36131
|
-
startTime: chunk.startTime,
|
|
36132
|
-
endTime: chunk.endTime,
|
|
36133
|
-
limit: 100
|
|
36134
|
-
});
|
|
36135
|
-
if (chunkRows.length >= 100) {
|
|
36136
|
-
console.log(
|
|
36137
|
-
import_chalk48.default.yellow(
|
|
36138
|
-
"runtime compare: exchange entry executions returned 100 rows (connector cap); older entry trades in this chunk may be truncated"
|
|
36139
|
-
)
|
|
36140
|
-
);
|
|
36141
|
-
}
|
|
36142
|
-
rows.push(...chunkRows);
|
|
36143
|
-
} catch (error) {
|
|
36144
|
-
console.log(
|
|
36145
|
-
import_chalk48.default.yellow(
|
|
36146
|
-
`runtime compare: getEntryExecutions failed: ${formatRuntimeTradeSyncError(error)}`
|
|
36147
|
-
)
|
|
36148
|
-
);
|
|
36149
|
-
}
|
|
36150
|
-
}
|
|
36151
|
-
return mergeExchangeEntryExecutions(rows);
|
|
36152
|
-
};
|
|
36153
36134
|
buildPriceDeltaPct3 = (leftPrice, rightPrice) => {
|
|
36154
36135
|
if (leftPrice == null || rightPrice == null || !Number.isFinite(leftPrice) || !Number.isFinite(rightPrice) || leftPrice === 0) {
|
|
36155
36136
|
return null;
|
|
36156
36137
|
}
|
|
36157
36138
|
return Math.abs((rightPrice - leftPrice) / leftPrice * 100);
|
|
36158
36139
|
};
|
|
36159
|
-
loadExchangeEntriesForComparison = async ({
|
|
36160
|
-
connector,
|
|
36161
|
-
startTime,
|
|
36162
|
-
endTime
|
|
36163
|
-
}) => {
|
|
36164
|
-
const entryRows = await loadExchangeEntryRows({
|
|
36165
|
-
connector,
|
|
36166
|
-
startTime,
|
|
36167
|
-
endTime
|
|
36168
|
-
});
|
|
36169
|
-
const closedPnlRows = await loadClosedPnlRows({
|
|
36170
|
-
connector,
|
|
36171
|
-
startTime,
|
|
36172
|
-
endTime,
|
|
36173
|
-
callbacks: {
|
|
36174
|
-
onUnsupported: () => {
|
|
36175
|
-
console.log(
|
|
36176
|
-
import_chalk48.default.yellow(
|
|
36177
|
-
"runtime compare: connector does not support getClosedPnl, using runtime trade records as-is"
|
|
36178
|
-
)
|
|
36179
|
-
);
|
|
36180
|
-
},
|
|
36181
|
-
onCapped: () => {
|
|
36182
|
-
console.log(
|
|
36183
|
-
import_chalk48.default.yellow(
|
|
36184
|
-
"runtime compare: exchange closed pnl returned 100 rows (connector cap); older closed trades in the window may be truncated"
|
|
36185
|
-
)
|
|
36186
|
-
);
|
|
36187
|
-
},
|
|
36188
|
-
onError: (error) => {
|
|
36189
|
-
console.log(
|
|
36190
|
-
import_chalk48.default.yellow(
|
|
36191
|
-
`runtime compare: getClosedPnl failed: ${formatRuntimeTradeSyncError(error)}`
|
|
36192
|
-
)
|
|
36193
|
-
);
|
|
36194
|
-
}
|
|
36195
|
-
}
|
|
36196
|
-
});
|
|
36197
|
-
const closedPnlByOrderId = new Map(
|
|
36198
|
-
closedPnlRows.filter((row) => typeof row.orderId === "string" && row.orderId.trim()).map((row) => [row.orderId, row])
|
|
36199
|
-
);
|
|
36200
|
-
return entryRows.map((entry) => {
|
|
36201
|
-
const closedPnl = typeof entry.orderId === "string" ? closedPnlByOrderId.get(entry.orderId) : null;
|
|
36202
|
-
return {
|
|
36203
|
-
...entry,
|
|
36204
|
-
exitPrice: closedPnl?.exitPrice ?? entry.exitPrice ?? null,
|
|
36205
|
-
exitTimestamp: closedPnl?.closedAt ?? entry.exitTimestamp ?? null,
|
|
36206
|
-
closedPnl: closedPnl?.closedPnl ?? entry.closedPnl ?? null,
|
|
36207
|
-
openFee: closedPnl?.openFee ?? entry.openFee ?? null,
|
|
36208
|
-
closeFee: closedPnl?.closeFee ?? entry.closeFee ?? null,
|
|
36209
|
-
fundingFee: closedPnl?.fundingFee ?? entry.fundingFee ?? null,
|
|
36210
|
-
totalFee: closedPnl?.totalFee ?? entry.totalFee ?? null
|
|
36211
|
-
};
|
|
36212
|
-
});
|
|
36213
|
-
};
|
|
36214
36140
|
compareExchangeEntriesToBacktest = ({
|
|
36215
36141
|
exchangeEntries,
|
|
36216
36142
|
backtestEntries,
|
|
@@ -36243,30 +36169,20 @@ var init_runtimeComparison = __esm({
|
|
|
36243
36169
|
const exchangeGroup = [...groupedExchange.get(key) ?? []].sort(
|
|
36244
36170
|
(left, right) => left.entryTimestamp - right.entryTimestamp
|
|
36245
36171
|
);
|
|
36246
|
-
const
|
|
36247
|
-
(left, right) => left.timestamp - right.timestamp
|
|
36248
|
-
);
|
|
36249
|
-
const unmatchedBacktest = availableBacktest.map((entry) => ({
|
|
36250
|
-
entry,
|
|
36251
|
-
used: false
|
|
36252
|
-
}));
|
|
36172
|
+
const unmatchedBacktest = [...groupedBacktest.get(key) ?? []].sort((left, right) => left.timestamp - right.timestamp).map((entry) => ({ entry, used: false }));
|
|
36253
36173
|
for (const exchangeEntry of exchangeGroup) {
|
|
36254
36174
|
let bestIndex = -1;
|
|
36255
36175
|
let bestDiff = Number.POSITIVE_INFINITY;
|
|
36256
36176
|
for (let index = 0; index < unmatchedBacktest.length; index += 1) {
|
|
36257
36177
|
const candidate = unmatchedBacktest[index];
|
|
36258
|
-
if (candidate.used)
|
|
36259
|
-
continue;
|
|
36260
|
-
}
|
|
36178
|
+
if (candidate.used) continue;
|
|
36261
36179
|
const diff = Math.abs(
|
|
36262
36180
|
getBacktestParityComparisonTimestamp(
|
|
36263
36181
|
candidate.entry,
|
|
36264
36182
|
backtestTimestampOffsetMs
|
|
36265
36183
|
) - exchangeEntry.entryTimestamp
|
|
36266
36184
|
);
|
|
36267
|
-
if (diff > toleranceMs || diff >= bestDiff)
|
|
36268
|
-
continue;
|
|
36269
|
-
}
|
|
36185
|
+
if (diff > toleranceMs || diff >= bestDiff) continue;
|
|
36270
36186
|
bestIndex = index;
|
|
36271
36187
|
bestDiff = diff;
|
|
36272
36188
|
}
|
|
@@ -36289,9 +36205,7 @@ var init_runtimeComparison = __esm({
|
|
|
36289
36205
|
}
|
|
36290
36206
|
}
|
|
36291
36207
|
for (const entry of backtestEntries) {
|
|
36292
|
-
if (!matchedBacktestEntries.has(entry))
|
|
36293
|
-
backtestOnly.push(entry);
|
|
36294
|
-
}
|
|
36208
|
+
if (!matchedBacktestEntries.has(entry)) backtestOnly.push(entry);
|
|
36295
36209
|
}
|
|
36296
36210
|
matched.sort(
|
|
36297
36211
|
(left, right) => left.exchange.entryTimestamp - right.exchange.entryTimestamp || left.backtest.strategy.localeCompare(right.backtest.strategy)
|
|
@@ -36300,11 +36214,7 @@ var init_runtimeComparison = __esm({
|
|
|
36300
36214
|
(left, right) => left.entryTimestamp - right.entryTimestamp
|
|
36301
36215
|
);
|
|
36302
36216
|
backtestOnly.sort((left, right) => left.timestamp - right.timestamp);
|
|
36303
|
-
return {
|
|
36304
|
-
matched,
|
|
36305
|
-
exchangeOnly,
|
|
36306
|
-
backtestOnly
|
|
36307
|
-
};
|
|
36217
|
+
return { matched, exchangeOnly, backtestOnly };
|
|
36308
36218
|
};
|
|
36309
36219
|
findExchangeRuntimeOutcome = ({
|
|
36310
36220
|
exchangeEntry,
|
|
@@ -36324,59 +36234,496 @@ var init_runtimeComparison = __esm({
|
|
|
36324
36234
|
});
|
|
36325
36235
|
}
|
|
36326
36236
|
}
|
|
36327
|
-
for (const evaluation of runtimeSignalEvaluations) {
|
|
36328
|
-
if (evaluation.strategy === strategyName && evaluation.symbol === exchangeEntry.symbol && (evaluation.direction == null || evaluation.direction === exchangeEntry.direction)) {
|
|
36329
|
-
candidates.push({
|
|
36330
|
-
timestamp: evaluation.timestamp,
|
|
36331
|
-
orderStatus: evaluation.orderStatus,
|
|
36332
|
-
reason: evaluation.orderSkipReason || evaluation.reason || evaluation.orderStatus
|
|
36237
|
+
for (const evaluation of runtimeSignalEvaluations) {
|
|
36238
|
+
if (evaluation.strategy === strategyName && evaluation.symbol === exchangeEntry.symbol && (evaluation.direction == null || evaluation.direction === exchangeEntry.direction)) {
|
|
36239
|
+
candidates.push({
|
|
36240
|
+
timestamp: evaluation.timestamp,
|
|
36241
|
+
orderStatus: evaluation.orderStatus,
|
|
36242
|
+
reason: evaluation.orderSkipReason || evaluation.reason || evaluation.orderStatus
|
|
36243
|
+
});
|
|
36244
|
+
}
|
|
36245
|
+
}
|
|
36246
|
+
return candidates.map((candidate) => ({
|
|
36247
|
+
...candidate,
|
|
36248
|
+
timestampDiffMs: Math.abs(
|
|
36249
|
+
candidate.timestamp + signalTimestampOffsetMs - exchangeEntry.entryTimestamp
|
|
36250
|
+
)
|
|
36251
|
+
})).filter((candidate) => candidate.timestampDiffMs <= toleranceMs).sort(
|
|
36252
|
+
(left, right) => left.timestampDiffMs - right.timestampDiffMs || Number(right.orderStatus === "failed") - Number(left.orderStatus === "failed")
|
|
36253
|
+
)[0] ?? null;
|
|
36254
|
+
};
|
|
36255
|
+
hasLineageLinkedRuntimeOutcome = ({
|
|
36256
|
+
exchangeEntry,
|
|
36257
|
+
strategyName,
|
|
36258
|
+
runtimeSignals,
|
|
36259
|
+
runtimeSignalEvaluations,
|
|
36260
|
+
toleranceMs,
|
|
36261
|
+
signalTimestampOffsetMs
|
|
36262
|
+
}) => findExchangeRuntimeOutcome({
|
|
36263
|
+
exchangeEntry,
|
|
36264
|
+
strategyName,
|
|
36265
|
+
runtimeSignals,
|
|
36266
|
+
runtimeSignalEvaluations,
|
|
36267
|
+
toleranceMs,
|
|
36268
|
+
signalTimestampOffsetMs
|
|
36269
|
+
}) != null;
|
|
36270
|
+
splitExchangeMatchesByRuntimeOrderStatus = ({
|
|
36271
|
+
matched,
|
|
36272
|
+
strategyNameByOrderLinkKey,
|
|
36273
|
+
runtimeSignals,
|
|
36274
|
+
runtimeSignalEvaluations,
|
|
36275
|
+
toleranceMs,
|
|
36276
|
+
signalTimestampOffsetMs
|
|
36277
|
+
}) => {
|
|
36278
|
+
const orderFailed = [];
|
|
36279
|
+
const completed = matched.filter((item) => {
|
|
36280
|
+
const strategyName = resolveReplayStrategyNameFromExchangeEntry({
|
|
36281
|
+
exchangeEntry: item.exchange,
|
|
36282
|
+
strategyNameByOrderLinkKey
|
|
36283
|
+
});
|
|
36284
|
+
if (!strategyName) return true;
|
|
36285
|
+
const outcome = findExchangeRuntimeOutcome({
|
|
36286
|
+
exchangeEntry: item.exchange,
|
|
36287
|
+
strategyName,
|
|
36288
|
+
runtimeSignals,
|
|
36289
|
+
runtimeSignalEvaluations,
|
|
36290
|
+
toleranceMs,
|
|
36291
|
+
signalTimestampOffsetMs
|
|
36292
|
+
});
|
|
36293
|
+
if (outcome?.orderStatus !== "failed") return true;
|
|
36294
|
+
orderFailed.push({
|
|
36295
|
+
...item,
|
|
36296
|
+
reason: outcome.reason || "orderStatus=failed"
|
|
36297
|
+
});
|
|
36298
|
+
return false;
|
|
36299
|
+
});
|
|
36300
|
+
return { completed, orderFailed };
|
|
36301
|
+
};
|
|
36302
|
+
summarizeBacktestPnlByStrategy = (entries) => {
|
|
36303
|
+
const totals = /* @__PURE__ */ new Map();
|
|
36304
|
+
for (const entry of entries) {
|
|
36305
|
+
const pnl = typeof entry.expectedPnl === "number" && Number.isFinite(entry.expectedPnl) ? entry.expectedPnl : 0;
|
|
36306
|
+
totals.set(entry.strategy, (totals.get(entry.strategy) ?? 0) + pnl);
|
|
36307
|
+
}
|
|
36308
|
+
return totals;
|
|
36309
|
+
};
|
|
36310
|
+
buildExchangeComparisonRows = ({
|
|
36311
|
+
liveStrategySummaries,
|
|
36312
|
+
backtestEntries,
|
|
36313
|
+
matched,
|
|
36314
|
+
orderFailed,
|
|
36315
|
+
exchangeOnly,
|
|
36316
|
+
strategyNameByOrderLinkKey
|
|
36317
|
+
}) => {
|
|
36318
|
+
const backtestPnlByStrategy = summarizeBacktestPnlByStrategy(backtestEntries);
|
|
36319
|
+
const rowByStrategy = /* @__PURE__ */ new Map();
|
|
36320
|
+
const ensureRow = (strategyName) => {
|
|
36321
|
+
const existing = rowByStrategy.get(strategyName);
|
|
36322
|
+
if (existing) return existing;
|
|
36323
|
+
const next = {
|
|
36324
|
+
strategyName,
|
|
36325
|
+
backtestEntries: 0,
|
|
36326
|
+
backtestNetProfit: backtestPnlByStrategy.get(strategyName) ?? 0,
|
|
36327
|
+
runtimeTrades: 0,
|
|
36328
|
+
runtimePnl: 0,
|
|
36329
|
+
matched: 0,
|
|
36330
|
+
orderFailed: 0,
|
|
36331
|
+
runtimeOnly: 0,
|
|
36332
|
+
backtestOnly: 0
|
|
36333
|
+
};
|
|
36334
|
+
rowByStrategy.set(strategyName, next);
|
|
36335
|
+
return next;
|
|
36336
|
+
};
|
|
36337
|
+
for (const summary of liveStrategySummaries) ensureRow(summary.strategyName);
|
|
36338
|
+
for (const entry of backtestEntries) {
|
|
36339
|
+
ensureRow(entry.strategy).backtestEntries += 1;
|
|
36340
|
+
}
|
|
36341
|
+
for (const item of matched) {
|
|
36342
|
+
const row = ensureRow(item.backtest.strategy);
|
|
36343
|
+
row.runtimeTrades += 1;
|
|
36344
|
+
row.matched += 1;
|
|
36345
|
+
if (typeof item.exchange.closedPnl === "number" && Number.isFinite(item.exchange.closedPnl)) {
|
|
36346
|
+
row.runtimePnl += item.exchange.closedPnl;
|
|
36347
|
+
}
|
|
36348
|
+
}
|
|
36349
|
+
for (const item of orderFailed) {
|
|
36350
|
+
const row = ensureRow(item.backtest.strategy);
|
|
36351
|
+
row.runtimeTrades += 1;
|
|
36352
|
+
row.orderFailed += 1;
|
|
36353
|
+
}
|
|
36354
|
+
const matchedBacktestEntries = /* @__PURE__ */ new Set([
|
|
36355
|
+
...matched.map(({ backtest: backtest2 }) => backtest2),
|
|
36356
|
+
...orderFailed.map(({ backtest: backtest2 }) => backtest2)
|
|
36357
|
+
]);
|
|
36358
|
+
for (const entry of backtestEntries) {
|
|
36359
|
+
if (!matchedBacktestEntries.has(entry)) {
|
|
36360
|
+
ensureRow(entry.strategy).backtestOnly += 1;
|
|
36361
|
+
}
|
|
36362
|
+
}
|
|
36363
|
+
for (const entry of exchangeOnly) {
|
|
36364
|
+
const strategyName = resolveReplayStrategyNameFromExchangeEntry({
|
|
36365
|
+
exchangeEntry: entry,
|
|
36366
|
+
strategyNameByOrderLinkKey
|
|
36367
|
+
}) ?? "[exchange-unmatched]";
|
|
36368
|
+
const row = ensureRow(strategyName);
|
|
36369
|
+
row.runtimeTrades += 1;
|
|
36370
|
+
row.runtimeOnly += 1;
|
|
36371
|
+
if (typeof entry.closedPnl === "number" && Number.isFinite(entry.closedPnl)) {
|
|
36372
|
+
row.runtimePnl += entry.closedPnl;
|
|
36373
|
+
}
|
|
36374
|
+
}
|
|
36375
|
+
return [...rowByStrategy.values()].map((row) => ({
|
|
36376
|
+
...row,
|
|
36377
|
+
runtimePnl: Number(row.runtimePnl.toFixed(2))
|
|
36378
|
+
})).sort((left, right) => left.strategyName.localeCompare(right.strategyName));
|
|
36379
|
+
};
|
|
36380
|
+
buildRuntimeComparisonRows = ({
|
|
36381
|
+
liveStrategySummaries,
|
|
36382
|
+
runtimeSummaries,
|
|
36383
|
+
parityRows,
|
|
36384
|
+
backtestEntries
|
|
36385
|
+
}) => {
|
|
36386
|
+
const runtimeSummaryByStrategy = new Map(
|
|
36387
|
+
runtimeSummaries.map((summary) => [summary.strategyName, summary])
|
|
36388
|
+
);
|
|
36389
|
+
const parityByStrategy = new Map(parityRows);
|
|
36390
|
+
const backtestPnlByStrategy = summarizeBacktestPnlByStrategy(backtestEntries);
|
|
36391
|
+
const strategyNames = /* @__PURE__ */ new Set([
|
|
36392
|
+
...liveStrategySummaries.map(({ strategyName }) => strategyName),
|
|
36393
|
+
...runtimeSummaryByStrategy.keys(),
|
|
36394
|
+
...parityByStrategy.keys()
|
|
36395
|
+
]);
|
|
36396
|
+
return [...strategyNames].sort((left, right) => left.localeCompare(right)).map((strategyName) => {
|
|
36397
|
+
const runtimeSummary = runtimeSummaryByStrategy.get(strategyName);
|
|
36398
|
+
const parity = parityByStrategy.get(strategyName);
|
|
36399
|
+
return {
|
|
36400
|
+
strategyName,
|
|
36401
|
+
backtestEntries: parity?.backtest ?? 0,
|
|
36402
|
+
backtestNetProfit: backtestPnlByStrategy.get(strategyName) ?? 0,
|
|
36403
|
+
runtimeTrades: runtimeSummary?.trades ?? 0,
|
|
36404
|
+
runtimePnl: runtimeSummary?.totalPnl ?? 0,
|
|
36405
|
+
matched: parity?.matched ?? 0,
|
|
36406
|
+
orderFailed: 0,
|
|
36407
|
+
runtimeOnly: parity?.runtimeOnly ?? 0,
|
|
36408
|
+
backtestOnly: parity?.backtestOnly ?? 0
|
|
36409
|
+
};
|
|
36410
|
+
});
|
|
36411
|
+
};
|
|
36412
|
+
}
|
|
36413
|
+
});
|
|
36414
|
+
|
|
36415
|
+
// src/lib/replay/runtimeComparisonReporting.ts
|
|
36416
|
+
var import_chalk49, import_time13, formatDrilldownSummary, colorizeRows, buildReplayComparisonReport, buildNoExchangeEntriesReport, buildNoRuntimeTradesReport, writeReplayComparisonReport;
|
|
36417
|
+
var init_runtimeComparisonReporting = __esm({
|
|
36418
|
+
"src/lib/replay/runtimeComparisonReporting.ts"() {
|
|
36419
|
+
"use strict";
|
|
36420
|
+
import_chalk49 = __toESM(require("chalk"));
|
|
36421
|
+
import_time13 = require("@tradejs/core/time");
|
|
36422
|
+
init_runFormatting();
|
|
36423
|
+
init_support();
|
|
36424
|
+
formatDrilldownSummary = (summary) => Object.entries(summary).filter(([, count]) => count > 0).map(([classification, count]) => `${classification}=${count}`).join(", ");
|
|
36425
|
+
colorizeRows = (rows) => rows.map((row) => {
|
|
36426
|
+
const btPnlColor = row.backtestNetProfit > 0 ? import_chalk49.default.green : row.backtestNetProfit < 0 ? import_chalk49.default.red : import_chalk49.default.gray;
|
|
36427
|
+
const rtPnlColor = row.runtimePnl > 0 ? import_chalk49.default.green : row.runtimePnl < 0 ? import_chalk49.default.red : import_chalk49.default.gray;
|
|
36428
|
+
return [
|
|
36429
|
+
import_chalk49.default.blue(row.strategyName),
|
|
36430
|
+
import_chalk49.default.cyan(String(row.backtestEntries)),
|
|
36431
|
+
btPnlColor(`${row.backtestNetProfit.toFixed(2)}$`),
|
|
36432
|
+
import_chalk49.default.yellow(String(row.runtimeTrades)),
|
|
36433
|
+
rtPnlColor(`${row.runtimePnl.toFixed(2)}$`),
|
|
36434
|
+
import_chalk49.default.green(String(row.matched)),
|
|
36435
|
+
import_chalk49.default.red(String(row.orderFailed)),
|
|
36436
|
+
import_chalk49.default.yellow(String(row.runtimeOnly)),
|
|
36437
|
+
import_chalk49.default.magenta(String(row.backtestOnly)),
|
|
36438
|
+
import_chalk49.default.red(String(getReplayRuntimeUnmatchedCount(row)))
|
|
36439
|
+
];
|
|
36440
|
+
});
|
|
36441
|
+
buildReplayComparisonReport = (context) => {
|
|
36442
|
+
const source = context.mode === "exchange" ? "EXCHANGE" : "RUNTIME";
|
|
36443
|
+
const inference = context.mode === "exchange" ? ", inferredStrategy=orderLinkId | nearest backtest entry" : "";
|
|
36444
|
+
const runtimeOnlyLabel = context.mode === "exchange" ? "exchangeOnly" : "runtimeOnly";
|
|
36445
|
+
const runtimeOnlyDrilldownSummary = formatDrilldownSummary(
|
|
36446
|
+
context.details?.mismatchDrilldown?.summary.runtimeOnly ?? {}
|
|
36447
|
+
);
|
|
36448
|
+
const backtestOnlyDrilldownSummary = formatDrilldownSummary(
|
|
36449
|
+
context.details?.mismatchDrilldown?.summary.backtestOnly ?? {}
|
|
36450
|
+
);
|
|
36451
|
+
const lines = [
|
|
36452
|
+
"",
|
|
36453
|
+
`SIGNALS REPLAY VS ${source} BY STRATEGY (connector=${context.connectorName}${inference}, tolerance=${formatReplayRuntimeCompareTolerance()})`,
|
|
36454
|
+
createTable(REPLAY_RUNTIME_COMPARISON_HEADERS, colorizeRows(context.rows))
|
|
36455
|
+
];
|
|
36456
|
+
if (runtimeOnlyDrilldownSummary || backtestOnlyDrilldownSummary) {
|
|
36457
|
+
lines.push(
|
|
36458
|
+
import_chalk49.default.gray(
|
|
36459
|
+
`Mismatch drilldown: ${runtimeOnlyLabel}=[${runtimeOnlyDrilldownSummary || "none"}], backtestOnly=[${backtestOnlyDrilldownSummary || "none"}]`
|
|
36460
|
+
)
|
|
36461
|
+
);
|
|
36462
|
+
}
|
|
36463
|
+
lines.push("");
|
|
36464
|
+
return lines;
|
|
36465
|
+
};
|
|
36466
|
+
buildNoExchangeEntriesReport = ({
|
|
36467
|
+
connectorName,
|
|
36468
|
+
window
|
|
36469
|
+
}) => [
|
|
36470
|
+
"",
|
|
36471
|
+
import_chalk49.default.yellow(
|
|
36472
|
+
`SIGNALS REPLAY VS EXCHANGE: no lineage-linked exchange entry executions found for ${connectorName} in ${(0, import_time13.formatUnix)(
|
|
36473
|
+
window.start
|
|
36474
|
+
)} -> ${(0, import_time13.formatUnix)(window.end)}`
|
|
36475
|
+
),
|
|
36476
|
+
""
|
|
36477
|
+
];
|
|
36478
|
+
buildNoRuntimeTradesReport = ({
|
|
36479
|
+
connectorName,
|
|
36480
|
+
window
|
|
36481
|
+
}) => [
|
|
36482
|
+
"",
|
|
36483
|
+
import_chalk49.default.yellow(
|
|
36484
|
+
`SIGNALS REPLAY VS RUNTIME: no local runtime trades found for ${connectorName} in ${(0, import_time13.formatUnix)(
|
|
36485
|
+
window.start
|
|
36486
|
+
)} -> ${(0, import_time13.formatUnix)(window.end)} with matching lineage; checking lineage-linked exchange executions`
|
|
36487
|
+
),
|
|
36488
|
+
""
|
|
36489
|
+
];
|
|
36490
|
+
writeReplayComparisonReport = (lines) => {
|
|
36491
|
+
for (const line of lines) {
|
|
36492
|
+
console.log(line);
|
|
36493
|
+
}
|
|
36494
|
+
};
|
|
36495
|
+
}
|
|
36496
|
+
});
|
|
36497
|
+
|
|
36498
|
+
// src/lib/replay/runtimeComparison.ts
|
|
36499
|
+
var import_chalk50, getReplayEntryTimestampCompareOffsetMs, buildReplaySignalEvaluations, toFiniteNumberOrNull4, sumOptionalNumbers, firstFiniteNumber, firstString, getExchangeExecutionMergeKey, mergeExchangeExecutionGroup, mergeExchangeEntryExecutions, loadExchangeEntryRows, loadExchangeEntriesForComparison, saveAndPrintReplayExchangeComparison, saveAndPrintReplayRuntimeComparison;
|
|
36500
|
+
var init_runtimeComparison = __esm({
|
|
36501
|
+
"src/lib/replay/runtimeComparison.ts"() {
|
|
36502
|
+
"use strict";
|
|
36503
|
+
import_chalk50 = __toESM(require("chalk"));
|
|
36504
|
+
init_runtimeParity();
|
|
36505
|
+
init_paritySummary();
|
|
36506
|
+
init_runtimeRedis();
|
|
36507
|
+
init_runtimeSignalsLoader();
|
|
36508
|
+
init_runtimeTradeSync();
|
|
36509
|
+
init_runtimeParityDetails();
|
|
36510
|
+
init_runState();
|
|
36511
|
+
init_cliConfig2();
|
|
36512
|
+
init_runtimeEvidenceSource();
|
|
36513
|
+
init_support();
|
|
36514
|
+
init_runtimeComparisonCalculations();
|
|
36515
|
+
init_runtimeComparisonReporting();
|
|
36516
|
+
init_runtimeComparisonCalculations();
|
|
36517
|
+
getReplayEntryTimestampCompareOffsetMs = () => {
|
|
36518
|
+
const intervalMinutes = Number(replayInterval);
|
|
36519
|
+
return Number.isFinite(intervalMinutes) && intervalMinutes > 0 ? intervalMinutes * 60 * 1e3 : 15 * 60 * 1e3;
|
|
36520
|
+
};
|
|
36521
|
+
buildReplaySignalEvaluations = (signals2) => signals2.map((signal) => ({
|
|
36522
|
+
evaluationId: `${signal.strategy}:${signal.symbol}:${signal.timestamp}`,
|
|
36523
|
+
userName: replayUserName,
|
|
36524
|
+
strategy: signal.strategy,
|
|
36525
|
+
symbol: signal.symbol,
|
|
36526
|
+
interval: signal.interval,
|
|
36527
|
+
timestamp: signal.timestamp,
|
|
36528
|
+
evaluatedAt: signal.timestamp,
|
|
36529
|
+
status: "signal",
|
|
36530
|
+
reason: signal.orderSkipReason || signal.orderStatus || "SIGNAL",
|
|
36531
|
+
signalId: signal.signalId,
|
|
36532
|
+
direction: signal.direction,
|
|
36533
|
+
accountId: signal.accountId,
|
|
36534
|
+
deploymentId: signal.deploymentId,
|
|
36535
|
+
runtimeLineage: signal.runtimeLineage,
|
|
36536
|
+
orderStatus: signal.orderStatus,
|
|
36537
|
+
orderSkipReason: signal.orderSkipReason,
|
|
36538
|
+
...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
|
|
36539
|
+
...signal.ml ? { ml: signal.ml } : {}
|
|
36540
|
+
}));
|
|
36541
|
+
toFiniteNumberOrNull4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
36542
|
+
sumOptionalNumbers = (values) => {
|
|
36543
|
+
let sum3 = 0;
|
|
36544
|
+
let hasValue = false;
|
|
36545
|
+
for (const value of values) {
|
|
36546
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
36547
|
+
continue;
|
|
36548
|
+
}
|
|
36549
|
+
sum3 += value;
|
|
36550
|
+
hasValue = true;
|
|
36551
|
+
}
|
|
36552
|
+
return hasValue ? Number(sum3.toFixed(12)) : null;
|
|
36553
|
+
};
|
|
36554
|
+
firstFiniteNumber = (values) => {
|
|
36555
|
+
for (const value of values) {
|
|
36556
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
36557
|
+
return value;
|
|
36558
|
+
}
|
|
36559
|
+
}
|
|
36560
|
+
return null;
|
|
36561
|
+
};
|
|
36562
|
+
firstString = (values) => values.find((value) => typeof value === "string" && value.trim());
|
|
36563
|
+
getExchangeExecutionMergeKey = (entry) => {
|
|
36564
|
+
const orderId = firstString([entry.orderId, entry.orderLinkId]);
|
|
36565
|
+
return orderId ? `${entry.symbol}::${entry.direction}::${orderId}` : null;
|
|
36566
|
+
};
|
|
36567
|
+
mergeExchangeExecutionGroup = (group) => {
|
|
36568
|
+
const [first] = group;
|
|
36569
|
+
const qty = sumOptionalNumbers(group.map((entry) => entry.qty)) ?? first.qty;
|
|
36570
|
+
const weightedPriceSum = group.reduce((sum3, entry) => {
|
|
36571
|
+
const entryQty = toFiniteNumberOrNull4(entry.qty);
|
|
36572
|
+
const entryPrice2 = toFiniteNumberOrNull4(entry.entryPrice);
|
|
36573
|
+
return entryQty != null && entryPrice2 != null ? sum3 + entryQty * entryPrice2 : sum3;
|
|
36574
|
+
}, 0);
|
|
36575
|
+
const entryPrice = qty > 0 && weightedPriceSum > 0 ? Number((weightedPriceSum / qty).toFixed(12)) : firstFiniteNumber(group.map((entry) => entry.entryPrice));
|
|
36576
|
+
const openFee = sumOptionalNumbers(group.map((entry) => entry.openFee));
|
|
36577
|
+
const closeFee = sumOptionalNumbers(group.map((entry) => entry.closeFee));
|
|
36578
|
+
const fundingFee = firstFiniteNumber(group.map((entry) => entry.fundingFee));
|
|
36579
|
+
const totalFee = sumOptionalNumbers([openFee, closeFee, fundingFee]) ?? firstFiniteNumber(group.map((entry) => entry.totalFee));
|
|
36580
|
+
return {
|
|
36581
|
+
symbol: first.symbol,
|
|
36582
|
+
direction: first.direction,
|
|
36583
|
+
qty,
|
|
36584
|
+
entryPrice,
|
|
36585
|
+
entryTimestamp: Math.min(...group.map((entry) => entry.entryTimestamp)),
|
|
36586
|
+
...firstString(group.map((entry) => entry.orderId)) ? { orderId: firstString(group.map((entry) => entry.orderId)) } : {},
|
|
36587
|
+
...firstString(group.map((entry) => entry.orderLinkId)) ? { orderLinkId: firstString(group.map((entry) => entry.orderLinkId)) } : {},
|
|
36588
|
+
...firstFiniteNumber(group.map((entry) => entry.takeProfitPrice)) != null ? {
|
|
36589
|
+
takeProfitPrice: firstFiniteNumber(
|
|
36590
|
+
group.map((entry) => entry.takeProfitPrice)
|
|
36591
|
+
)
|
|
36592
|
+
} : {},
|
|
36593
|
+
...firstFiniteNumber(group.map((entry) => entry.stopLossPrice)) != null ? {
|
|
36594
|
+
stopLossPrice: firstFiniteNumber(
|
|
36595
|
+
group.map((entry) => entry.stopLossPrice)
|
|
36596
|
+
)
|
|
36597
|
+
} : {},
|
|
36598
|
+
...firstFiniteNumber(group.map((entry) => entry.exitPrice)) != null ? { exitPrice: firstFiniteNumber(group.map((entry) => entry.exitPrice)) } : {},
|
|
36599
|
+
...firstFiniteNumber(group.map((entry) => entry.exitTimestamp)) != null ? {
|
|
36600
|
+
exitTimestamp: firstFiniteNumber(
|
|
36601
|
+
group.map((entry) => entry.exitTimestamp)
|
|
36602
|
+
)
|
|
36603
|
+
} : {},
|
|
36604
|
+
...sumOptionalNumbers(group.map((entry) => entry.closedPnl)) != null ? { closedPnl: sumOptionalNumbers(group.map((entry) => entry.closedPnl)) } : {},
|
|
36605
|
+
...openFee != null ? { openFee } : {},
|
|
36606
|
+
...closeFee != null ? { closeFee } : {},
|
|
36607
|
+
...fundingFee != null ? { fundingFee } : {},
|
|
36608
|
+
...totalFee != null ? { totalFee } : {}
|
|
36609
|
+
};
|
|
36610
|
+
};
|
|
36611
|
+
mergeExchangeEntryExecutions = (rows) => {
|
|
36612
|
+
const mergedRows = [];
|
|
36613
|
+
const groups = /* @__PURE__ */ new Map();
|
|
36614
|
+
for (const row of rows) {
|
|
36615
|
+
const key = getExchangeExecutionMergeKey(row);
|
|
36616
|
+
if (!key) {
|
|
36617
|
+
mergedRows.push(row);
|
|
36618
|
+
continue;
|
|
36619
|
+
}
|
|
36620
|
+
const group = groups.get(key) ?? [];
|
|
36621
|
+
group.push(row);
|
|
36622
|
+
groups.set(key, group);
|
|
36623
|
+
}
|
|
36624
|
+
for (const group of groups.values()) {
|
|
36625
|
+
mergedRows.push(
|
|
36626
|
+
group.length === 1 ? group[0] : mergeExchangeExecutionGroup(group)
|
|
36627
|
+
);
|
|
36628
|
+
}
|
|
36629
|
+
return mergedRows.sort(
|
|
36630
|
+
(left, right) => left.entryTimestamp - right.entryTimestamp
|
|
36631
|
+
);
|
|
36632
|
+
};
|
|
36633
|
+
loadExchangeEntryRows = async ({
|
|
36634
|
+
connector,
|
|
36635
|
+
startTime,
|
|
36636
|
+
endTime
|
|
36637
|
+
}) => {
|
|
36638
|
+
if (typeof connector.getEntryExecutions !== "function") {
|
|
36639
|
+
console.log(
|
|
36640
|
+
import_chalk50.default.yellow(
|
|
36641
|
+
"runtime compare: connector does not support entry execution history"
|
|
36642
|
+
)
|
|
36643
|
+
);
|
|
36644
|
+
return [];
|
|
36645
|
+
}
|
|
36646
|
+
const rows = [];
|
|
36647
|
+
const chunks = splitExchangeHistoryTimeRange({ startTime, endTime });
|
|
36648
|
+
for (const chunk of chunks) {
|
|
36649
|
+
try {
|
|
36650
|
+
const chunkRows = await connector.getEntryExecutions({
|
|
36651
|
+
startTime: chunk.startTime,
|
|
36652
|
+
endTime: chunk.endTime,
|
|
36653
|
+
limit: 100
|
|
36333
36654
|
});
|
|
36655
|
+
if (chunkRows.length >= 100) {
|
|
36656
|
+
console.log(
|
|
36657
|
+
import_chalk50.default.yellow(
|
|
36658
|
+
"runtime compare: exchange entry executions returned 100 rows (connector cap); older entry trades in this chunk may be truncated"
|
|
36659
|
+
)
|
|
36660
|
+
);
|
|
36661
|
+
}
|
|
36662
|
+
rows.push(...chunkRows);
|
|
36663
|
+
} catch (error) {
|
|
36664
|
+
console.log(
|
|
36665
|
+
import_chalk50.default.yellow(
|
|
36666
|
+
`runtime compare: getEntryExecutions failed: ${formatRuntimeTradeSyncError(error)}`
|
|
36667
|
+
)
|
|
36668
|
+
);
|
|
36334
36669
|
}
|
|
36335
36670
|
}
|
|
36336
|
-
return
|
|
36337
|
-
...candidate,
|
|
36338
|
-
timestampDiffMs: Math.abs(
|
|
36339
|
-
candidate.timestamp + signalTimestampOffsetMs - exchangeEntry.entryTimestamp
|
|
36340
|
-
)
|
|
36341
|
-
})).filter((candidate) => candidate.timestampDiffMs <= toleranceMs).sort(
|
|
36342
|
-
(left, right) => left.timestampDiffMs - right.timestampDiffMs || Number(right.orderStatus === "failed") - Number(left.orderStatus === "failed")
|
|
36343
|
-
)[0] ?? null;
|
|
36671
|
+
return mergeExchangeEntryExecutions(rows);
|
|
36344
36672
|
};
|
|
36345
|
-
|
|
36346
|
-
|
|
36347
|
-
|
|
36348
|
-
|
|
36349
|
-
runtimeSignalEvaluations,
|
|
36350
|
-
toleranceMs,
|
|
36351
|
-
signalTimestampOffsetMs
|
|
36673
|
+
loadExchangeEntriesForComparison = async ({
|
|
36674
|
+
connector,
|
|
36675
|
+
startTime,
|
|
36676
|
+
endTime
|
|
36352
36677
|
}) => {
|
|
36353
|
-
const
|
|
36354
|
-
|
|
36355
|
-
|
|
36356
|
-
|
|
36357
|
-
|
|
36358
|
-
|
|
36359
|
-
|
|
36360
|
-
|
|
36361
|
-
|
|
36362
|
-
|
|
36363
|
-
|
|
36364
|
-
|
|
36365
|
-
|
|
36366
|
-
|
|
36367
|
-
|
|
36368
|
-
|
|
36369
|
-
|
|
36370
|
-
|
|
36371
|
-
|
|
36678
|
+
const entryRows = await loadExchangeEntryRows({
|
|
36679
|
+
connector,
|
|
36680
|
+
startTime,
|
|
36681
|
+
endTime
|
|
36682
|
+
});
|
|
36683
|
+
const closedPnlRows = await loadClosedPnlRows({
|
|
36684
|
+
connector,
|
|
36685
|
+
startTime,
|
|
36686
|
+
endTime,
|
|
36687
|
+
callbacks: {
|
|
36688
|
+
onUnsupported: () => {
|
|
36689
|
+
console.log(
|
|
36690
|
+
import_chalk50.default.yellow(
|
|
36691
|
+
"runtime compare: connector does not support getClosedPnl, using runtime trade records as-is"
|
|
36692
|
+
)
|
|
36693
|
+
);
|
|
36694
|
+
},
|
|
36695
|
+
onCapped: () => {
|
|
36696
|
+
console.log(
|
|
36697
|
+
import_chalk50.default.yellow(
|
|
36698
|
+
"runtime compare: exchange closed pnl returned 100 rows (connector cap); older closed trades in the window may be truncated"
|
|
36699
|
+
)
|
|
36700
|
+
);
|
|
36701
|
+
},
|
|
36702
|
+
onError: (error) => {
|
|
36703
|
+
console.log(
|
|
36704
|
+
import_chalk50.default.yellow(
|
|
36705
|
+
`runtime compare: getClosedPnl failed: ${formatRuntimeTradeSyncError(error)}`
|
|
36706
|
+
)
|
|
36707
|
+
);
|
|
36708
|
+
}
|
|
36372
36709
|
}
|
|
36373
|
-
orderFailed.push({
|
|
36374
|
-
...item,
|
|
36375
|
-
reason: outcome.reason || "orderStatus=failed"
|
|
36376
|
-
});
|
|
36377
|
-
return false;
|
|
36378
36710
|
});
|
|
36379
|
-
|
|
36711
|
+
const closedPnlByOrderId = new Map(
|
|
36712
|
+
closedPnlRows.filter((row) => typeof row.orderId === "string" && row.orderId.trim()).map((row) => [row.orderId, row])
|
|
36713
|
+
);
|
|
36714
|
+
return entryRows.map((entry) => {
|
|
36715
|
+
const closedPnl = typeof entry.orderId === "string" ? closedPnlByOrderId.get(entry.orderId) : null;
|
|
36716
|
+
return {
|
|
36717
|
+
...entry,
|
|
36718
|
+
exitPrice: closedPnl?.exitPrice ?? entry.exitPrice ?? null,
|
|
36719
|
+
exitTimestamp: closedPnl?.closedAt ?? entry.exitTimestamp ?? null,
|
|
36720
|
+
closedPnl: closedPnl?.closedPnl ?? entry.closedPnl ?? null,
|
|
36721
|
+
openFee: closedPnl?.openFee ?? entry.openFee ?? null,
|
|
36722
|
+
closeFee: closedPnl?.closeFee ?? entry.closeFee ?? null,
|
|
36723
|
+
fundingFee: closedPnl?.fundingFee ?? entry.fundingFee ?? null,
|
|
36724
|
+
totalFee: closedPnl?.totalFee ?? entry.totalFee ?? null
|
|
36725
|
+
};
|
|
36726
|
+
});
|
|
36380
36727
|
};
|
|
36381
36728
|
saveAndPrintReplayExchangeComparison = async ({
|
|
36382
36729
|
liveStrategySummaries,
|
|
@@ -36396,7 +36743,6 @@ var init_runtimeComparison = __esm({
|
|
|
36396
36743
|
const strategyNameByOrderLinkKey = buildStrategyNameByOrderLinkKey(
|
|
36397
36744
|
liveStrategySummaries.map((summary) => summary.strategyName)
|
|
36398
36745
|
);
|
|
36399
|
-
const exchangeOutcomeByEntry = /* @__PURE__ */ new Map();
|
|
36400
36746
|
const exchangeEntries = rawExchangeEntries.filter((entry) => {
|
|
36401
36747
|
const strategyName = resolveReplayStrategyNameFromExchangeEntry({
|
|
36402
36748
|
exchangeEntry: entry,
|
|
@@ -36405,7 +36751,7 @@ var init_runtimeComparison = __esm({
|
|
|
36405
36751
|
if (!strategyName) {
|
|
36406
36752
|
return false;
|
|
36407
36753
|
}
|
|
36408
|
-
|
|
36754
|
+
return hasLineageLinkedRuntimeOutcome({
|
|
36409
36755
|
exchangeEntry: entry,
|
|
36410
36756
|
strategyName,
|
|
36411
36757
|
runtimeSignals,
|
|
@@ -36413,24 +36759,18 @@ var init_runtimeComparison = __esm({
|
|
|
36413
36759
|
toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
|
|
36414
36760
|
signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
|
|
36415
36761
|
});
|
|
36416
|
-
exchangeOutcomeByEntry.set(entry, outcome);
|
|
36417
|
-
return outcome != null;
|
|
36418
36762
|
});
|
|
36419
36763
|
const comparisonLineage = {
|
|
36420
36764
|
...lineage,
|
|
36421
36765
|
excludedExchangeEntries: lineage.excludedExchangeEntries + rawExchangeEntries.length - exchangeEntries.length
|
|
36422
36766
|
};
|
|
36423
|
-
const backtestPnlByStrategy = summarizeBacktestPnlByStrategy(backtestEntries);
|
|
36424
36767
|
if (!exchangeEntries.length) {
|
|
36425
|
-
|
|
36426
|
-
|
|
36427
|
-
|
|
36428
|
-
|
|
36429
|
-
|
|
36430
|
-
)} -> ${(0, import_time11.formatUnix)(window.end)}`
|
|
36431
|
-
)
|
|
36768
|
+
writeReplayComparisonReport(
|
|
36769
|
+
buildNoExchangeEntriesReport({
|
|
36770
|
+
connectorName,
|
|
36771
|
+
window
|
|
36772
|
+
})
|
|
36432
36773
|
);
|
|
36433
|
-
console.log("");
|
|
36434
36774
|
const details2 = buildReplayExchangeComparisonDetails({
|
|
36435
36775
|
matched: [],
|
|
36436
36776
|
exchangeOnly: [],
|
|
@@ -36445,6 +36785,16 @@ var init_runtimeComparison = __esm({
|
|
|
36445
36785
|
replaySignals,
|
|
36446
36786
|
replaySignalEvaluations
|
|
36447
36787
|
});
|
|
36788
|
+
const emptyRowsByStrategy = new Map(
|
|
36789
|
+
buildExchangeComparisonRows({
|
|
36790
|
+
liveStrategySummaries,
|
|
36791
|
+
backtestEntries,
|
|
36792
|
+
matched: [],
|
|
36793
|
+
orderFailed: [],
|
|
36794
|
+
exchangeOnly: [],
|
|
36795
|
+
strategyNameByOrderLinkKey
|
|
36796
|
+
}).map((row) => [row.strategyName, row])
|
|
36797
|
+
);
|
|
36448
36798
|
return {
|
|
36449
36799
|
mode: "exchange",
|
|
36450
36800
|
syncedTradesCount: 0,
|
|
@@ -36457,21 +36807,9 @@ var init_runtimeComparison = __esm({
|
|
|
36457
36807
|
backtestOnlyCount: backtestEntries.length,
|
|
36458
36808
|
details: details2,
|
|
36459
36809
|
lineage: comparisonLineage,
|
|
36460
|
-
rows: liveStrategySummaries.map(
|
|
36461
|
-
strategyName
|
|
36462
|
-
|
|
36463
|
-
(entry) => entry.strategy === summary.strategyName
|
|
36464
|
-
).length,
|
|
36465
|
-
backtestNetProfit: backtestPnlByStrategy.get(summary.strategyName) ?? 0,
|
|
36466
|
-
runtimeTrades: 0,
|
|
36467
|
-
runtimePnl: 0,
|
|
36468
|
-
matched: 0,
|
|
36469
|
-
orderFailed: 0,
|
|
36470
|
-
runtimeOnly: 0,
|
|
36471
|
-
backtestOnly: backtestEntries.filter(
|
|
36472
|
-
(entry) => entry.strategy === summary.strategyName
|
|
36473
|
-
).length
|
|
36474
|
-
}))
|
|
36810
|
+
rows: liveStrategySummaries.map(
|
|
36811
|
+
({ strategyName }) => emptyRowsByStrategy.get(strategyName)
|
|
36812
|
+
)
|
|
36475
36813
|
};
|
|
36476
36814
|
}
|
|
36477
36815
|
const comparison = compareExchangeEntriesToBacktest({
|
|
@@ -36488,62 +36826,6 @@ var init_runtimeComparison = __esm({
|
|
|
36488
36826
|
toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
|
|
36489
36827
|
signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
|
|
36490
36828
|
});
|
|
36491
|
-
const rowByStrategy = /* @__PURE__ */ new Map();
|
|
36492
|
-
const ensureRow = (strategyName) => {
|
|
36493
|
-
const existing = rowByStrategy.get(strategyName);
|
|
36494
|
-
if (existing) {
|
|
36495
|
-
return existing;
|
|
36496
|
-
}
|
|
36497
|
-
const next = {
|
|
36498
|
-
strategyName,
|
|
36499
|
-
backtestEntries: 0,
|
|
36500
|
-
backtestNetProfit: backtestPnlByStrategy.get(strategyName) ?? 0,
|
|
36501
|
-
runtimeTrades: 0,
|
|
36502
|
-
runtimePnl: 0,
|
|
36503
|
-
matched: 0,
|
|
36504
|
-
orderFailed: 0,
|
|
36505
|
-
runtimeOnly: 0,
|
|
36506
|
-
backtestOnly: 0
|
|
36507
|
-
};
|
|
36508
|
-
rowByStrategy.set(strategyName, next);
|
|
36509
|
-
return next;
|
|
36510
|
-
};
|
|
36511
|
-
for (const summary of liveStrategySummaries) {
|
|
36512
|
-
ensureRow(summary.strategyName);
|
|
36513
|
-
}
|
|
36514
|
-
for (const entry of backtestEntries) {
|
|
36515
|
-
ensureRow(entry.strategy).backtestEntries += 1;
|
|
36516
|
-
}
|
|
36517
|
-
for (const item of matched) {
|
|
36518
|
-
const row = ensureRow(item.backtest.strategy);
|
|
36519
|
-
row.runtimeTrades += 1;
|
|
36520
|
-
row.matched += 1;
|
|
36521
|
-
if (typeof item.exchange.closedPnl === "number" && Number.isFinite(item.exchange.closedPnl)) {
|
|
36522
|
-
row.runtimePnl += item.exchange.closedPnl;
|
|
36523
|
-
}
|
|
36524
|
-
}
|
|
36525
|
-
for (const item of orderFailed) {
|
|
36526
|
-
const row = ensureRow(item.backtest.strategy);
|
|
36527
|
-
row.runtimeTrades += 1;
|
|
36528
|
-
row.orderFailed += 1;
|
|
36529
|
-
}
|
|
36530
|
-
for (const entry of comparison.backtestOnly) {
|
|
36531
|
-
ensureRow(entry.strategy).backtestOnly += 1;
|
|
36532
|
-
}
|
|
36533
|
-
if (comparison.exchangeOnly.length) {
|
|
36534
|
-
for (const entry of comparison.exchangeOnly) {
|
|
36535
|
-
const strategyName = resolveReplayStrategyNameFromExchangeEntry({
|
|
36536
|
-
exchangeEntry: entry,
|
|
36537
|
-
strategyNameByOrderLinkKey
|
|
36538
|
-
}) ?? "[exchange-unmatched]";
|
|
36539
|
-
const row = ensureRow(strategyName);
|
|
36540
|
-
row.runtimeTrades += 1;
|
|
36541
|
-
row.runtimeOnly += 1;
|
|
36542
|
-
if (typeof entry.closedPnl === "number" && Number.isFinite(entry.closedPnl)) {
|
|
36543
|
-
row.runtimePnl += entry.closedPnl;
|
|
36544
|
-
}
|
|
36545
|
-
}
|
|
36546
|
-
}
|
|
36547
36829
|
const details = buildReplayExchangeComparisonDetails({
|
|
36548
36830
|
matched,
|
|
36549
36831
|
orderFailed,
|
|
@@ -36559,45 +36841,22 @@ var init_runtimeComparison = __esm({
|
|
|
36559
36841
|
replaySignals,
|
|
36560
36842
|
replaySignalEvaluations
|
|
36561
36843
|
});
|
|
36562
|
-
const rows =
|
|
36563
|
-
|
|
36564
|
-
|
|
36565
|
-
|
|
36566
|
-
|
|
36567
|
-
|
|
36568
|
-
|
|
36569
|
-
return [
|
|
36570
|
-
import_chalk48.default.blue(row.strategyName),
|
|
36571
|
-
import_chalk48.default.cyan(String(row.backtestEntries)),
|
|
36572
|
-
btPnlColor(`${row.backtestNetProfit.toFixed(2)}$`),
|
|
36573
|
-
import_chalk48.default.yellow(String(row.runtimeTrades)),
|
|
36574
|
-
rtPnlColor(`${row.runtimePnl.toFixed(2)}$`),
|
|
36575
|
-
import_chalk48.default.green(String(row.matched)),
|
|
36576
|
-
import_chalk48.default.red(String(row.orderFailed)),
|
|
36577
|
-
import_chalk48.default.yellow(String(row.runtimeOnly)),
|
|
36578
|
-
import_chalk48.default.magenta(String(row.backtestOnly)),
|
|
36579
|
-
import_chalk48.default.red(String(getReplayRuntimeUnmatchedCount(row)))
|
|
36580
|
-
];
|
|
36844
|
+
const rows = buildExchangeComparisonRows({
|
|
36845
|
+
liveStrategySummaries,
|
|
36846
|
+
backtestEntries,
|
|
36847
|
+
matched,
|
|
36848
|
+
orderFailed,
|
|
36849
|
+
exchangeOnly: comparison.exchangeOnly,
|
|
36850
|
+
strategyNameByOrderLinkKey
|
|
36581
36851
|
});
|
|
36582
|
-
|
|
36583
|
-
|
|
36584
|
-
|
|
36585
|
-
|
|
36586
|
-
|
|
36587
|
-
|
|
36588
|
-
|
|
36589
|
-
);
|
|
36590
|
-
const backtestOnlyDrilldownSummary = formatDrilldownSummary(
|
|
36591
|
-
details.mismatchDrilldown?.summary.backtestOnly ?? {}
|
|
36852
|
+
writeReplayComparisonReport(
|
|
36853
|
+
buildReplayComparisonReport({
|
|
36854
|
+
mode: "exchange",
|
|
36855
|
+
connectorName,
|
|
36856
|
+
rows,
|
|
36857
|
+
details
|
|
36858
|
+
})
|
|
36592
36859
|
);
|
|
36593
|
-
if (runtimeOnlyDrilldownSummary || backtestOnlyDrilldownSummary) {
|
|
36594
|
-
console.log(
|
|
36595
|
-
import_chalk48.default.gray(
|
|
36596
|
-
`Mismatch drilldown: exchangeOnly=[${runtimeOnlyDrilldownSummary || "none"}], backtestOnly=[${backtestOnlyDrilldownSummary || "none"}]`
|
|
36597
|
-
)
|
|
36598
|
-
);
|
|
36599
|
-
}
|
|
36600
|
-
console.log("");
|
|
36601
36860
|
return {
|
|
36602
36861
|
mode: "exchange",
|
|
36603
36862
|
syncedTradesCount: exchangeEntries.length,
|
|
@@ -36664,7 +36923,7 @@ var init_runtimeComparison = __esm({
|
|
|
36664
36923
|
]);
|
|
36665
36924
|
if (runtimeEvidence2) {
|
|
36666
36925
|
console.log(
|
|
36667
|
-
|
|
36926
|
+
import_chalk50.default.gray(
|
|
36668
36927
|
`runtime compare: using immutable evidence ${runtimeEvidence2.path}`
|
|
36669
36928
|
)
|
|
36670
36929
|
);
|
|
@@ -36678,7 +36937,7 @@ var init_runtimeComparison = __esm({
|
|
|
36678
36937
|
openPositionCallbacks: {
|
|
36679
36938
|
onError: (error) => {
|
|
36680
36939
|
console.log(
|
|
36681
|
-
|
|
36940
|
+
import_chalk50.default.yellow(
|
|
36682
36941
|
`runtime compare: getOpenPositionPnl failed: ${formatRuntimeTradeSyncError(error)}; continuing without open-position mark prices`
|
|
36683
36942
|
)
|
|
36684
36943
|
);
|
|
@@ -36687,21 +36946,21 @@ var init_runtimeComparison = __esm({
|
|
|
36687
36946
|
closedPnlCallbacks: {
|
|
36688
36947
|
onUnsupported: () => {
|
|
36689
36948
|
console.log(
|
|
36690
|
-
|
|
36949
|
+
import_chalk50.default.yellow(
|
|
36691
36950
|
"runtime compare: connector does not support getClosedPnl, using runtime trade records as-is"
|
|
36692
36951
|
)
|
|
36693
36952
|
);
|
|
36694
36953
|
},
|
|
36695
36954
|
onCapped: () => {
|
|
36696
36955
|
console.log(
|
|
36697
|
-
|
|
36956
|
+
import_chalk50.default.yellow(
|
|
36698
36957
|
"runtime compare: exchange closed pnl returned 100 rows (connector cap); older closed trades in the window may be truncated"
|
|
36699
36958
|
)
|
|
36700
36959
|
);
|
|
36701
36960
|
},
|
|
36702
36961
|
onError: (error) => {
|
|
36703
36962
|
console.log(
|
|
36704
|
-
|
|
36963
|
+
import_chalk50.default.yellow(
|
|
36705
36964
|
`runtime compare: getClosedPnl failed: ${formatRuntimeTradeSyncError(error)}`
|
|
36706
36965
|
)
|
|
36707
36966
|
);
|
|
@@ -36721,15 +36980,9 @@ var init_runtimeComparison = __esm({
|
|
|
36721
36980
|
(trade) => trade.entryTimestamp >= window.start && trade.entryTimestamp < window.end && relevantStrategies.has(trade.strategy)
|
|
36722
36981
|
);
|
|
36723
36982
|
if (!windowRuntimeTrades.length && !runtimeEvidence2) {
|
|
36724
|
-
|
|
36725
|
-
|
|
36726
|
-
import_chalk48.default.yellow(
|
|
36727
|
-
`SIGNALS REPLAY VS RUNTIME: no local runtime trades found for ${connectorName} in ${(0, import_time11.formatUnix)(
|
|
36728
|
-
window.start
|
|
36729
|
-
)} -> ${(0, import_time11.formatUnix)(window.end)} with matching lineage; checking lineage-linked exchange executions`
|
|
36730
|
-
)
|
|
36983
|
+
writeReplayComparisonReport(
|
|
36984
|
+
buildNoRuntimeTradesReport({ connectorName, window })
|
|
36731
36985
|
);
|
|
36732
|
-
console.log("");
|
|
36733
36986
|
return saveAndPrintReplayExchangeComparison({
|
|
36734
36987
|
liveStrategySummaries,
|
|
36735
36988
|
backtestEntries: comparableBacktestEntries,
|
|
@@ -36741,9 +36994,6 @@ var init_runtimeComparison = __esm({
|
|
|
36741
36994
|
});
|
|
36742
36995
|
}
|
|
36743
36996
|
const runtimeSummaries = summarizeRuntimeTradesByStrategy(windowRuntimeTrades);
|
|
36744
|
-
const runtimeSummaryByStrategy = new Map(
|
|
36745
|
-
runtimeSummaries.map((summary) => [summary.strategyName, summary])
|
|
36746
|
-
);
|
|
36747
36997
|
const rawRuntimeEntries = extractRuntimeParityEntries(windowRuntimeTrades);
|
|
36748
36998
|
const runtimeDedupe = dedupeRuntimeParityEntries(rawRuntimeEntries);
|
|
36749
36999
|
const comparison = compareTradeParityEntries({
|
|
@@ -36773,68 +37023,20 @@ var init_runtimeComparison = __esm({
|
|
|
36773
37023
|
runtimeOnlyEntries: comparison.runtimeOnly,
|
|
36774
37024
|
backtestOnlyEntries: comparison.backtestOnly
|
|
36775
37025
|
});
|
|
36776
|
-
const
|
|
36777
|
-
|
|
36778
|
-
|
|
36779
|
-
|
|
36780
|
-
|
|
36781
|
-
comparableBacktestEntries
|
|
36782
|
-
);
|
|
36783
|
-
const strategyNames = /* @__PURE__ */ new Set([
|
|
36784
|
-
...liveSummaryByStrategy.keys(),
|
|
36785
|
-
...runtimeSummaryByStrategy.keys(),
|
|
36786
|
-
...parityByStrategy.keys()
|
|
36787
|
-
]);
|
|
36788
|
-
const rows = [...strategyNames].sort((left, right) => left.localeCompare(right)).map((strategyName) => {
|
|
36789
|
-
const runtimeSummary = runtimeSummaryByStrategy.get(strategyName);
|
|
36790
|
-
const parity = parityByStrategy.get(strategyName);
|
|
36791
|
-
return {
|
|
36792
|
-
strategyName,
|
|
36793
|
-
backtestEntries: parity?.backtest ?? 0,
|
|
36794
|
-
backtestNetProfit: backtestPnlByStrategy.get(strategyName) ?? 0,
|
|
36795
|
-
runtimeTrades: runtimeSummary?.trades ?? 0,
|
|
36796
|
-
runtimePnl: runtimeSummary?.totalPnl ?? 0,
|
|
36797
|
-
matched: parity?.matched ?? 0,
|
|
36798
|
-
orderFailed: 0,
|
|
36799
|
-
runtimeOnly: parity?.runtimeOnly ?? 0,
|
|
36800
|
-
backtestOnly: parity?.backtestOnly ?? 0
|
|
36801
|
-
};
|
|
36802
|
-
});
|
|
36803
|
-
const colorizedRows = rows.map((row) => {
|
|
36804
|
-
const btPnlColor = row.backtestNetProfit > 0 ? import_chalk48.default.green : row.backtestNetProfit < 0 ? import_chalk48.default.red : import_chalk48.default.gray;
|
|
36805
|
-
const rtPnlColor = row.runtimePnl > 0 ? import_chalk48.default.green : row.runtimePnl < 0 ? import_chalk48.default.red : import_chalk48.default.gray;
|
|
36806
|
-
return [
|
|
36807
|
-
import_chalk48.default.blue(row.strategyName),
|
|
36808
|
-
import_chalk48.default.cyan(String(row.backtestEntries)),
|
|
36809
|
-
btPnlColor(`${row.backtestNetProfit.toFixed(2)}$`),
|
|
36810
|
-
import_chalk48.default.yellow(String(row.runtimeTrades)),
|
|
36811
|
-
rtPnlColor(`${row.runtimePnl.toFixed(2)}$`),
|
|
36812
|
-
import_chalk48.default.green(String(row.matched)),
|
|
36813
|
-
import_chalk48.default.red(String(row.orderFailed)),
|
|
36814
|
-
import_chalk48.default.yellow(String(row.runtimeOnly)),
|
|
36815
|
-
import_chalk48.default.magenta(String(row.backtestOnly)),
|
|
36816
|
-
import_chalk48.default.red(String(getReplayRuntimeUnmatchedCount(row)))
|
|
36817
|
-
];
|
|
37026
|
+
const rows = buildRuntimeComparisonRows({
|
|
37027
|
+
liveStrategySummaries,
|
|
37028
|
+
runtimeSummaries,
|
|
37029
|
+
parityRows,
|
|
37030
|
+
backtestEntries: comparableBacktestEntries
|
|
36818
37031
|
});
|
|
36819
|
-
|
|
36820
|
-
|
|
36821
|
-
|
|
36822
|
-
|
|
36823
|
-
|
|
36824
|
-
|
|
36825
|
-
|
|
36826
|
-
);
|
|
36827
|
-
const backtestOnlyDrilldownSummary = formatDrilldownSummary(
|
|
36828
|
-
details.mismatchDrilldown?.summary.backtestOnly ?? {}
|
|
37032
|
+
writeReplayComparisonReport(
|
|
37033
|
+
buildReplayComparisonReport({
|
|
37034
|
+
mode: "runtime",
|
|
37035
|
+
connectorName,
|
|
37036
|
+
rows,
|
|
37037
|
+
details
|
|
37038
|
+
})
|
|
36829
37039
|
);
|
|
36830
|
-
if (runtimeOnlyDrilldownSummary || backtestOnlyDrilldownSummary) {
|
|
36831
|
-
console.log(
|
|
36832
|
-
import_chalk48.default.gray(
|
|
36833
|
-
`Mismatch drilldown: runtimeOnly=[${runtimeOnlyDrilldownSummary || "none"}], backtestOnly=[${backtestOnlyDrilldownSummary || "none"}]`
|
|
36834
|
-
)
|
|
36835
|
-
);
|
|
36836
|
-
}
|
|
36837
|
-
console.log("");
|
|
36838
37040
|
return {
|
|
36839
37041
|
mode: "runtime",
|
|
36840
37042
|
syncedTradesCount: syncedRuntimeTrades.length,
|
|
@@ -37280,14 +37482,14 @@ var init_outputReport = __esm({
|
|
|
37280
37482
|
});
|
|
37281
37483
|
|
|
37282
37484
|
// src/scripts/replayRunner.ts
|
|
37283
|
-
var
|
|
37485
|
+
var import_chalk51, import_constants19, import_data5, import_time14, import_redis21, prepareReplayBinanceMarketContext, finishReplay, replayBacktest;
|
|
37284
37486
|
var init_replayRunner = __esm({
|
|
37285
37487
|
"src/scripts/replayRunner.ts"() {
|
|
37286
37488
|
"use strict";
|
|
37287
|
-
|
|
37489
|
+
import_chalk51 = __toESM(require("chalk"));
|
|
37288
37490
|
import_constants19 = require("@tradejs/core/constants");
|
|
37289
37491
|
import_data5 = require("@tradejs/core/data");
|
|
37290
|
-
|
|
37492
|
+
import_time14 = require("@tradejs/core/time");
|
|
37291
37493
|
import_redis21 = require("@tradejs/infra/redis");
|
|
37292
37494
|
init_runFormatting();
|
|
37293
37495
|
init_runEnvironment();
|
|
@@ -37318,7 +37520,7 @@ var init_replayRunner = __esm({
|
|
|
37318
37520
|
aiEnabled: preparedRun.aiEnabled,
|
|
37319
37521
|
mlEnabled: preparedRun.mlEnabled,
|
|
37320
37522
|
strategyNames: preparedRun.strategyNames,
|
|
37321
|
-
log: (message) => console.log(
|
|
37523
|
+
log: (message) => console.log(import_chalk51.default.gray(message))
|
|
37322
37524
|
});
|
|
37323
37525
|
};
|
|
37324
37526
|
finishReplay = async ({
|
|
@@ -37346,9 +37548,9 @@ var init_replayRunner = __esm({
|
|
|
37346
37548
|
const replayChartSnapshot = replayFlags.chart ? buildReplayChartSnapshot({
|
|
37347
37549
|
replayResult,
|
|
37348
37550
|
generatedAt: finishedAt.getTime(),
|
|
37349
|
-
runLabel: `${(0,
|
|
37551
|
+
runLabel: `${(0, import_time14.formatUnix)(
|
|
37350
37552
|
replayResult.orderLog[0]?.timestamp ?? finishedAt.getTime()
|
|
37351
|
-
)} -> ${(0,
|
|
37553
|
+
)} -> ${(0, import_time14.formatUnix)(
|
|
37352
37554
|
replayResult.orderLog[replayResult.orderLog.length - 1]?.timestamp ?? finishedAt.getTime()
|
|
37353
37555
|
)}`
|
|
37354
37556
|
}) : null;
|
|
@@ -37371,8 +37573,8 @@ var init_replayRunner = __esm({
|
|
|
37371
37573
|
strategySnapshot: replayStrategySnapshot,
|
|
37372
37574
|
runtimeComparison: replayRuntimeComparison
|
|
37373
37575
|
});
|
|
37374
|
-
console.log(
|
|
37375
|
-
console.log(
|
|
37576
|
+
console.log(import_chalk51.default.green(`Replay report: ${outputReport.markdownPath}`));
|
|
37577
|
+
console.log(import_chalk51.default.green(`Replay report JSON: ${outputReport.jsonPath}`));
|
|
37376
37578
|
await (0, import_redis21.setData)(
|
|
37377
37579
|
replayKey,
|
|
37378
37580
|
{
|
|
@@ -37525,9 +37727,9 @@ var init_replayRunner = __esm({
|
|
|
37525
37727
|
),
|
|
37526
37728
|
strategyNames: replayStrategies.map(({ strategyName }) => strategyName)
|
|
37527
37729
|
});
|
|
37528
|
-
console.log(
|
|
37730
|
+
console.log(import_chalk51.default.yellow(`tickers: ${preparedRun.tickers.length}`));
|
|
37529
37731
|
console.log(
|
|
37530
|
-
|
|
37732
|
+
import_chalk51.default.gray(
|
|
37531
37733
|
`mode: replay (${replayStrategies.map(({ strategyName }) => strategyName).join(", ")})`
|
|
37532
37734
|
)
|
|
37533
37735
|
);
|
|
@@ -38410,12 +38612,12 @@ var user_add_exports = {};
|
|
|
38410
38612
|
__export(user_add_exports, {
|
|
38411
38613
|
main: () => main47
|
|
38412
38614
|
});
|
|
38413
|
-
var import_args34,
|
|
38615
|
+
var import_args34, import_chalk52, import_bcryptjs, import_redis22, flags31, main47;
|
|
38414
38616
|
var init_user_add = __esm({
|
|
38415
38617
|
"src/scripts/user-add.ts"() {
|
|
38416
38618
|
"use strict";
|
|
38417
38619
|
import_args34 = __toESM(require("args"));
|
|
38418
|
-
|
|
38620
|
+
import_chalk52 = __toESM(require("chalk"));
|
|
38419
38621
|
import_bcryptjs = __toESM(require("bcryptjs"));
|
|
38420
38622
|
import_redis22 = require("@tradejs/infra/redis");
|
|
38421
38623
|
import_args34.default.example("yarn user-add -u myname -p 123456", "Create or update user");
|
|
@@ -38426,7 +38628,7 @@ var init_user_add = __esm({
|
|
|
38426
38628
|
const userName3 = String(flags31.user || "").trim();
|
|
38427
38629
|
const password = String(flags31.password || "");
|
|
38428
38630
|
if (!userName3 || !password) {
|
|
38429
|
-
console.error(
|
|
38631
|
+
console.error(import_chalk52.default.red("Missing -U <user> or -P <password>"));
|
|
38430
38632
|
process.exit(1);
|
|
38431
38633
|
}
|
|
38432
38634
|
const passwordHash = await import_bcryptjs.default.hash(password, 10);
|
|
@@ -38440,7 +38642,7 @@ var init_user_add = __esm({
|
|
|
38440
38642
|
await (0, import_redis22.setData)(import_redis22.redisKeys.user(userName3), next, {
|
|
38441
38643
|
expire: 0
|
|
38442
38644
|
});
|
|
38443
|
-
console.log(
|
|
38645
|
+
console.log(import_chalk52.default.green(`User ${userName3} updated`));
|
|
38444
38646
|
process.exit(0);
|
|
38445
38647
|
};
|
|
38446
38648
|
}
|