@tradejs/cli 3.1.29-beta.286 → 3.1.29-beta.287

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 CHANGED
@@ -15259,6 +15259,513 @@ var init_backtest = __esm({
15259
15259
  }
15260
15260
  });
15261
15261
 
15262
+ // src/lib/runtimeModeConfig.ts
15263
+ var hasRuntimeEntryGateEnabled, resolveReplayStrategyEnv, buildRuntimeModeStrategyConfig;
15264
+ var init_runtimeModeConfig = __esm({
15265
+ "src/lib/runtimeModeConfig.ts"() {
15266
+ "use strict";
15267
+ hasRuntimeEntryGateEnabled = (strategyConfig) => strategyConfig.AI_ENABLED === true || strategyConfig.ML_ENABLED === true;
15268
+ resolveReplayStrategyEnv = ({
15269
+ strategyConfig,
15270
+ forceRuntimeGates = false
15271
+ }) => forceRuntimeGates || hasRuntimeEntryGateEnabled(strategyConfig) ? "PARITY" : "BACKTEST";
15272
+ buildRuntimeModeStrategyConfig = ({
15273
+ strategyConfig,
15274
+ env,
15275
+ interval: interval4,
15276
+ makeOrders,
15277
+ recordRuntimeTrades,
15278
+ aiReplayAnalyses
15279
+ }) => ({
15280
+ ...strategyConfig,
15281
+ ENV: env,
15282
+ MAKE_ORDERS: makeOrders,
15283
+ INTERVAL: strategyConfig.INTERVAL ?? interval4,
15284
+ ...typeof recordRuntimeTrades === "boolean" ? { RECORD_RUNTIME_TRADES: recordRuntimeTrades } : {},
15285
+ ...aiReplayAnalyses?.length ? { AI_REPLAY_ANALYSES: aiReplayAnalyses } : {}
15286
+ });
15287
+ }
15288
+ });
15289
+
15290
+ // src/lib/replay/support.ts
15291
+ var import_chalk16, REPLAY_RESULTS_BY_STRATEGY_HEADERS, REPLAY_RUNTIME_COMPARISON_HEADERS, REPLAY_RESULTS_CONFIG, REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS, REPLAY_RUNTIME_COMPARE_TOLERANCE_MS, formatReplayRuntimeCompareTolerance, getReplayRuntimeUnmatchedCount, buildReplayStrategyConfig, toRecord, compactReplayAdditionalIndicators, compactReplaySignal;
15292
+ var init_support = __esm({
15293
+ "src/lib/replay/support.ts"() {
15294
+ "use strict";
15295
+ import_chalk16 = __toESM(require("chalk"));
15296
+ init_runtimeModeConfig();
15297
+ REPLAY_RESULTS_BY_STRATEGY_HEADERS = [
15298
+ import_chalk16.default.blue("STRATEGY"),
15299
+ import_chalk16.default.yellow("TICKERS"),
15300
+ import_chalk16.default.yellow("TRADE TICKERS"),
15301
+ import_chalk16.default.cyan("ORDERS"),
15302
+ import_chalk16.default.cyan("WIN/LOSS (%)"),
15303
+ import_chalk16.default.cyan("PROFIT"),
15304
+ import_chalk16.default.cyan("AVG TRADE")
15305
+ ];
15306
+ REPLAY_RUNTIME_COMPARISON_HEADERS = [
15307
+ import_chalk16.default.blue("STRATEGY"),
15308
+ import_chalk16.default.cyan("BT ENTRIES"),
15309
+ import_chalk16.default.cyan("BT PNL"),
15310
+ import_chalk16.default.yellow("RT TRADES"),
15311
+ import_chalk16.default.yellow("RT PNL"),
15312
+ import_chalk16.default.green("MATCHED"),
15313
+ import_chalk16.default.red("ORDER FAILED"),
15314
+ import_chalk16.default.yellow("RT ONLY"),
15315
+ import_chalk16.default.magenta("BT ONLY"),
15316
+ import_chalk16.default.red("UNMATCHED")
15317
+ ];
15318
+ REPLAY_RESULTS_CONFIG = "replay";
15319
+ REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS = 1;
15320
+ REPLAY_RUNTIME_COMPARE_TOLERANCE_MS = REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS * 15 * 60 * 1e3;
15321
+ formatReplayRuntimeCompareTolerance = () => `${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS} ${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS === 1 ? "bar" : "bars"}`;
15322
+ getReplayRuntimeUnmatchedCount = (row) => row.orderFailed + row.runtimeOnly + row.backtestOnly;
15323
+ buildReplayStrategyConfig = ({
15324
+ strategyConfig,
15325
+ interval: interval4
15326
+ }) => ({
15327
+ ...buildRuntimeModeStrategyConfig({
15328
+ strategyConfig,
15329
+ env: "PARITY",
15330
+ interval: interval4,
15331
+ makeOrders: false,
15332
+ recordRuntimeTrades: false
15333
+ }),
15334
+ SIMULATE_ORDERS: true
15335
+ });
15336
+ toRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
15337
+ compactReplayAdditionalIndicators = (additionalIndicators) => {
15338
+ const additional = toRecord(additionalIndicators);
15339
+ if (!additional) {
15340
+ return void 0;
15341
+ }
15342
+ const compact = {};
15343
+ if (additional.executionSlippage != null) {
15344
+ compact.executionSlippage = additional.executionSlippage;
15345
+ }
15346
+ if (additional.marketContext != null) {
15347
+ compact.marketContext = additional.marketContext;
15348
+ }
15349
+ const baseContext = toRecord(additional.baseContext);
15350
+ const relative = toRecord(baseContext?.relative);
15351
+ const execution = toRecord(relative?.execution);
15352
+ if (execution) {
15353
+ compact.baseContext = {
15354
+ relative: {
15355
+ execution
15356
+ }
15357
+ };
15358
+ }
15359
+ return Object.keys(compact).length ? compact : void 0;
15360
+ };
15361
+ compactReplaySignal = (signal) => {
15362
+ if (!signal) {
15363
+ return void 0;
15364
+ }
15365
+ return {
15366
+ signalId: signal.signalId,
15367
+ strategy: signal.strategy,
15368
+ symbol: signal.symbol,
15369
+ interval: signal.interval,
15370
+ direction: signal.direction,
15371
+ timestamp: signal.timestamp,
15372
+ prices: signal.prices,
15373
+ indicators: {},
15374
+ figures: {},
15375
+ additionalIndicators: compactReplayAdditionalIndicators(
15376
+ signal.additionalIndicators
15377
+ ),
15378
+ isConfigFromBacktest: signal.isConfigFromBacktest,
15379
+ ...signal.orderStatus ? { orderStatus: signal.orderStatus } : {},
15380
+ ...signal.orderSkipReason ? { orderSkipReason: signal.orderSkipReason } : {},
15381
+ ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
15382
+ ...signal.ml ? { ml: signal.ml } : {}
15383
+ };
15384
+ };
15385
+ }
15386
+ });
15387
+
15388
+ // src/lib/marketData/windows.ts
15389
+ var import_indicators2, resolveOpenTimestamp, getClosedCandlesForInterval, splitCandlesForReplayWindow, alignSymbolWithBtcReference;
15390
+ var init_windows = __esm({
15391
+ "src/lib/marketData/windows.ts"() {
15392
+ "use strict";
15393
+ import_indicators2 = require("@tradejs/core/indicators");
15394
+ resolveOpenTimestamp = (timestamp, intervalMs) => Number.isFinite(intervalMs) && intervalMs > 0 ? Math.floor(timestamp / intervalMs) * intervalMs : timestamp;
15395
+ getClosedCandlesForInterval = (candles, currentTimestamp, intervalMs) => {
15396
+ const currentOpenTimestamp = resolveOpenTimestamp(
15397
+ currentTimestamp,
15398
+ intervalMs
15399
+ );
15400
+ return candles.filter((candle) => candle.timestamp < currentOpenTimestamp);
15401
+ };
15402
+ splitCandlesForReplayWindow = (candles, start, preloadStart) => {
15403
+ const prevData = [];
15404
+ const replayData = [];
15405
+ for (const candle of candles) {
15406
+ if (candle.timestamp < preloadStart) {
15407
+ continue;
15408
+ }
15409
+ if (candle.timestamp < start) {
15410
+ prevData.push(candle);
15411
+ continue;
15412
+ }
15413
+ replayData.push(candle);
15414
+ }
15415
+ return { prevData, replayData };
15416
+ };
15417
+ alignSymbolWithBtcReference = (symbolCandles, btcCandles) => (0, import_indicators2.alignSortedCandlesByTimestamp)(symbolCandles, btcCandles);
15418
+ }
15419
+ });
15420
+
15421
+ // src/lib/replay/historicalSignalsReplayPreparation.ts
15422
+ var import_data2, buildPreparedData, strategyIncludesSymbol, buildSymbolSharedReplayKey, resolveHistoricalReplayIntervals, prepareMultiIntervalHistoricalReplay, assertBaseReplayCoverage, prepareHistoricalReplay, releaseHistoricalReplayPlan, releaseHistoricalReplayCaches;
15423
+ var init_historicalSignalsReplayPreparation = __esm({
15424
+ "src/lib/replay/historicalSignalsReplayPreparation.ts"() {
15425
+ "use strict";
15426
+ import_data2 = require("@tradejs/core/data");
15427
+ init_support();
15428
+ init_windows();
15429
+ buildPreparedData = ({
15430
+ data,
15431
+ btcData,
15432
+ ethData,
15433
+ btcBinanceData,
15434
+ btcCoinbaseData,
15435
+ start,
15436
+ preloadStart
15437
+ }) => {
15438
+ const { prevData: prevDataRaw, replayData: replayDataRaw } = splitCandlesForReplayWindow(data, start, preloadStart);
15439
+ const { prevData: btcPrevDataRaw, replayData: btcReplayDataRaw } = splitCandlesForReplayWindow(btcData, start, preloadStart);
15440
+ const { prevData: ethPrevDataRaw, replayData: ethReplayDataRaw } = splitCandlesForReplayWindow(ethData, start, preloadStart);
15441
+ const { prevData: btcBinancePrevDataRaw } = splitCandlesForReplayWindow(
15442
+ btcBinanceData,
15443
+ start,
15444
+ preloadStart
15445
+ );
15446
+ const { prevData: btcCoinbasePrevDataRaw } = splitCandlesForReplayWindow(
15447
+ btcCoinbaseData,
15448
+ start,
15449
+ preloadStart
15450
+ );
15451
+ const { alignedCoinCandles: prevData, alignedBtcCandles: btcPrevData } = alignSymbolWithBtcReference(prevDataRaw, btcPrevDataRaw);
15452
+ const { alignedCoinCandles: replayData, alignedBtcCandles: btcReplayData } = alignSymbolWithBtcReference(replayDataRaw, btcReplayDataRaw);
15453
+ const { alignedBtcCandles: ethPrevData } = alignSymbolWithBtcReference(
15454
+ prevData,
15455
+ ethPrevDataRaw
15456
+ );
15457
+ const { alignedBtcCandles: ethReplayData } = alignSymbolWithBtcReference(
15458
+ replayData,
15459
+ ethReplayDataRaw
15460
+ );
15461
+ const { alignedBtcCandles: btcBinancePrevData } = alignSymbolWithBtcReference(
15462
+ prevDataRaw,
15463
+ btcBinancePrevDataRaw
15464
+ );
15465
+ const { alignedBtcCandles: btcCoinbasePrevData } = alignSymbolWithBtcReference(prevDataRaw, btcCoinbasePrevDataRaw);
15466
+ return {
15467
+ prevData,
15468
+ btcPrevData,
15469
+ ethPrevData,
15470
+ replayData,
15471
+ btcReplayData,
15472
+ ethReplayData,
15473
+ btcBinancePrevData,
15474
+ btcCoinbasePrevData
15475
+ };
15476
+ };
15477
+ strategyIncludesSymbol = (strategy, symbol) => {
15478
+ const tickers = strategy.selection?.tickers;
15479
+ if (!tickers) return true;
15480
+ const normalizedSymbol = symbol.trim().toUpperCase();
15481
+ return tickers.some(
15482
+ (ticker) => ticker.trim().toUpperCase() === normalizedSymbol
15483
+ );
15484
+ };
15485
+ buildSymbolSharedReplayKey = ({
15486
+ userName: userName3,
15487
+ connectorName,
15488
+ interval: interval4,
15489
+ symbol,
15490
+ start,
15491
+ end
15492
+ }) => ["replay", userName3, connectorName, symbol, interval4, start, end].join(":");
15493
+ resolveHistoricalReplayIntervals = (baseInterval, strategyIntervals) => {
15494
+ const intervals = [
15495
+ ...new Set(
15496
+ strategyIntervals.map((value) => {
15497
+ if (!value || !Number.isFinite((0, import_data2.intervalToMs)(value)) || (0, import_data2.intervalToMs)(value) <= 0)
15498
+ throw new Error(`Invalid replay strategy interval: ${value}`);
15499
+ return value;
15500
+ })
15501
+ )
15502
+ ].sort((a, b) => (0, import_data2.intervalToMs)(a) - (0, import_data2.intervalToMs)(b));
15503
+ if (intervals.length > 1 && intervals.includes("M")) {
15504
+ throw new Error("Mixed replay does not support calendar intervals");
15505
+ }
15506
+ const baseMs = (0, import_data2.intervalToMs)(baseInterval);
15507
+ if (intervals.some(
15508
+ (value) => (0, import_data2.intervalToMs)(value) < baseMs || (0, import_data2.intervalToMs)(value) % baseMs !== 0
15509
+ )) {
15510
+ throw new Error(
15511
+ "Replay strategy intervals must be multiples of the base timeframe"
15512
+ );
15513
+ }
15514
+ if (!intervals.length || !intervals.includes(baseInterval)) {
15515
+ throw new Error(
15516
+ "Replay base timeframe must match an active strategy interval"
15517
+ );
15518
+ }
15519
+ return intervals;
15520
+ };
15521
+ prepareMultiIntervalHistoricalReplay = async (context, adapters, loadReferences) => {
15522
+ const intervals = resolveHistoricalReplayIntervals(
15523
+ context.interval,
15524
+ context.strategies.map((s) => s.strategyConfig.INTERVAL)
15525
+ );
15526
+ const baseMs = (0, import_data2.intervalToMs)(context.interval);
15527
+ const plan = {
15528
+ symbolRuntimes: [],
15529
+ orderedTimestamps: [],
15530
+ sharedReplayKeyPrefixes: [],
15531
+ runtimeLineages: []
15532
+ };
15533
+ const timestamps = /* @__PURE__ */ new Set();
15534
+ const baseRuntimes = /* @__PURE__ */ new Map();
15535
+ const validatedSymbols = /* @__PURE__ */ new Set();
15536
+ try {
15537
+ for (const interval4 of intervals) {
15538
+ const offset = (0, import_data2.intervalToMs)(interval4) - baseMs;
15539
+ const nativeRun = {
15540
+ ...context.preparedRun,
15541
+ preloadStart: context.preparedRun.preloadStart - offset,
15542
+ window: {
15543
+ ...context.preparedRun.window,
15544
+ start: context.preparedRun.window.start - offset,
15545
+ end: context.preparedRun.window.end - offset
15546
+ }
15547
+ };
15548
+ const part = await prepareHistoricalReplay(
15549
+ {
15550
+ ...context,
15551
+ interval: interval4,
15552
+ preparedRun: nativeRun,
15553
+ strategies: context.strategies.filter(
15554
+ (strategy) => strategy.strategyConfig.INTERVAL === interval4
15555
+ ),
15556
+ references: interval4 === context.interval ? context.references : await loadReferences(interval4, nativeRun)
15557
+ },
15558
+ adapters
15559
+ );
15560
+ plan.sharedReplayKeyPrefixes.push(...part.sharedReplayKeyPrefixes);
15561
+ plan.runtimeLineages.push(...part.runtimeLineages);
15562
+ for (const runtime of part.symbolRuntimes) {
15563
+ if (interval4 === context.interval) {
15564
+ baseRuntimes.set(runtime.symbol, runtime);
15565
+ } else if (runtime.strategies.length && !validatedSymbols.has(runtime.symbol)) {
15566
+ assertBaseReplayCoverage(
15567
+ baseRuntimes.get(runtime.symbol),
15568
+ runtime.symbol,
15569
+ context.preparedRun.window,
15570
+ baseMs
15571
+ );
15572
+ validatedSymbols.add(runtime.symbol);
15573
+ }
15574
+ runtime.timestampOffsetMs = offset;
15575
+ runtime.advanceMarket = interval4 === context.interval;
15576
+ plan.symbolRuntimes.push(runtime);
15577
+ }
15578
+ for (const timestamp of part.orderedTimestamps) {
15579
+ const scheduled = timestamp + offset;
15580
+ if (scheduled >= context.preparedRun.window.start && scheduled <= context.preparedRun.window.end)
15581
+ timestamps.add(scheduled);
15582
+ }
15583
+ }
15584
+ plan.orderedTimestamps = [...timestamps].sort((a, b) => a - b);
15585
+ return plan;
15586
+ } catch (error) {
15587
+ releaseHistoricalReplayCaches(plan, adapters);
15588
+ throw error;
15589
+ }
15590
+ };
15591
+ assertBaseReplayCoverage = (runtime, symbol, window, baseMs) => {
15592
+ let index = 0;
15593
+ for (let timestamp = Math.ceil(window.start / baseMs) * baseMs; timestamp <= window.end; timestamp += baseMs) {
15594
+ if (runtime?.replayData[index]?.timestamp !== timestamp) {
15595
+ throw new Error(`Missing base replay candle: ${symbol} at ${timestamp}`);
15596
+ }
15597
+ index += 1;
15598
+ }
15599
+ };
15600
+ prepareHistoricalReplay = async (context, adapters) => {
15601
+ const {
15602
+ userName: userName3,
15603
+ projectRoot: projectRoot18,
15604
+ preparedRun,
15605
+ interval: interval4,
15606
+ connectorName,
15607
+ replayConnector,
15608
+ strategies,
15609
+ references
15610
+ } = context;
15611
+ const symbolRuntimes = [];
15612
+ const orderedTimestamps = /* @__PURE__ */ new Set();
15613
+ const sharedReplayKeyPrefixes = [];
15614
+ const runtimeLineages = [];
15615
+ let skippedSymbols = 0;
15616
+ try {
15617
+ for (const symbol of preparedRun.tickers) {
15618
+ const data = await preparedRun.marketConnector.kline({
15619
+ symbol,
15620
+ start: preparedRun.preloadStart,
15621
+ end: preparedRun.window.end,
15622
+ cacheOnly: true,
15623
+ interval: interval4
15624
+ });
15625
+ if (symbolRuntimes.length % 50 === 0)
15626
+ adapters.sampleMemory?.("history:loaded", {
15627
+ symbols: symbolRuntimes.length + 1,
15628
+ candles: data.length
15629
+ });
15630
+ const preparedData = buildPreparedData({
15631
+ data,
15632
+ btcData: references.btcMarketData,
15633
+ ethData: references.ethMarketData,
15634
+ btcBinanceData: references.btcBinanceData,
15635
+ btcCoinbaseData: references.btcCoinbaseData,
15636
+ start: preparedRun.window.start,
15637
+ preloadStart: preparedRun.preloadStart
15638
+ });
15639
+ if (!preparedData.replayData.length || !preparedData.btcReplayData.length) {
15640
+ skippedSymbols += 1;
15641
+ adapters.progress.tick({
15642
+ skipped: adapters.display.skipped(skippedSymbols),
15643
+ symbol: adapters.display.symbol(symbol)
15644
+ });
15645
+ continue;
15646
+ }
15647
+ const sharedIndicatorsReplayKey = buildSymbolSharedReplayKey({
15648
+ userName: userName3,
15649
+ connectorName,
15650
+ interval: interval4,
15651
+ symbol,
15652
+ start: preparedRun.window.start,
15653
+ end: preparedRun.window.end
15654
+ });
15655
+ sharedReplayKeyPrefixes.push(sharedIndicatorsReplayKey);
15656
+ const initializedStrategies = await Promise.allSettled(
15657
+ strategies.filter((strategy) => strategyIncludesSymbol(strategy, symbol)).map(async (strategy) => {
15658
+ const replayStrategyConfig = buildReplayStrategyConfig({
15659
+ strategyConfig: strategy.strategyConfig,
15660
+ interval: interval4
15661
+ });
15662
+ const runtimeLineage = await adapters.buildLineage({
15663
+ projectRoot: projectRoot18,
15664
+ strategyName: strategy.strategyName,
15665
+ strategyRevision: strategy.strategyRevision,
15666
+ deploymentCompositionId: strategy.deploymentCompositionId,
15667
+ strategyPackageVersion: strategy.strategyPackageVersion,
15668
+ strategyDependencyVersions: strategy.strategyDependencyVersions,
15669
+ runtimePackageVersion: strategy.runtimePackageVersion,
15670
+ config: {
15671
+ configId: strategy.strategyRevision,
15672
+ strategyConfig: strategy.strategyConfig,
15673
+ symbolResultConfig: strategy.strategyResults?.[symbol]?.config ?? null
15674
+ },
15675
+ runContext: {
15676
+ connectorName: connectorName.toLowerCase(),
15677
+ interval: String(interval4),
15678
+ universe: preparedRun.universe ?? null
15679
+ }
15680
+ });
15681
+ runtimeLineages.push({
15682
+ strategy: strategy.strategyName,
15683
+ symbol,
15684
+ ...preparedRun.deploymentId ? { deploymentId: preparedRun.deploymentId } : {},
15685
+ ...preparedRun.accountId ? { accountId: preparedRun.accountId } : {},
15686
+ lineage: runtimeLineage
15687
+ });
15688
+ return {
15689
+ strategyName: strategy.strategyName,
15690
+ strategyConfig: strategy.strategyConfig,
15691
+ runtimeLineage,
15692
+ run: await strategy.strategyCreator({
15693
+ userName: userName3,
15694
+ connectorName,
15695
+ runtimeConfigId: strategy.strategyRevision,
15696
+ strategyRevision: strategy.strategyRevision,
15697
+ runtimeLineage,
15698
+ universe: preparedRun.universe,
15699
+ accountId: preparedRun.accountId,
15700
+ deploymentId: preparedRun.deploymentId,
15701
+ runtimeConfigSnapshot: {
15702
+ userConfig: replayStrategyConfig
15703
+ },
15704
+ config: replayStrategyConfig,
15705
+ symbol,
15706
+ data: preparedData.prevData,
15707
+ btcData: preparedData.btcPrevData,
15708
+ ethData: preparedData.ethPrevData,
15709
+ btcBinanceData: preparedData.btcBinancePrevData,
15710
+ btcCoinbaseData: preparedData.btcCoinbasePrevData,
15711
+ connector: replayConnector,
15712
+ sharedIndicatorsReplayKey
15713
+ })
15714
+ };
15715
+ })
15716
+ );
15717
+ const strategiesForSymbol = initializedStrategies.map((result) => {
15718
+ if (result.status === "rejected") throw result.reason;
15719
+ return result.value;
15720
+ });
15721
+ const symbolRuntime = {
15722
+ symbol,
15723
+ replayData: preparedData.replayData,
15724
+ btcReplayData: preparedData.btcReplayData,
15725
+ ethReplayData: preparedData.ethReplayData,
15726
+ currentIndex: 0,
15727
+ strategies: strategiesForSymbol
15728
+ };
15729
+ symbolRuntimes.push(symbolRuntime);
15730
+ if (symbolRuntimes.length % 50 === 0 || symbol === preparedRun.tickers.at(-1))
15731
+ adapters.sampleMemory?.("strategies:initializing", {
15732
+ symbols: symbolRuntimes.length,
15733
+ strategiesPerSymbol: strategiesForSymbol.length
15734
+ });
15735
+ adapters.progress.tick({
15736
+ skipped: adapters.display.skipped(skippedSymbols),
15737
+ symbol: adapters.display.symbol(symbol)
15738
+ });
15739
+ for (const candle of preparedData.replayData) {
15740
+ orderedTimestamps.add(candle.timestamp);
15741
+ }
15742
+ }
15743
+ return {
15744
+ symbolRuntimes,
15745
+ orderedTimestamps: [...orderedTimestamps].sort(
15746
+ (left, right) => left - right
15747
+ ),
15748
+ sharedReplayKeyPrefixes,
15749
+ runtimeLineages
15750
+ };
15751
+ } catch (error) {
15752
+ releaseHistoricalReplayCaches({ sharedReplayKeyPrefixes }, adapters);
15753
+ throw error;
15754
+ }
15755
+ };
15756
+ releaseHistoricalReplayPlan = (plan) => {
15757
+ plan.symbolRuntimes.length = 0;
15758
+ plan.sharedReplayKeyPrefixes.length = 0;
15759
+ };
15760
+ releaseHistoricalReplayCaches = (plan, adapters) => {
15761
+ for (const keyPrefix of plan.sharedReplayKeyPrefixes) {
15762
+ adapters.releaseIndicatorsCache(keyPrefix);
15763
+ adapters.releaseReplayCache(keyPrefix);
15764
+ }
15765
+ };
15766
+ }
15767
+ });
15768
+
15262
15769
  // src/lib/replay/cliConfig.ts
15263
15770
  var import_args7, REPLAY_UNSUPPORTED_FLAGS, hasCliFlag2, assertNoUnsupportedReplayFlags, replayNormalizedArgv, replayFlags, replayInterval, replayUserName, replayProjectRoot, replayRuntimeEvidencePath, replayDeploymentId, isReplayUpdateOnlyRun;
15264
15771
  var init_cliConfig2 = __esm({
@@ -15636,149 +16143,23 @@ var init_runtimeEvidenceSource = __esm({
15636
16143
  assertRuntimeRowsMatchDeployment({
15637
16144
  label: "evaluation",
15638
16145
  rows: evaluations,
15639
- deployment
15640
- });
15641
- assertRuntimeRowsMatchDeployment({
15642
- label: "lineage scope",
15643
- rows: storedLineageScopes,
15644
- deployment
15645
- });
15646
- return {
15647
- path: resolvedPath,
15648
- userName: userName3,
15649
- startTime,
15650
- endTime,
15651
- trades,
15652
- signals: signals2,
15653
- evaluations,
15654
- lineageScopes: storedLineageScopes.length ? storedLineageScopes : buildRuntimeEvidenceLineageScopes({ signals: signals2, evaluations }),
15655
- deployment
15656
- };
15657
- };
15658
- }
15659
- });
15660
-
15661
- // src/lib/runtimeModeConfig.ts
15662
- var hasRuntimeEntryGateEnabled, resolveReplayStrategyEnv, buildRuntimeModeStrategyConfig;
15663
- var init_runtimeModeConfig = __esm({
15664
- "src/lib/runtimeModeConfig.ts"() {
15665
- "use strict";
15666
- hasRuntimeEntryGateEnabled = (strategyConfig) => strategyConfig.AI_ENABLED === true || strategyConfig.ML_ENABLED === true;
15667
- resolveReplayStrategyEnv = ({
15668
- strategyConfig,
15669
- forceRuntimeGates = false
15670
- }) => forceRuntimeGates || hasRuntimeEntryGateEnabled(strategyConfig) ? "PARITY" : "BACKTEST";
15671
- buildRuntimeModeStrategyConfig = ({
15672
- strategyConfig,
15673
- env,
15674
- interval: interval4,
15675
- makeOrders,
15676
- recordRuntimeTrades,
15677
- aiReplayAnalyses
15678
- }) => ({
15679
- ...strategyConfig,
15680
- ENV: env,
15681
- MAKE_ORDERS: makeOrders,
15682
- INTERVAL: strategyConfig.INTERVAL ?? interval4,
15683
- ...typeof recordRuntimeTrades === "boolean" ? { RECORD_RUNTIME_TRADES: recordRuntimeTrades } : {},
15684
- ...aiReplayAnalyses?.length ? { AI_REPLAY_ANALYSES: aiReplayAnalyses } : {}
15685
- });
15686
- }
15687
- });
15688
-
15689
- // src/lib/replay/support.ts
15690
- var import_chalk16, REPLAY_RESULTS_BY_STRATEGY_HEADERS, REPLAY_RUNTIME_COMPARISON_HEADERS, REPLAY_RESULTS_CONFIG, REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS, REPLAY_RUNTIME_COMPARE_TOLERANCE_MS, formatReplayRuntimeCompareTolerance, getReplayRuntimeUnmatchedCount, buildReplayStrategyConfig, toRecord, compactReplayAdditionalIndicators, compactReplaySignal;
15691
- var init_support = __esm({
15692
- "src/lib/replay/support.ts"() {
15693
- "use strict";
15694
- import_chalk16 = __toESM(require("chalk"));
15695
- init_runtimeModeConfig();
15696
- REPLAY_RESULTS_BY_STRATEGY_HEADERS = [
15697
- import_chalk16.default.blue("STRATEGY"),
15698
- import_chalk16.default.yellow("TICKERS"),
15699
- import_chalk16.default.yellow("TRADE TICKERS"),
15700
- import_chalk16.default.cyan("ORDERS"),
15701
- import_chalk16.default.cyan("WIN/LOSS (%)"),
15702
- import_chalk16.default.cyan("PROFIT"),
15703
- import_chalk16.default.cyan("AVG TRADE")
15704
- ];
15705
- REPLAY_RUNTIME_COMPARISON_HEADERS = [
15706
- import_chalk16.default.blue("STRATEGY"),
15707
- import_chalk16.default.cyan("BT ENTRIES"),
15708
- import_chalk16.default.cyan("BT PNL"),
15709
- import_chalk16.default.yellow("RT TRADES"),
15710
- import_chalk16.default.yellow("RT PNL"),
15711
- import_chalk16.default.green("MATCHED"),
15712
- import_chalk16.default.red("ORDER FAILED"),
15713
- import_chalk16.default.yellow("RT ONLY"),
15714
- import_chalk16.default.magenta("BT ONLY"),
15715
- import_chalk16.default.red("UNMATCHED")
15716
- ];
15717
- REPLAY_RESULTS_CONFIG = "replay";
15718
- REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS = 1;
15719
- REPLAY_RUNTIME_COMPARE_TOLERANCE_MS = REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS * 15 * 60 * 1e3;
15720
- formatReplayRuntimeCompareTolerance = () => `${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS} ${REPLAY_RUNTIME_COMPARE_TOLERANCE_BARS === 1 ? "bar" : "bars"}`;
15721
- getReplayRuntimeUnmatchedCount = (row) => row.orderFailed + row.runtimeOnly + row.backtestOnly;
15722
- buildReplayStrategyConfig = ({
15723
- strategyConfig,
15724
- interval: interval4
15725
- }) => ({
15726
- ...buildRuntimeModeStrategyConfig({
15727
- strategyConfig,
15728
- env: "PARITY",
15729
- interval: interval4,
15730
- makeOrders: false,
15731
- recordRuntimeTrades: false
15732
- }),
15733
- SIMULATE_ORDERS: true
15734
- });
15735
- toRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
15736
- compactReplayAdditionalIndicators = (additionalIndicators) => {
15737
- const additional = toRecord(additionalIndicators);
15738
- if (!additional) {
15739
- return void 0;
15740
- }
15741
- const compact = {};
15742
- if (additional.executionSlippage != null) {
15743
- compact.executionSlippage = additional.executionSlippage;
15744
- }
15745
- if (additional.marketContext != null) {
15746
- compact.marketContext = additional.marketContext;
15747
- }
15748
- const baseContext = toRecord(additional.baseContext);
15749
- const relative = toRecord(baseContext?.relative);
15750
- const execution = toRecord(relative?.execution);
15751
- if (execution) {
15752
- compact.baseContext = {
15753
- relative: {
15754
- execution
15755
- }
15756
- };
15757
- }
15758
- return Object.keys(compact).length ? compact : void 0;
15759
- };
15760
- compactReplaySignal = (signal) => {
15761
- if (!signal) {
15762
- return void 0;
15763
- }
16146
+ deployment
16147
+ });
16148
+ assertRuntimeRowsMatchDeployment({
16149
+ label: "lineage scope",
16150
+ rows: storedLineageScopes,
16151
+ deployment
16152
+ });
15764
16153
  return {
15765
- signalId: signal.signalId,
15766
- strategy: signal.strategy,
15767
- symbol: signal.symbol,
15768
- interval: signal.interval,
15769
- direction: signal.direction,
15770
- timestamp: signal.timestamp,
15771
- prices: signal.prices,
15772
- indicators: {},
15773
- figures: {},
15774
- additionalIndicators: compactReplayAdditionalIndicators(
15775
- signal.additionalIndicators
15776
- ),
15777
- isConfigFromBacktest: signal.isConfigFromBacktest,
15778
- ...signal.orderStatus ? { orderStatus: signal.orderStatus } : {},
15779
- ...signal.orderSkipReason ? { orderSkipReason: signal.orderSkipReason } : {},
15780
- ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
15781
- ...signal.ml ? { ml: signal.ml } : {}
16154
+ path: resolvedPath,
16155
+ userName: userName3,
16156
+ startTime,
16157
+ endTime,
16158
+ trades,
16159
+ signals: signals2,
16160
+ evaluations,
16161
+ lineageScopes: storedLineageScopes.length ? storedLineageScopes : buildRuntimeEvidenceLineageScopes({ signals: signals2, evaluations }),
16162
+ deployment
15782
16163
  };
15783
16164
  };
15784
16165
  }
@@ -15846,6 +16227,7 @@ var init_portfolioReplayConnector = __esm({
15846
16227
  createPortfolioReplayConnector = (connector) => {
15847
16228
  let state = {};
15848
16229
  let amount = import_constants10.INITIAL_BACKTEST_AMOUNT;
16230
+ let executionTimestamp = null;
15849
16231
  const positionsBySymbol = /* @__PURE__ */ new Map();
15850
16232
  const currentPriceBySymbol = /* @__PURE__ */ new Map();
15851
16233
  const orderLog = [];
@@ -16107,6 +16489,7 @@ var init_portfolioReplayConnector = __esm({
16107
16489
  });
16108
16490
  const nextPosition = {
16109
16491
  ...order,
16492
+ timestamp: executionTimestamp ?? order.timestamp,
16110
16493
  price: entryPrice,
16111
16494
  signal: compactReplaySignal(order.signal),
16112
16495
  amount,
@@ -16131,6 +16514,7 @@ var init_portfolioReplayConnector = __esm({
16131
16514
  positionState,
16132
16515
  data: {
16133
16516
  ...order,
16517
+ timestamp: executionTimestamp ?? order.timestamp,
16134
16518
  price: entryPrice,
16135
16519
  profit,
16136
16520
  fee,
@@ -16194,6 +16578,7 @@ var init_portfolioReplayConnector = __esm({
16194
16578
  positionState,
16195
16579
  data: {
16196
16580
  ...order,
16581
+ timestamp: executionTimestamp ?? order.timestamp,
16197
16582
  price: executionPrice,
16198
16583
  qty: position.qty,
16199
16584
  profit,
@@ -16202,11 +16587,12 @@ var init_portfolioReplayConnector = __esm({
16202
16587
  ...getExecutionSlippageLogData(slippageBreakdown, "exit")
16203
16588
  }
16204
16589
  });
16205
- clearPosition(order.symbol, order.timestamp);
16590
+ clearPosition(order.symbol, executionTimestamp ?? order.timestamp);
16206
16591
  return true;
16207
16592
  },
16208
16593
  getTickers: connector.getTickers,
16209
16594
  advanceMarket: async ({ symbol, candle }) => {
16595
+ executionTimestamp = candle.timestamp;
16210
16596
  currentPriceBySymbol.set(symbol, candle.close);
16211
16597
  await checkSl({ symbol, candle });
16212
16598
  await checkTp({ symbol, candle });
@@ -16255,270 +16641,12 @@ var init_hooks = __esm({
16255
16641
  }
16256
16642
  });
16257
16643
 
16258
- // src/lib/marketData/windows.ts
16259
- var import_indicators2, resolveOpenTimestamp, getClosedCandlesForInterval, splitCandlesForReplayWindow, alignSymbolWithBtcReference;
16260
- var init_windows = __esm({
16261
- "src/lib/marketData/windows.ts"() {
16262
- "use strict";
16263
- import_indicators2 = require("@tradejs/core/indicators");
16264
- resolveOpenTimestamp = (timestamp, intervalMs) => Number.isFinite(intervalMs) && intervalMs > 0 ? Math.floor(timestamp / intervalMs) * intervalMs : timestamp;
16265
- getClosedCandlesForInterval = (candles, currentTimestamp, intervalMs) => {
16266
- const currentOpenTimestamp = resolveOpenTimestamp(
16267
- currentTimestamp,
16268
- intervalMs
16269
- );
16270
- return candles.filter((candle) => candle.timestamp < currentOpenTimestamp);
16271
- };
16272
- splitCandlesForReplayWindow = (candles, start, preloadStart) => {
16273
- const prevData = [];
16274
- const replayData = [];
16275
- for (const candle of candles) {
16276
- if (candle.timestamp < preloadStart) {
16277
- continue;
16278
- }
16279
- if (candle.timestamp < start) {
16280
- prevData.push(candle);
16281
- continue;
16282
- }
16283
- replayData.push(candle);
16284
- }
16285
- return { prevData, replayData };
16286
- };
16287
- alignSymbolWithBtcReference = (symbolCandles, btcCandles) => (0, import_indicators2.alignSortedCandlesByTimestamp)(symbolCandles, btcCandles);
16288
- }
16289
- });
16290
-
16291
- // src/lib/replay/historicalSignalsReplayPreparation.ts
16292
- var buildPreparedData, strategyIncludesSymbol, buildSymbolSharedReplayKey, prepareHistoricalReplay, releaseHistoricalReplayPlan;
16293
- var init_historicalSignalsReplayPreparation = __esm({
16294
- "src/lib/replay/historicalSignalsReplayPreparation.ts"() {
16295
- "use strict";
16296
- init_support();
16297
- init_windows();
16298
- buildPreparedData = ({
16299
- data,
16300
- btcData,
16301
- ethData,
16302
- btcBinanceData,
16303
- btcCoinbaseData,
16304
- start,
16305
- preloadStart
16306
- }) => {
16307
- const { prevData: prevDataRaw, replayData: replayDataRaw } = splitCandlesForReplayWindow(data, start, preloadStart);
16308
- const { prevData: btcPrevDataRaw, replayData: btcReplayDataRaw } = splitCandlesForReplayWindow(btcData, start, preloadStart);
16309
- const { prevData: ethPrevDataRaw, replayData: ethReplayDataRaw } = splitCandlesForReplayWindow(ethData, start, preloadStart);
16310
- const { prevData: btcBinancePrevDataRaw } = splitCandlesForReplayWindow(
16311
- btcBinanceData,
16312
- start,
16313
- preloadStart
16314
- );
16315
- const { prevData: btcCoinbasePrevDataRaw } = splitCandlesForReplayWindow(
16316
- btcCoinbaseData,
16317
- start,
16318
- preloadStart
16319
- );
16320
- const { alignedCoinCandles: prevData, alignedBtcCandles: btcPrevData } = alignSymbolWithBtcReference(prevDataRaw, btcPrevDataRaw);
16321
- const { alignedCoinCandles: replayData, alignedBtcCandles: btcReplayData } = alignSymbolWithBtcReference(replayDataRaw, btcReplayDataRaw);
16322
- const { alignedBtcCandles: ethPrevData } = alignSymbolWithBtcReference(
16323
- prevData,
16324
- ethPrevDataRaw
16325
- );
16326
- const { alignedBtcCandles: ethReplayData } = alignSymbolWithBtcReference(
16327
- replayData,
16328
- ethReplayDataRaw
16329
- );
16330
- const { alignedBtcCandles: btcBinancePrevData } = alignSymbolWithBtcReference(
16331
- prevDataRaw,
16332
- btcBinancePrevDataRaw
16333
- );
16334
- const { alignedBtcCandles: btcCoinbasePrevData } = alignSymbolWithBtcReference(prevDataRaw, btcCoinbasePrevDataRaw);
16335
- return {
16336
- prevData,
16337
- btcPrevData,
16338
- ethPrevData,
16339
- replayData,
16340
- btcReplayData,
16341
- ethReplayData,
16342
- btcBinancePrevData,
16343
- btcCoinbasePrevData
16344
- };
16345
- };
16346
- strategyIncludesSymbol = (strategy, symbol) => {
16347
- const tickers = strategy.selection?.tickers;
16348
- if (!tickers) return true;
16349
- const normalizedSymbol = symbol.trim().toUpperCase();
16350
- return tickers.some(
16351
- (ticker) => ticker.trim().toUpperCase() === normalizedSymbol
16352
- );
16353
- };
16354
- buildSymbolSharedReplayKey = ({
16355
- userName: userName3,
16356
- connectorName,
16357
- interval: interval4,
16358
- symbol,
16359
- start,
16360
- end
16361
- }) => ["replay", userName3, connectorName, symbol, interval4, start, end].join(":");
16362
- prepareHistoricalReplay = async (context, adapters) => {
16363
- const {
16364
- userName: userName3,
16365
- projectRoot: projectRoot18,
16366
- preparedRun,
16367
- interval: interval4,
16368
- connectorName,
16369
- replayConnector,
16370
- strategies,
16371
- references
16372
- } = context;
16373
- const symbolRuntimes = [];
16374
- const orderedTimestamps = /* @__PURE__ */ new Set();
16375
- const sharedReplayKeyPrefixes = [];
16376
- const runtimeLineages = [];
16377
- let skippedSymbols = 0;
16378
- for (const symbol of preparedRun.tickers) {
16379
- const data = await preparedRun.marketConnector.kline({
16380
- symbol,
16381
- start: preparedRun.preloadStart,
16382
- end: preparedRun.window.end,
16383
- cacheOnly: true,
16384
- interval: interval4
16385
- });
16386
- if (symbolRuntimes.length % 50 === 0)
16387
- adapters.sampleMemory?.("history:loaded", {
16388
- symbols: symbolRuntimes.length + 1,
16389
- candles: data.length
16390
- });
16391
- const preparedData = buildPreparedData({
16392
- data,
16393
- btcData: references.btcMarketData,
16394
- ethData: references.ethMarketData,
16395
- btcBinanceData: references.btcBinanceData,
16396
- btcCoinbaseData: references.btcCoinbaseData,
16397
- start: preparedRun.window.start,
16398
- preloadStart: preparedRun.preloadStart
16399
- });
16400
- if (!preparedData.replayData.length || !preparedData.btcReplayData.length) {
16401
- skippedSymbols += 1;
16402
- adapters.progress.tick({
16403
- skipped: adapters.display.skipped(skippedSymbols),
16404
- symbol: adapters.display.symbol(symbol)
16405
- });
16406
- continue;
16407
- }
16408
- const sharedIndicatorsReplayKey = buildSymbolSharedReplayKey({
16409
- userName: userName3,
16410
- connectorName,
16411
- interval: interval4,
16412
- symbol,
16413
- start: preparedRun.window.start,
16414
- end: preparedRun.window.end
16415
- });
16416
- sharedReplayKeyPrefixes.push(sharedIndicatorsReplayKey);
16417
- const strategiesForSymbol = await Promise.all(
16418
- strategies.filter((strategy) => strategyIncludesSymbol(strategy, symbol)).map(async (strategy) => {
16419
- const replayStrategyConfig = buildReplayStrategyConfig({
16420
- strategyConfig: strategy.strategyConfig,
16421
- interval: interval4
16422
- });
16423
- const runtimeLineage = await adapters.buildLineage({
16424
- projectRoot: projectRoot18,
16425
- strategyName: strategy.strategyName,
16426
- strategyRevision: strategy.strategyRevision,
16427
- deploymentCompositionId: strategy.deploymentCompositionId,
16428
- strategyPackageVersion: strategy.strategyPackageVersion,
16429
- strategyDependencyVersions: strategy.strategyDependencyVersions,
16430
- runtimePackageVersion: strategy.runtimePackageVersion,
16431
- config: {
16432
- configId: strategy.strategyRevision,
16433
- strategyConfig: strategy.strategyConfig,
16434
- symbolResultConfig: strategy.strategyResults?.[symbol]?.config ?? null
16435
- },
16436
- runContext: {
16437
- connectorName: connectorName.toLowerCase(),
16438
- interval: String(interval4),
16439
- universe: preparedRun.universe ?? null
16440
- }
16441
- });
16442
- runtimeLineages.push({
16443
- strategy: strategy.strategyName,
16444
- symbol,
16445
- ...preparedRun.deploymentId ? { deploymentId: preparedRun.deploymentId } : {},
16446
- ...preparedRun.accountId ? { accountId: preparedRun.accountId } : {},
16447
- lineage: runtimeLineage
16448
- });
16449
- return {
16450
- strategyName: strategy.strategyName,
16451
- strategyConfig: strategy.strategyConfig,
16452
- runtimeLineage,
16453
- run: await strategy.strategyCreator({
16454
- userName: userName3,
16455
- connectorName,
16456
- runtimeConfigId: strategy.strategyRevision,
16457
- strategyRevision: strategy.strategyRevision,
16458
- runtimeLineage,
16459
- universe: preparedRun.universe,
16460
- accountId: preparedRun.accountId,
16461
- deploymentId: preparedRun.deploymentId,
16462
- runtimeConfigSnapshot: {
16463
- userConfig: replayStrategyConfig
16464
- },
16465
- config: replayStrategyConfig,
16466
- symbol,
16467
- data: preparedData.prevData,
16468
- btcData: preparedData.btcPrevData,
16469
- ethData: preparedData.ethPrevData,
16470
- btcBinanceData: preparedData.btcBinancePrevData,
16471
- btcCoinbaseData: preparedData.btcCoinbasePrevData,
16472
- connector: replayConnector,
16473
- sharedIndicatorsReplayKey
16474
- })
16475
- };
16476
- })
16477
- );
16478
- const symbolRuntime = {
16479
- symbol,
16480
- replayData: preparedData.replayData,
16481
- btcReplayData: preparedData.btcReplayData,
16482
- ethReplayData: preparedData.ethReplayData,
16483
- currentIndex: 0,
16484
- strategies: strategiesForSymbol
16485
- };
16486
- symbolRuntimes.push(symbolRuntime);
16487
- if (symbolRuntimes.length % 50 === 0 || symbol === preparedRun.tickers.at(-1))
16488
- adapters.sampleMemory?.("strategies:initializing", {
16489
- symbols: symbolRuntimes.length,
16490
- strategiesPerSymbol: strategiesForSymbol.length
16491
- });
16492
- adapters.progress.tick({
16493
- skipped: adapters.display.skipped(skippedSymbols),
16494
- symbol: adapters.display.symbol(symbol)
16495
- });
16496
- for (const candle of preparedData.replayData) {
16497
- orderedTimestamps.add(candle.timestamp);
16498
- }
16499
- }
16500
- return {
16501
- symbolRuntimes,
16502
- orderedTimestamps: [...orderedTimestamps].sort(
16503
- (left, right) => left - right
16504
- ),
16505
- sharedReplayKeyPrefixes,
16506
- runtimeLineages
16507
- };
16508
- };
16509
- releaseHistoricalReplayPlan = (plan) => {
16510
- plan.symbolRuntimes.length = 0;
16511
- plan.sharedReplayKeyPrefixes.length = 0;
16512
- };
16513
- }
16514
- });
16515
-
16516
16644
  // src/lib/replay/historicalSignalsReplaySkipEvidence.ts
16517
- var import_data2, replayScopeKey, appendReplaySignalEvaluationRun, createHistoricalReplaySkipEvidence;
16645
+ var import_data3, replayScopeKey, appendReplaySignalEvaluationRun, createHistoricalReplaySkipEvidence;
16518
16646
  var init_historicalSignalsReplaySkipEvidence = __esm({
16519
16647
  "src/lib/replay/historicalSignalsReplaySkipEvidence.ts"() {
16520
16648
  "use strict";
16521
- import_data2 = require("@tradejs/core/data");
16649
+ import_data3 = require("@tradejs/core/data");
16522
16650
  replayScopeKey = (strategy, symbol) => `${strategy}::${symbol}`;
16523
16651
  appendReplaySignalEvaluationRun = ({
16524
16652
  runs = [],
@@ -16526,7 +16654,7 @@ var init_historicalSignalsReplaySkipEvidence = __esm({
16526
16654
  timestamp,
16527
16655
  interval: interval4
16528
16656
  }) => {
16529
- const stepMs = (0, import_data2.intervalToMs)(interval4);
16657
+ const stepMs = (0, import_data3.intervalToMs)(interval4);
16530
16658
  const last = runs.at(-1);
16531
16659
  if (last?.reason === reason && last.stepMs === stepMs && timestamp === last.lastTimestamp + stepMs) {
16532
16660
  return [...runs.slice(0, -1), { ...last, lastTimestamp: timestamp }];
@@ -16602,14 +16730,16 @@ var advanceCycleMarket, advanceAbortedCycle, executeHistoricalReplay;
16602
16730
  var init_historicalSignalsReplayExecution = __esm({
16603
16731
  "src/lib/replay/historicalSignalsReplayExecution.ts"() {
16604
16732
  "use strict";
16733
+ init_historicalSignalsReplayPreparation();
16605
16734
  init_historicalSignalsReplaySkipEvidence();
16606
16735
  advanceCycleMarket = async (plan, connector, timestamp) => {
16607
16736
  const cycleSymbols = plan.symbolRuntimes.filter(
16608
- (symbolRuntime) => symbolRuntime.replayData[symbolRuntime.currentIndex]?.timestamp === timestamp
16737
+ (symbolRuntime) => (symbolRuntime.replayData[symbolRuntime.currentIndex]?.timestamp ?? NaN) + (symbolRuntime.timestampOffsetMs ?? 0) === timestamp
16609
16738
  );
16610
16739
  for (const symbolRuntime of cycleSymbols) {
16611
16740
  const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
16612
- if (candle?.timestamp === timestamp) {
16741
+ if (candle && candle.timestamp + (symbolRuntime.timestampOffsetMs ?? 0) === timestamp) {
16742
+ if (symbolRuntime.advanceMarket === false) continue;
16613
16743
  await connector.advanceMarket({
16614
16744
  symbol: symbolRuntime.symbol,
16615
16745
  candle
@@ -16621,7 +16751,7 @@ var init_historicalSignalsReplayExecution = __esm({
16621
16751
  advanceAbortedCycle = (cycleSymbols, timestamp) => {
16622
16752
  for (const symbolRuntime of cycleSymbols ?? []) {
16623
16753
  const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
16624
- if (candle?.timestamp === timestamp) {
16754
+ if (candle && candle.timestamp + (symbolRuntime.timestampOffsetMs ?? 0) === timestamp) {
16625
16755
  symbolRuntime.currentIndex += 1;
16626
16756
  }
16627
16757
  }
@@ -16670,21 +16800,21 @@ var init_historicalSignalsReplayExecution = __esm({
16670
16800
  const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
16671
16801
  const btcCandle = symbolRuntime.btcReplayData[symbolRuntime.currentIndex];
16672
16802
  const ethCandle = symbolRuntime.ethReplayData[symbolRuntime.currentIndex];
16673
- if (!candle || !btcCandle || candle.timestamp !== timestamp || btcCandle.timestamp !== timestamp) {
16803
+ if (!candle || !btcCandle || candle.timestamp + (symbolRuntime.timestampOffsetMs ?? 0) !== timestamp || btcCandle.timestamp !== candle.timestamp) {
16674
16804
  continue;
16675
16805
  }
16676
16806
  for (const strategyRuntime of symbolRuntime.strategies) {
16677
16807
  const result = await strategyRuntime.run(
16678
16808
  candle,
16679
16809
  btcCandle,
16680
- ethCandle?.timestamp === timestamp ? ethCandle : void 0
16810
+ ethCandle?.timestamp === candle.timestamp ? ethCandle : void 0
16681
16811
  );
16682
16812
  skipEvidence.record({
16683
16813
  strategyName: strategyRuntime.strategyName,
16684
16814
  symbol: symbolRuntime.symbol,
16685
16815
  strategyConfig: strategyRuntime.strategyConfig,
16686
16816
  runtimeLineage: strategyRuntime.runtimeLineage,
16687
- timestamp,
16817
+ timestamp: candle.timestamp,
16688
16818
  result
16689
16819
  });
16690
16820
  if (result && typeof result !== "string") {
@@ -16705,10 +16835,7 @@ var init_historicalSignalsReplayExecution = __esm({
16705
16835
  reportProgress(timestamp);
16706
16836
  }
16707
16837
  } finally {
16708
- for (const keyPrefix of context.plan.sharedReplayKeyPrefixes) {
16709
- adapters.releaseIndicatorsCache(keyPrefix);
16710
- adapters.releaseReplayCache(keyPrefix);
16711
- }
16838
+ releaseHistoricalReplayCaches(context.plan, adapters);
16712
16839
  }
16713
16840
  return {
16714
16841
  signals: signals2,
@@ -17013,7 +17140,19 @@ var init_historicalSignalsReplay = __esm({
17013
17140
  }
17014
17141
  );
17015
17142
  memory?.("references:loaded");
17016
- const plan = await prepareHistoricalReplay(
17143
+ const cacheAdapters = {
17144
+ releaseIndicatorsCache: (keyPrefix) => releaseReplayCacheCopies(
17145
+ keyPrefix,
17146
+ import_strategies5.releaseStrategyIndicatorsReplayCache,
17147
+ getProjectReplayCacheReleasers()?.releaseStrategyIndicatorsReplayCache
17148
+ ),
17149
+ releaseReplayCache: (keyPrefix) => releaseReplayCacheCopies(
17150
+ keyPrefix,
17151
+ import_strategies5.releaseStrategyReplayCache,
17152
+ getProjectReplayCacheReleasers()?.releaseStrategyReplayCache
17153
+ )
17154
+ };
17155
+ const plan = await prepareMultiIntervalHistoricalReplay(
17017
17156
  {
17018
17157
  userName: replayUserName,
17019
17158
  projectRoot: replayProjectRoot,
@@ -17030,6 +17169,7 @@ var init_historicalSignalsReplay = __esm({
17030
17169
  }
17031
17170
  },
17032
17171
  {
17172
+ ...cacheAdapters,
17033
17173
  progress: {
17034
17174
  tick: (tokens) => prepareBar.tick(1, tokens)
17035
17175
  },
@@ -17039,7 +17179,11 @@ var init_historicalSignalsReplay = __esm({
17039
17179
  },
17040
17180
  buildLineage: buildRuntimeLineage,
17041
17181
  sampleMemory: memory
17042
- }
17182
+ },
17183
+ (nativeInterval, nativeRun) => loadHistoricalReplayReferences({
17184
+ preparedRun: nativeRun,
17185
+ interval: nativeInterval
17186
+ })
17043
17187
  );
17044
17188
  memory?.("strategies:prepared", { symbols: plan.symbolRuntimes.length });
17045
17189
  const hookContext = buildAfterSignalsContext({
@@ -17066,6 +17210,7 @@ var init_historicalSignalsReplay = __esm({
17066
17210
  hookContext
17067
17211
  },
17068
17212
  {
17213
+ ...cacheAdapters,
17069
17214
  clock: { now: Date.now },
17070
17215
  progress: {
17071
17216
  enabled: showProgress,
@@ -17078,17 +17223,7 @@ var init_historicalSignalsReplay = __esm({
17078
17223
  },
17079
17224
  invokeBeforeSignals: invokeBeforeSignalsHooks,
17080
17225
  invokeAfterSignals: invokeAfterSignalsHooks,
17081
- enrichSignal: (signal) => (0, import_strategies6.enrichSignalWithBinanceMarketContext)({ signal, env: "PARITY" }),
17082
- releaseIndicatorsCache: (keyPrefix) => releaseReplayCacheCopies(
17083
- keyPrefix,
17084
- import_strategies5.releaseStrategyIndicatorsReplayCache,
17085
- getProjectReplayCacheReleasers()?.releaseStrategyIndicatorsReplayCache
17086
- ),
17087
- releaseReplayCache: (keyPrefix) => releaseReplayCacheCopies(
17088
- keyPrefix,
17089
- import_strategies5.releaseStrategyReplayCache,
17090
- getProjectReplayCacheReleasers()?.releaseStrategyReplayCache
17091
- )
17226
+ enrichSignal: (signal) => (0, import_strategies6.enrichSignalWithBinanceMarketContext)({ signal, env: "PARITY" })
17092
17227
  }
17093
17228
  );
17094
17229
  memory?.("execution:complete", { signals: execution.signals.length });
@@ -17668,18 +17803,18 @@ var init_paritySummary = __esm({
17668
17803
  });
17669
17804
 
17670
17805
  // src/lib/runtimeSignalsStorage.ts
17671
- var import_constants12, import_data3, import_time5, appendRuntimeSignalEvaluationRun, SECONDS_PER_DAY, RUNTIME_SIGNAL_RETENTION_DAYS_ENV, DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS, RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS, parsePositiveNumber, getRuntimeSignalRetentionTtlSeconds, toRuntimeSignalBucketRef, shouldStoreRuntimeSignalDiagnostics, toStoredRuntimeSignal, isRuntimeSignalBucketRef, normalizeRuntimeSignalSkipReason, buildStatsReasonField, buildRuntimeSignalStatsIncrements, shouldStoreDetailedRuntimeSignalEvaluation, toCount, parseRuntimeSignalStatsBucket;
17806
+ var import_constants12, import_data4, import_time5, appendRuntimeSignalEvaluationRun, SECONDS_PER_DAY, RUNTIME_SIGNAL_RETENTION_DAYS_ENV, DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS, RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS, parsePositiveNumber, getRuntimeSignalRetentionTtlSeconds, toRuntimeSignalBucketRef, shouldStoreRuntimeSignalDiagnostics, toStoredRuntimeSignal, isRuntimeSignalBucketRef, normalizeRuntimeSignalSkipReason, buildStatsReasonField, buildRuntimeSignalStatsIncrements, shouldStoreDetailedRuntimeSignalEvaluation, toCount, parseRuntimeSignalStatsBucket;
17672
17807
  var init_runtimeSignalsStorage = __esm({
17673
17808
  "src/lib/runtimeSignalsStorage.ts"() {
17674
17809
  "use strict";
17675
17810
  import_constants12 = require("@tradejs/core/constants");
17676
- import_data3 = require("@tradejs/core/data");
17811
+ import_data4 = require("@tradejs/core/data");
17677
17812
  import_time5 = require("@tradejs/core/time");
17678
17813
  appendRuntimeSignalEvaluationRun = (runs = [], evaluation) => {
17679
17814
  if (evaluation.status !== "skip") {
17680
17815
  return [...runs];
17681
17816
  }
17682
- const stepMs = (0, import_data3.intervalToMs)(evaluation.interval);
17817
+ const stepMs = (0, import_data4.intervalToMs)(evaluation.interval);
17683
17818
  const reason = evaluation.reason?.trim() || "NO_SIGNAL";
17684
17819
  const existing = runs.find(
17685
17820
  (run) => run.status === "skip" && run.reason === reason && run.stepMs === stepMs && evaluation.timestamp >= run.firstTimestamp && evaluation.timestamp <= run.lastTimestamp && (evaluation.timestamp - run.firstTimestamp) % stepMs === 0
@@ -19419,7 +19554,10 @@ var init_runtimeComparisonCalculations = __esm({
19419
19554
  return candidates.map((candidate) => ({
19420
19555
  ...candidate,
19421
19556
  timestampDiffMs: Math.abs(
19422
- candidate.timestamp + signalTimestampOffsetMs - exchangeEntry.entryTimestamp
19557
+ candidate.timestamp + resolveTradeParityTimestampOffset(
19558
+ strategyName,
19559
+ signalTimestampOffsetMs
19560
+ ) - exchangeEntry.entryTimestamp
19423
19561
  )
19424
19562
  })).filter((candidate) => candidate.timestampDiffMs <= toleranceMs).sort(
19425
19563
  (left, right) => left.timestampDiffMs - right.timestampDiffMs || Number(right.orderStatus === "failed") - Number(left.orderStatus === "failed")
@@ -19669,11 +19807,12 @@ var init_runtimeComparisonReporting = __esm({
19669
19807
  });
19670
19808
 
19671
19809
  // src/lib/replay/runtimeComparison.ts
19672
- var import_chalk20, getReplayEntryTimestampCompareOffsetMs, buildReplaySignalEvaluations, toFiniteNumberOrNull4, sumOptionalNumbers, firstFiniteNumber, firstString, getExchangeExecutionMergeKey, mergeExchangeExecutionGroup, mergeExchangeEntryExecutions, loadExchangeEntryRows, loadExchangeEntriesForComparison, saveAndPrintReplayExchangeComparison, saveAndPrintReplayRuntimeComparison;
19810
+ var import_chalk20, import_data5, replayStrategyIntervals, buildReplaySignalEvaluations, toFiniteNumberOrNull4, sumOptionalNumbers, firstFiniteNumber, firstString, getExchangeExecutionMergeKey, mergeExchangeExecutionGroup, mergeExchangeEntryExecutions, loadExchangeEntryRows, loadExchangeEntriesForComparison, saveAndPrintReplayExchangeComparison, saveAndPrintReplayRuntimeComparison;
19673
19811
  var init_runtimeComparison = __esm({
19674
19812
  "src/lib/replay/runtimeComparison.ts"() {
19675
19813
  "use strict";
19676
19814
  import_chalk20 = __toESM(require("chalk"));
19815
+ import_data5 = require("@tradejs/core/data");
19677
19816
  init_runtimeParity();
19678
19817
  init_paritySummary();
19679
19818
  init_runtimeRedis();
@@ -19688,10 +19827,13 @@ var init_runtimeComparison = __esm({
19688
19827
  init_runtimeComparisonCalculations();
19689
19828
  init_runtimeComparisonReporting();
19690
19829
  init_runtimeComparisonCalculations();
19691
- getReplayEntryTimestampCompareOffsetMs = () => {
19692
- const intervalMinutes = Number(replayInterval);
19693
- return Number.isFinite(intervalMinutes) && intervalMinutes > 0 ? intervalMinutes * 60 * 1e3 : 15 * 60 * 1e3;
19694
- };
19830
+ replayStrategyIntervals = (summaries) => summaries.map(({ strategyName, strategyConfig }) => {
19831
+ const durationMs = (0, import_data5.intervalToMs)(strategyConfig.INTERVAL);
19832
+ if (!Number.isFinite(durationMs) || durationMs <= 0) {
19833
+ throw new Error(`Invalid replay interval for ${strategyName}`);
19834
+ }
19835
+ return { strategyName, interval: String(durationMs / 6e4) };
19836
+ });
19695
19837
  buildReplaySignalEvaluations = (signals2) => signals2.map((signal) => ({
19696
19838
  evaluationId: `${signal.strategy}:${signal.symbol}:${signal.timestamp}`,
19697
19839
  userName: replayUserName,
@@ -19908,7 +20050,8 @@ var init_runtimeComparison = __esm({
19908
20050
  runtimeSignals,
19909
20051
  runtimeSignalEvaluations,
19910
20052
  runtimeLineageScopes,
19911
- lineage
20053
+ lineage,
20054
+ timestampOffsetMs
19912
20055
  }) => {
19913
20056
  const { connector, connectorName, window } = getRuntimeCompareContext();
19914
20057
  const rawExchangeEntries = await loadExchangeEntriesForComparison({
@@ -19933,7 +20076,7 @@ var init_runtimeComparison = __esm({
19933
20076
  runtimeSignals,
19934
20077
  runtimeSignalEvaluations,
19935
20078
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
19936
- signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
20079
+ signalTimestampOffsetMs: timestampOffsetMs
19937
20080
  });
19938
20081
  });
19939
20082
  const comparisonLineage = {
@@ -19955,7 +20098,7 @@ var init_runtimeComparison = __esm({
19955
20098
  backtestEntries,
19956
20099
  strategyNameByOrderLinkKey,
19957
20100
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
19958
- backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs(),
20101
+ backtestTimestampOffsetMs: timestampOffsetMs,
19959
20102
  runtimeSignals,
19960
20103
  runtimeSignalEvaluations,
19961
20104
  replaySignals,
@@ -19994,7 +20137,7 @@ var init_runtimeComparison = __esm({
19994
20137
  exchangeEntries,
19995
20138
  backtestEntries,
19996
20139
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
19997
- backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
20140
+ backtestTimestampOffsetMs: timestampOffsetMs
19998
20141
  });
19999
20142
  const { completed: matched, orderFailed } = splitExchangeMatchesByRuntimeOrderStatus({
20000
20143
  matched: comparison.matched,
@@ -20002,7 +20145,7 @@ var init_runtimeComparison = __esm({
20002
20145
  runtimeSignals,
20003
20146
  runtimeSignalEvaluations,
20004
20147
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
20005
- signalTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs()
20148
+ signalTimestampOffsetMs: timestampOffsetMs
20006
20149
  });
20007
20150
  const details = buildReplayExchangeComparisonDetails({
20008
20151
  matched,
@@ -20013,7 +20156,7 @@ var init_runtimeComparison = __esm({
20013
20156
  backtestEntries,
20014
20157
  strategyNameByOrderLinkKey,
20015
20158
  toleranceMs: REPLAY_RUNTIME_COMPARE_TOLERANCE_MS,
20016
- backtestTimestampOffsetMs: getReplayEntryTimestampCompareOffsetMs(),
20159
+ backtestTimestampOffsetMs: timestampOffsetMs,
20017
20160
  runtimeSignals,
20018
20161
  runtimeSignalEvaluations,
20019
20162
  replaySignals,
@@ -20074,9 +20217,16 @@ var init_runtimeComparison = __esm({
20074
20217
  expectedUserName: replayUserName,
20075
20218
  expectedWindow: window
20076
20219
  }) : null;
20077
- const replayTimestampOffset = runtimeEvidence2 ? buildTradeParityTimestampOffset(
20078
- (0, runtimeEvidenceDeployment_exports.activeRuntimeEvidenceStrategies)(runtimeEvidence2.deployment)
20079
- ) : getReplayEntryTimestampCompareOffsetMs();
20220
+ const declaredIntervals = runtimeEvidence2 ? [] : replayStrategyIntervals(liveStrategySummaries);
20221
+ const replayTimestampOffset = buildTradeParityTimestampOffset(
20222
+ runtimeEvidence2 ? (0, runtimeEvidenceDeployment_exports.activeRuntimeEvidenceStrategies)(runtimeEvidence2.deployment) : declaredIntervals
20223
+ );
20224
+ const metadataStart = runtimeEvidence2 ? window.start : window.start - Math.max(
20225
+ 0,
20226
+ ...declaredIntervals.map(
20227
+ ({ interval: interval4 }) => Number(interval4) * 6e4 - (0, import_data5.intervalToMs)(replayInterval)
20228
+ )
20229
+ );
20080
20230
  const [
20081
20231
  rawRuntimeTrades,
20082
20232
  runtimeSignals,
@@ -20093,15 +20243,15 @@ var init_runtimeComparison = __esm({
20093
20243
  endTime: window.end
20094
20244
  }),
20095
20245
  loadRuntimeSignals(replayUserName, {
20096
- startTime: window.start,
20246
+ startTime: metadataStart,
20097
20247
  endTime: window.end
20098
20248
  }),
20099
20249
  loadRuntimeSignalEvaluations(replayUserName, {
20100
- startTime: window.start,
20250
+ startTime: metadataStart,
20101
20251
  endTime: window.end
20102
20252
  }),
20103
20253
  loadRuntimeLineageScopes(replayUserName, {
20104
- startTime: window.start,
20254
+ startTime: metadataStart,
20105
20255
  endTime: window.end
20106
20256
  })
20107
20257
  ]);
@@ -20169,6 +20319,7 @@ var init_runtimeComparison = __esm({
20169
20319
  );
20170
20320
  return saveAndPrintReplayExchangeComparison({
20171
20321
  liveStrategySummaries,
20322
+ timestampOffsetMs: replayTimestampOffset,
20172
20323
  backtestEntries: comparableBacktestEntries,
20173
20324
  replaySignals,
20174
20325
  replaySignalEvaluations,
@@ -21086,13 +21237,14 @@ ${markdownTable2(strategyRows)}
21086
21237
  });
21087
21238
 
21088
21239
  // src/scripts/replayRunner.ts
21089
- var import_chalk21, import_constants14, import_data4, import_time8, import_redis11, import_candles, import_runtimeStrategies4, import_strategies7, prepareReplayBinanceMarketContext, replayMarketContextInterval, cachedTickerSymbols, finishReplay, replayBacktest;
21240
+ var import_chalk21, import_constants14, import_data6, import_time8, import_redis11, import_candles, import_runtimeStrategies4, import_strategies7, prepareReplayBinanceMarketContext, replayMarketContextInterval, cachedTickerSymbols, finishReplay, replayBacktest;
21090
21241
  var init_replayRunner = __esm({
21091
21242
  "src/scripts/replayRunner.ts"() {
21092
21243
  "use strict";
21244
+ init_historicalSignalsReplayPreparation();
21093
21245
  import_chalk21 = __toESM(require("chalk"));
21094
21246
  import_constants14 = require("@tradejs/core/constants");
21095
- import_data4 = require("@tradejs/core/data");
21247
+ import_data6 = require("@tradejs/core/data");
21096
21248
  import_time8 = require("@tradejs/core/time");
21097
21249
  import_redis11 = require("@tradejs/infra/redis");
21098
21250
  import_candles = require("@tradejs/infra/timescale/candles");
@@ -21368,16 +21520,10 @@ var init_replayRunner = __esm({
21368
21520
  if (!replayStrategies.length) {
21369
21521
  throw new Error(`No enabled strategies in deployment ${deployment.id}`);
21370
21522
  }
21371
- const strategyIntervals = new Set(
21372
- replayStrategies.map(
21373
- ({ strategyConfig }) => String(strategyConfig.INTERVAL)
21374
- )
21523
+ resolveHistoricalReplayIntervals(
21524
+ replayInterval,
21525
+ replayStrategies.map(({ strategyConfig }) => strategyConfig.INTERVAL)
21375
21526
  );
21376
- if (strategyIntervals.size !== 1 || !strategyIntervals.has(replayInterval)) {
21377
- throw new Error(
21378
- `Replay interval does not match deployment ${deployment.id}: replay=${replayInterval}, strategies=${[...strategyIntervals].join(",")}`
21379
- );
21380
- }
21381
21527
  const strategyUniverses = new Set(
21382
21528
  replayStrategies.map(
21383
21529
  ({ strategyConfig }) => String(strategyConfig.UNIVERSE)
@@ -21423,13 +21569,13 @@ var init_replayRunner = __esm({
21423
21569
  deploymentId: deployment.id,
21424
21570
  assetClasses: deployment.assetClasses,
21425
21571
  deployment,
21426
- closedIntervalMs: (0, import_data4.intervalToMs)(replayInterval)
21572
+ closedIntervalMs: (0, import_data6.intervalToMs)(replayInterval)
21427
21573
  });
21428
21574
  if (!preparedRun || isReplayUpdateOnlyRun) {
21429
21575
  return;
21430
21576
  }
21431
21577
  if (useAllData) {
21432
- const intervalMs = (0, import_data4.intervalToMs)(replayInterval);
21578
+ const intervalMs = (0, import_data6.intervalToMs)(replayInterval);
21433
21579
  const edges = await (0, import_candles.getDataEdgesForSymbols)(
21434
21580
  deployment.provider,
21435
21581
  preparedRun.tickers,
@@ -21914,13 +22060,13 @@ var init_binanceMarketIngest = __esm({
21914
22060
  });
21915
22061
 
21916
22062
  // src/lib/runBot.ts
21917
- var import_lodash, import_connectors10, import_data5, import_strategies10, import_async3, import_constants15, import_time9, import_logger2, import_redis12, projectRoot4, runBot;
22063
+ var import_lodash, import_connectors10, import_data7, import_strategies10, import_async3, import_constants15, import_time9, import_logger2, import_redis12, projectRoot4, runBot;
21918
22064
  var init_runBot = __esm({
21919
22065
  "src/lib/runBot.ts"() {
21920
22066
  "use strict";
21921
22067
  import_lodash = __toESM(require("lodash"));
21922
22068
  import_connectors10 = require("@tradejs/node/connectors");
21923
- import_data5 = require("@tradejs/core/data");
22069
+ import_data7 = require("@tradejs/core/data");
21924
22070
  import_strategies10 = require("@tradejs/node/strategies");
21925
22071
  import_async3 = require("@tradejs/core/async");
21926
22072
  import_constants15 = require("@tradejs/core/constants");
@@ -22039,11 +22185,11 @@ var init_runBot = __esm({
22039
22185
  status: JSON.stringify(status)
22040
22186
  });
22041
22187
  } catch (err) {
22042
- import_logger2.logger.log("error", "bot %s error: %s", bot.symbol, (0, import_data5.toJson)(err, false));
22188
+ import_logger2.logger.log("error", "bot %s error: %s", bot.symbol, (0, import_data7.toJson)(err, false));
22043
22189
  }
22044
22190
  }
22045
22191
  }
22046
- import_logger2.logger.log("info", "botResults: %s", (0, import_data5.toJson)(botResults, true));
22192
+ import_logger2.logger.log("info", "botResults: %s", (0, import_data7.toJson)(botResults, true));
22047
22193
  return botResults;
22048
22194
  };
22049
22195
  }
@@ -23502,12 +23648,12 @@ var init_doctor = __esm({
23502
23648
  });
23503
23649
 
23504
23650
  // src/lib/executionCalibration.ts
23505
- var import_constants17, import_data6, import_trade3, roundMetric, asRecord3, asArray, finiteNumber2, finiteString, finiteDirection, metricValues, quantile, summarizeNumbers, getIntervalMs, getSignalKey, addSignalToMap, getRuntimePayload, extractRuntimeTradeRows, normalizeReplayMatch, extractReplayMatches, buildReplayMatchMaps, getSignalCandles, adverseEntryBps, feeBps, latencyDelta, resolveTelemetryQuality, finiteTelemetryQuality, countBy, toLatencyBucket, toSpreadBucket, buildSample, buildReplayOnlySample, hasTelemetry, hasFullTelemetry, buildGroupSummary, groupSamples, positiveMetricValues, buildRecommendation, buildExecutionCalibrationReport;
23651
+ var import_constants17, import_data8, import_trade3, roundMetric, asRecord3, asArray, finiteNumber2, finiteString, finiteDirection, metricValues, quantile, summarizeNumbers, getIntervalMs, getSignalKey, addSignalToMap, getRuntimePayload, extractRuntimeTradeRows, normalizeReplayMatch, extractReplayMatches, buildReplayMatchMaps, getSignalCandles, adverseEntryBps, feeBps, latencyDelta, resolveTelemetryQuality, finiteTelemetryQuality, countBy, toLatencyBucket, toSpreadBucket, buildSample, buildReplayOnlySample, hasTelemetry, hasFullTelemetry, buildGroupSummary, groupSamples, positiveMetricValues, buildRecommendation, buildExecutionCalibrationReport;
23506
23652
  var init_executionCalibration = __esm({
23507
23653
  "src/lib/executionCalibration.ts"() {
23508
23654
  "use strict";
23509
23655
  import_constants17 = require("@tradejs/core/constants");
23510
- import_data6 = require("@tradejs/core/data");
23656
+ import_data8 = require("@tradejs/core/data");
23511
23657
  import_trade3 = require("@tradejs/core/trade");
23512
23658
  roundMetric = (value, decimals = 6) => typeof value === "number" && Number.isFinite(value) ? Number(value.toFixed(decimals)) : null;
23513
23659
  asRecord3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -23567,7 +23713,7 @@ var init_executionCalibration = __esm({
23567
23713
  return null;
23568
23714
  }
23569
23715
  try {
23570
- return (0, import_data6.intervalToMs)(interval4);
23716
+ return (0, import_data8.intervalToMs)(interval4);
23571
23717
  } catch {
23572
23718
  return null;
23573
23719
  }
@@ -24795,14 +24941,16 @@ const timer = setInterval(() => {
24795
24941
  if (result.exitCode !== 0) {
24796
24942
  let fd;
24797
24943
  try {
24798
- fd = fs.openSync(file + '.log', fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
24944
+ const logId = result.logRequestId === undefined ? id : result.logRequestId;
24945
+ if (typeof logId !== 'string' || !/^[a-f0-9]{64}$/.test(logId)) throw new Error('Invalid replay log identity');
24946
+ fd = fs.openSync(path.join(root, logId) + '.log', fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
24799
24947
  const size = fs.fstatSync(fd).size;
24800
24948
  const tail = Buffer.alloc(Math.min(size, 65536));
24801
24949
  fs.readSync(fd, tail, 0, tail.length, Math.max(0, size - tail.length));
24802
24950
  // The worker redacts each line before persisting or returning it through MCP.
24803
24951
  for (const line of tail.toString('utf8').split(/[\\r\\n]+/).filter(Boolean).slice(-150)) console.error('Host replay: ' + line);
24804
24952
  } catch (error) {
24805
- console.error('Host replay log unavailable: ' + error.code);
24953
+ console.error('Host replay: ' + result.message + '; log unavailable: ' + (error.code || error.message));
24806
24954
  } finally { if (fd !== undefined) fs.closeSync(fd); }
24807
24955
  }
24808
24956
  clearInterval(timer);
@@ -31438,6 +31586,10 @@ var init_specification = __esm({
31438
31586
  };
31439
31587
  if (!spec.budget || !spec.selection || !spec.diagnostics)
31440
31588
  throw new Error("Missing research budgets or criteria");
31589
+ if (spec.featurePaths !== void 0 && (!Array.isArray(spec.featurePaths) || !spec.featurePaths.length || spec.featurePaths.length > 64 || new Set(spec.featurePaths).size !== spec.featurePaths.length || spec.featurePaths.some(
31590
+ (path56) => typeof path56 !== "string" || !/^additionalIndicators\.[a-zA-Z0-9_.]+$/.test(path56) || classifyAiPocketFeaturePath(path56.split(".")) !== "eligible"
31591
+ )))
31592
+ throw new Error("Feature paths must be unique causal market features");
31441
31593
  positive(spec.budget.maxVariants, "maxVariants", 12);
31442
31594
  if (!Number.isInteger(spec.minQuality) || spec.minQuality < 1 || spec.minQuality > 5)
31443
31595
  throw new Error("minQuality must be 1 through 5");
@@ -33609,6 +33761,7 @@ var init_orchestrator2 = __esm({
33609
33761
  parentSpec.selection,
33610
33762
  parentSpec.portfolio,
33611
33763
  parentSpec.minQuality,
33764
+ parentSpec.featurePaths,
33612
33765
  parentSpec.diagnostics
33613
33766
  ]) !== sha256Json([
33614
33767
  spec.core.variants.map((item) => item.resolvedConfig),
@@ -33617,6 +33770,7 @@ var init_orchestrator2 = __esm({
33617
33770
  spec.selection,
33618
33771
  spec.portfolio,
33619
33772
  spec.minQuality,
33773
+ spec.featurePaths,
33620
33774
  spec.diagnostics
33621
33775
  ]) || parentState.lineage.runtimeSha256 !== state.lineage.runtimeSha256 || parentState.lineage.contextEnvironmentSha256 !== state.lineage.contextEnvironmentSha256)
33622
33776
  throw new Error(
@@ -34132,7 +34286,7 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
34132
34286
  });
34133
34287
 
34134
34288
  // src/lib/strategyResearch/native.ts
34135
- var import_promises32, import_node_fs9, import_node_path29, import_node_os4, import_node_readline4, import_node_child_process8, import_node_module3, import_redis17, import_ai9, import_backtest7, import_registry5, import_connectors14, import_data7, import_coreResearch2, import_jev6, validateResearchJevTraining, nativeConfig, runResearchCommand, createNativeStrategyResearchBackend;
34289
+ var import_promises32, import_node_fs9, import_node_path29, import_node_os4, import_node_readline4, import_node_child_process8, import_node_module3, import_redis17, import_ai9, import_backtest7, import_registry5, import_connectors14, import_data9, import_coreResearch2, import_jev6, validateResearchJevTraining, nativeConfig, runResearchCommand, createNativeStrategyResearchBackend;
34136
34290
  var init_native = __esm({
34137
34291
  "src/lib/strategyResearch/native.ts"() {
34138
34292
  "use strict";
@@ -34157,7 +34311,7 @@ var init_native = __esm({
34157
34311
  init_io2();
34158
34312
  init_aiTrainResearch();
34159
34313
  init_events();
34160
- import_data7 = require("@tradejs/core/data");
34314
+ import_data9 = require("@tradejs/core/data");
34161
34315
  import_coreResearch2 = require("@tradejs/infra/coreResearch");
34162
34316
  init_trace();
34163
34317
  import_jev6 = require("@tradejs/core/jev");
@@ -34325,9 +34479,10 @@ var init_native = __esm({
34325
34479
  throw new Error(
34326
34480
  "Tester workers are already active; research will resume when the host is free"
34327
34481
  );
34328
- if (import_node_os4.default.freemem() < spec.budget.minimumFreeMemoryMb * 1024 * 1024)
34482
+ const availableBytes = import_node_os4.default.platform() === "darwin" ? process.availableMemory() : import_node_os4.default.freemem();
34483
+ if (!Number.isFinite(availableBytes) || availableBytes < spec.budget.minimumFreeMemoryMb * 1024 * 1024)
34329
34484
  throw new Error(
34330
- "Insufficient free host memory for the frozen research budget"
34485
+ "Insufficient available host memory for the frozen research budget"
34331
34486
  );
34332
34487
  };
34333
34488
  return {
@@ -34551,7 +34706,7 @@ var init_native = __esm({
34551
34706
  );
34552
34707
  }
34553
34708
  if (!coverage.some(
34554
- (item) => (item.last ?? 0) >= spec.core.window.end - 2 * (0, import_data7.intervalToMs)(spec.core.execution.interval)
34709
+ (item) => (item.last ?? 0) >= spec.core.window.end - 2 * (0, import_data9.intervalToMs)(spec.core.execution.interval)
34555
34710
  ))
34556
34711
  throw new Error(
34557
34712
  "Cached market history ends before the requested window; choose a covered end or explicitly refresh history"
@@ -34780,9 +34935,14 @@ var init_native = __esm({
34780
34935
  throw new Error(
34781
34936
  `Native/export reconciliation is ${reconciliation.status}: ${reconciliation.reasons.join("; ")}`
34782
34937
  );
34938
+ const researchSignalId = (symbol, signalId) => JSON.stringify([symbol, signalId]);
34783
34939
  const trades = loaded.trades.filter(
34784
34940
  (trade) => trade.signalTimestamp >= spec.core.window.start && trade.signalTimestamp < spec.core.window.end && trade.exitTimestamp < spec.core.window.end
34785
- );
34941
+ ).map((trade) => ({
34942
+ ...trade,
34943
+ // Strategy-generated signal IDs need not be unique across symbols.
34944
+ signalId: researchSignalId(trade.symbol, trade.signalId)
34945
+ }));
34786
34946
  const tradeBySignal = new Map(
34787
34947
  trades.map((trade) => [trade.signalId, trade])
34788
34948
  );
@@ -34806,9 +34966,10 @@ var init_native = __esm({
34806
34966
  for await (const line of lines) {
34807
34967
  if (!line.trim()) continue;
34808
34968
  const row = JSON.parse(line);
34809
- const trade = tradeBySignal.get(row.signalId);
34810
- if (!trade || seen.has(row.signalId)) continue;
34811
- seen.add(row.signalId);
34969
+ const signalId = researchSignalId(row.symbol, row.signalId);
34970
+ const trade = tradeBySignal.get(signalId);
34971
+ if (!trade || seen.has(signalId)) continue;
34972
+ seen.add(signalId);
34812
34973
  if (discoveryOnly && (row.timestamp >= spec.testSince || trade.exitTimestamp >= spec.testSince)) {
34813
34974
  if (row.timestamp < spec.testSince) purgedLabels++;
34814
34975
  continue;
@@ -34846,7 +35007,7 @@ var init_native = __esm({
34846
35007
  featureProfile: "all"
34847
35008
  });
34848
35009
  for (const key of Object.keys(features))
34849
- if (!key.startsWith("additionalIndicators.") || features[key] == null)
35010
+ if (!key.startsWith("additionalIndicators.") || features[key] == null || spec.featurePaths && !spec.featurePaths.includes(key))
34850
35011
  delete features[key];
34851
35012
  featureValues += Object.keys(features).length;
34852
35013
  if (featureValues > 5e6)
@@ -34865,7 +35026,7 @@ var init_native = __esm({
34865
35026
  pathCoverage[key]++;
34866
35027
  }
34867
35028
  rows.push({
34868
- signalId: row.signalId,
35029
+ signalId,
34869
35030
  symbol: row.symbol,
34870
35031
  strategy: row.strategyName,
34871
35032
  timestamp: row.timestamp,
@@ -34880,7 +35041,7 @@ var init_native = __esm({
34880
35041
  });
34881
35042
  if (discoveryOnly) {
34882
35043
  eventInputs.push({
34883
- signalId: row.signalId,
35044
+ signalId,
34884
35045
  timestamp: row.timestamp,
34885
35046
  symbol: row.symbol,
34886
35047
  direction: row.direction,
@@ -34891,8 +35052,8 @@ var init_native = __esm({
34891
35052
  });
34892
35053
  const key = `${row.direction}:${trade.netProfit > 0}`;
34893
35054
  const bucket = sampleBuckets.get(key) ?? [];
34894
- bucket.push(row.signalId);
34895
- sampleInputs.set(row.signalId, row);
35055
+ bucket.push(signalId);
35056
+ sampleInputs.set(signalId, row);
34896
35057
  bucket.sort((a, b) => sha256Json(a).localeCompare(sha256Json(b)));
34897
35058
  while (bucket.length > spec.diagnostics.samplePerSideAndOutcome)
34898
35059
  sampleInputs.delete(bucket.pop());
@@ -34927,7 +35088,7 @@ var init_native = __esm({
34927
35088
  grouped.set(input.symbol, group);
34928
35089
  }
34929
35090
  for (const [symbol, events] of grouped) {
34930
- const intervalMs = (0, import_data7.intervalToMs)(
35091
+ const intervalMs = (0, import_data9.intervalToMs)(
34931
35092
  spec.core.execution.interval
34932
35093
  );
34933
35094
  const candles = await provider.kline({
@@ -34960,7 +35121,7 @@ var init_native = __esm({
34960
35121
  const trade = tradeBySignal.get(row.signalId);
34961
35122
  const original = sampleInputs.get(row.signalId);
34962
35123
  if (!trade || !original) continue;
34963
- const intervalMs = (0, import_data7.intervalToMs)(
35124
+ const intervalMs = (0, import_data9.intervalToMs)(
34964
35125
  spec.core.execution.interval
34965
35126
  );
34966
35127
  const candles = await provider.kline({