@tradejs/cli 2.0.1 → 2.0.3

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.
@@ -3224,6 +3224,38 @@ var resolveDerivativesContextMissingFetchFromMs = (params) => {
3224
3224
  return null;
3225
3225
  };
3226
3226
  var resolveDerivativesContextFetchFromMs = (params) => resolveDerivativesContextMissingFetchFromMs(params) ?? (params.refreshClosedTail ? params.toMs : null);
3227
+ var resolveDerivativesContextRequiredFetchFromMs = (params) => {
3228
+ const dataCoverageFromMs = params.dataCoverageKeyExists ? null : params.mode === "signals" ? params.fromMs : resolveDerivativesContextMissingCoverageFetchFromMs({
3229
+ ranges: params.dataCoverageRanges,
3230
+ fromMs: params.fromMs,
3231
+ toMs: params.toMs,
3232
+ intervalMs: params.intervalMs
3233
+ });
3234
+ const edgesFromMs = dataCoverageFromMs == null ? null : resolveDerivativesContextFetchFromMs({
3235
+ edges: params.edges,
3236
+ fromMs: params.fromMs,
3237
+ toMs: params.toMs,
3238
+ intervalMs: params.intervalMs,
3239
+ refreshClosedTail: params.mode === "signals"
3240
+ });
3241
+ const dataFetchFromMs = dataCoverageFromMs != null && edgesFromMs != null ? Math.max(dataCoverageFromMs, edgesFromMs) : null;
3242
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3243
+ interval: params.interval,
3244
+ fromMs: params.fromMs,
3245
+ toMs: params.toMs,
3246
+ nowMs: params.nowMs
3247
+ });
3248
+ const liquidationFetchFromMs = confirmedLiquidationWindow ? resolveDerivativesContextMissingCoverageFetchFromMs({
3249
+ ranges: params.liquidationCoverageRanges,
3250
+ fromMs: confirmedLiquidationWindow.fromMs,
3251
+ toMs: confirmedLiquidationWindow.toMs,
3252
+ intervalMs: params.intervalMs
3253
+ }) : null;
3254
+ const fetchStarts = [dataFetchFromMs, liquidationFetchFromMs].filter(
3255
+ (value) => value != null
3256
+ );
3257
+ return fetchStarts.length ? Math.min(...fetchStarts) : null;
3258
+ };
3227
3259
  var countBackfillWindows = (params) => {
3228
3260
  let count = 0;
3229
3261
  let cursor = params.fromMs;
@@ -3559,7 +3591,8 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3559
3591
  mode,
3560
3592
  startMs,
3561
3593
  endMs,
3562
- preloadStartMs: params.preloadStartMs
3594
+ preloadStartMs: params.preloadStartMs,
3595
+ nowMs: Date.now()
3563
3596
  });
3564
3597
  if (safeEndMs <= fromMs) {
3565
3598
  return skippedBackfillResult2();
@@ -3593,15 +3626,33 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3593
3626
  );
3594
3627
  const coverageKeysByInterval = /* @__PURE__ */ new Map();
3595
3628
  const coverageRangesByInterval = /* @__PURE__ */ new Map();
3629
+ const liquidationCoverageRangesByInterval = /* @__PURE__ */ new Map();
3630
+ const coverageNowMs = Date.now();
3596
3631
  await Promise.all(
3597
3632
  intervalWindows.map(async (window2) => {
3598
- const coverageRows = await (0, import_timescale2.getDerivativesBackfillCoverage)({
3599
- source: "coinalyze",
3600
- symbols,
3633
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3601
3634
  interval: window2.interval,
3602
3635
  fromMs: window2.fromMs,
3603
- toMs: window2.toMs
3636
+ toMs: window2.toMs,
3637
+ nowMs: coverageNowMs
3604
3638
  });
3639
+ const [coverageRows, liquidationCoverageRows] = await Promise.all([
3640
+ (0, import_timescale2.getDerivativesBackfillCoverage)({
3641
+ source: "coinalyze",
3642
+ symbols,
3643
+ interval: window2.interval,
3644
+ fromMs: window2.fromMs,
3645
+ toMs: window2.toMs
3646
+ }),
3647
+ confirmedLiquidationWindow ? (0, import_timescale2.getDerivativesMetricCoverage)({
3648
+ source: "coinalyze",
3649
+ metric: "liquidation",
3650
+ symbols,
3651
+ interval: window2.interval,
3652
+ fromMs: confirmedLiquidationWindow.fromMs,
3653
+ toMs: confirmedLiquidationWindow.toMs
3654
+ }) : Promise.resolve([])
3655
+ ]);
3605
3656
  coverageKeysByInterval.set(
3606
3657
  window2.interval,
3607
3658
  new Set(
@@ -3623,6 +3674,17 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3623
3674
  rangesBySymbol.set(symbol, ranges);
3624
3675
  }
3625
3676
  coverageRangesByInterval.set(window2.interval, rangesBySymbol);
3677
+ const liquidationRangesBySymbol = /* @__PURE__ */ new Map();
3678
+ for (const row of liquidationCoverageRows) {
3679
+ const symbol = row.symbol.toUpperCase();
3680
+ const ranges = liquidationRangesBySymbol.get(symbol) ?? [];
3681
+ ranges.push({ fromMs: row.fromMs, toMs: row.toMs });
3682
+ liquidationRangesBySymbol.set(symbol, ranges);
3683
+ }
3684
+ liquidationCoverageRangesByInterval.set(
3685
+ window2.interval,
3686
+ liquidationRangesBySymbol
3687
+ );
3626
3688
  })
3627
3689
  );
3628
3690
  const cachedWindows = intervalWindows.reduce(
@@ -3647,7 +3709,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3647
3709
  (symbol) => backfillWindows.every((backfillWindow) => {
3648
3710
  const normalizedSymbol = symbol.toUpperCase();
3649
3711
  const coverageRanges = coverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [];
3650
- return hasDerivativesWindowCoverage({
3712
+ const dataCovered = hasDerivativesWindowCoverage({
3651
3713
  edges: edgesBySymbol?.get(normalizedSymbol),
3652
3714
  fromMs: backfillWindow.fromMs,
3653
3715
  toMs: backfillWindow.toMs
@@ -3664,6 +3726,19 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3664
3726
  toMs: backfillWindow.toMs
3665
3727
  })
3666
3728
  );
3729
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3730
+ interval: window2.interval,
3731
+ fromMs: backfillWindow.fromMs,
3732
+ toMs: backfillWindow.toMs,
3733
+ nowMs: coverageNowMs
3734
+ });
3735
+ const liquidationCovered = confirmedLiquidationWindow == null || resolveDerivativesContextMissingCoverageFetchFromMs({
3736
+ ranges: liquidationCoverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [],
3737
+ fromMs: confirmedLiquidationWindow.fromMs,
3738
+ toMs: confirmedLiquidationWindow.toMs,
3739
+ intervalMs: window2.intervalMs
3740
+ }) == null;
3741
+ return dataCovered && liquidationCovered;
3667
3742
  })
3668
3743
  );
3669
3744
  });
@@ -3748,6 +3823,11 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3748
3823
  coverageKeysByInterval.set(interval, coverageKeys);
3749
3824
  const coverageRangesBySymbol = coverageRangesByInterval.get(interval) ?? /* @__PURE__ */ new Map();
3750
3825
  coverageRangesByInterval.set(interval, coverageRangesBySymbol);
3826
+ const liquidationCoverageRangesBySymbol = liquidationCoverageRangesByInterval.get(interval) ?? /* @__PURE__ */ new Map();
3827
+ liquidationCoverageRangesByInterval.set(
3828
+ interval,
3829
+ liquidationCoverageRangesBySymbol
3830
+ );
3751
3831
  for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
3752
3832
  const batch = symbolBatches[batchIdx];
3753
3833
  let cursor = window2.fromMs;
@@ -3760,33 +3840,25 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3760
3840
  fromMs: cursor,
3761
3841
  toMs
3762
3842
  });
3763
- if (coverageKeys.has(key)) {
3764
- return null;
3765
- }
3766
3843
  const normalizedSymbol = item.symbol.toUpperCase();
3767
- const coverageFromMs = mode === "signals" ? cursor : resolveDerivativesContextMissingCoverageFetchFromMs({
3768
- ranges: coverageRangesBySymbol.get(normalizedSymbol),
3769
- fromMs: cursor,
3770
- toMs,
3771
- intervalMs
3772
- });
3773
- if (coverageFromMs == null) {
3774
- return null;
3775
- }
3776
- const edges = edgesBySymbol.get(normalizedSymbol);
3777
- const edgesFromMs = resolveDerivativesContextFetchFromMs({
3778
- edges,
3779
- fromMs: cursor,
3780
- toMs,
3781
- intervalMs,
3782
- refreshClosedTail: mode === "signals"
3783
- });
3784
- if (edgesFromMs == null) {
3785
- return null;
3786
- }
3844
+ const requiredFromMs = resolveDerivativesContextRequiredFetchFromMs(
3845
+ {
3846
+ mode,
3847
+ interval,
3848
+ intervalMs,
3849
+ fromMs: cursor,
3850
+ toMs,
3851
+ nowMs: coverageNowMs,
3852
+ dataCoverageKeyExists: coverageKeys.has(key),
3853
+ dataCoverageRanges: coverageRangesBySymbol.get(normalizedSymbol),
3854
+ liquidationCoverageRanges: liquidationCoverageRangesBySymbol.get(normalizedSymbol),
3855
+ edges: edgesBySymbol.get(normalizedSymbol)
3856
+ }
3857
+ );
3858
+ if (requiredFromMs == null) return null;
3787
3859
  return {
3788
3860
  item,
3789
- fromMs: Math.max(coverageFromMs, edgesFromMs)
3861
+ fromMs: requiredFromMs
3790
3862
  };
3791
3863
  }).filter(
3792
3864
  (item) => item != null
@@ -3811,6 +3883,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3811
3883
  );
3812
3884
  let rows = [];
3813
3885
  let missingClosedSymbols = [];
3886
+ let liquidationRowsByMarket = /* @__PURE__ */ new Map();
3814
3887
  for (let attempt = 1; attempt <= maxClosedBarAttempts; attempt += 1) {
3815
3888
  const oiMap = await fetchMetricBatch({
3816
3889
  endpoint: oiPath,
@@ -3830,7 +3903,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3830
3903
  fromMs: group.fromMs,
3831
3904
  toMs
3832
3905
  });
3833
- const liqMap = await fetchMetricBatch({
3906
+ liquidationRowsByMarket = await fetchMetricBatch({
3834
3907
  endpoint: liqPath,
3835
3908
  metric: "liq",
3836
3909
  marketSymbols,
@@ -3845,7 +3918,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3845
3918
  symbol: item.symbol,
3846
3919
  oiRaw: oiMap.get(marketSymbol) ?? [],
3847
3920
  fundingRaw: fundingMap.get(marketSymbol) ?? [],
3848
- liqRaw: liqMap.get(marketSymbol) ?? []
3921
+ liqRaw: liquidationRowsByMarket.get(marketSymbol) ?? []
3849
3922
  });
3850
3923
  return (0, import_indicators.coinalyzePointsToRows)(points, interval, "coinalyze");
3851
3924
  });
@@ -3868,6 +3941,42 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
3868
3941
  await (0, import_timescale2.upsertDerivatives)(rows);
3869
3942
  totalRows += rows.length;
3870
3943
  }
3944
+ const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
3945
+ interval,
3946
+ fromMs: group.fromMs,
3947
+ toMs,
3948
+ nowMs: coverageNowMs
3949
+ });
3950
+ if (confirmedLiquidationWindow) {
3951
+ const metricCoverageRows = missingBatch.map((item) => {
3952
+ const marketSymbol = item.marketSymbol.toUpperCase();
3953
+ const eventRowsCount = (liquidationRowsByMarket.get(marketSymbol) ?? []).filter((point) => {
3954
+ const timestamp = (0, import_indicators.toCoinalyzeTimestampMs)(
3955
+ point.t ?? point.ts ?? point.time ?? point.timestamp
3956
+ );
3957
+ return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
3958
+ }).length;
3959
+ return {
3960
+ source: "coinalyze",
3961
+ metric: "liquidation",
3962
+ symbol: item.symbol,
3963
+ interval,
3964
+ fromMs: confirmedLiquidationWindow.fromMs,
3965
+ toMs: confirmedLiquidationWindow.toMs,
3966
+ eventRowsCount
3967
+ };
3968
+ });
3969
+ await (0, import_timescale2.applyDerivativesMetricCoverage)(metricCoverageRows);
3970
+ for (const coverageRow of metricCoverageRows) {
3971
+ const symbol = coverageRow.symbol.toUpperCase();
3972
+ const ranges = liquidationCoverageRangesBySymbol.get(symbol) ?? [];
3973
+ ranges.push({
3974
+ fromMs: coverageRow.fromMs,
3975
+ toMs: coverageRow.toMs
3976
+ });
3977
+ liquidationCoverageRangesBySymbol.set(symbol, ranges);
3978
+ }
3979
+ }
3871
3980
  if (mode === "backtest") {
3872
3981
  const rowsCountBySymbol = /* @__PURE__ */ new Map();
3873
3982
  for (const row of rows) {
@@ -6800,7 +6909,9 @@ var import_constants5 = require("@tradejs/core/constants");
6800
6909
  var import_time5 = require("@tradejs/core/time");
6801
6910
  var import_redis4 = require("@tradejs/infra/redis");
6802
6911
  var CLOSED_PNL_LIMIT = 100;
6912
+ var CLOSED_PNL_RECONCILIATION_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
6803
6913
  var EXCHANGE_HISTORY_MAX_RANGE_MS = 7 * 24 * 60 * 60 * 1e3;
6914
+ var isRuntimeTradeSyncFallbackClose = (trade) => trade.status === "closed" && trade.exitTimestamp === trade.lastSyncedAt && trade.exitPrice == null && trade.actualExitPrice == null && trade.closeFee == null && trade.fundingFee == null;
6804
6915
  var splitExchangeHistoryTimeRange = ({
6805
6916
  startTime,
6806
6917
  endTime,
@@ -6929,7 +7040,7 @@ var syncRuntimeTrades = async ({
6929
7040
  );
6930
7041
  const closedPnlRows = await loadClosedPnlRows({
6931
7042
  connector,
6932
- startTime,
7043
+ startTime: Math.max(0, startTime - CLOSED_PNL_RECONCILIATION_LOOKBACK_MS),
6933
7044
  endTime,
6934
7045
  callbacks: closedPnlCallbacks
6935
7046
  });
@@ -6941,14 +7052,14 @@ var syncRuntimeTrades = async ({
6941
7052
  }
6942
7053
  const syncedTrades = [];
6943
7054
  for (const trade of trades) {
6944
- if (trade.status !== "active") {
7055
+ if (trade.status !== "active" && !isRuntimeTradeSyncFallbackClose(trade)) {
6945
7056
  syncedTrades.push(trade);
6946
7057
  continue;
6947
7058
  }
6948
7059
  const openPosition = openPositionsBySymbol.get(trade.symbol);
6949
7060
  const activeOrderId = activeOrderIdBySymbol.get(trade.symbol);
6950
7061
  const isCurrentActiveTrade = activeOrderId === trade.orderId;
6951
- if (!openPositionsReliable) {
7062
+ if (trade.status === "active" && !openPositionsReliable) {
6952
7063
  syncedTrades.push({
6953
7064
  ...trade,
6954
7065
  status: "active",
@@ -6956,7 +7067,7 @@ var syncRuntimeTrades = async ({
6956
7067
  });
6957
7068
  continue;
6958
7069
  }
6959
- if (isCurrentActiveTrade && openPosition && openPosition.direction === trade.direction) {
7070
+ if (trade.status === "active" && isCurrentActiveTrade && openPosition && openPosition.direction === trade.direction) {
6960
7071
  const nextTrade2 = {
6961
7072
  ...trade,
6962
7073
  status: "active",
@@ -6984,21 +7095,24 @@ var syncRuntimeTrades = async ({
6984
7095
  continue;
6985
7096
  }
6986
7097
  const matchedClosedPnl = consumeClosedPnlMatch(closedPnlBuckets, trade);
7098
+ if (!matchedClosedPnl) {
7099
+ continue;
7100
+ }
6987
7101
  const nextTrade = {
6988
7102
  ...trade,
6989
7103
  status: "closed",
6990
- currentPrice: matchedClosedPnl?.exitPrice ?? trade.currentPrice ?? null,
6991
- currentPnl: matchedClosedPnl?.closedPnl ?? trade.closedPnl ?? trade.currentPnl ?? null,
6992
- closedPnl: matchedClosedPnl?.closedPnl ?? trade.closedPnl ?? trade.currentPnl ?? null,
6993
- actualEntryPrice: matchedClosedPnl?.entryPrice ?? trade.actualEntryPrice ?? null,
6994
- exitPrice: matchedClosedPnl?.exitPrice ?? trade.exitPrice ?? null,
6995
- actualExitPrice: matchedClosedPnl?.exitPrice ?? trade.actualExitPrice ?? null,
6996
- exitTimestamp: matchedClosedPnl?.closedAt ?? trade.exitTimestamp ?? endTime,
7104
+ currentPrice: matchedClosedPnl.exitPrice ?? trade.currentPrice ?? null,
7105
+ currentPnl: matchedClosedPnl.closedPnl,
7106
+ closedPnl: matchedClosedPnl.closedPnl,
7107
+ actualEntryPrice: matchedClosedPnl.entryPrice ?? trade.actualEntryPrice ?? null,
7108
+ exitPrice: matchedClosedPnl.exitPrice ?? trade.exitPrice ?? null,
7109
+ actualExitPrice: matchedClosedPnl.exitPrice ?? trade.actualExitPrice ?? null,
7110
+ exitTimestamp: matchedClosedPnl.closedAt,
6997
7111
  exitType: trade.exitType ?? null,
6998
- openFee: matchedClosedPnl?.openFee ?? trade.openFee ?? null,
6999
- closeFee: matchedClosedPnl?.closeFee ?? trade.closeFee ?? null,
7000
- fundingFee: matchedClosedPnl?.fundingFee ?? trade.fundingFee ?? null,
7001
- totalFee: matchedClosedPnl?.totalFee ?? trade.totalFee ?? null,
7112
+ openFee: matchedClosedPnl.openFee ?? trade.openFee ?? null,
7113
+ closeFee: matchedClosedPnl.closeFee ?? trade.closeFee ?? null,
7114
+ fundingFee: matchedClosedPnl.fundingFee ?? trade.fundingFee ?? null,
7115
+ totalFee: matchedClosedPnl.totalFee ?? trade.totalFee ?? null,
7002
7116
  lastSyncedAt: endTime
7003
7117
  };
7004
7118
  await Promise.all([