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.cjs CHANGED
@@ -4147,53 +4147,6 @@ const toProfitLossDto = (signal, priceClose) => {
4147
4147
  };
4148
4148
  };
4149
4149
 
4150
- /**
4151
- * Converts markdown content to plain text with minimal formatting
4152
- * @param content - Markdown string to convert
4153
- * @returns Plain text representation
4154
- */
4155
- const toPlainString = (content) => {
4156
- if (!content) {
4157
- return "";
4158
- }
4159
- let text = content;
4160
- // Remove code blocks
4161
- text = text.replace(/```[\s\S]*?```/g, "");
4162
- text = text.replace(/`([^`]+)`/g, "$1");
4163
- // Remove images
4164
- text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
4165
- // Convert links to text only (keep link text, remove URL)
4166
- text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
4167
- // Remove headers (convert to plain text)
4168
- text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
4169
- // Remove bold and italic markers
4170
- text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
4171
- text = text.replace(/\*\*(.+?)\*\*/g, "$1");
4172
- text = text.replace(/\*(.+?)\*/g, "$1");
4173
- text = text.replace(/___(.+?)___/g, "$1");
4174
- text = text.replace(/__(.+?)__/g, "$1");
4175
- text = text.replace(/_(.+?)_/g, "$1");
4176
- // Remove strikethrough
4177
- text = text.replace(/~~(.+?)~~/g, "$1");
4178
- // Convert lists to plain text with bullets
4179
- text = text.replace(/^\s*[-*+]\s+/gm, "• ");
4180
- text = text.replace(/^\s*\d+\.\s+/gm, "• ");
4181
- // Remove blockquotes
4182
- text = text.replace(/^\s*>\s+/gm, "");
4183
- // Remove horizontal rules
4184
- text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
4185
- // Remove HTML tags
4186
- text = text.replace(/<[^>]+>/g, "");
4187
- // Remove excessive whitespace and normalize line breaks
4188
- text = text.replace(/\n[\s\n]*\n/g, "\n");
4189
- text = text.replace(/[ \t]+/g, " ");
4190
- // Remove all newline characters
4191
- text = text.replace(/\n/g, " ");
4192
- // Remove excessive spaces after newline removal
4193
- text = text.replace(/\s+/g, " ");
4194
- return text.trim();
4195
- };
4196
-
4197
4150
  /**
4198
4151
  * Returns the total closed state of a position using costBasisAtClose snapshots.
4199
4152
  *
@@ -4828,6 +4781,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4828
4781
  originalPriceOpen: publicSignal.originalPriceOpen,
4829
4782
  scheduledAt: publicSignal.scheduledAt,
4830
4783
  pendingAt: publicSignal.pendingAt,
4784
+ note: publicSignal.note,
4831
4785
  });
4832
4786
  continue;
4833
4787
  }
@@ -4855,6 +4809,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4855
4809
  originalPriceOpen: publicSignal.originalPriceOpen,
4856
4810
  scheduledAt: publicSignal.scheduledAt,
4857
4811
  pendingAt: publicSignal.pendingAt,
4812
+ note: publicSignal.note,
4858
4813
  });
4859
4814
  continue;
4860
4815
  }
@@ -4881,6 +4836,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4881
4836
  originalPriceOpen: publicSignal.originalPriceOpen,
4882
4837
  scheduledAt: publicSignal.scheduledAt,
4883
4838
  pendingAt: publicSignal.pendingAt,
4839
+ note: publicSignal.note,
4884
4840
  });
4885
4841
  continue;
4886
4842
  }
@@ -4908,6 +4864,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4908
4864
  originalPriceOpen: publicSignal.originalPriceOpen,
4909
4865
  scheduledAt: publicSignal.scheduledAt,
4910
4866
  pendingAt: publicSignal.pendingAt,
4867
+ note: publicSignal.note,
4911
4868
  });
4912
4869
  continue;
4913
4870
  }
@@ -4935,6 +4892,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4935
4892
  originalPriceOpen: publicSignal.originalPriceOpen,
4936
4893
  scheduledAt: publicSignal.scheduledAt,
4937
4894
  pendingAt: publicSignal.pendingAt,
4895
+ note: publicSignal.note,
4938
4896
  });
4939
4897
  continue;
4940
4898
  }
@@ -4964,6 +4922,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4964
4922
  originalPriceOpen: publicSignal.originalPriceOpen,
4965
4923
  scheduledAt: publicSignal.scheduledAt,
4966
4924
  pendingAt: publicSignal.pendingAt,
4925
+ note: publicSignal.note,
4967
4926
  });
4968
4927
  continue;
4969
4928
  }
@@ -5062,7 +5021,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
5062
5021
  const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
5063
5022
  const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
5064
5023
  const signal = await Promise.race([
5065
- self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
5024
+ self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when, currentPrice),
5066
5025
  functoolsKit.sleep(timeoutMs).then(() => TIMEOUT_SYMBOL),
5067
5026
  ]);
5068
5027
  if (typeof signal === "symbol") {
@@ -5092,7 +5051,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
5092
5051
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5093
5052
  priceOpen: signal.priceOpen, // Используем priceOpen из сигнала
5094
5053
  position: signal.position,
5095
- note: toPlainString(signal.note),
5054
+ note: signal.note || "",
5096
5055
  priceTakeProfit: signal.priceTakeProfit,
5097
5056
  priceStopLoss: signal.priceStopLoss,
5098
5057
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
@@ -5118,7 +5077,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
5118
5077
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5119
5078
  priceOpen: signal.priceOpen,
5120
5079
  position: signal.position,
5121
- note: toPlainString(signal.note),
5080
+ note: signal.note || "",
5122
5081
  priceTakeProfit: signal.priceTakeProfit,
5123
5082
  priceStopLoss: signal.priceStopLoss,
5124
5083
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
@@ -5143,7 +5102,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
5143
5102
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5144
5103
  priceOpen: currentPrice,
5145
5104
  ...structuredClone(signal),
5146
- note: toPlainString(signal.note),
5105
+ note: signal.note || "",
5147
5106
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
5148
5107
  symbol: self.params.execution.context.symbol,
5149
5108
  exchangeName: self.params.method.context.exchangeName,
@@ -5890,6 +5849,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
5890
5849
  totalPartials: scheduled._partial?.length ?? 0,
5891
5850
  originalPriceOpen: scheduled.priceOpen,
5892
5851
  pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
5852
+ note: scheduled.note,
5893
5853
  });
5894
5854
  return null;
5895
5855
  }
@@ -6658,6 +6618,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
6658
6618
  totalPartials: scheduled._partial?.length ?? 0,
6659
6619
  originalPriceOpen: scheduled.priceOpen,
6660
6620
  pnl: toProfitLossDto(scheduled, averagePrice),
6621
+ note: scheduled.note,
6661
6622
  });
6662
6623
  }
6663
6624
  await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
@@ -6736,6 +6697,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
6736
6697
  totalPartials: scheduled._partial?.length ?? 0,
6737
6698
  originalPriceOpen: scheduled.priceOpen,
6738
6699
  pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
6700
+ note: scheduled.note,
6739
6701
  });
6740
6702
  return false;
6741
6703
  }
@@ -6823,6 +6785,7 @@ const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, aver
6823
6785
  totalPartials: closedSignal._partial?.length ?? 0,
6824
6786
  originalPriceOpen: closedSignal.priceOpen,
6825
6787
  pnl: toProfitLossDto(closedSignal, averagePrice),
6788
+ note: closedSignal.note,
6826
6789
  });
6827
6790
  await CALL_CLOSE_CALLBACKS_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
6828
6791
  await CALL_PARTIAL_CLEAR_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
@@ -6923,6 +6886,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6923
6886
  totalPartials: activatedSignal._partial?.length ?? 0,
6924
6887
  originalPriceOpen: activatedSignal.priceOpen,
6925
6888
  pnl: toProfitLossDto(activatedSignal, averagePrice),
6889
+ note: activatedSignal.note,
6926
6890
  });
6927
6891
  return { outcome: "pending" };
6928
6892
  }
@@ -6954,6 +6918,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6954
6918
  pendingAt: publicSignalForCommit.pendingAt,
6955
6919
  totalEntries: publicSignalForCommit.totalEntries,
6956
6920
  totalPartials: publicSignalForCommit.totalPartials,
6921
+ note: publicSignalForCommit.note,
6957
6922
  });
6958
6923
  await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
6959
6924
  await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
@@ -8138,6 +8103,7 @@ class ClientStrategy {
8138
8103
  totalPartials: cancelledSignal._partial?.length ?? 0,
8139
8104
  originalPriceOpen: cancelledSignal.priceOpen,
8140
8105
  pnl: toProfitLossDto(cancelledSignal, currentPrice),
8106
+ note: cancelledSignal.note,
8141
8107
  });
8142
8108
  // Call onCancel callback
8143
8109
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8191,6 +8157,7 @@ class ClientStrategy {
8191
8157
  totalPartials: closedSignal._partial?.length ?? 0,
8192
8158
  originalPriceOpen: closedSignal.priceOpen,
8193
8159
  pnl: toProfitLossDto(closedSignal, currentPrice),
8160
+ note: closedSignal.note,
8194
8161
  });
8195
8162
  // Call onClose callback
8196
8163
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8272,6 +8239,7 @@ class ClientStrategy {
8272
8239
  totalPartials: activatedSignal._partial?.length ?? 0,
8273
8240
  originalPriceOpen: activatedSignal.priceOpen,
8274
8241
  pnl: toProfitLossDto(activatedSignal, currentPrice),
8242
+ note: activatedSignal.note,
8275
8243
  });
8276
8244
  return await RETURN_IDLE_FN(this, currentPrice);
8277
8245
  }
@@ -8302,6 +8270,7 @@ class ClientStrategy {
8302
8270
  pendingAt: publicSignalForCommit.pendingAt,
8303
8271
  totalEntries: publicSignalForCommit.totalEntries,
8304
8272
  totalPartials: publicSignalForCommit.totalPartials,
8273
+ note: publicSignalForCommit.note,
8305
8274
  });
8306
8275
  // Call onOpen callback
8307
8276
  await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8437,6 +8406,7 @@ class ClientStrategy {
8437
8406
  totalPartials: cancelledSignal._partial?.length ?? 0,
8438
8407
  originalPriceOpen: cancelledSignal.priceOpen,
8439
8408
  pnl: toProfitLossDto(cancelledSignal, currentPrice),
8409
+ note: cancelledSignal.note,
8440
8410
  });
8441
8411
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
8442
8412
  const cancelledResult = {
@@ -8491,6 +8461,7 @@ class ClientStrategy {
8491
8461
  totalPartials: closedSignal._partial?.length ?? 0,
8492
8462
  originalPriceOpen: closedSignal.priceOpen,
8493
8463
  pnl: toProfitLossDto(closedSignal, currentPrice),
8464
+ note: closedSignal.note,
8494
8465
  });
8495
8466
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
8496
8467
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
@@ -10040,6 +10011,8 @@ class MergeRisk {
10040
10011
  }
10041
10012
  }
10042
10013
 
10014
+ /** Default interval for strategies that do not specify one */
10015
+ const STRATEGY_DEFAULT_INTERVAL = "1m";
10043
10016
  /**
10044
10017
  * If syncSubject listener or any registered action throws, it means the signal was not properly synchronized
10045
10018
  * to the exchange (e.g. limit order failed to fill).
@@ -10425,7 +10398,7 @@ class StrategyConnectionService {
10425
10398
  * @returns Configured ClientStrategy instance
10426
10399
  */
10427
10400
  this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10428
- const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
10401
+ const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10429
10402
  return new ClientStrategy({
10430
10403
  symbol,
10431
10404
  interval,
@@ -16144,8 +16117,8 @@ class StrategySchemaService {
16144
16117
  if (strategySchema.actions?.some((value) => typeof value !== "string")) {
16145
16118
  throw new Error(`strategy schema validation failed: invalid actions for strategyName=${strategySchema.strategyName} actions=[${strategySchema.actions}]`);
16146
16119
  }
16147
- if (typeof strategySchema.interval !== "string") {
16148
- throw new Error(`strategy schema validation failed: missing interval for strategyName=${strategySchema.strategyName}`);
16120
+ if (strategySchema.interval && typeof strategySchema.interval !== "string") {
16121
+ throw new Error(`strategy schema validation failed: invalid interval for strategyName=${strategySchema.strategyName}`);
16149
16122
  }
16150
16123
  if (typeof strategySchema.getSignal !== "function") {
16151
16124
  throw new Error(`strategy schema validation failed: missing getSignal for strategyName=${strategySchema.strategyName}`);
@@ -18091,6 +18064,53 @@ class WalkerCommandService {
18091
18064
  }
18092
18065
  }
18093
18066
 
18067
+ /**
18068
+ * Converts markdown content to plain text with minimal formatting
18069
+ * @param content - Markdown string to convert
18070
+ * @returns Plain text representation
18071
+ */
18072
+ const toPlainString = (content) => {
18073
+ if (!content) {
18074
+ return "";
18075
+ }
18076
+ let text = content;
18077
+ // Remove code blocks
18078
+ text = text.replace(/```[\s\S]*?```/g, "");
18079
+ text = text.replace(/`([^`]+)`/g, "$1");
18080
+ // Remove images
18081
+ text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
18082
+ // Convert links to text only (keep link text, remove URL)
18083
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
18084
+ // Remove headers (convert to plain text)
18085
+ text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
18086
+ // Remove bold and italic markers
18087
+ text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
18088
+ text = text.replace(/\*\*(.+?)\*\*/g, "$1");
18089
+ text = text.replace(/\*(.+?)\*/g, "$1");
18090
+ text = text.replace(/___(.+?)___/g, "$1");
18091
+ text = text.replace(/__(.+?)__/g, "$1");
18092
+ text = text.replace(/_(.+?)_/g, "$1");
18093
+ // Remove strikethrough
18094
+ text = text.replace(/~~(.+?)~~/g, "$1");
18095
+ // Convert lists to plain text with bullets
18096
+ text = text.replace(/^\s*[-*+]\s+/gm, "• ");
18097
+ text = text.replace(/^\s*\d+\.\s+/gm, "• ");
18098
+ // Remove blockquotes
18099
+ text = text.replace(/^\s*>\s+/gm, "");
18100
+ // Remove horizontal rules
18101
+ text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
18102
+ // Remove HTML tags
18103
+ text = text.replace(/<[^>]+>/g, "");
18104
+ // Remove excessive whitespace and normalize line breaks
18105
+ text = text.replace(/\n[\s\n]*\n/g, "\n");
18106
+ text = text.replace(/[ \t]+/g, " ");
18107
+ // Remove all newline characters
18108
+ text = text.replace(/\n/g, " ");
18109
+ // Remove excessive spaces after newline removal
18110
+ text = text.replace(/\s+/g, " ");
18111
+ return text.trim();
18112
+ };
18113
+
18094
18114
  /**
18095
18115
  * Column configuration for backtest markdown reports.
18096
18116
  *
@@ -18766,7 +18786,7 @@ const partial_columns = [
18766
18786
  {
18767
18787
  key: "note",
18768
18788
  label: "Note",
18769
- format: (data) => data.note || "",
18789
+ format: (data) => toPlainString(data.note ?? "N/A"),
18770
18790
  isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
18771
18791
  },
18772
18792
  {
@@ -18926,7 +18946,7 @@ const breakeven_columns = [
18926
18946
  {
18927
18947
  key: "note",
18928
18948
  label: "Note",
18929
- format: (data) => data.note || "",
18949
+ format: (data) => toPlainString(data.note ?? "N/A"),
18930
18950
  isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
18931
18951
  },
18932
18952
  {
@@ -51665,6 +51685,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
51665
51685
  pnlEntries: data.signal.pnl.pnlEntries,
51666
51686
  scheduledAt: data.signal.scheduledAt,
51667
51687
  currentPrice: data.currentPrice,
51688
+ note: data.signal.note,
51668
51689
  createdAt: data.createdAt,
51669
51690
  };
51670
51691
  }
@@ -51694,6 +51715,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
51694
51715
  duration: durationMin,
51695
51716
  scheduledAt: data.signal.scheduledAt,
51696
51717
  pendingAt: data.signal.pendingAt,
51718
+ note: data.signal.note,
51697
51719
  createdAt: data.createdAt,
51698
51720
  };
51699
51721
  }
@@ -51730,6 +51752,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
51730
51752
  pnlPriceClose: data.data.pnl.priceClose,
51731
51753
  pnlCost: data.data.pnl.pnlCost,
51732
51754
  pnlEntries: data.data.pnl.pnlEntries,
51755
+ note: data.data.note,
51733
51756
  scheduledAt: data.data.scheduledAt,
51734
51757
  pendingAt: data.data.pendingAt,
51735
51758
  createdAt: data.timestamp,
@@ -51765,6 +51788,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
51765
51788
  pnlPriceClose: data.data.pnl.priceClose,
51766
51789
  pnlCost: data.data.pnl.pnlCost,
51767
51790
  pnlEntries: data.data.pnl.pnlEntries,
51791
+ note: data.data.note,
51768
51792
  scheduledAt: data.data.scheduledAt,
51769
51793
  pendingAt: data.data.pendingAt,
51770
51794
  createdAt: data.timestamp,
@@ -51799,6 +51823,7 @@ const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
51799
51823
  pnlPriceClose: data.data.pnl.priceClose,
51800
51824
  pnlCost: data.data.pnl.pnlCost,
51801
51825
  pnlEntries: data.data.pnl.pnlEntries,
51826
+ note: data.data.note,
51802
51827
  scheduledAt: data.data.scheduledAt,
51803
51828
  pendingAt: data.data.pendingAt,
51804
51829
  createdAt: data.timestamp,
@@ -51840,6 +51865,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51840
51865
  pnlEntries: data.pnl.pnlEntries,
51841
51866
  scheduledAt: data.scheduledAt,
51842
51867
  pendingAt: data.pendingAt,
51868
+ note: data.note,
51843
51869
  createdAt: data.timestamp,
51844
51870
  };
51845
51871
  }
@@ -51872,6 +51898,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51872
51898
  pnlEntries: data.pnl.pnlEntries,
51873
51899
  scheduledAt: data.scheduledAt,
51874
51900
  pendingAt: data.pendingAt,
51901
+ note: data.note,
51875
51902
  createdAt: data.timestamp,
51876
51903
  };
51877
51904
  }
@@ -51903,6 +51930,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51903
51930
  pnlEntries: data.pnl.pnlEntries,
51904
51931
  scheduledAt: data.scheduledAt,
51905
51932
  pendingAt: data.pendingAt,
51933
+ note: data.note,
51906
51934
  createdAt: data.timestamp,
51907
51935
  };
51908
51936
  }
@@ -51935,6 +51963,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51935
51963
  pnlEntries: data.pnl.pnlEntries,
51936
51964
  scheduledAt: data.scheduledAt,
51937
51965
  pendingAt: data.pendingAt,
51966
+ note: data.note,
51938
51967
  createdAt: data.timestamp,
51939
51968
  };
51940
51969
  }
@@ -51967,6 +51996,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51967
51996
  pnlEntries: data.pnl.pnlEntries,
51968
51997
  scheduledAt: data.scheduledAt,
51969
51998
  pendingAt: data.pendingAt,
51999
+ note: data.note,
51970
52000
  createdAt: data.timestamp,
51971
52001
  };
51972
52002
  }
@@ -51999,6 +52029,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51999
52029
  pnlEntries: data.pnl.pnlEntries,
52000
52030
  scheduledAt: data.scheduledAt,
52001
52031
  pendingAt: data.pendingAt,
52032
+ note: data.note,
52002
52033
  createdAt: data.timestamp,
52003
52034
  };
52004
52035
  }
@@ -52032,6 +52063,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
52032
52063
  pnlEntries: data.pnl.pnlEntries,
52033
52064
  scheduledAt: data.scheduledAt,
52034
52065
  pendingAt: data.pendingAt,
52066
+ note: data.note,
52035
52067
  createdAt: data.timestamp,
52036
52068
  };
52037
52069
  }
@@ -52055,6 +52087,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
52055
52087
  pnlPriceClose: data.pnl.priceClose,
52056
52088
  pnlCost: data.pnl.pnlCost,
52057
52089
  pnlEntries: data.pnl.pnlEntries,
52090
+ note: data.note,
52058
52091
  createdAt: data.timestamp,
52059
52092
  };
52060
52093
  }
@@ -52078,6 +52111,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
52078
52111
  pnlPriceClose: data.pnl.priceClose,
52079
52112
  pnlCost: data.pnl.pnlCost,
52080
52113
  pnlEntries: data.pnl.pnlEntries,
52114
+ note: data.note,
52081
52115
  createdAt: data.timestamp,
52082
52116
  };
52083
52117
  }
@@ -52119,6 +52153,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
52119
52153
  totalPartials: data.totalPartials,
52120
52154
  scheduledAt: data.scheduledAt,
52121
52155
  pendingAt: data.pendingAt,
52156
+ note: data.signal.note,
52122
52157
  createdAt: data.timestamp,
52123
52158
  };
52124
52159
  }
@@ -52151,6 +52186,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
52151
52186
  scheduledAt: data.scheduledAt,
52152
52187
  pendingAt: data.pendingAt,
52153
52188
  closeReason: data.closeReason,
52189
+ note: data.signal.note,
52154
52190
  createdAt: data.timestamp,
52155
52191
  };
52156
52192
  }
@@ -53650,6 +53686,7 @@ const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
53650
53686
  const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
53651
53687
  const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
53652
53688
  const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
53689
+ const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
53653
53690
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
53654
53691
  const MS_PER_MINUTE$1 = 60000;
53655
53692
  const INTERVAL_MINUTES$1 = {
@@ -53901,7 +53938,7 @@ class CacheFileInstance {
53901
53938
  /**
53902
53939
  * Clears the index counter.
53903
53940
  */
53904
- static clearCounter() {
53941
+ static resetCounter() {
53905
53942
  CacheFileInstance._indexCounter = 0;
53906
53943
  }
53907
53944
  /**
@@ -54156,11 +54193,17 @@ class CacheUtils {
54156
54193
  */
54157
54194
  this.clear = () => {
54158
54195
  backtest.loggerService.info(CACHE_METHOD_NAME_CLEAR);
54159
- {
54160
- this._getFnInstance.clear();
54161
- this._getFileInstance.clear();
54162
- }
54163
- CacheFileInstance.clearCounter();
54196
+ this._getFnInstance.clear();
54197
+ this._getFileInstance.clear();
54198
+ };
54199
+ /**
54200
+ * Resets the CacheFileInstance index counter to zero.
54201
+ * This is useful when process.cwd() changes between strategy iterations to ensure
54202
+ * that new CacheFileInstance objects start with index 0 and do not collide with old instances.
54203
+ */
54204
+ this.resetCounter = () => {
54205
+ backtest.loggerService.info(CACHE_METHOD_NAME_RESET_COUNTER);
54206
+ CacheFileInstance.resetCounter();
54164
54207
  };
54165
54208
  }
54166
54209
  }
@@ -54184,10 +54227,12 @@ const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
54184
54227
  const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
54185
54228
  const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
54186
54229
  const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
54230
+ const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
54187
54231
  const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
54188
54232
  const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
54189
54233
  const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
54190
54234
  const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
54235
+ const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
54191
54236
  const MS_PER_MINUTE = 60000;
54192
54237
  const INTERVAL_MINUTES = {
54193
54238
  "1m": 1,
@@ -54254,6 +54299,8 @@ const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
54254
54299
  *
54255
54300
  * State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
54256
54301
  *
54302
+ * @template F - Concrete function type
54303
+ *
54257
54304
  * @example
54258
54305
  * ```typescript
54259
54306
  * const instance = new IntervalFnInstance(mySignalFn, "1h");
@@ -54269,30 +54316,34 @@ class IntervalFnInstance {
54269
54316
  *
54270
54317
  * @param fn - Function to fire once per interval
54271
54318
  * @param interval - Candle interval that controls the firing boundary
54319
+ * @param key - Optional key generator for argument-based state separation.
54320
+ * Default: `([symbol]) => symbol`
54272
54321
  */
54273
- constructor(fn, interval) {
54322
+ constructor(fn, interval, key = ([symbol]) => symbol) {
54274
54323
  this.fn = fn;
54275
54324
  this.interval = interval;
54276
- /** Stores the last aligned timestamp per context+symbol key. */
54325
+ this.key = key;
54326
+ /** Stores the last aligned timestamp per context+symbol+args key. */
54277
54327
  this._stateMap = new Map();
54278
54328
  /**
54279
54329
  * Execute the signal function with once-per-interval enforcement.
54280
54330
  *
54281
54331
  * Algorithm:
54282
54332
  * 1. Align the current execution context `when` to the interval boundary.
54283
- * 2. If the stored aligned timestamp for this context+symbol equals the current one → return `null`.
54284
- * 3. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
54333
+ * 2. Build state key from context + key generator result.
54334
+ * 3. If the stored aligned timestamp for this key equals the current one return `null`.
54335
+ * 4. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
54285
54336
  * the signal. If it returns `null`, leave state unchanged so the next call retries.
54286
54337
  *
54287
54338
  * Requires active method context and execution context.
54288
54339
  *
54289
- * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
54340
+ * @param args - Arguments forwarded to the wrapped function
54290
54341
  * @returns The value returned by `fn` on the first non-null fire, `null` on all subsequent calls
54291
54342
  * within the same interval or when `fn` itself returned `null`
54292
54343
  * @throws Error if method context, execution context, or interval is missing
54293
54344
  */
54294
- this.run = async (symbol) => {
54295
- backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { symbol });
54345
+ this.run = async (...args) => {
54346
+ backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
54296
54347
  const step = INTERVAL_MINUTES[this.interval];
54297
54348
  {
54298
54349
  if (!MethodContextService.hasContext()) {
@@ -54306,15 +54357,16 @@ class IntervalFnInstance {
54306
54357
  }
54307
54358
  }
54308
54359
  const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
54309
- const key = `${contextKey}:${symbol}`;
54310
54360
  const currentWhen = backtest.executionContextService.context.when;
54311
54361
  const currentAligned = align(currentWhen.getTime(), this.interval);
54312
- if (this._stateMap.get(key) === currentAligned) {
54362
+ const argKey = this.key(args);
54363
+ const stateKey = `${contextKey}:${argKey}`;
54364
+ if (this._stateMap.get(stateKey) === currentAligned) {
54313
54365
  return null;
54314
54366
  }
54315
- const result = await this.fn(symbol, currentWhen);
54367
+ const result = await this.fn.apply(null, args);
54316
54368
  if (result !== null) {
54317
- this._stateMap.set(key, currentAligned);
54369
+ this._stateMap.set(stateKey, currentAligned);
54318
54370
  }
54319
54371
  return result;
54320
54372
  };
@@ -54335,6 +54387,28 @@ class IntervalFnInstance {
54335
54387
  }
54336
54388
  }
54337
54389
  };
54390
+ /**
54391
+ * Garbage collect expired state entries.
54392
+ *
54393
+ * Removes all entries whose aligned timestamp differs from the current interval boundary.
54394
+ * Call this periodically to free memory from stale state entries.
54395
+ *
54396
+ * Requires active execution context to get current time.
54397
+ *
54398
+ * @returns Number of entries removed
54399
+ */
54400
+ this.gc = () => {
54401
+ const currentWhen = backtest.executionContextService.context.when;
54402
+ const currentAligned = align(currentWhen.getTime(), this.interval);
54403
+ let removed = 0;
54404
+ for (const [key, storedAligned] of this._stateMap.entries()) {
54405
+ if (storedAligned !== currentAligned) {
54406
+ this._stateMap.delete(key);
54407
+ removed++;
54408
+ }
54409
+ }
54410
+ return removed;
54411
+ };
54338
54412
  }
54339
54413
  }
54340
54414
  /**
@@ -54348,7 +54422,7 @@ class IntervalFnInstance {
54348
54422
  *
54349
54423
  * Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
54350
54424
  *
54351
- * @template T - Async function type: `(symbol: string, ...args) => Promise<R | null>`
54425
+ * @template F - Concrete async function type
54352
54426
  *
54353
54427
  * @example
54354
54428
  * ```typescript
@@ -54369,7 +54443,7 @@ class IntervalFileInstance {
54369
54443
  * Resets the index counter to zero.
54370
54444
  * Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
54371
54445
  */
54372
- static clearCounter() {
54446
+ static resetCounter() {
54373
54447
  IntervalFileInstance._indexCounter = 0;
54374
54448
  }
54375
54449
  /**
@@ -54378,18 +54452,21 @@ class IntervalFileInstance {
54378
54452
  * @param fn - Async signal function to fire once per interval
54379
54453
  * @param interval - Candle interval that controls the firing boundary
54380
54454
  * @param name - Human-readable bucket name used as the directory prefix
54455
+ * @param key - Dynamic key generator; receives `[symbol, alignMs, ...rest]`.
54456
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
54381
54457
  */
54382
- constructor(fn, interval, name) {
54458
+ constructor(fn, interval, name, key = ([symbol, alignMs]) => `${symbol}_${alignMs}`) {
54383
54459
  this.fn = fn;
54384
54460
  this.interval = interval;
54385
54461
  this.name = name;
54462
+ this.key = key;
54386
54463
  /**
54387
54464
  * Execute the async function with persistent once-per-interval enforcement.
54388
54465
  *
54389
54466
  * Algorithm:
54390
54467
  * 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
54391
- * 2. Align execution context `when` to interval boundary → `alignedTs`.
54392
- * 3. Build entity key = `${symbol}_${alignedTs}`.
54468
+ * 2. Align execution context `when` to interval boundary → `alignedMs`.
54469
+ * 3. Build entity key from the key generator (receives `[symbol, alignedMs, ...rest]`).
54393
54470
  * 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
54394
54471
  * 5. On hit — return `null` (interval already fired).
54395
54472
  * 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
@@ -54397,12 +54474,13 @@ class IntervalFileInstance {
54397
54474
  * Requires active method context and execution context.
54398
54475
  *
54399
54476
  * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
54477
+ * @param args - Additional arguments forwarded to the wrapped function
54400
54478
  * @returns The value on the first non-null fire, `null` if already fired this interval
54401
54479
  * or if `fn` itself returned `null`
54402
54480
  * @throws Error if method context, execution context, or interval is missing
54403
54481
  */
54404
- this.run = async (symbol) => {
54405
- backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { symbol });
54482
+ this.run = async (...args) => {
54483
+ backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
54406
54484
  const step = INTERVAL_MINUTES[this.interval];
54407
54485
  {
54408
54486
  if (!MethodContextService.hasContext()) {
@@ -54415,15 +54493,16 @@ class IntervalFileInstance {
54415
54493
  throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
54416
54494
  }
54417
54495
  }
54496
+ const [symbol, ...rest] = args;
54418
54497
  const { when } = backtest.executionContextService.context;
54419
- const alignedTs = align(when.getTime(), this.interval);
54498
+ const alignedMs = align(when.getTime(), this.interval);
54420
54499
  const bucket = `${this.name}_${this.interval}_${this.index}`;
54421
- const entityKey = `${symbol}_${alignedTs}`;
54500
+ const entityKey = this.key([symbol, alignedMs, ...rest]);
54422
54501
  const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
54423
54502
  if (cached !== null) {
54424
54503
  return null;
54425
54504
  }
54426
- const result = await this.fn(symbol, when);
54505
+ const result = await this.fn.call(null, ...args);
54427
54506
  if (result !== null) {
54428
54507
  await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
54429
54508
  }
@@ -54464,12 +54543,12 @@ class IntervalUtils {
54464
54543
  * Memoized factory to get or create an `IntervalFnInstance` for a function.
54465
54544
  * Each function reference gets its own isolated instance.
54466
54545
  */
54467
- this._getInstance = functoolsKit.memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
54546
+ this._getInstance = functoolsKit.memoize(([run]) => run, (run, interval, key) => new IntervalFnInstance(run, interval, key));
54468
54547
  /**
54469
54548
  * Memoized factory to get or create an `IntervalFileInstance` for an async function.
54470
54549
  * Each function reference gets its own isolated persistent instance.
54471
54550
  */
54472
- this._getFileInstance = functoolsKit.memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
54551
+ this._getFileInstance = functoolsKit.memoize(([run]) => run, (run, interval, name, key) => new IntervalFileInstance(run, interval, name, key));
54473
54552
  /**
54474
54553
  * Wrap a signal function with in-memory once-per-interval firing.
54475
54554
  *
@@ -54481,21 +54560,30 @@ class IntervalUtils {
54481
54560
  *
54482
54561
  * @param run - Signal function to wrap
54483
54562
  * @param context.interval - Candle interval that controls the firing boundary
54484
- * @returns Wrapped function with the same signature as `TIntervalFn<T>`, plus a `clear()` method
54563
+ * @param context.key - Optional key generator for argument-based state separation
54564
+ * @returns Wrapped function with the same signature as `F`, plus a `clear()` method
54485
54565
  *
54486
54566
  * @example
54487
54567
  * ```typescript
54568
+ * // Without extra args
54488
54569
  * const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
54489
- *
54490
54570
  * await fireOnce("BTCUSDT"); // → T or null (fn called)
54491
54571
  * await fireOnce("BTCUSDT"); // → null (same interval, skipped)
54572
+ *
54573
+ * // With extra args and key
54574
+ * const fireOnce = Interval.fn(mySignalFn, {
54575
+ * interval: "15m",
54576
+ * key: ([symbol, period]) => `${symbol}_${period}`,
54577
+ * });
54578
+ * await fireOnce("BTCUSDT", 14); // → T or null
54579
+ * await fireOnce("BTCUSDT", 28); // → T or null (separate state)
54492
54580
  * ```
54493
54581
  */
54494
54582
  this.fn = (run, context) => {
54495
54583
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
54496
- const wrappedFn = (symbol) => {
54497
- const instance = this._getInstance(run, context.interval);
54498
- return instance.run(symbol);
54584
+ const wrappedFn = (...args) => {
54585
+ const instance = this._getInstance(run, context.interval, context.key);
54586
+ return instance.run(...args);
54499
54587
  };
54500
54588
  wrappedFn.clear = () => {
54501
54589
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
@@ -54509,6 +54597,14 @@ class IntervalUtils {
54509
54597
  }
54510
54598
  this._getInstance.get(run)?.clear();
54511
54599
  };
54600
+ wrappedFn.gc = () => {
54601
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
54602
+ if (!ExecutionContextService.hasContext()) {
54603
+ backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_GC} called without execution context, skipping`);
54604
+ return;
54605
+ }
54606
+ return this._getInstance.get(run)?.gc();
54607
+ };
54512
54608
  return wrappedFn;
54513
54609
  };
54514
54610
  /**
@@ -54521,28 +54617,33 @@ class IntervalUtils {
54521
54617
  * The `run` function reference is used as the memoization key for the underlying
54522
54618
  * `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
54523
54619
  *
54524
- * @template T - Async function type to wrap
54620
+ * @template F - Concrete async function type
54525
54621
  * @param run - Async signal function to wrap with persistent once-per-interval firing
54526
54622
  * @param context.interval - Candle interval that controls the firing boundary
54527
54623
  * @param context.name - Human-readable bucket name; becomes the directory prefix
54528
- * @returns Wrapped function with the same signature as `T`, plus an async `clear()` method
54529
- * that deletes persisted records from disk and disposes the memoized instance
54624
+ * @param context.key - Optional entity key generator. Receives `[symbol, alignMs, ...rest]`.
54625
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
54626
+ * @returns Wrapped function with the same signature as `F`, plus an async `clear()` method
54530
54627
  *
54531
54628
  * @example
54532
54629
  * ```typescript
54533
- * const fetchSignal = async (symbol: string, when: Date) => { ... };
54534
- * const fireOnce = Interval.file(fetchSignal, { interval: "1h", name: "fetchSignal" });
54535
- * await fireOnce.clear(); // delete disk records so the function fires again next call
54630
+ * const fetchSignal = async (symbol: string, period: number) => { ... };
54631
+ * const fireOnce = Interval.file(fetchSignal, {
54632
+ * interval: "1h",
54633
+ * name: "fetchSignal",
54634
+ * key: ([symbol, alignMs, period]) => `${symbol}_${alignMs}_${period}`,
54635
+ * });
54636
+ * await fireOnce("BTCUSDT", 14);
54536
54637
  * ```
54537
54638
  */
54538
54639
  this.file = (run, context) => {
54539
54640
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
54540
54641
  {
54541
- this._getFileInstance(run, context.interval, context.name);
54642
+ this._getFileInstance(run, context.interval, context.name, context.key);
54542
54643
  }
54543
- const wrappedFn = (symbol) => {
54544
- const instance = this._getFileInstance(run, context.interval, context.name);
54545
- return instance.run(symbol);
54644
+ const wrappedFn = (...args) => {
54645
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
54646
+ return instance.run(...args);
54546
54647
  };
54547
54648
  wrappedFn.clear = async () => {
54548
54649
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
@@ -54568,10 +54669,10 @@ class IntervalUtils {
54568
54669
  this.dispose = (run) => {
54569
54670
  backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
54570
54671
  this._getInstance.clear(run);
54672
+ this._getFileInstance.clear(run);
54571
54673
  };
54572
54674
  /**
54573
- * Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects and
54574
- * resets the `IntervalFileInstance` index counter.
54675
+ * Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects.
54575
54676
  * Call this when `process.cwd()` changes between strategy iterations
54576
54677
  * so new instances are created with the updated base path.
54577
54678
  */
@@ -54579,7 +54680,15 @@ class IntervalUtils {
54579
54680
  backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
54580
54681
  this._getInstance.clear();
54581
54682
  this._getFileInstance.clear();
54582
- IntervalFileInstance.clearCounter();
54683
+ };
54684
+ /**
54685
+ * Resets the IntervalFileInstance index counter to zero.
54686
+ * This is useful when process.cwd() changes between strategy iterations to ensure
54687
+ * that new IntervalFileInstance objects start with index 0 and do not collide with old instances.
54688
+ */
54689
+ this.resetCounter = () => {
54690
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_RESET_COUNTER);
54691
+ IntervalFileInstance.resetCounter();
54583
54692
  };
54584
54693
  }
54585
54694
  }