backtest-kit 6.10.0 → 6.11.0

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/build/index.mjs CHANGED
@@ -4127,53 +4127,6 @@ const toProfitLossDto = (signal, priceClose) => {
4127
4127
  };
4128
4128
  };
4129
4129
 
4130
- /**
4131
- * Converts markdown content to plain text with minimal formatting
4132
- * @param content - Markdown string to convert
4133
- * @returns Plain text representation
4134
- */
4135
- const toPlainString = (content) => {
4136
- if (!content) {
4137
- return "";
4138
- }
4139
- let text = content;
4140
- // Remove code blocks
4141
- text = text.replace(/```[\s\S]*?```/g, "");
4142
- text = text.replace(/`([^`]+)`/g, "$1");
4143
- // Remove images
4144
- text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
4145
- // Convert links to text only (keep link text, remove URL)
4146
- text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
4147
- // Remove headers (convert to plain text)
4148
- text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
4149
- // Remove bold and italic markers
4150
- text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
4151
- text = text.replace(/\*\*(.+?)\*\*/g, "$1");
4152
- text = text.replace(/\*(.+?)\*/g, "$1");
4153
- text = text.replace(/___(.+?)___/g, "$1");
4154
- text = text.replace(/__(.+?)__/g, "$1");
4155
- text = text.replace(/_(.+?)_/g, "$1");
4156
- // Remove strikethrough
4157
- text = text.replace(/~~(.+?)~~/g, "$1");
4158
- // Convert lists to plain text with bullets
4159
- text = text.replace(/^\s*[-*+]\s+/gm, "• ");
4160
- text = text.replace(/^\s*\d+\.\s+/gm, "• ");
4161
- // Remove blockquotes
4162
- text = text.replace(/^\s*>\s+/gm, "");
4163
- // Remove horizontal rules
4164
- text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
4165
- // Remove HTML tags
4166
- text = text.replace(/<[^>]+>/g, "");
4167
- // Remove excessive whitespace and normalize line breaks
4168
- text = text.replace(/\n[\s\n]*\n/g, "\n");
4169
- text = text.replace(/[ \t]+/g, " ");
4170
- // Remove all newline characters
4171
- text = text.replace(/\n/g, " ");
4172
- // Remove excessive spaces after newline removal
4173
- text = text.replace(/\s+/g, " ");
4174
- return text.trim();
4175
- };
4176
-
4177
4130
  /**
4178
4131
  * Returns the total closed state of a position using costBasisAtClose snapshots.
4179
4132
  *
@@ -4808,6 +4761,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4808
4761
  originalPriceOpen: publicSignal.originalPriceOpen,
4809
4762
  scheduledAt: publicSignal.scheduledAt,
4810
4763
  pendingAt: publicSignal.pendingAt,
4764
+ note: publicSignal.note,
4811
4765
  });
4812
4766
  continue;
4813
4767
  }
@@ -4835,6 +4789,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4835
4789
  originalPriceOpen: publicSignal.originalPriceOpen,
4836
4790
  scheduledAt: publicSignal.scheduledAt,
4837
4791
  pendingAt: publicSignal.pendingAt,
4792
+ note: publicSignal.note,
4838
4793
  });
4839
4794
  continue;
4840
4795
  }
@@ -4861,6 +4816,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4861
4816
  originalPriceOpen: publicSignal.originalPriceOpen,
4862
4817
  scheduledAt: publicSignal.scheduledAt,
4863
4818
  pendingAt: publicSignal.pendingAt,
4819
+ note: publicSignal.note,
4864
4820
  });
4865
4821
  continue;
4866
4822
  }
@@ -4888,6 +4844,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4888
4844
  originalPriceOpen: publicSignal.originalPriceOpen,
4889
4845
  scheduledAt: publicSignal.scheduledAt,
4890
4846
  pendingAt: publicSignal.pendingAt,
4847
+ note: publicSignal.note,
4891
4848
  });
4892
4849
  continue;
4893
4850
  }
@@ -4915,6 +4872,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4915
4872
  originalPriceOpen: publicSignal.originalPriceOpen,
4916
4873
  scheduledAt: publicSignal.scheduledAt,
4917
4874
  pendingAt: publicSignal.pendingAt,
4875
+ note: publicSignal.note,
4918
4876
  });
4919
4877
  continue;
4920
4878
  }
@@ -4944,6 +4902,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4944
4902
  originalPriceOpen: publicSignal.originalPriceOpen,
4945
4903
  scheduledAt: publicSignal.scheduledAt,
4946
4904
  pendingAt: publicSignal.pendingAt,
4905
+ note: publicSignal.note,
4947
4906
  });
4948
4907
  continue;
4949
4908
  }
@@ -5042,7 +5001,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
5042
5001
  const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
5043
5002
  const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
5044
5003
  const signal = await Promise.race([
5045
- self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
5004
+ self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when, currentPrice),
5046
5005
  sleep(timeoutMs).then(() => TIMEOUT_SYMBOL),
5047
5006
  ]);
5048
5007
  if (typeof signal === "symbol") {
@@ -5072,7 +5031,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
5072
5031
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5073
5032
  priceOpen: signal.priceOpen, // Используем priceOpen из сигнала
5074
5033
  position: signal.position,
5075
- note: toPlainString(signal.note),
5034
+ note: signal.note || "",
5076
5035
  priceTakeProfit: signal.priceTakeProfit,
5077
5036
  priceStopLoss: signal.priceStopLoss,
5078
5037
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
@@ -5098,7 +5057,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
5098
5057
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5099
5058
  priceOpen: signal.priceOpen,
5100
5059
  position: signal.position,
5101
- note: toPlainString(signal.note),
5060
+ note: signal.note || "",
5102
5061
  priceTakeProfit: signal.priceTakeProfit,
5103
5062
  priceStopLoss: signal.priceStopLoss,
5104
5063
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
@@ -5123,7 +5082,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
5123
5082
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5124
5083
  priceOpen: currentPrice,
5125
5084
  ...structuredClone(signal),
5126
- note: toPlainString(signal.note),
5085
+ note: signal.note || "",
5127
5086
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
5128
5087
  symbol: self.params.execution.context.symbol,
5129
5088
  exchangeName: self.params.method.context.exchangeName,
@@ -5870,6 +5829,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
5870
5829
  totalPartials: scheduled._partial?.length ?? 0,
5871
5830
  originalPriceOpen: scheduled.priceOpen,
5872
5831
  pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
5832
+ note: scheduled.note,
5873
5833
  });
5874
5834
  return null;
5875
5835
  }
@@ -6638,6 +6598,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
6638
6598
  totalPartials: scheduled._partial?.length ?? 0,
6639
6599
  originalPriceOpen: scheduled.priceOpen,
6640
6600
  pnl: toProfitLossDto(scheduled, averagePrice),
6601
+ note: scheduled.note,
6641
6602
  });
6642
6603
  }
6643
6604
  await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
@@ -6716,6 +6677,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
6716
6677
  totalPartials: scheduled._partial?.length ?? 0,
6717
6678
  originalPriceOpen: scheduled.priceOpen,
6718
6679
  pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
6680
+ note: scheduled.note,
6719
6681
  });
6720
6682
  return false;
6721
6683
  }
@@ -6803,6 +6765,7 @@ const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, aver
6803
6765
  totalPartials: closedSignal._partial?.length ?? 0,
6804
6766
  originalPriceOpen: closedSignal.priceOpen,
6805
6767
  pnl: toProfitLossDto(closedSignal, averagePrice),
6768
+ note: closedSignal.note,
6806
6769
  });
6807
6770
  await CALL_CLOSE_CALLBACKS_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
6808
6771
  await CALL_PARTIAL_CLEAR_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
@@ -6903,6 +6866,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6903
6866
  totalPartials: activatedSignal._partial?.length ?? 0,
6904
6867
  originalPriceOpen: activatedSignal.priceOpen,
6905
6868
  pnl: toProfitLossDto(activatedSignal, averagePrice),
6869
+ note: activatedSignal.note,
6906
6870
  });
6907
6871
  return { outcome: "pending" };
6908
6872
  }
@@ -6934,6 +6898,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6934
6898
  pendingAt: publicSignalForCommit.pendingAt,
6935
6899
  totalEntries: publicSignalForCommit.totalEntries,
6936
6900
  totalPartials: publicSignalForCommit.totalPartials,
6901
+ note: publicSignalForCommit.note,
6937
6902
  });
6938
6903
  await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
6939
6904
  await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
@@ -8118,6 +8083,7 @@ class ClientStrategy {
8118
8083
  totalPartials: cancelledSignal._partial?.length ?? 0,
8119
8084
  originalPriceOpen: cancelledSignal.priceOpen,
8120
8085
  pnl: toProfitLossDto(cancelledSignal, currentPrice),
8086
+ note: cancelledSignal.note,
8121
8087
  });
8122
8088
  // Call onCancel callback
8123
8089
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8171,6 +8137,7 @@ class ClientStrategy {
8171
8137
  totalPartials: closedSignal._partial?.length ?? 0,
8172
8138
  originalPriceOpen: closedSignal.priceOpen,
8173
8139
  pnl: toProfitLossDto(closedSignal, currentPrice),
8140
+ note: closedSignal.note,
8174
8141
  });
8175
8142
  // Call onClose callback
8176
8143
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8252,6 +8219,7 @@ class ClientStrategy {
8252
8219
  totalPartials: activatedSignal._partial?.length ?? 0,
8253
8220
  originalPriceOpen: activatedSignal.priceOpen,
8254
8221
  pnl: toProfitLossDto(activatedSignal, currentPrice),
8222
+ note: activatedSignal.note,
8255
8223
  });
8256
8224
  return await RETURN_IDLE_FN(this, currentPrice);
8257
8225
  }
@@ -8282,6 +8250,7 @@ class ClientStrategy {
8282
8250
  pendingAt: publicSignalForCommit.pendingAt,
8283
8251
  totalEntries: publicSignalForCommit.totalEntries,
8284
8252
  totalPartials: publicSignalForCommit.totalPartials,
8253
+ note: publicSignalForCommit.note,
8285
8254
  });
8286
8255
  // Call onOpen callback
8287
8256
  await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8417,6 +8386,7 @@ class ClientStrategy {
8417
8386
  totalPartials: cancelledSignal._partial?.length ?? 0,
8418
8387
  originalPriceOpen: cancelledSignal.priceOpen,
8419
8388
  pnl: toProfitLossDto(cancelledSignal, currentPrice),
8389
+ note: cancelledSignal.note,
8420
8390
  });
8421
8391
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
8422
8392
  const cancelledResult = {
@@ -8471,6 +8441,7 @@ class ClientStrategy {
8471
8441
  totalPartials: closedSignal._partial?.length ?? 0,
8472
8442
  originalPriceOpen: closedSignal.priceOpen,
8473
8443
  pnl: toProfitLossDto(closedSignal, currentPrice),
8444
+ note: closedSignal.note,
8474
8445
  });
8475
8446
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
8476
8447
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
@@ -10020,6 +9991,8 @@ class MergeRisk {
10020
9991
  }
10021
9992
  }
10022
9993
 
9994
+ /** Default interval for strategies that do not specify one */
9995
+ const STRATEGY_DEFAULT_INTERVAL = "1m";
10023
9996
  /**
10024
9997
  * If syncSubject listener or any registered action throws, it means the signal was not properly synchronized
10025
9998
  * to the exchange (e.g. limit order failed to fill).
@@ -10405,7 +10378,7 @@ class StrategyConnectionService {
10405
10378
  * @returns Configured ClientStrategy instance
10406
10379
  */
10407
10380
  this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10408
- const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
10381
+ const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10409
10382
  return new ClientStrategy({
10410
10383
  symbol,
10411
10384
  interval,
@@ -16124,8 +16097,8 @@ class StrategySchemaService {
16124
16097
  if (strategySchema.actions?.some((value) => typeof value !== "string")) {
16125
16098
  throw new Error(`strategy schema validation failed: invalid actions for strategyName=${strategySchema.strategyName} actions=[${strategySchema.actions}]`);
16126
16099
  }
16127
- if (typeof strategySchema.interval !== "string") {
16128
- throw new Error(`strategy schema validation failed: missing interval for strategyName=${strategySchema.strategyName}`);
16100
+ if (strategySchema.interval && typeof strategySchema.interval !== "string") {
16101
+ throw new Error(`strategy schema validation failed: invalid interval for strategyName=${strategySchema.strategyName}`);
16129
16102
  }
16130
16103
  if (typeof strategySchema.getSignal !== "function") {
16131
16104
  throw new Error(`strategy schema validation failed: missing getSignal for strategyName=${strategySchema.strategyName}`);
@@ -18071,6 +18044,53 @@ class WalkerCommandService {
18071
18044
  }
18072
18045
  }
18073
18046
 
18047
+ /**
18048
+ * Converts markdown content to plain text with minimal formatting
18049
+ * @param content - Markdown string to convert
18050
+ * @returns Plain text representation
18051
+ */
18052
+ const toPlainString = (content) => {
18053
+ if (!content) {
18054
+ return "";
18055
+ }
18056
+ let text = content;
18057
+ // Remove code blocks
18058
+ text = text.replace(/```[\s\S]*?```/g, "");
18059
+ text = text.replace(/`([^`]+)`/g, "$1");
18060
+ // Remove images
18061
+ text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
18062
+ // Convert links to text only (keep link text, remove URL)
18063
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
18064
+ // Remove headers (convert to plain text)
18065
+ text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
18066
+ // Remove bold and italic markers
18067
+ text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
18068
+ text = text.replace(/\*\*(.+?)\*\*/g, "$1");
18069
+ text = text.replace(/\*(.+?)\*/g, "$1");
18070
+ text = text.replace(/___(.+?)___/g, "$1");
18071
+ text = text.replace(/__(.+?)__/g, "$1");
18072
+ text = text.replace(/_(.+?)_/g, "$1");
18073
+ // Remove strikethrough
18074
+ text = text.replace(/~~(.+?)~~/g, "$1");
18075
+ // Convert lists to plain text with bullets
18076
+ text = text.replace(/^\s*[-*+]\s+/gm, "• ");
18077
+ text = text.replace(/^\s*\d+\.\s+/gm, "• ");
18078
+ // Remove blockquotes
18079
+ text = text.replace(/^\s*>\s+/gm, "");
18080
+ // Remove horizontal rules
18081
+ text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
18082
+ // Remove HTML tags
18083
+ text = text.replace(/<[^>]+>/g, "");
18084
+ // Remove excessive whitespace and normalize line breaks
18085
+ text = text.replace(/\n[\s\n]*\n/g, "\n");
18086
+ text = text.replace(/[ \t]+/g, " ");
18087
+ // Remove all newline characters
18088
+ text = text.replace(/\n/g, " ");
18089
+ // Remove excessive spaces after newline removal
18090
+ text = text.replace(/\s+/g, " ");
18091
+ return text.trim();
18092
+ };
18093
+
18074
18094
  /**
18075
18095
  * Column configuration for backtest markdown reports.
18076
18096
  *
@@ -18746,7 +18766,7 @@ const partial_columns = [
18746
18766
  {
18747
18767
  key: "note",
18748
18768
  label: "Note",
18749
- format: (data) => data.note || "",
18769
+ format: (data) => toPlainString(data.note ?? "N/A"),
18750
18770
  isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
18751
18771
  },
18752
18772
  {
@@ -18906,7 +18926,7 @@ const breakeven_columns = [
18906
18926
  {
18907
18927
  key: "note",
18908
18928
  label: "Note",
18909
- format: (data) => data.note || "",
18929
+ format: (data) => toPlainString(data.note ?? "N/A"),
18910
18930
  isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
18911
18931
  },
18912
18932
  {
@@ -51645,6 +51665,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
51645
51665
  pnlEntries: data.signal.pnl.pnlEntries,
51646
51666
  scheduledAt: data.signal.scheduledAt,
51647
51667
  currentPrice: data.currentPrice,
51668
+ note: data.signal.note,
51648
51669
  createdAt: data.createdAt,
51649
51670
  };
51650
51671
  }
@@ -51674,6 +51695,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
51674
51695
  duration: durationMin,
51675
51696
  scheduledAt: data.signal.scheduledAt,
51676
51697
  pendingAt: data.signal.pendingAt,
51698
+ note: data.signal.note,
51677
51699
  createdAt: data.createdAt,
51678
51700
  };
51679
51701
  }
@@ -51710,6 +51732,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
51710
51732
  pnlPriceClose: data.data.pnl.priceClose,
51711
51733
  pnlCost: data.data.pnl.pnlCost,
51712
51734
  pnlEntries: data.data.pnl.pnlEntries,
51735
+ note: data.data.note,
51713
51736
  scheduledAt: data.data.scheduledAt,
51714
51737
  pendingAt: data.data.pendingAt,
51715
51738
  createdAt: data.timestamp,
@@ -51745,6 +51768,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
51745
51768
  pnlPriceClose: data.data.pnl.priceClose,
51746
51769
  pnlCost: data.data.pnl.pnlCost,
51747
51770
  pnlEntries: data.data.pnl.pnlEntries,
51771
+ note: data.data.note,
51748
51772
  scheduledAt: data.data.scheduledAt,
51749
51773
  pendingAt: data.data.pendingAt,
51750
51774
  createdAt: data.timestamp,
@@ -51779,6 +51803,7 @@ const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
51779
51803
  pnlPriceClose: data.data.pnl.priceClose,
51780
51804
  pnlCost: data.data.pnl.pnlCost,
51781
51805
  pnlEntries: data.data.pnl.pnlEntries,
51806
+ note: data.data.note,
51782
51807
  scheduledAt: data.data.scheduledAt,
51783
51808
  pendingAt: data.data.pendingAt,
51784
51809
  createdAt: data.timestamp,
@@ -51820,6 +51845,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51820
51845
  pnlEntries: data.pnl.pnlEntries,
51821
51846
  scheduledAt: data.scheduledAt,
51822
51847
  pendingAt: data.pendingAt,
51848
+ note: data.note,
51823
51849
  createdAt: data.timestamp,
51824
51850
  };
51825
51851
  }
@@ -51852,6 +51878,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51852
51878
  pnlEntries: data.pnl.pnlEntries,
51853
51879
  scheduledAt: data.scheduledAt,
51854
51880
  pendingAt: data.pendingAt,
51881
+ note: data.note,
51855
51882
  createdAt: data.timestamp,
51856
51883
  };
51857
51884
  }
@@ -51883,6 +51910,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51883
51910
  pnlEntries: data.pnl.pnlEntries,
51884
51911
  scheduledAt: data.scheduledAt,
51885
51912
  pendingAt: data.pendingAt,
51913
+ note: data.note,
51886
51914
  createdAt: data.timestamp,
51887
51915
  };
51888
51916
  }
@@ -51915,6 +51943,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51915
51943
  pnlEntries: data.pnl.pnlEntries,
51916
51944
  scheduledAt: data.scheduledAt,
51917
51945
  pendingAt: data.pendingAt,
51946
+ note: data.note,
51918
51947
  createdAt: data.timestamp,
51919
51948
  };
51920
51949
  }
@@ -51947,6 +51976,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51947
51976
  pnlEntries: data.pnl.pnlEntries,
51948
51977
  scheduledAt: data.scheduledAt,
51949
51978
  pendingAt: data.pendingAt,
51979
+ note: data.note,
51950
51980
  createdAt: data.timestamp,
51951
51981
  };
51952
51982
  }
@@ -51979,6 +52009,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51979
52009
  pnlEntries: data.pnl.pnlEntries,
51980
52010
  scheduledAt: data.scheduledAt,
51981
52011
  pendingAt: data.pendingAt,
52012
+ note: data.note,
51982
52013
  createdAt: data.timestamp,
51983
52014
  };
51984
52015
  }
@@ -52012,6 +52043,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
52012
52043
  pnlEntries: data.pnl.pnlEntries,
52013
52044
  scheduledAt: data.scheduledAt,
52014
52045
  pendingAt: data.pendingAt,
52046
+ note: data.note,
52015
52047
  createdAt: data.timestamp,
52016
52048
  };
52017
52049
  }
@@ -52035,6 +52067,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
52035
52067
  pnlPriceClose: data.pnl.priceClose,
52036
52068
  pnlCost: data.pnl.pnlCost,
52037
52069
  pnlEntries: data.pnl.pnlEntries,
52070
+ note: data.note,
52038
52071
  createdAt: data.timestamp,
52039
52072
  };
52040
52073
  }
@@ -52058,6 +52091,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
52058
52091
  pnlPriceClose: data.pnl.priceClose,
52059
52092
  pnlCost: data.pnl.pnlCost,
52060
52093
  pnlEntries: data.pnl.pnlEntries,
52094
+ note: data.note,
52061
52095
  createdAt: data.timestamp,
52062
52096
  };
52063
52097
  }
@@ -52099,6 +52133,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
52099
52133
  totalPartials: data.totalPartials,
52100
52134
  scheduledAt: data.scheduledAt,
52101
52135
  pendingAt: data.pendingAt,
52136
+ note: data.signal.note,
52102
52137
  createdAt: data.timestamp,
52103
52138
  };
52104
52139
  }
@@ -52131,6 +52166,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
52131
52166
  scheduledAt: data.scheduledAt,
52132
52167
  pendingAt: data.pendingAt,
52133
52168
  closeReason: data.closeReason,
52169
+ note: data.signal.note,
52134
52170
  createdAt: data.timestamp,
52135
52171
  };
52136
52172
  }
@@ -53630,6 +53666,7 @@ const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
53630
53666
  const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
53631
53667
  const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
53632
53668
  const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
53669
+ const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
53633
53670
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
53634
53671
  const MS_PER_MINUTE$1 = 60000;
53635
53672
  const INTERVAL_MINUTES$1 = {
@@ -53881,7 +53918,7 @@ class CacheFileInstance {
53881
53918
  /**
53882
53919
  * Clears the index counter.
53883
53920
  */
53884
- static clearCounter() {
53921
+ static resetCounter() {
53885
53922
  CacheFileInstance._indexCounter = 0;
53886
53923
  }
53887
53924
  /**
@@ -54136,11 +54173,17 @@ class CacheUtils {
54136
54173
  */
54137
54174
  this.clear = () => {
54138
54175
  backtest.loggerService.info(CACHE_METHOD_NAME_CLEAR);
54139
- {
54140
- this._getFnInstance.clear();
54141
- this._getFileInstance.clear();
54142
- }
54143
- CacheFileInstance.clearCounter();
54176
+ this._getFnInstance.clear();
54177
+ this._getFileInstance.clear();
54178
+ };
54179
+ /**
54180
+ * Resets the CacheFileInstance index counter to zero.
54181
+ * This is useful when process.cwd() changes between strategy iterations to ensure
54182
+ * that new CacheFileInstance objects start with index 0 and do not collide with old instances.
54183
+ */
54184
+ this.resetCounter = () => {
54185
+ backtest.loggerService.info(CACHE_METHOD_NAME_RESET_COUNTER);
54186
+ CacheFileInstance.resetCounter();
54144
54187
  };
54145
54188
  }
54146
54189
  }
@@ -54164,10 +54207,12 @@ const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
54164
54207
  const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
54165
54208
  const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
54166
54209
  const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
54210
+ const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
54167
54211
  const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
54168
54212
  const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
54169
54213
  const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
54170
54214
  const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
54215
+ const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
54171
54216
  const MS_PER_MINUTE = 60000;
54172
54217
  const INTERVAL_MINUTES = {
54173
54218
  "1m": 1,
@@ -54234,6 +54279,8 @@ const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
54234
54279
  *
54235
54280
  * State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
54236
54281
  *
54282
+ * @template F - Concrete function type
54283
+ *
54237
54284
  * @example
54238
54285
  * ```typescript
54239
54286
  * const instance = new IntervalFnInstance(mySignalFn, "1h");
@@ -54249,30 +54296,34 @@ class IntervalFnInstance {
54249
54296
  *
54250
54297
  * @param fn - Function to fire once per interval
54251
54298
  * @param interval - Candle interval that controls the firing boundary
54299
+ * @param key - Optional key generator for argument-based state separation.
54300
+ * Default: `([symbol]) => symbol`
54252
54301
  */
54253
- constructor(fn, interval) {
54302
+ constructor(fn, interval, key = ([symbol]) => symbol) {
54254
54303
  this.fn = fn;
54255
54304
  this.interval = interval;
54256
- /** Stores the last aligned timestamp per context+symbol key. */
54305
+ this.key = key;
54306
+ /** Stores the last aligned timestamp per context+symbol+args key. */
54257
54307
  this._stateMap = new Map();
54258
54308
  /**
54259
54309
  * Execute the signal function with once-per-interval enforcement.
54260
54310
  *
54261
54311
  * Algorithm:
54262
54312
  * 1. Align the current execution context `when` to the interval boundary.
54263
- * 2. If the stored aligned timestamp for this context+symbol equals the current one → return `null`.
54264
- * 3. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
54313
+ * 2. Build state key from context + key generator result.
54314
+ * 3. If the stored aligned timestamp for this key equals the current one return `null`.
54315
+ * 4. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
54265
54316
  * the signal. If it returns `null`, leave state unchanged so the next call retries.
54266
54317
  *
54267
54318
  * Requires active method context and execution context.
54268
54319
  *
54269
- * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
54320
+ * @param args - Arguments forwarded to the wrapped function
54270
54321
  * @returns The value returned by `fn` on the first non-null fire, `null` on all subsequent calls
54271
54322
  * within the same interval or when `fn` itself returned `null`
54272
54323
  * @throws Error if method context, execution context, or interval is missing
54273
54324
  */
54274
- this.run = async (symbol) => {
54275
- backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { symbol });
54325
+ this.run = async (...args) => {
54326
+ backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
54276
54327
  const step = INTERVAL_MINUTES[this.interval];
54277
54328
  {
54278
54329
  if (!MethodContextService.hasContext()) {
@@ -54286,15 +54337,16 @@ class IntervalFnInstance {
54286
54337
  }
54287
54338
  }
54288
54339
  const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
54289
- const key = `${contextKey}:${symbol}`;
54290
54340
  const currentWhen = backtest.executionContextService.context.when;
54291
54341
  const currentAligned = align(currentWhen.getTime(), this.interval);
54292
- if (this._stateMap.get(key) === currentAligned) {
54342
+ const argKey = this.key(args);
54343
+ const stateKey = `${contextKey}:${argKey}`;
54344
+ if (this._stateMap.get(stateKey) === currentAligned) {
54293
54345
  return null;
54294
54346
  }
54295
- const result = await this.fn(symbol, currentWhen);
54347
+ const result = await this.fn.apply(null, args);
54296
54348
  if (result !== null) {
54297
- this._stateMap.set(key, currentAligned);
54349
+ this._stateMap.set(stateKey, currentAligned);
54298
54350
  }
54299
54351
  return result;
54300
54352
  };
@@ -54315,6 +54367,28 @@ class IntervalFnInstance {
54315
54367
  }
54316
54368
  }
54317
54369
  };
54370
+ /**
54371
+ * Garbage collect expired state entries.
54372
+ *
54373
+ * Removes all entries whose aligned timestamp differs from the current interval boundary.
54374
+ * Call this periodically to free memory from stale state entries.
54375
+ *
54376
+ * Requires active execution context to get current time.
54377
+ *
54378
+ * @returns Number of entries removed
54379
+ */
54380
+ this.gc = () => {
54381
+ const currentWhen = backtest.executionContextService.context.when;
54382
+ const currentAligned = align(currentWhen.getTime(), this.interval);
54383
+ let removed = 0;
54384
+ for (const [key, storedAligned] of this._stateMap.entries()) {
54385
+ if (storedAligned !== currentAligned) {
54386
+ this._stateMap.delete(key);
54387
+ removed++;
54388
+ }
54389
+ }
54390
+ return removed;
54391
+ };
54318
54392
  }
54319
54393
  }
54320
54394
  /**
@@ -54328,7 +54402,7 @@ class IntervalFnInstance {
54328
54402
  *
54329
54403
  * Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
54330
54404
  *
54331
- * @template T - Async function type: `(symbol: string, ...args) => Promise<R | null>`
54405
+ * @template F - Concrete async function type
54332
54406
  *
54333
54407
  * @example
54334
54408
  * ```typescript
@@ -54349,7 +54423,7 @@ class IntervalFileInstance {
54349
54423
  * Resets the index counter to zero.
54350
54424
  * Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
54351
54425
  */
54352
- static clearCounter() {
54426
+ static resetCounter() {
54353
54427
  IntervalFileInstance._indexCounter = 0;
54354
54428
  }
54355
54429
  /**
@@ -54358,18 +54432,21 @@ class IntervalFileInstance {
54358
54432
  * @param fn - Async signal function to fire once per interval
54359
54433
  * @param interval - Candle interval that controls the firing boundary
54360
54434
  * @param name - Human-readable bucket name used as the directory prefix
54435
+ * @param key - Dynamic key generator; receives `[symbol, alignMs, ...rest]`.
54436
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
54361
54437
  */
54362
- constructor(fn, interval, name) {
54438
+ constructor(fn, interval, name, key = ([symbol, alignMs]) => `${symbol}_${alignMs}`) {
54363
54439
  this.fn = fn;
54364
54440
  this.interval = interval;
54365
54441
  this.name = name;
54442
+ this.key = key;
54366
54443
  /**
54367
54444
  * Execute the async function with persistent once-per-interval enforcement.
54368
54445
  *
54369
54446
  * Algorithm:
54370
54447
  * 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
54371
- * 2. Align execution context `when` to interval boundary → `alignedTs`.
54372
- * 3. Build entity key = `${symbol}_${alignedTs}`.
54448
+ * 2. Align execution context `when` to interval boundary → `alignedMs`.
54449
+ * 3. Build entity key from the key generator (receives `[symbol, alignedMs, ...rest]`).
54373
54450
  * 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
54374
54451
  * 5. On hit — return `null` (interval already fired).
54375
54452
  * 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
@@ -54377,12 +54454,13 @@ class IntervalFileInstance {
54377
54454
  * Requires active method context and execution context.
54378
54455
  *
54379
54456
  * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
54457
+ * @param args - Additional arguments forwarded to the wrapped function
54380
54458
  * @returns The value on the first non-null fire, `null` if already fired this interval
54381
54459
  * or if `fn` itself returned `null`
54382
54460
  * @throws Error if method context, execution context, or interval is missing
54383
54461
  */
54384
- this.run = async (symbol) => {
54385
- backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { symbol });
54462
+ this.run = async (...args) => {
54463
+ backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
54386
54464
  const step = INTERVAL_MINUTES[this.interval];
54387
54465
  {
54388
54466
  if (!MethodContextService.hasContext()) {
@@ -54395,15 +54473,16 @@ class IntervalFileInstance {
54395
54473
  throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
54396
54474
  }
54397
54475
  }
54476
+ const [symbol, ...rest] = args;
54398
54477
  const { when } = backtest.executionContextService.context;
54399
- const alignedTs = align(when.getTime(), this.interval);
54478
+ const alignedMs = align(when.getTime(), this.interval);
54400
54479
  const bucket = `${this.name}_${this.interval}_${this.index}`;
54401
- const entityKey = `${symbol}_${alignedTs}`;
54480
+ const entityKey = this.key([symbol, alignedMs, ...rest]);
54402
54481
  const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
54403
54482
  if (cached !== null) {
54404
54483
  return null;
54405
54484
  }
54406
- const result = await this.fn(symbol, when);
54485
+ const result = await this.fn.call(null, ...args);
54407
54486
  if (result !== null) {
54408
54487
  await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
54409
54488
  }
@@ -54444,12 +54523,12 @@ class IntervalUtils {
54444
54523
  * Memoized factory to get or create an `IntervalFnInstance` for a function.
54445
54524
  * Each function reference gets its own isolated instance.
54446
54525
  */
54447
- this._getInstance = memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
54526
+ this._getInstance = memoize(([run]) => run, (run, interval, key) => new IntervalFnInstance(run, interval, key));
54448
54527
  /**
54449
54528
  * Memoized factory to get or create an `IntervalFileInstance` for an async function.
54450
54529
  * Each function reference gets its own isolated persistent instance.
54451
54530
  */
54452
- this._getFileInstance = memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
54531
+ this._getFileInstance = memoize(([run]) => run, (run, interval, name, key) => new IntervalFileInstance(run, interval, name, key));
54453
54532
  /**
54454
54533
  * Wrap a signal function with in-memory once-per-interval firing.
54455
54534
  *
@@ -54461,21 +54540,30 @@ class IntervalUtils {
54461
54540
  *
54462
54541
  * @param run - Signal function to wrap
54463
54542
  * @param context.interval - Candle interval that controls the firing boundary
54464
- * @returns Wrapped function with the same signature as `TIntervalFn<T>`, plus a `clear()` method
54543
+ * @param context.key - Optional key generator for argument-based state separation
54544
+ * @returns Wrapped function with the same signature as `F`, plus a `clear()` method
54465
54545
  *
54466
54546
  * @example
54467
54547
  * ```typescript
54548
+ * // Without extra args
54468
54549
  * const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
54469
- *
54470
54550
  * await fireOnce("BTCUSDT"); // → T or null (fn called)
54471
54551
  * await fireOnce("BTCUSDT"); // → null (same interval, skipped)
54552
+ *
54553
+ * // With extra args and key
54554
+ * const fireOnce = Interval.fn(mySignalFn, {
54555
+ * interval: "15m",
54556
+ * key: ([symbol, period]) => `${symbol}_${period}`,
54557
+ * });
54558
+ * await fireOnce("BTCUSDT", 14); // → T or null
54559
+ * await fireOnce("BTCUSDT", 28); // → T or null (separate state)
54472
54560
  * ```
54473
54561
  */
54474
54562
  this.fn = (run, context) => {
54475
54563
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
54476
- const wrappedFn = (symbol) => {
54477
- const instance = this._getInstance(run, context.interval);
54478
- return instance.run(symbol);
54564
+ const wrappedFn = (...args) => {
54565
+ const instance = this._getInstance(run, context.interval, context.key);
54566
+ return instance.run(...args);
54479
54567
  };
54480
54568
  wrappedFn.clear = () => {
54481
54569
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
@@ -54489,6 +54577,14 @@ class IntervalUtils {
54489
54577
  }
54490
54578
  this._getInstance.get(run)?.clear();
54491
54579
  };
54580
+ wrappedFn.gc = () => {
54581
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
54582
+ if (!ExecutionContextService.hasContext()) {
54583
+ backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_GC} called without execution context, skipping`);
54584
+ return;
54585
+ }
54586
+ return this._getInstance.get(run)?.gc();
54587
+ };
54492
54588
  return wrappedFn;
54493
54589
  };
54494
54590
  /**
@@ -54501,28 +54597,33 @@ class IntervalUtils {
54501
54597
  * The `run` function reference is used as the memoization key for the underlying
54502
54598
  * `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
54503
54599
  *
54504
- * @template T - Async function type to wrap
54600
+ * @template F - Concrete async function type
54505
54601
  * @param run - Async signal function to wrap with persistent once-per-interval firing
54506
54602
  * @param context.interval - Candle interval that controls the firing boundary
54507
54603
  * @param context.name - Human-readable bucket name; becomes the directory prefix
54508
- * @returns Wrapped function with the same signature as `T`, plus an async `clear()` method
54509
- * that deletes persisted records from disk and disposes the memoized instance
54604
+ * @param context.key - Optional entity key generator. Receives `[symbol, alignMs, ...rest]`.
54605
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
54606
+ * @returns Wrapped function with the same signature as `F`, plus an async `clear()` method
54510
54607
  *
54511
54608
  * @example
54512
54609
  * ```typescript
54513
- * const fetchSignal = async (symbol: string, when: Date) => { ... };
54514
- * const fireOnce = Interval.file(fetchSignal, { interval: "1h", name: "fetchSignal" });
54515
- * await fireOnce.clear(); // delete disk records so the function fires again next call
54610
+ * const fetchSignal = async (symbol: string, period: number) => { ... };
54611
+ * const fireOnce = Interval.file(fetchSignal, {
54612
+ * interval: "1h",
54613
+ * name: "fetchSignal",
54614
+ * key: ([symbol, alignMs, period]) => `${symbol}_${alignMs}_${period}`,
54615
+ * });
54616
+ * await fireOnce("BTCUSDT", 14);
54516
54617
  * ```
54517
54618
  */
54518
54619
  this.file = (run, context) => {
54519
54620
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
54520
54621
  {
54521
- this._getFileInstance(run, context.interval, context.name);
54622
+ this._getFileInstance(run, context.interval, context.name, context.key);
54522
54623
  }
54523
- const wrappedFn = (symbol) => {
54524
- const instance = this._getFileInstance(run, context.interval, context.name);
54525
- return instance.run(symbol);
54624
+ const wrappedFn = (...args) => {
54625
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
54626
+ return instance.run(...args);
54526
54627
  };
54527
54628
  wrappedFn.clear = async () => {
54528
54629
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
@@ -54548,10 +54649,10 @@ class IntervalUtils {
54548
54649
  this.dispose = (run) => {
54549
54650
  backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
54550
54651
  this._getInstance.clear(run);
54652
+ this._getFileInstance.clear(run);
54551
54653
  };
54552
54654
  /**
54553
- * Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects and
54554
- * resets the `IntervalFileInstance` index counter.
54655
+ * Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects.
54555
54656
  * Call this when `process.cwd()` changes between strategy iterations
54556
54657
  * so new instances are created with the updated base path.
54557
54658
  */
@@ -54559,7 +54660,15 @@ class IntervalUtils {
54559
54660
  backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
54560
54661
  this._getInstance.clear();
54561
54662
  this._getFileInstance.clear();
54562
- IntervalFileInstance.clearCounter();
54663
+ };
54664
+ /**
54665
+ * Resets the IntervalFileInstance index counter to zero.
54666
+ * This is useful when process.cwd() changes between strategy iterations to ensure
54667
+ * that new IntervalFileInstance objects start with index 0 and do not collide with old instances.
54668
+ */
54669
+ this.resetCounter = () => {
54670
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_RESET_COUNTER);
54671
+ IntervalFileInstance.resetCounter();
54563
54672
  };
54564
54673
  }
54565
54674
  }