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.mjs CHANGED
@@ -781,6 +781,11 @@ const PERSIST_LOG_UTILS_METHOD_NAME_WRITE_DATA = "PersistLogUtils.writeLogData";
781
781
  const PERSIST_LOG_UTILS_METHOD_NAME_USE_JSON = "PersistLogUtils.useJson";
782
782
  const PERSIST_LOG_UTILS_METHOD_NAME_USE_DUMMY = "PersistLogUtils.useDummy";
783
783
  const PERSIST_LOG_UTILS_METHOD_NAME_USE_PERSIST_LOG_ADAPTER = "PersistLogUtils.usePersistLogAdapter";
784
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA = "PersistMeasureUtils.readMeasureData";
785
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA = "PersistMeasureUtils.writeMeasureData";
786
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON = "PersistMeasureUtils.useJson";
787
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY = "PersistMeasureUtils.useDummy";
788
+ const PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER = "PersistMeasureUtils.usePersistMeasureAdapter";
784
789
  const BASE_WAIT_FOR_INIT_FN_METHOD_NAME = "PersistBase.waitForInitFn";
785
790
  const BASE_UNLINK_RETRY_COUNT = 5;
786
791
  const BASE_UNLINK_RETRY_DELAY = 1000;
@@ -2051,6 +2056,92 @@ class PersistLogUtils {
2051
2056
  * Used by LogPersistUtils for log entry persistence.
2052
2057
  */
2053
2058
  const PersistLogAdapter = new PersistLogUtils();
2059
+ /**
2060
+ * Utility class for managing external API response cache persistence.
2061
+ *
2062
+ * Features:
2063
+ * - Memoized storage instances per cache bucket (aligned timestamp + symbol)
2064
+ * - Custom adapter support
2065
+ * - Atomic read/write operations
2066
+ * - Crash-safe cache state management
2067
+ *
2068
+ * Used by Cache.file for persistent caching of external API responses.
2069
+ */
2070
+ class PersistMeasureUtils {
2071
+ constructor() {
2072
+ this.PersistMeasureFactory = PersistBase;
2073
+ this.getMeasureStorage = memoize(([bucket]) => bucket, (bucket) => Reflect.construct(this.PersistMeasureFactory, [
2074
+ bucket,
2075
+ `./dump/data/measure/`,
2076
+ ]));
2077
+ /**
2078
+ * Reads cached measure data for a given bucket and key.
2079
+ *
2080
+ * @param bucket - Storage bucket (e.g. aligned timestamp + symbol)
2081
+ * @param key - Dynamic cache key within the bucket
2082
+ * @returns Promise resolving to cached value or null if not found
2083
+ */
2084
+ this.readMeasureData = async (bucket, key) => {
2085
+ bt.loggerService.info(PERSIST_MEASURE_UTILS_METHOD_NAME_READ_DATA, {
2086
+ bucket,
2087
+ key,
2088
+ });
2089
+ const isInitial = !this.getMeasureStorage.has(bucket);
2090
+ const stateStorage = this.getMeasureStorage(bucket);
2091
+ await stateStorage.waitForInit(isInitial);
2092
+ if (await stateStorage.hasValue(key)) {
2093
+ return await stateStorage.readValue(key);
2094
+ }
2095
+ return null;
2096
+ };
2097
+ /**
2098
+ * Writes measure data to disk with atomic file writes.
2099
+ *
2100
+ * @param data - Data to cache
2101
+ * @param bucket - Storage bucket (e.g. aligned timestamp + symbol)
2102
+ * @param key - Dynamic cache key within the bucket
2103
+ * @returns Promise that resolves when write is complete
2104
+ */
2105
+ this.writeMeasureData = async (data, bucket, key) => {
2106
+ bt.loggerService.info(PERSIST_MEASURE_UTILS_METHOD_NAME_WRITE_DATA, {
2107
+ bucket,
2108
+ key,
2109
+ });
2110
+ const isInitial = !this.getMeasureStorage.has(bucket);
2111
+ const stateStorage = this.getMeasureStorage(bucket);
2112
+ await stateStorage.waitForInit(isInitial);
2113
+ await stateStorage.writeValue(key, data);
2114
+ };
2115
+ }
2116
+ /**
2117
+ * Registers a custom persistence adapter.
2118
+ *
2119
+ * @param Ctor - Custom PersistBase constructor
2120
+ */
2121
+ usePersistMeasureAdapter(Ctor) {
2122
+ bt.loggerService.info(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_PERSIST_MEASURE_ADAPTER);
2123
+ this.PersistMeasureFactory = Ctor;
2124
+ }
2125
+ /**
2126
+ * Switches to the default JSON persist adapter.
2127
+ */
2128
+ useJson() {
2129
+ bt.loggerService.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_JSON);
2130
+ this.usePersistMeasureAdapter(PersistBase);
2131
+ }
2132
+ /**
2133
+ * Switches to a dummy persist adapter that discards all writes.
2134
+ */
2135
+ useDummy() {
2136
+ bt.loggerService.log(PERSIST_MEASURE_UTILS_METHOD_NAME_USE_DUMMY);
2137
+ this.usePersistMeasureAdapter(PersistDummy);
2138
+ }
2139
+ }
2140
+ /**
2141
+ * Global singleton instance of PersistMeasureUtils.
2142
+ * Used by Cache.file for persistent caching of external API responses.
2143
+ */
2144
+ const PersistMeasureAdapter = new PersistMeasureUtils();
2054
2145
 
2055
2146
  var _a$3, _b$3;
2056
2147
  const BUSY_DELAY = 100;
@@ -10351,7 +10442,7 @@ class ActionBase {
10351
10442
  * @example
10352
10443
  * ```typescript
10353
10444
  * pingScheduled(event: SchedulePingContract) {
10354
- * const waitTime = Date.now() - event.data.timestampScheduled;
10445
+ * const waitTime = getTimestamp() - event.data.timestampScheduled;
10355
10446
  * const waitMinutes = Math.floor(waitTime / 60000);
10356
10447
  * console.log(`Scheduled signal waiting ${waitMinutes} minutes`);
10357
10448
  * }
@@ -10380,7 +10471,7 @@ class ActionBase {
10380
10471
  * @example
10381
10472
  * ```typescript
10382
10473
  * pingActive(event: ActivePingContract) {
10383
- * const holdTime = Date.now() - event.data.pendingAt;
10474
+ * const holdTime = getTimestamp() - event.data.pendingAt;
10384
10475
  * const holdMinutes = Math.floor(holdTime / 60000);
10385
10476
  * console.log(`Active signal holding ${holdMinutes} minutes`);
10386
10477
  * }
@@ -16082,6 +16173,20 @@ const COLUMN_CONFIG = {
16082
16173
  */
16083
16174
  const DEFAULT_COLUMNS = Object.freeze({ ...COLUMN_CONFIG });
16084
16175
 
16176
+ /**
16177
+ * Retrieves the current timestamp based on the execution context.
16178
+ * 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.
16179
+ * If no execution context is active (e.g., during live operation), it returns the current real-world timestamp.
16180
+ * This function helps maintain accurate timing for logs, metrics, and other time-sensitive operations across both live and backtest modes.
16181
+ * @return {number} The current timestamp in milliseconds, either from the execution context or the real-world clock.
16182
+ */
16183
+ const getContextTimestamp = () => {
16184
+ if (ExecutionContextService.hasContext()) {
16185
+ return bt.executionContextService.context.when.getTime();
16186
+ }
16187
+ return Date.now();
16188
+ };
16189
+
16085
16190
  var _a$2, _b$2;
16086
16191
  const MARKDOWN_METHOD_NAME_ENABLE = "MarkdownUtils.enable";
16087
16192
  const MARKDOWN_METHOD_NAME_DISABLE = "MarkdownUtils.disable";
@@ -16215,7 +16320,7 @@ class MarkdownFileBase {
16215
16320
  markdownName: this.markdownName,
16216
16321
  data,
16217
16322
  ...searchFlags,
16218
- timestamp: Date.now(),
16323
+ timestamp: getContextTimestamp(),
16219
16324
  }) + "\n";
16220
16325
  const status = await this[WRITE_SAFE_SYMBOL$2](line);
16221
16326
  if (status === TIMEOUT_SYMBOL$1) {
@@ -16759,7 +16864,7 @@ let ReportStorage$7 = class ReportStorage {
16759
16864
  */
16760
16865
  async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
16761
16866
  const markdown = await this.getReport(strategyName, columns);
16762
- const timestamp = Date.now();
16867
+ const timestamp = getContextTimestamp();
16763
16868
  const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
16764
16869
  await Markdown.writeData("backtest", markdown, {
16765
16870
  path,
@@ -17094,7 +17199,7 @@ let ReportStorage$6 = class ReportStorage {
17094
17199
  */
17095
17200
  addIdleEvent(currentPrice) {
17096
17201
  const newEvent = {
17097
- timestamp: Date.now(),
17202
+ timestamp: getContextTimestamp(),
17098
17203
  action: "idle",
17099
17204
  currentPrice,
17100
17205
  };
@@ -17150,7 +17255,7 @@ let ReportStorage$6 = class ReportStorage {
17150
17255
  */
17151
17256
  addActiveEvent(data) {
17152
17257
  const newEvent = {
17153
- timestamp: Date.now(),
17258
+ timestamp: getContextTimestamp(),
17154
17259
  action: "active",
17155
17260
  symbol: data.signal.symbol,
17156
17261
  signalId: data.signal.id,
@@ -17252,7 +17357,7 @@ let ReportStorage$6 = class ReportStorage {
17252
17357
  */
17253
17358
  addWaitingEvent(data) {
17254
17359
  const newEvent = {
17255
- timestamp: Date.now(),
17360
+ timestamp: getContextTimestamp(),
17256
17361
  action: "waiting",
17257
17362
  symbol: data.signal.symbol,
17258
17363
  signalId: data.signal.id,
@@ -17446,7 +17551,7 @@ let ReportStorage$6 = class ReportStorage {
17446
17551
  */
17447
17552
  async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
17448
17553
  const markdown = await this.getReport(strategyName, columns);
17449
- const timestamp = Date.now();
17554
+ const timestamp = getContextTimestamp();
17450
17555
  const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
17451
17556
  await Markdown.writeData("live", markdown, {
17452
17557
  path,
@@ -17978,7 +18083,7 @@ let ReportStorage$5 = class ReportStorage {
17978
18083
  */
17979
18084
  async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
17980
18085
  const markdown = await this.getReport(strategyName, columns);
17981
- const timestamp = Date.now();
18086
+ const timestamp = getContextTimestamp();
17982
18087
  const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
17983
18088
  await Markdown.writeData("schedule", markdown, {
17984
18089
  path,
@@ -18443,7 +18548,7 @@ class PerformanceStorage {
18443
18548
  */
18444
18549
  async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
18445
18550
  const markdown = await this.getReport(strategyName, columns);
18446
- const timestamp = Date.now();
18551
+ const timestamp = getContextTimestamp();
18447
18552
  const filename = CREATE_FILE_NAME_FN$6(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
18448
18553
  await Markdown.writeData("performance", markdown, {
18449
18554
  path,
@@ -18894,7 +18999,7 @@ let ReportStorage$4 = class ReportStorage {
18894
18999
  */
18895
19000
  async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
18896
19001
  const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
18897
- const timestamp = Date.now();
19002
+ const timestamp = getContextTimestamp();
18898
19003
  const filename = CREATE_FILE_NAME_FN$5(this.walkerName, timestamp);
18899
19004
  await Markdown.writeData("walker", markdown, {
18900
19005
  path,
@@ -19448,7 +19553,7 @@ class HeatmapStorage {
19448
19553
  */
19449
19554
  async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
19450
19555
  const markdown = await this.getReport(strategyName, columns);
19451
- const timestamp = Date.now();
19556
+ const timestamp = getContextTimestamp();
19452
19557
  const filename = CREATE_FILE_NAME_FN$4(strategyName, this.exchangeName, this.frameName, timestamp);
19453
19558
  await Markdown.writeData("heat", markdown, {
19454
19559
  path,
@@ -21128,7 +21233,7 @@ let ReportStorage$3 = class ReportStorage {
21128
21233
  */
21129
21234
  async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
21130
21235
  const markdown = await this.getReport(symbol, strategyName, columns);
21131
- const timestamp = Date.now();
21236
+ const timestamp = getContextTimestamp();
21132
21237
  const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
21133
21238
  await Markdown.writeData("partial", markdown, {
21134
21239
  path,
@@ -22221,7 +22326,7 @@ let ReportStorage$2 = class ReportStorage {
22221
22326
  */
22222
22327
  async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
22223
22328
  const markdown = await this.getReport(symbol, strategyName, columns);
22224
- const timestamp = Date.now();
22329
+ const timestamp = getContextTimestamp();
22225
22330
  const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
22226
22331
  await Markdown.writeData("breakeven", markdown, {
22227
22332
  path,
@@ -22889,7 +22994,7 @@ let ReportStorage$1 = class ReportStorage {
22889
22994
  */
22890
22995
  async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
22891
22996
  const markdown = await this.getReport(symbol, strategyName, columns);
22892
- const timestamp = Date.now();
22997
+ const timestamp = getContextTimestamp();
22893
22998
  const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
22894
22999
  await Markdown.writeData("risk", markdown, {
22895
23000
  path,
@@ -23373,7 +23478,7 @@ class ReportBase {
23373
23478
  reportName: this.reportName,
23374
23479
  data,
23375
23480
  ...searchFlags,
23376
- timestamp: Date.now(),
23481
+ timestamp: getContextTimestamp(),
23377
23482
  }) + "\n";
23378
23483
  const status = await this[WRITE_SAFE_SYMBOL$1](line);
23379
23484
  if (status === TIMEOUT_SYMBOL$1) {
@@ -23712,7 +23817,7 @@ class BacktestReportService {
23712
23817
  this.tick = async (data) => {
23713
23818
  this.loggerService.log(BACKTEST_REPORT_METHOD_NAME_TICK, { data });
23714
23819
  const baseEvent = {
23715
- timestamp: Date.now(),
23820
+ timestamp: getContextTimestamp(),
23716
23821
  action: data.action,
23717
23822
  symbol: data.symbol,
23718
23823
  strategyName: data.strategyName,
@@ -23891,7 +23996,7 @@ class LiveReportService {
23891
23996
  this.tick = async (data) => {
23892
23997
  this.loggerService.log(LIVE_REPORT_METHOD_NAME_TICK, { data });
23893
23998
  const baseEvent = {
23894
- timestamp: Date.now(),
23999
+ timestamp: getContextTimestamp(),
23895
24000
  action: data.action,
23896
24001
  symbol: data.symbol,
23897
24002
  strategyName: data.strategyName,
@@ -24414,7 +24519,7 @@ class WalkerReportService {
24414
24519
  this.tick = async (data) => {
24415
24520
  this.loggerService.log(WALKER_REPORT_METHOD_NAME_TICK, { data });
24416
24521
  await Report.writeData("walker", {
24417
- timestamp: Date.now(),
24522
+ timestamp: getContextTimestamp(),
24418
24523
  walkerName: data.walkerName,
24419
24524
  symbol: data.symbol,
24420
24525
  exchangeName: data.exchangeName,
@@ -24541,7 +24646,7 @@ class HeatReportService {
24541
24646
  return;
24542
24647
  }
24543
24648
  await Report.writeData("heat", {
24544
- timestamp: Date.now(),
24649
+ timestamp: getContextTimestamp(),
24545
24650
  action: data.action,
24546
24651
  symbol: data.symbol,
24547
24652
  strategyName: data.strategyName,
@@ -25863,7 +25968,7 @@ class ReportStorage {
25863
25968
  */
25864
25969
  async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
25865
25970
  const markdown = await this.getReport(symbol, strategyName, columns);
25866
- const timestamp = Date.now();
25971
+ const timestamp = getContextTimestamp();
25867
25972
  const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
25868
25973
  await Markdown.writeData("strategy", markdown, {
25869
25974
  path,
@@ -28179,6 +28284,7 @@ const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
28179
28284
  const FORMAT_PRICE_METHOD_NAME = "exchange.formatPrice";
28180
28285
  const FORMAT_QUANTITY_METHOD_NAME = "exchange.formatQuantity";
28181
28286
  const GET_DATE_METHOD_NAME = "exchange.getDate";
28287
+ const GET_TIMESTAMP_METHOD_NAME = "exchange.getTimestamp";
28182
28288
  const GET_MODE_METHOD_NAME = "exchange.getMode";
28183
28289
  const GET_SYMBOL_METHOD_NAME = "exchange.getSymbol";
28184
28290
  const GET_CONTEXT_METHOD_NAME = "exchange.getContext";
@@ -28344,6 +28450,26 @@ async function getDate() {
28344
28450
  const { when } = bt.executionContextService.context;
28345
28451
  return new Date(when.getTime());
28346
28452
  }
28453
+ /**
28454
+ * Gets the current timestamp from execution context.
28455
+ *
28456
+ * In backtest mode: returns the current timeframe timestamp being processed
28457
+ * In live mode: returns current real-time timestamp
28458
+ *
28459
+ * @returns Promise resolving to current execution context timestamp in milliseconds
28460
+ * @example
28461
+ * ```typescript
28462
+ * const timestamp = await getTimestamp();
28463
+ * console.log(timestamp); // 1700000000000
28464
+ * ```
28465
+ */
28466
+ async function getTimestamp() {
28467
+ bt.loggerService.info(GET_TIMESTAMP_METHOD_NAME);
28468
+ if (!ExecutionContextService.hasContext()) {
28469
+ throw new Error("getTimestamp requires an execution context");
28470
+ }
28471
+ return getContextTimestamp();
28472
+ }
28347
28473
  /**
28348
28474
  * Gets the current execution mode.
28349
28475
  *
@@ -32541,7 +32667,6 @@ const ADD_ACTION_METHOD_NAME = "add.addActionSchema";
32541
32667
  * priceTakeProfit: 51000,
32542
32668
  * priceStopLoss: 49000,
32543
32669
  * minuteEstimatedTime: 60,
32544
- * timestamp: Date.now(),
32545
32670
  * }),
32546
32671
  * callbacks: {
32547
32672
  * onOpen: (symbol, signal, currentPrice, backtest) => console.log("Signal opened"),
@@ -33468,8 +33593,8 @@ const WAIT_FOR_INIT_SYMBOL = Symbol("wait-for-init");
33468
33593
  const WRITE_SAFE_SYMBOL = Symbol("write-safe");
33469
33594
  /**
33470
33595
  * Backtest execution time retrieval function.
33471
- * Returns the 'when' timestamp from the execution context if available, otherwise returns the current time.
33472
- * 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.
33596
+ * Returns the 'when' priority from the execution context if available, otherwise returns the current time.
33597
+ * 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.
33473
33598
  */
33474
33599
  const GET_DATE_FN = () => {
33475
33600
  if (ExecutionContextService.hasContext()) {
@@ -33491,7 +33616,7 @@ const GET_METHOD_CONTEXT_FN = () => {
33491
33616
  /**
33492
33617
  * Execution context retrieval function.
33493
33618
  * Returns the current execution context from ExecutionContextService if available, otherwise returns null.
33494
- * 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.
33619
+ * 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.
33495
33620
  */
33496
33621
  const GET_EXECUTION_CONTEXT_FN = () => {
33497
33622
  if (ExecutionContextService.hasContext()) {
@@ -33521,7 +33646,7 @@ class LogPersistUtils {
33521
33646
  this.waitForInit = singleshot(async () => {
33522
33647
  bt.loggerService.info(LOG_PERSIST_METHOD_NAME_WAIT_FOR_INIT);
33523
33648
  const list = await PersistLogAdapter.readLogData();
33524
- list.sort((a, b) => a.timestamp - b.timestamp);
33649
+ list.sort((a, b) => a.priority - b.priority);
33525
33650
  this._entries = list.slice(-GLOBAL_CONFIG.CC_MAX_LOG_LINES);
33526
33651
  });
33527
33652
  /**
@@ -33537,7 +33662,8 @@ class LogPersistUtils {
33537
33662
  this._entries.push({
33538
33663
  id: randomString(),
33539
33664
  type: "log",
33540
- timestamp: Date.now(),
33665
+ priority: Date.now(),
33666
+ timestamp: getContextTimestamp(),
33541
33667
  createdAt: date.toISOString(),
33542
33668
  methodContext: GET_METHOD_CONTEXT_FN(),
33543
33669
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33560,7 +33686,8 @@ class LogPersistUtils {
33560
33686
  this._entries.push({
33561
33687
  id: randomString(),
33562
33688
  type: "debug",
33563
- timestamp: Date.now(),
33689
+ priority: Date.now(),
33690
+ timestamp: getContextTimestamp(),
33564
33691
  createdAt: date.toISOString(),
33565
33692
  methodContext: GET_METHOD_CONTEXT_FN(),
33566
33693
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33583,7 +33710,8 @@ class LogPersistUtils {
33583
33710
  this._entries.push({
33584
33711
  id: randomString(),
33585
33712
  type: "info",
33586
- timestamp: Date.now(),
33713
+ priority: Date.now(),
33714
+ timestamp: getContextTimestamp(),
33587
33715
  createdAt: date.toISOString(),
33588
33716
  methodContext: GET_METHOD_CONTEXT_FN(),
33589
33717
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33606,7 +33734,8 @@ class LogPersistUtils {
33606
33734
  this._entries.push({
33607
33735
  id: randomString(),
33608
33736
  type: "warn",
33609
- timestamp: Date.now(),
33737
+ priority: Date.now(),
33738
+ timestamp: getContextTimestamp(),
33610
33739
  createdAt: date.toISOString(),
33611
33740
  methodContext: GET_METHOD_CONTEXT_FN(),
33612
33741
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33662,7 +33791,8 @@ class LogMemoryUtils {
33662
33791
  this._entries.push({
33663
33792
  id: randomString(),
33664
33793
  type: "log",
33665
- timestamp: Date.now(),
33794
+ priority: Date.now(),
33795
+ timestamp: getContextTimestamp(),
33666
33796
  createdAt: date.toISOString(),
33667
33797
  methodContext: GET_METHOD_CONTEXT_FN(),
33668
33798
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33683,7 +33813,8 @@ class LogMemoryUtils {
33683
33813
  this._entries.push({
33684
33814
  id: randomString(),
33685
33815
  type: "debug",
33686
- timestamp: Date.now(),
33816
+ priority: Date.now(),
33817
+ timestamp: getContextTimestamp(),
33687
33818
  createdAt: date.toISOString(),
33688
33819
  methodContext: GET_METHOD_CONTEXT_FN(),
33689
33820
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33704,7 +33835,8 @@ class LogMemoryUtils {
33704
33835
  this._entries.push({
33705
33836
  id: randomString(),
33706
33837
  type: "info",
33707
- timestamp: Date.now(),
33838
+ priority: Date.now(),
33839
+ timestamp: getContextTimestamp(),
33708
33840
  createdAt: date.toISOString(),
33709
33841
  methodContext: GET_METHOD_CONTEXT_FN(),
33710
33842
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33725,7 +33857,8 @@ class LogMemoryUtils {
33725
33857
  this._entries.push({
33726
33858
  id: randomString(),
33727
33859
  type: "warn",
33728
- timestamp: Date.now(),
33860
+ priority: Date.now(),
33861
+ timestamp: getContextTimestamp(),
33729
33862
  createdAt: date.toISOString(),
33730
33863
  methodContext: GET_METHOD_CONTEXT_FN(),
33731
33864
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33839,7 +33972,8 @@ class LogJsonlUtils {
33839
33972
  await this._append({
33840
33973
  id: randomString(),
33841
33974
  type: "log",
33842
- timestamp: Date.now(),
33975
+ priority: Date.now(),
33976
+ timestamp: getContextTimestamp(),
33843
33977
  createdAt: date.toISOString(),
33844
33978
  methodContext: GET_METHOD_CONTEXT_FN(),
33845
33979
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33858,7 +33992,8 @@ class LogJsonlUtils {
33858
33992
  await this._append({
33859
33993
  id: randomString(),
33860
33994
  type: "debug",
33861
- timestamp: Date.now(),
33995
+ priority: Date.now(),
33996
+ timestamp: getContextTimestamp(),
33862
33997
  createdAt: date.toISOString(),
33863
33998
  methodContext: GET_METHOD_CONTEXT_FN(),
33864
33999
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33877,7 +34012,8 @@ class LogJsonlUtils {
33877
34012
  await this._append({
33878
34013
  id: randomString(),
33879
34014
  type: "info",
33880
- timestamp: Date.now(),
34015
+ priority: Date.now(),
34016
+ timestamp: getContextTimestamp(),
33881
34017
  createdAt: date.toISOString(),
33882
34018
  methodContext: GET_METHOD_CONTEXT_FN(),
33883
34019
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -33896,7 +34032,8 @@ class LogJsonlUtils {
33896
34032
  await this._append({
33897
34033
  id: randomString(),
33898
34034
  type: "warn",
33899
- timestamp: Date.now(),
34035
+ priority: Date.now(),
34036
+ timestamp: getContextTimestamp(),
33900
34037
  createdAt: date.toISOString(),
33901
34038
  methodContext: GET_METHOD_CONTEXT_FN(),
33902
34039
  executionContext: GET_EXECUTION_CONTEXT_FN(),
@@ -38323,6 +38460,8 @@ const CACHE_METHOD_NAME_CLEAR = "CacheInstance.clear";
38323
38460
  const CACHE_METHOD_NAME_RUN = "CacheInstance.run";
38324
38461
  const CACHE_METHOD_NAME_GC = "CacheInstance.gc";
38325
38462
  const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
38463
+ const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
38464
+ const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
38326
38465
  const MS_PER_MINUTE$1 = 60000;
38327
38466
  const INTERVAL_MINUTES$1 = {
38328
38467
  "1m": 1,
@@ -38544,6 +38683,95 @@ class CacheInstance {
38544
38683
  };
38545
38684
  }
38546
38685
  }
38686
+ /**
38687
+ * Instance class for caching async function results in persistent file storage.
38688
+ *
38689
+ * Provides automatic cache invalidation based on candle intervals.
38690
+ * Cache key = `${alignedTimestamp}_${symbol}` (bucket) + dynamic key (file within bucket).
38691
+ * On cache hit reads from disk, on miss calls the function and writes the result.
38692
+ *
38693
+ * @template T - Async function type to cache
38694
+ * @template K - Dynamic key type
38695
+ *
38696
+ * @example
38697
+ * ```typescript
38698
+ * const instance = new CacheFileInstance(fetchFromApi, "1h");
38699
+ * const result = await instance.run("BTCUSDT", extraArg);
38700
+ * ```
38701
+ */
38702
+ class CacheFileInstance {
38703
+ /**
38704
+ * Allocates a new unique index. Called once in the constructor to give each
38705
+ * CacheFileInstance its own namespace in the persistent key space.
38706
+ */
38707
+ static createIndex() {
38708
+ return CacheFileInstance._indexCounter++;
38709
+ }
38710
+ /**
38711
+ * Creates a new CacheFileInstance.
38712
+ *
38713
+ * @param fn - Async function to cache
38714
+ * @param interval - Candle interval for cache invalidation
38715
+ * @param name - Human-readable bucket name used as the directory key (replaces symbol in bucket path)
38716
+ * @param key - Dynamic key generator; receives all args, must return a string.
38717
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
38718
+ */
38719
+ constructor(fn, interval, name, key = ([symbol, alignMs]) => `${symbol}_${alignMs}`) {
38720
+ this.fn = fn;
38721
+ this.interval = interval;
38722
+ this.name = name;
38723
+ this.key = key;
38724
+ /**
38725
+ * Execute async function with persistent file caching.
38726
+ *
38727
+ * Algorithm:
38728
+ * 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name
38729
+ * 2. Align execution context `when` to interval boundary → `alignedTs`
38730
+ * 3. Build entity key from the key generator (receives `[symbol, alignedTs, ...rest]`)
38731
+ * 4. Try to read from PersistMeasureAdapter using (bucket, entityKey)
38732
+ * 5. On hit — return cached value
38733
+ * 6. On miss — call fn, write result to disk, return result
38734
+ *
38735
+ * Cache invalidation happens through the entity key: the default key embeds `alignedTs`,
38736
+ * so each new candle interval produces a new file name while the bucket directory stays the same.
38737
+ *
38738
+ * Requires active execution context (symbol, when) and method context.
38739
+ *
38740
+ * @param args - Arguments forwarded to the wrapped function
38741
+ * @returns Cached or freshly computed result
38742
+ */
38743
+ this.run = async (...args) => {
38744
+ bt.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_RUN, { args });
38745
+ const step = INTERVAL_MINUTES$1[this.interval];
38746
+ {
38747
+ if (!MethodContextService.hasContext()) {
38748
+ throw new Error("CacheFileInstance run requires method context");
38749
+ }
38750
+ if (!ExecutionContextService.hasContext()) {
38751
+ throw new Error("CacheFileInstance run requires execution context");
38752
+ }
38753
+ if (!step) {
38754
+ throw new Error(`CacheFileInstance unknown cache ttl interval=${this.interval}`);
38755
+ }
38756
+ }
38757
+ const [symbol, ...rest] = args;
38758
+ const { when } = bt.executionContextService.context;
38759
+ const alignedTs = align(when.getTime(), this.interval);
38760
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
38761
+ const entityKey = this.key([symbol, alignedTs, ...rest]);
38762
+ const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
38763
+ if (cached !== null) {
38764
+ return cached;
38765
+ }
38766
+ const result = await this.fn.call(null, ...args);
38767
+ await PersistMeasureAdapter.writeMeasureData(result, bucket, entityKey);
38768
+ return result;
38769
+ };
38770
+ this.index = CacheFileInstance.createIndex();
38771
+ }
38772
+ }
38773
+ /** Global counter — incremented once per CacheFileInstance construction */
38774
+ CacheFileInstance._indexCounter = 0;
38547
38775
  /**
38548
38776
  * Utility class for function caching with timeframe-based invalidation.
38549
38777
  *
@@ -38565,7 +38793,12 @@ class CacheUtils {
38565
38793
  * Memoized function to get or create CacheInstance for a function.
38566
38794
  * Each function gets its own isolated cache instance.
38567
38795
  */
38568
- this._getInstance = memoize(([run]) => run, (run, interval, key) => new CacheInstance(run, interval, key));
38796
+ this._getFnInstance = memoize(([run]) => run, (run, interval, key) => new CacheInstance(run, interval, key));
38797
+ /**
38798
+ * Memoized function to get or create CacheFileInstance for an async function.
38799
+ * Each function gets its own isolated file-cache instance.
38800
+ */
38801
+ this._getFileInstance = memoize(([run]) => run, (run, interval, name, key) => new CacheFileInstance(run, interval, name, key));
38569
38802
  /**
38570
38803
  * Wrap a function with caching based on timeframe intervals.
38571
38804
  *
@@ -38604,11 +38837,57 @@ class CacheUtils {
38604
38837
  context,
38605
38838
  });
38606
38839
  const wrappedFn = (...args) => {
38607
- const instance = this._getInstance(run, context.interval, context.key);
38840
+ const instance = this._getFnInstance(run, context.interval, context.key);
38608
38841
  return instance.run(...args).value;
38609
38842
  };
38610
38843
  return wrappedFn;
38611
38844
  };
38845
+ /**
38846
+ * Wrap an async function with persistent file-based caching.
38847
+ *
38848
+ * Returns a wrapped version of the function that reads from disk on cache hit
38849
+ * and writes the result to disk on cache miss. Files are stored under
38850
+ * `./dump/data/measure/{name}_{interval}_{index}/`.
38851
+ *
38852
+ * The `run` function reference is used as the memoization key for the underlying
38853
+ * `CacheFileInstance`, so each unique function reference gets its own isolated instance.
38854
+ * Pass the same function reference each time to reuse the same cache.
38855
+ *
38856
+ * @template T - Async function type to cache
38857
+ * @param run - Async function to wrap with file caching
38858
+ * @param context.interval - Candle interval for cache invalidation
38859
+ * @param context.name - Human-readable bucket name; becomes the directory prefix
38860
+ * @param context.key - Optional entity key generator. Receives `[symbol, alignMs, ...rest]`
38861
+ * where `alignMs` is the timestamp aligned to `interval`.
38862
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
38863
+ * @returns Wrapped async function with automatic persistent caching
38864
+ *
38865
+ * @example
38866
+ * ```typescript
38867
+ * const fetchData = async (symbol: string, period: number) => {
38868
+ * return await externalApi.fetch(symbol, period);
38869
+ * };
38870
+ *
38871
+ * // Default key — one cache file per symbol per aligned candle
38872
+ * const cachedFetch = Cache.file(fetchData, { interval: "1h", name: "fetchData" });
38873
+ *
38874
+ * // Custom key — one cache file per symbol + period combination
38875
+ * const cachedFetch = Cache.file(fetchData, {
38876
+ * interval: "1h",
38877
+ * name: "fetchData",
38878
+ * key: ([symbol, alignMs, period]) => `${symbol}_${alignMs}_${period}`,
38879
+ * });
38880
+ * const result = await cachedFetch("BTCUSDT", 14);
38881
+ * ```
38882
+ */
38883
+ this.file = (run, context) => {
38884
+ bt.loggerService.info(CACHE_METHOD_NAME_FILE, { context });
38885
+ const wrappedFn = (...args) => {
38886
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
38887
+ return instance.run(...args);
38888
+ };
38889
+ return wrappedFn;
38890
+ };
38612
38891
  /**
38613
38892
  * Flush (remove) cached CacheInstance for a specific function or all functions.
38614
38893
  *
@@ -38642,7 +38921,7 @@ class CacheUtils {
38642
38921
  bt.loggerService.info(CACHE_METHOD_NAME_FLUSH, {
38643
38922
  run,
38644
38923
  });
38645
- this._getInstance.clear(run);
38924
+ this._getFnInstance.clear(run);
38646
38925
  };
38647
38926
  /**
38648
38927
  * Clear cached value for current execution context of a specific function.
@@ -38685,7 +38964,7 @@ class CacheUtils {
38685
38964
  console.warn(`${CACHE_METHOD_NAME_CLEAR} called without execution context, skipping clear`);
38686
38965
  return;
38687
38966
  }
38688
- this._getInstance.get(run).clear();
38967
+ this._getFnInstance.get(run).clear();
38689
38968
  };
38690
38969
  /**
38691
38970
  * Garbage collect expired cache entries for a specific function.
@@ -38717,7 +38996,7 @@ class CacheUtils {
38717
38996
  console.warn(`${CACHE_METHOD_NAME_GC} called without execution context, skipping garbage collection`);
38718
38997
  return;
38719
38998
  }
38720
- return this._getInstance.get(run).gc();
38999
+ return this._getFnInstance.get(run).gc();
38721
39000
  };
38722
39001
  }
38723
39002
  }
@@ -39287,4 +39566,4 @@ const set = (object, path, value) => {
39287
39566
  }
39288
39567
  };
39289
39568
 
39290
- export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, shutdown, stopStrategy, validate, waitForCandle, warmCandles };
39569
+ export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, shutdown, stopStrategy, validate, waitForCandle, warmCandles };