backtest-kit 3.6.0 → 3.7.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
@@ -801,6 +801,11 @@ const PERSIST_LOG_UTILS_METHOD_NAME_WRITE_DATA = "PersistLogUtils.writeLogData";
801
801
  const PERSIST_LOG_UTILS_METHOD_NAME_USE_JSON = "PersistLogUtils.useJson";
802
802
  const PERSIST_LOG_UTILS_METHOD_NAME_USE_DUMMY = "PersistLogUtils.useDummy";
803
803
  const PERSIST_LOG_UTILS_METHOD_NAME_USE_PERSIST_LOG_ADAPTER = "PersistLogUtils.usePersistLogAdapter";
804
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA = "PersistMeasureUtils.readMeasureData";
805
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA = "PersistMeasureUtils.writeMeasureData";
806
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON = "PersistMeasureUtils.useJson";
807
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY = "PersistMeasureUtils.useDummy";
808
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER = "PersistMeasureUtils.usePersistMeasureAdapter";
804
809
  const BASE_WAIT_FOR_INIT_FN_METHOD_NAME = "PersistBase.waitForInitFn";
805
810
  const BASE_UNLINK_RETRY_COUNT = 5;
806
811
  const BASE_UNLINK_RETRY_DELAY = 1000;
@@ -2071,6 +2076,92 @@ class PersistLogUtils {
2071
2076
  * Used by LogPersistUtils for log entry persistence.
2072
2077
  */
2073
2078
  const PersistLogAdapter = new PersistLogUtils();
2079
+ /**
2080
+ * Utility class for managing external API response cache persistence.
2081
+ *
2082
+ * Features:
2083
+ * - Memoized storage instances per cache bucket (aligned timestamp + symbol)
2084
+ * - Custom adapter support
2085
+ * - Atomic read/write operations
2086
+ * - Crash-safe cache state management
2087
+ *
2088
+ * Used by Cache.file for persistent caching of external API responses.
2089
+ */
2090
+ class PersistMeasureUtils {
2091
+ constructor() {
2092
+ this.PersistMeasureFactory = PersistBase;
2093
+ this.getMeasureStorage = functoolsKit.memoize(([bucket]) => bucket, (bucket) => Reflect.construct(this.PersistMeasureFactory, [
2094
+ bucket,
2095
+ `./dump/data/measure/`,
2096
+ ]));
2097
+ /**
2098
+ * Reads cached measure data for a given bucket and key.
2099
+ *
2100
+ * @param bucket - Storage bucket (e.g. aligned timestamp + symbol)
2101
+ * @param key - Dynamic cache key within the bucket
2102
+ * @returns Promise resolving to cached value or null if not found
2103
+ */
2104
+ this.readMeasureData = async (bucket, key) => {
2105
+ bt.loggerService.info(PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA, {
2106
+ bucket,
2107
+ key,
2108
+ });
2109
+ const isInitial = !this.getMeasureStorage.has(bucket);
2110
+ const stateStorage = this.getMeasureStorage(bucket);
2111
+ await stateStorage.waitForInit(isInitial);
2112
+ if (await stateStorage.hasValue(key)) {
2113
+ return await stateStorage.readValue(key);
2114
+ }
2115
+ return null;
2116
+ };
2117
+ /**
2118
+ * Writes measure data to disk with atomic file writes.
2119
+ *
2120
+ * @param data - Data to cache
2121
+ * @param bucket - Storage bucket (e.g. aligned timestamp + symbol)
2122
+ * @param key - Dynamic cache key within the bucket
2123
+ * @returns Promise that resolves when write is complete
2124
+ */
2125
+ this.writeMeasureData = async (data, bucket, key) => {
2126
+ bt.loggerService.info(PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA, {
2127
+ bucket,
2128
+ key,
2129
+ });
2130
+ const isInitial = !this.getMeasureStorage.has(bucket);
2131
+ const stateStorage = this.getMeasureStorage(bucket);
2132
+ await stateStorage.waitForInit(isInitial);
2133
+ await stateStorage.writeValue(key, data);
2134
+ };
2135
+ }
2136
+ /**
2137
+ * Registers a custom persistence adapter.
2138
+ *
2139
+ * @param Ctor - Custom PersistBase constructor
2140
+ */
2141
+ usePersistMeasureAdapter(Ctor) {
2142
+ bt.loggerService.info(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER);
2143
+ this.PersistMeasureFactory = Ctor;
2144
+ }
2145
+ /**
2146
+ * Switches to the default JSON persist adapter.
2147
+ */
2148
+ useJson() {
2149
+ bt.loggerService.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON);
2150
+ this.usePersistMeasureAdapter(PersistBase);
2151
+ }
2152
+ /**
2153
+ * Switches to a dummy persist adapter that discards all writes.
2154
+ */
2155
+ useDummy() {
2156
+ bt.loggerService.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY);
2157
+ this.usePersistMeasureAdapter(PersistDummy);
2158
+ }
2159
+ }
2160
+ /**
2161
+ * Global singleton instance of PersistMeasureUtils.
2162
+ * Used by Cache.file for persistent caching of external API responses.
2163
+ */
2164
+ const PersistMeasureAdapter = new PersistMeasureUtils();
2074
2165
 
2075
2166
  var _a$3, _b$3;
2076
2167
  const BUSY_DELAY = 100;
@@ -10371,7 +10462,7 @@ class ActionBase {
10371
10462
  * @example
10372
10463
  * ```typescript
10373
10464
  * pingScheduled(event: SchedulePingContract) {
10374
- * const waitTime = Date.now() - event.data.timestampScheduled;
10465
+ * const waitTime = getTimestamp() - event.data.timestampScheduled;
10375
10466
  * const waitMinutes = Math.floor(waitTime / 60000);
10376
10467
  * console.log(`Scheduled signal waiting ${waitMinutes} minutes`);
10377
10468
  * }
@@ -10400,7 +10491,7 @@ class ActionBase {
10400
10491
  * @example
10401
10492
  * ```typescript
10402
10493
  * pingActive(event: ActivePingContract) {
10403
- * const holdTime = Date.now() - event.data.pendingAt;
10494
+ * const holdTime = getTimestamp() - event.data.pendingAt;
10404
10495
  * const holdMinutes = Math.floor(holdTime / 60000);
10405
10496
  * console.log(`Active signal holding ${holdMinutes} minutes`);
10406
10497
  * }
@@ -16102,6 +16193,20 @@ const COLUMN_CONFIG = {
16102
16193
  */
16103
16194
  const DEFAULT_COLUMNS = Object.freeze({ ...COLUMN_CONFIG });
16104
16195
 
16196
+ /**
16197
+ * Retrieves the current timestamp based on the execution context.
16198
+ * If an execution context is active (e.g., during a backtest), it returns the timestamp from the context to ensure consistency with the simulated time.
16199
+ * If no execution context is active (e.g., during live operation), it returns the current real-world timestamp.
16200
+ * This function helps maintain accurate timing for logs, metrics, and other time-sensitive operations across both live and backtest modes.
16201
+ * @return {number} The current timestamp in milliseconds, either from the execution context or the real-world clock.
16202
+ */
16203
+ const getContextTimestamp = () => {
16204
+ if (ExecutionContextService.hasContext()) {
16205
+ return bt.executionContextService.context.when.getTime();
16206
+ }
16207
+ return Date.now();
16208
+ };
16209
+
16105
16210
  var _a$2, _b$2;
16106
16211
  const MARKDOWN_METHOD_NAME_ENABLE = "MarkdownUtils.enable";
16107
16212
  const MARKDOWN_METHOD_NAME_DISABLE = "MarkdownUtils.disable";
@@ -16235,7 +16340,7 @@ class MarkdownFileBase {
16235
16340
  markdownName: this.markdownName,
16236
16341
  data,
16237
16342
  ...searchFlags,
16238
- timestamp: Date.now(),
16343
+ timestamp: getContextTimestamp(),
16239
16344
  }) + "\n";
16240
16345
  const status = await this[WRITE_SAFE_SYMBOL$2](line);
16241
16346
  if (status === functoolsKit.TIMEOUT_SYMBOL) {
@@ -16779,7 +16884,7 @@ let ReportStorage$7 = class ReportStorage {
16779
16884
  */
16780
16885
  async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
16781
16886
  const markdown = await this.getReport(strategyName, columns);
16782
- const timestamp = Date.now();
16887
+ const timestamp = getContextTimestamp();
16783
16888
  const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16784
16889
  await Markdown.writeData("backtest", markdown, {
16785
16890
  path,
@@ -17114,7 +17219,7 @@ let ReportStorage$6 = class ReportStorage {
17114
17219
  */
17115
17220
  addIdleEvent(currentPrice) {
17116
17221
  const newEvent = {
17117
- timestamp: Date.now(),
17222
+ timestamp: getContextTimestamp(),
17118
17223
  action: "idle",
17119
17224
  currentPrice,
17120
17225
  };
@@ -17170,7 +17275,7 @@ let ReportStorage$6 = class ReportStorage {
17170
17275
  */
17171
17276
  addActiveEvent(data) {
17172
17277
  const newEvent = {
17173
- timestamp: Date.now(),
17278
+ timestamp: getContextTimestamp(),
17174
17279
  action: "active",
17175
17280
  symbol: data.signal.symbol,
17176
17281
  signalId: data.signal.id,
@@ -17272,7 +17377,7 @@ let ReportStorage$6 = class ReportStorage {
17272
17377
  */
17273
17378
  addWaitingEvent(data) {
17274
17379
  const newEvent = {
17275
- timestamp: Date.now(),
17380
+ timestamp: getContextTimestamp(),
17276
17381
  action: "waiting",
17277
17382
  symbol: data.signal.symbol,
17278
17383
  signalId: data.signal.id,
@@ -17466,7 +17571,7 @@ let ReportStorage$6 = class ReportStorage {
17466
17571
  */
17467
17572
  async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
17468
17573
  const markdown = await this.getReport(strategyName, columns);
17469
- const timestamp = Date.now();
17574
+ const timestamp = getContextTimestamp();
17470
17575
  const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
17471
17576
  await Markdown.writeData("live", markdown, {
17472
17577
  path,
@@ -17998,7 +18103,7 @@ let ReportStorage$5 = class ReportStorage {
17998
18103
  */
17999
18104
  async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
18000
18105
  const markdown = await this.getReport(strategyName, columns);
18001
- const timestamp = Date.now();
18106
+ const timestamp = getContextTimestamp();
18002
18107
  const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
18003
18108
  await Markdown.writeData("schedule", markdown, {
18004
18109
  path,
@@ -18463,7 +18568,7 @@ class PerformanceStorage {
18463
18568
  */
18464
18569
  async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
18465
18570
  const markdown = await this.getReport(strategyName, columns);
18466
- const timestamp = Date.now();
18571
+ const timestamp = getContextTimestamp();
18467
18572
  const filename = CREATE_FILE_NAME_FN$6(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
18468
18573
  await Markdown.writeData("performance", markdown, {
18469
18574
  path,
@@ -18914,7 +19019,7 @@ let ReportStorage$4 = class ReportStorage {
18914
19019
  */
18915
19020
  async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
18916
19021
  const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
18917
- const timestamp = Date.now();
19022
+ const timestamp = getContextTimestamp();
18918
19023
  const filename = CREATE_FILE_NAME_FN$5(this.walkerName, timestamp);
18919
19024
  await Markdown.writeData("walker", markdown, {
18920
19025
  path,
@@ -19468,7 +19573,7 @@ class HeatmapStorage {
19468
19573
  */
19469
19574
  async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
19470
19575
  const markdown = await this.getReport(strategyName, columns);
19471
- const timestamp = Date.now();
19576
+ const timestamp = getContextTimestamp();
19472
19577
  const filename = CREATE_FILE_NAME_FN$4(strategyName, this.exchangeName, this.frameName, timestamp);
19473
19578
  await Markdown.writeData("heat", markdown, {
19474
19579
  path,
@@ -21148,7 +21253,7 @@ let ReportStorage$3 = class ReportStorage {
21148
21253
  */
21149
21254
  async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
21150
21255
  const markdown = await this.getReport(symbol, strategyName, columns);
21151
- const timestamp = Date.now();
21256
+ const timestamp = getContextTimestamp();
21152
21257
  const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
21153
21258
  await Markdown.writeData("partial", markdown, {
21154
21259
  path,
@@ -22241,7 +22346,7 @@ let ReportStorage$2 = class ReportStorage {
22241
22346
  */
22242
22347
  async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
22243
22348
  const markdown = await this.getReport(symbol, strategyName, columns);
22244
- const timestamp = Date.now();
22349
+ const timestamp = getContextTimestamp();
22245
22350
  const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
22246
22351
  await Markdown.writeData("breakeven", markdown, {
22247
22352
  path,
@@ -22909,7 +23014,7 @@ let ReportStorage$1 = class ReportStorage {
22909
23014
  */
22910
23015
  async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
22911
23016
  const markdown = await this.getReport(symbol, strategyName, columns);
22912
- const timestamp = Date.now();
23017
+ const timestamp = getContextTimestamp();
22913
23018
  const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
22914
23019
  await Markdown.writeData("risk", markdown, {
22915
23020
  path,
@@ -23393,7 +23498,7 @@ class ReportBase {
23393
23498
  reportName: this.reportName,
23394
23499
  data,
23395
23500
  ...searchFlags,
23396
- timestamp: Date.now(),
23501
+ timestamp: getContextTimestamp(),
23397
23502
  }) + "\n";
23398
23503
  const status = await this[WRITE_SAFE_SYMBOL$1](line);
23399
23504
  if (status === functoolsKit.TIMEOUT_SYMBOL) {
@@ -23732,7 +23837,7 @@ class BacktestReportService {
23732
23837
  this.tick = async (data) => {
23733
23838
  this.loggerService.log(BACKTEST_REPORT_METHOD_NAME_TICK, { data });
23734
23839
  const baseEvent = {
23735
- timestamp: Date.now(),
23840
+ timestamp: getContextTimestamp(),
23736
23841
  action: data.action,
23737
23842
  symbol: data.symbol,
23738
23843
  strategyName: data.strategyName,
@@ -23911,7 +24016,7 @@ class LiveReportService {
23911
24016
  this.tick = async (data) => {
23912
24017
  this.loggerService.log(LIVE_REPORT_METHOD_NAME_TICK, { data });
23913
24018
  const baseEvent = {
23914
- timestamp: Date.now(),
24019
+ timestamp: getContextTimestamp(),
23915
24020
  action: data.action,
23916
24021
  symbol: data.symbol,
23917
24022
  strategyName: data.strategyName,
@@ -24434,7 +24539,7 @@ class WalkerReportService {
24434
24539
  this.tick = async (data) => {
24435
24540
  this.loggerService.log(WALKER_REPORT_METHOD_NAME_TICK, { data });
24436
24541
  await Report.writeData("walker", {
24437
- timestamp: Date.now(),
24542
+ timestamp: getContextTimestamp(),
24438
24543
  walkerName: data.walkerName,
24439
24544
  symbol: data.symbol,
24440
24545
  exchangeName: data.exchangeName,
@@ -24561,7 +24666,7 @@ class HeatReportService {
24561
24666
  return;
24562
24667
  }
24563
24668
  await Report.writeData("heat", {
24564
- timestamp: Date.now(),
24669
+ timestamp: getContextTimestamp(),
24565
24670
  action: data.action,
24566
24671
  symbol: data.symbol,
24567
24672
  strategyName: data.strategyName,
@@ -25883,7 +25988,7 @@ class ReportStorage {
25883
25988
  */
25884
25989
  async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
25885
25990
  const markdown = await this.getReport(symbol, strategyName, columns);
25886
- const timestamp = Date.now();
25991
+ const timestamp = getContextTimestamp();
25887
25992
  const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
25888
25993
  await Markdown.writeData("strategy", markdown, {
25889
25994
  path,
@@ -28199,6 +28304,7 @@ const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
28199
28304
  const FORMAT_PRICE_METHOD_NAME = "exchange.formatPrice";
28200
28305
  const FORMAT_QUANTITY_METHOD_NAME = "exchange.formatQuantity";
28201
28306
  const GET_DATE_METHOD_NAME = "exchange.getDate";
28307
+ const GET_TIMESTAMP_METHOD_NAME = "exchange.getTimestamp";
28202
28308
  const GET_MODE_METHOD_NAME = "exchange.getMode";
28203
28309
  const GET_SYMBOL_METHOD_NAME = "exchange.getSymbol";
28204
28310
  const GET_CONTEXT_METHOD_NAME = "exchange.getContext";
@@ -28364,6 +28470,26 @@ async function getDate() {
28364
28470
  const { when } = bt.executionContextService.context;
28365
28471
  return new Date(when.getTime());
28366
28472
  }
28473
+ /**
28474
+ * Gets the current timestamp from execution context.
28475
+ *
28476
+ * In backtest mode: returns the current timeframe timestamp being processed
28477
+ * In live mode: returns current real-time timestamp
28478
+ *
28479
+ * @returns Promise resolving to current execution context timestamp in milliseconds
28480
+ * @example
28481
+ * ```typescript
28482
+ * const timestamp = await getTimestamp();
28483
+ * console.log(timestamp); // 1700000000000
28484
+ * ```
28485
+ */
28486
+ async function getTimestamp() {
28487
+ bt.loggerService.info(GET_TIMESTAMP_METHOD_NAME);
28488
+ if (!ExecutionContextService.hasContext()) {
28489
+ throw new Error("getTimestamp requires an execution context");
28490
+ }
28491
+ return getContextTimestamp();
28492
+ }
28367
28493
  /**
28368
28494
  * Gets the current execution mode.
28369
28495
  *
@@ -32561,7 +32687,6 @@ const ADD_ACTION_METHOD_NAME = "add.addActionSchema";
32561
32687
  * priceTakeProfit: 51000,
32562
32688
  * priceStopLoss: 49000,
32563
32689
  * minuteEstimatedTime: 60,
32564
- * timestamp: Date.now(),
32565
32690
  * }),
32566
32691
  * callbacks: {
32567
32692
  * onOpen: (symbol, signal, currentPrice, backtest) => console.log("Signal opened"),
@@ -33488,8 +33613,8 @@ const WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
33488
33613
  const WRITE_SAFE_SYMBOL = Symbol("write-safe");
33489
33614
  /**
33490
33615
  * Backtest execution time retrieval function.
33491
- * Returns the 'when' timestamp from the execution context if available, otherwise returns the current time.
33492
- * This allows log entries to be timestamped according to the backtest timeline rather than real-world time, improving log relevance and user experience during backtest analysis.
33616
+ * Returns the 'when' priority from the execution context if available, otherwise returns the current time.
33617
+ * This allows log entries to be priorityed according to the backtest timeline rather than real-world time, improving log relevance and user experience during backtest analysis.
33493
33618
  */
33494
33619
  const GET_DATE_FN = () => {
33495
33620
  if (ExecutionContextService.hasContext()) {
@@ -33511,7 +33636,7 @@ const GET_METHOD_CONTEXT_FN = () => {
33511
33636
  /**
33512
33637
  * Execution context retrieval function.
33513
33638
  * Returns the current execution context from ExecutionContextService if available, otherwise returns null.
33514
- * This allows log entries to include contextual information about the symbol, timestamp, and backtest mode associated with the log event, providing additional insights into the execution environment when analyzing logs.
33639
+ * This allows log entries to include contextual information about the symbol, priority, and backtest mode associated with the log event, providing additional insights into the execution environment when analyzing logs.
33515
33640
  */
33516
33641
  const GET_EXECUTION_CONTEXT_FN = () => {
33517
33642
  if (ExecutionContextService.hasContext()) {
@@ -33541,7 +33666,7 @@ class LogPersistUtils {
33541
33666
  this.waitForInit = functoolsKit.singleshot(async () => {
33542
33667
  bt.loggerService.info(LOG_PERSIST_METHOD_NAME_WAIT_FOR_INIT);
33543
33668
  const list = await PersistLogAdapter.readLogData();
33544
- list.sort((a, b) => a.timestamp - b.timestamp);
33669
+ list.sort((a, b) => a.priority - b.priority);
33545
33670
  this._entries = list.slice(-GLOBAL_CONFIG.CC_MAX_LOG_LINES);
33546
33671
  });
33547
33672
  /**
@@ -33557,7 +33682,8 @@ class LogPersistUtils {
33557
33682
  this._entries.push({
33558
33683
  id: functoolsKit.randomString(),
33559
33684
  type: "log",
33560
- timestamp: Date.now(),
33685
+ priority: Date.now(),
33686
+ timestamp: getContextTimestamp(),
33561
33687
  createdAt: date.toISOString(),
33562
33688
  methodContext: GET_METHOD_CONTEXT_FN(),
33563
33689
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33580,7 +33706,8 @@ class LogPersistUtils {
33580
33706
  this._entries.push({
33581
33707
  id: functoolsKit.randomString(),
33582
33708
  type: "debug",
33583
- timestamp: Date.now(),
33709
+ priority: Date.now(),
33710
+ timestamp: getContextTimestamp(),
33584
33711
  createdAt: date.toISOString(),
33585
33712
  methodContext: GET_METHOD_CONTEXT_FN(),
33586
33713
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33603,7 +33730,8 @@ class LogPersistUtils {
33603
33730
  this._entries.push({
33604
33731
  id: functoolsKit.randomString(),
33605
33732
  type: "info",
33606
- timestamp: Date.now(),
33733
+ priority: Date.now(),
33734
+ timestamp: getContextTimestamp(),
33607
33735
  createdAt: date.toISOString(),
33608
33736
  methodContext: GET_METHOD_CONTEXT_FN(),
33609
33737
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33626,7 +33754,8 @@ class LogPersistUtils {
33626
33754
  this._entries.push({
33627
33755
  id: functoolsKit.randomString(),
33628
33756
  type: "warn",
33629
- timestamp: Date.now(),
33757
+ priority: Date.now(),
33758
+ timestamp: getContextTimestamp(),
33630
33759
  createdAt: date.toISOString(),
33631
33760
  methodContext: GET_METHOD_CONTEXT_FN(),
33632
33761
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33682,7 +33811,8 @@ class LogMemoryUtils {
33682
33811
  this._entries.push({
33683
33812
  id: functoolsKit.randomString(),
33684
33813
  type: "log",
33685
- timestamp: Date.now(),
33814
+ priority: Date.now(),
33815
+ timestamp: getContextTimestamp(),
33686
33816
  createdAt: date.toISOString(),
33687
33817
  methodContext: GET_METHOD_CONTEXT_FN(),
33688
33818
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33703,7 +33833,8 @@ class LogMemoryUtils {
33703
33833
  this._entries.push({
33704
33834
  id: functoolsKit.randomString(),
33705
33835
  type: "debug",
33706
- timestamp: Date.now(),
33836
+ priority: Date.now(),
33837
+ timestamp: getContextTimestamp(),
33707
33838
  createdAt: date.toISOString(),
33708
33839
  methodContext: GET_METHOD_CONTEXT_FN(),
33709
33840
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33724,7 +33855,8 @@ class LogMemoryUtils {
33724
33855
  this._entries.push({
33725
33856
  id: functoolsKit.randomString(),
33726
33857
  type: "info",
33727
- timestamp: Date.now(),
33858
+ priority: Date.now(),
33859
+ timestamp: getContextTimestamp(),
33728
33860
  createdAt: date.toISOString(),
33729
33861
  methodContext: GET_METHOD_CONTEXT_FN(),
33730
33862
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33745,7 +33877,8 @@ class LogMemoryUtils {
33745
33877
  this._entries.push({
33746
33878
  id: functoolsKit.randomString(),
33747
33879
  type: "warn",
33748
- timestamp: Date.now(),
33880
+ priority: Date.now(),
33881
+ timestamp: getContextTimestamp(),
33749
33882
  createdAt: date.toISOString(),
33750
33883
  methodContext: GET_METHOD_CONTEXT_FN(),
33751
33884
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33859,7 +33992,8 @@ class LogJsonlUtils {
33859
33992
  await this._append({
33860
33993
  id: functoolsKit.randomString(),
33861
33994
  type: "log",
33862
- timestamp: Date.now(),
33995
+ priority: Date.now(),
33996
+ timestamp: getContextTimestamp(),
33863
33997
  createdAt: date.toISOString(),
33864
33998
  methodContext: GET_METHOD_CONTEXT_FN(),
33865
33999
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33878,7 +34012,8 @@ class LogJsonlUtils {
33878
34012
  await this._append({
33879
34013
  id: functoolsKit.randomString(),
33880
34014
  type: "debug",
33881
- timestamp: Date.now(),
34015
+ priority: Date.now(),
34016
+ timestamp: getContextTimestamp(),
33882
34017
  createdAt: date.toISOString(),
33883
34018
  methodContext: GET_METHOD_CONTEXT_FN(),
33884
34019
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33897,7 +34032,8 @@ class LogJsonlUtils {
33897
34032
  await this._append({
33898
34033
  id: functoolsKit.randomString(),
33899
34034
  type: "info",
33900
- timestamp: Date.now(),
34035
+ priority: Date.now(),
34036
+ timestamp: getContextTimestamp(),
33901
34037
  createdAt: date.toISOString(),
33902
34038
  methodContext: GET_METHOD_CONTEXT_FN(),
33903
34039
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33916,7 +34052,8 @@ class LogJsonlUtils {
33916
34052
  await this._append({
33917
34053
  id: functoolsKit.randomString(),
33918
34054
  type: "warn",
33919
- timestamp: Date.now(),
34055
+ priority: Date.now(),
34056
+ timestamp: getContextTimestamp(),
33920
34057
  createdAt: date.toISOString(),
33921
34058
  methodContext: GET_METHOD_CONTEXT_FN(),
33922
34059
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -38343,6 +38480,8 @@ const CACHE_METHOD_NAME_CLEAR = "CacheInstance.clear";
38343
38480
  const CACHE_METHOD_NAME_RUN = "CacheInstance.run";
38344
38481
  const CACHE_METHOD_NAME_GC = "CacheInstance.gc";
38345
38482
  const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
38483
+ const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
38484
+ const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
38346
38485
  const MS_PER_MINUTE$1 = 60000;
38347
38486
  const INTERVAL_MINUTES$1 = {
38348
38487
  "1m": 1,
@@ -38564,6 +38703,95 @@ class CacheInstance {
38564
38703
  };
38565
38704
  }
38566
38705
  }
38706
+ /**
38707
+ * Instance class for caching async function results in persistent file storage.
38708
+ *
38709
+ * Provides automatic cache invalidation based on candle intervals.
38710
+ * Cache key = `${alignedTimestamp}_${symbol}` (bucket) + dynamic key (file within bucket).
38711
+ * On cache hit reads from disk, on miss calls the function and writes the result.
38712
+ *
38713
+ * @template T - Async function type to cache
38714
+ * @template K - Dynamic key type
38715
+ *
38716
+ * @example
38717
+ * ```typescript
38718
+ * const instance = new CacheFileInstance(fetchFromApi, "1h");
38719
+ * const result = await instance.run("BTCUSDT", extraArg);
38720
+ * ```
38721
+ */
38722
+ class CacheFileInstance {
38723
+ /**
38724
+ * Allocates a new unique index. Called once in the constructor to give each
38725
+ * CacheFileInstance its own namespace in the persistent key space.
38726
+ */
38727
+ static createIndex() {
38728
+ return CacheFileInstance._indexCounter++;
38729
+ }
38730
+ /**
38731
+ * Creates a new CacheFileInstance.
38732
+ *
38733
+ * @param fn - Async function to cache
38734
+ * @param interval - Candle interval for cache invalidation
38735
+ * @param name - Human-readable bucket name used as the directory key (replaces symbol in bucket path)
38736
+ * @param key - Dynamic key generator; receives all args, must return a string.
38737
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
38738
+ */
38739
+ constructor(fn, interval, name, key = ([symbol, alignMs]) => `${symbol}_${alignMs}`) {
38740
+ this.fn = fn;
38741
+ this.interval = interval;
38742
+ this.name = name;
38743
+ this.key = key;
38744
+ /**
38745
+ * Execute async function with persistent file caching.
38746
+ *
38747
+ * Algorithm:
38748
+ * 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name
38749
+ * 2. Align execution context `when` to interval boundary → `alignedTs`
38750
+ * 3. Build entity key from the key generator (receives `[symbol, alignedTs, ...rest]`)
38751
+ * 4. Try to read from PersistMeasureAdapter using (bucket, entityKey)
38752
+ * 5. On hit — return cached value
38753
+ * 6. On miss — call fn, write result to disk, return result
38754
+ *
38755
+ * Cache invalidation happens through the entity key: the default key embeds `alignedTs`,
38756
+ * so each new candle interval produces a new file name while the bucket directory stays the same.
38757
+ *
38758
+ * Requires active execution context (symbol, when) and method context.
38759
+ *
38760
+ * @param args - Arguments forwarded to the wrapped function
38761
+ * @returns Cached or freshly computed result
38762
+ */
38763
+ this.run = async (...args) => {
38764
+ bt.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
38765
+ const step = INTERVAL_MINUTES$1[this.interval];
38766
+ {
38767
+ if (!MethodContextService.hasContext()) {
38768
+ throw new Error("CacheFileInstance run requires method context");
38769
+ }
38770
+ if (!ExecutionContextService.hasContext()) {
38771
+ throw new Error("CacheFileInstance run requires execution context");
38772
+ }
38773
+ if (!step) {
38774
+ throw new Error(`CacheFileInstance unknown cache ttl interval=${this.interval}`);
38775
+ }
38776
+ }
38777
+ const [symbol, ...rest] = args;
38778
+ const { when } = bt.executionContextService.context;
38779
+ const alignedTs = align(when.getTime(), this.interval);
38780
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
38781
+ const entityKey = this.key([symbol, alignedTs, ...rest]);
38782
+ const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
38783
+ if (cached !== null) {
38784
+ return cached;
38785
+ }
38786
+ const result = await this.fn.call(null, ...args);
38787
+ await PersistMeasureAdapter.writeMeasureData(result, bucket, entityKey);
38788
+ return result;
38789
+ };
38790
+ this.index = CacheFileInstance.createIndex();
38791
+ }
38792
+ }
38793
+ /** Global counter — incremented once per CacheFileInstance construction */
38794
+ CacheFileInstance._indexCounter = 0;
38567
38795
  /**
38568
38796
  * Utility class for function caching with timeframe-based invalidation.
38569
38797
  *
@@ -38585,7 +38813,12 @@ class CacheUtils {
38585
38813
  * Memoized function to get or create CacheInstance for a function.
38586
38814
  * Each function gets its own isolated cache instance.
38587
38815
  */
38588
- this._getInstance = functoolsKit.memoize(([run]) => run, (run, interval, key) => new CacheInstance(run, interval, key));
38816
+ this._getFnInstance = functoolsKit.memoize(([run]) => run, (run, interval, key) => new CacheInstance(run, interval, key));
38817
+ /**
38818
+ * Memoized function to get or create CacheFileInstance for an async function.
38819
+ * Each function gets its own isolated file-cache instance.
38820
+ */
38821
+ this._getFileInstance = functoolsKit.memoize(([run]) => run, (run, interval, name, key) => new CacheFileInstance(run, interval, name, key));
38589
38822
  /**
38590
38823
  * Wrap a function with caching based on timeframe intervals.
38591
38824
  *
@@ -38624,11 +38857,57 @@ class CacheUtils {
38624
38857
  context,
38625
38858
  });
38626
38859
  const wrappedFn = (...args) => {
38627
- const instance = this._getInstance(run, context.interval, context.key);
38860
+ const instance = this._getFnInstance(run, context.interval, context.key);
38628
38861
  return instance.run(...args).value;
38629
38862
  };
38630
38863
  return wrappedFn;
38631
38864
  };
38865
+ /**
38866
+ * Wrap an async function with persistent file-based caching.
38867
+ *
38868
+ * Returns a wrapped version of the function that reads from disk on cache hit
38869
+ * and writes the result to disk on cache miss. Files are stored under
38870
+ * `./dump/data/measure/{name}_{interval}_{index}/`.
38871
+ *
38872
+ * The `run` function reference is used as the memoization key for the underlying
38873
+ * `CacheFileInstance`, so each unique function reference gets its own isolated instance.
38874
+ * Pass the same function reference each time to reuse the same cache.
38875
+ *
38876
+ * @template T - Async function type to cache
38877
+ * @param run - Async function to wrap with file caching
38878
+ * @param context.interval - Candle interval for cache invalidation
38879
+ * @param context.name - Human-readable bucket name; becomes the directory prefix
38880
+ * @param context.key - Optional entity key generator. Receives `[symbol, alignMs, ...rest]`
38881
+ * where `alignMs` is the timestamp aligned to `interval`.
38882
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
38883
+ * @returns Wrapped async function with automatic persistent caching
38884
+ *
38885
+ * @example
38886
+ * ```typescript
38887
+ * const fetchData = async (symbol: string, period: number) => {
38888
+ * return await externalApi.fetch(symbol, period);
38889
+ * };
38890
+ *
38891
+ * // Default key — one cache file per symbol per aligned candle
38892
+ * const cachedFetch = Cache.file(fetchData, { interval: "1h", name: "fetchData" });
38893
+ *
38894
+ * // Custom key — one cache file per symbol + period combination
38895
+ * const cachedFetch = Cache.file(fetchData, {
38896
+ * interval: "1h",
38897
+ * name: "fetchData",
38898
+ * key: ([symbol, alignMs, period]) => `${symbol}_${alignMs}_${period}`,
38899
+ * });
38900
+ * const result = await cachedFetch("BTCUSDT", 14);
38901
+ * ```
38902
+ */
38903
+ this.file = (run, context) => {
38904
+ bt.loggerService.info(CACHE_METHOD_NAME_FILE, { context });
38905
+ const wrappedFn = (...args) => {
38906
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
38907
+ return instance.run(...args);
38908
+ };
38909
+ return wrappedFn;
38910
+ };
38632
38911
  /**
38633
38912
  * Flush (remove) cached CacheInstance for a specific function or all functions.
38634
38913
  *
@@ -38662,7 +38941,7 @@ class CacheUtils {
38662
38941
  bt.loggerService.info(CACHE_METHOD_NAME_FLUSH, {
38663
38942
  run,
38664
38943
  });
38665
- this._getInstance.clear(run);
38944
+ this._getFnInstance.clear(run);
38666
38945
  };
38667
38946
  /**
38668
38947
  * Clear cached value for current execution context of a specific function.
@@ -38705,7 +38984,7 @@ class CacheUtils {
38705
38984
  console.warn(`${CACHE_METHOD_NAME_CLEAR} called without execution context, skipping clear`);
38706
38985
  return;
38707
38986
  }
38708
- this._getInstance.get(run).clear();
38987
+ this._getFnInstance.get(run).clear();
38709
38988
  };
38710
38989
  /**
38711
38990
  * Garbage collect expired cache entries for a specific function.
@@ -38737,7 +39016,7 @@ class CacheUtils {
38737
39016
  console.warn(`${CACHE_METHOD_NAME_GC} called without execution context, skipping garbage collection`);
38738
39017
  return;
38739
39018
  }
38740
- return this._getInstance.get(run).gc();
39019
+ return this._getFnInstance.get(run).gc();
38741
39020
  };
38742
39021
  }
38743
39022
  }
@@ -39330,6 +39609,7 @@ exports.PersistBase = PersistBase;
39330
39609
  exports.PersistBreakevenAdapter = PersistBreakevenAdapter;
39331
39610
  exports.PersistCandleAdapter = PersistCandleAdapter;
39332
39611
  exports.PersistLogAdapter = PersistLogAdapter;
39612
+ exports.PersistMeasureAdapter = PersistMeasureAdapter;
39333
39613
  exports.PersistNotificationAdapter = PersistNotificationAdapter;
39334
39614
  exports.PersistPartialAdapter = PersistPartialAdapter;
39335
39615
  exports.PersistRiskAdapter = PersistRiskAdapter;
@@ -39390,6 +39670,7 @@ exports.getRiskSchema = getRiskSchema;
39390
39670
  exports.getSizingSchema = getSizingSchema;
39391
39671
  exports.getStrategySchema = getStrategySchema;
39392
39672
  exports.getSymbol = getSymbol;
39673
+ exports.getTimestamp = getTimestamp;
39393
39674
  exports.getWalkerSchema = getWalkerSchema;
39394
39675
  exports.hasTradeContext = hasTradeContext;
39395
39676
  exports.lib = backtest;