backtest-kit 1.11.9 โ†’ 1.12.1

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/README.md CHANGED
@@ -24,7 +24,7 @@ Build reliable trading systems: backtest on historical data, deploy live bots wi
24
24
  - ๐Ÿ“Š **Reports & Metrics**: Auto Markdown reports with PNL, Sharpe Ratio, win rate, and more.
25
25
  - ๐Ÿ›ก๏ธ **Risk Management**: Custom rules for position limits, time windows, and multi-strategy coordination.
26
26
  - ๐Ÿ”Œ **Pluggable**: Custom data sources (CCXT), persistence (file/Redis), and sizing calculators.
27
- - ๐Ÿงช **Tested**: 300+ unit/integration tests for validation, recovery, and events.
27
+ - ๐Ÿงช **Tested**: 350+ unit/integration tests for validation, recovery, and events.
28
28
  - ๐Ÿ”“ **Self hosted**: Zero dependency on third-party node_modules or platforms; run entirely in your own environment.
29
29
 
30
30
  ## ๐Ÿ“‹ Supported Order Types
@@ -38,15 +38,27 @@ Build reliable trading systems: backtest on historical data, deploy live bots wi
38
38
 
39
39
  ## ๐Ÿš€ Quick Start
40
40
 
41
- > **Talk is cheap.** Let me show you **the code**
42
- >
43
- > Link to ๐Ÿ‘‰ [the demo app](https://github.com/tripolskypetr/backtest-kit/tree/master/demo) ๐Ÿ‘ˆ
41
+ ### ๐ŸŽฏ The Fastest Way: Sidekick CLI
42
+
43
+ > Create a production-ready trading bot in seconds:
44
+
45
+ ```bash
46
+ # Create project with npx (recommended)
47
+ npx -y @backtest-kit/sidekick my-trading-bot
48
+ cd my-trading-bot
49
+ npm start
50
+ ```
51
+
52
+ ### ๐Ÿ“ฆ Manual Installation
53
+
54
+ > **Want to see the code?** ๐Ÿ‘‰ [Demo app](https://github.com/tripolskypetr/backtest-kit/tree/master/demo) ๐Ÿ‘ˆ
44
55
 
45
- ### ๐Ÿ“ฆ Installation
46
56
  ```bash
47
57
  npm install backtest-kit ccxt ollama uuid
48
58
  ```
49
59
 
60
+ ## ๐Ÿ“š Code Samples
61
+
50
62
  ### โš™๏ธ Basic Configuration
51
63
  ```typescript
52
64
  import { setLogger, setConfig } from 'backtest-kit';
@@ -240,6 +252,52 @@ Unlike cloud-based platforms, backtest-kit runs entirely in your environment. Yo
240
252
  - Full control over execution and data sources
241
253
  - [GUI](https://backtest-kit.github.io/documents/design_30_markdown-report-system.html#method-signatures) for visualization and monitoring
242
254
 
255
+ ## ๐ŸŒ Ecosystem
256
+
257
+ The `backtest-kit` ecosystem extends beyond the core library, offering complementary packages and tools to enhance your trading system development experience:
258
+
259
+ ### @backtest-kit/signals
260
+
261
+ > **[Explore on NPM](https://www.npmjs.com/package/@backtest-kit/signals)** ๐Ÿ“Š
262
+
263
+ The **@backtest-kit/signals** package is a technical analysis and trading signal generation library designed for AI-powered trading systems. It computes 50+ indicators across 4 timeframes and generates markdown reports optimized for LLM consumption.
264
+
265
+ #### Key Features
266
+ - ๐Ÿ“ˆ **Multi-Timeframe Analysis**: 1m, 15m, 30m, 1h with synchronized indicator computation
267
+ - ๐ŸŽฏ **50+ Technical Indicators**: RSI, MACD, Bollinger Bands, Stochastic, ADX, ATR, CCI, Fibonacci, Support/Resistance
268
+ - ๐Ÿ“Š **Order Book Analysis**: Bid/ask depth, spread, liquidity imbalance, top 20 levels
269
+ - ๐Ÿค– **AI-Ready Output**: Markdown reports formatted for LLM context injection
270
+ - โšก **Performance Optimized**: Intelligent caching with configurable TTL per timeframe
271
+
272
+ #### Use Case
273
+ Perfect for injecting comprehensive market context into your LLM-powered strategies. Instead of manually calculating indicators, `@backtest-kit/signals` provides a single function call that adds all technical analysis to your message context. Works seamlessly with `getSignal` function in backtest-kit strategies.
274
+
275
+ #### Get Started
276
+ ```bash
277
+ npm install @backtest-kit/signals backtest-kit
278
+ ```
279
+
280
+ ### @backtest-kit/ollama
281
+
282
+ > **[Explore on NPM](https://www.npmjs.com/package/@backtest-kit/ollama)** ๐Ÿค–
283
+
284
+ The **@backtest-kit/ollama** package is a multi-provider LLM inference library that supports 10+ providers including OpenAI, Claude, DeepSeek, Grok, Mistral, Perplexity, Cohere, Alibaba, Hugging Face, and Ollama with unified API and automatic token rotation.
285
+
286
+ #### Key Features
287
+ - ๐Ÿ”Œ **10+ LLM Providers**: OpenAI, Claude, DeepSeek, Grok, Mistral, Perplexity, Cohere, Alibaba, Hugging Face, Ollama
288
+ - ๐Ÿ”„ **Token Rotation**: Automatic API key rotation for Ollama (others throw clear errors)
289
+ - ๐ŸŽฏ **Structured Output**: Enforced JSON schema for trading signals (position, price levels, risk notes)
290
+ - ๐Ÿ”‘ **Flexible Auth**: Context-based API keys or environment variables
291
+ - โšก **Unified API**: Single interface across all providers
292
+ - ๐Ÿ“Š **Trading-First**: Built for backtest-kit with position sizing and risk management
293
+
294
+ #### Use Case
295
+ Ideal for building multi-provider LLM strategies with fallback chains and ensemble predictions. The package returns structured trading signals with validated TP/SL levels, making it perfect for use in `getSignal` functions. Supports both backtest and live trading modes.
296
+
297
+ #### Get Started
298
+ ```bash
299
+ npm install @backtest-kit/ollama agent-swarm-kit backtest-kit
300
+ ```
243
301
 
244
302
  ## ๐Ÿค– Are you a robot?
245
303
 
@@ -247,7 +305,7 @@ Unlike cloud-based platforms, backtest-kit runs entirely in your environment. Yo
247
305
 
248
306
  ## โœ… Tested & Reliable
249
307
 
250
- 300+ tests cover validation, recovery, reports, and events.
308
+ 350+ tests cover validation, recovery, reports, and events.
251
309
 
252
310
  ## ๐Ÿค Contribute
253
311
 
package/build/index.cjs CHANGED
@@ -651,7 +651,7 @@ const GET_CANDLES_FN = async (dto, since, self) => {
651
651
  let lastError;
652
652
  for (let i = 0; i !== GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT; i++) {
653
653
  try {
654
- const result = await self.params.getCandles(dto.symbol, dto.interval, since, dto.limit);
654
+ const result = await self.params.getCandles(dto.symbol, dto.interval, since, dto.limit, self.params.execution.context.backtest);
655
655
  VALIDATE_NO_INCOMPLETE_CANDLES_FN(result);
656
656
  return result;
657
657
  }
@@ -878,7 +878,7 @@ class ClientExchange {
878
878
  symbol,
879
879
  quantity,
880
880
  });
881
- return await this.params.formatQuantity(symbol, quantity);
881
+ return await this.params.formatQuantity(symbol, quantity, this.params.execution.context.backtest);
882
882
  }
883
883
  /**
884
884
  * Formats price according to exchange-specific rules for the given symbol.
@@ -893,7 +893,7 @@ class ClientExchange {
893
893
  symbol,
894
894
  price,
895
895
  });
896
- return await this.params.formatPrice(symbol, price);
896
+ return await this.params.formatPrice(symbol, price, this.params.execution.context.backtest);
897
897
  }
898
898
  /**
899
899
  * Fetches order book for a trading pair.
@@ -914,7 +914,7 @@ class ClientExchange {
914
914
  });
915
915
  const to = new Date(this.params.execution.context.when.getTime());
916
916
  const from = new Date(to.getTime() - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * 60 * 1000);
917
- return await this.params.getOrderBook(symbol, depth, from, to);
917
+ return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
918
918
  }
919
919
  }
920
920
 
@@ -922,21 +922,21 @@ class ClientExchange {
922
922
  * Default implementation for getCandles.
923
923
  * Throws an error indicating the method is not implemented.
924
924
  */
925
- const DEFAULT_GET_CANDLES_FN$1 = async (_symbol, _interval, _since, _limit) => {
925
+ const DEFAULT_GET_CANDLES_FN$1 = async (_symbol, _interval, _since, _limit, _backtest) => {
926
926
  throw new Error(`getCandles is not implemented for this exchange`);
927
927
  };
928
928
  /**
929
929
  * Default implementation for formatQuantity.
930
930
  * Returns Bitcoin precision on Binance (8 decimal places).
931
931
  */
932
- const DEFAULT_FORMAT_QUANTITY_FN$1 = async (_symbol, quantity) => {
932
+ const DEFAULT_FORMAT_QUANTITY_FN$1 = async (_symbol, quantity, _backtest) => {
933
933
  return quantity.toFixed(8);
934
934
  };
935
935
  /**
936
936
  * Default implementation for formatPrice.
937
937
  * Returns Bitcoin precision on Binance (2 decimal places).
938
938
  */
939
- const DEFAULT_FORMAT_PRICE_FN$1 = async (_symbol, price) => {
939
+ const DEFAULT_FORMAT_PRICE_FN$1 = async (_symbol, price, _backtest) => {
940
940
  return price.toFixed(2);
941
941
  };
942
942
  /**
@@ -947,8 +947,9 @@ const DEFAULT_FORMAT_PRICE_FN$1 = async (_symbol, price) => {
947
947
  * @param _depth - Maximum depth levels (unused)
948
948
  * @param _from - Start of time range (unused - can be ignored in live implementations)
949
949
  * @param _to - End of time range (unused - can be ignored in live implementations)
950
+ * @param _backtest - Whether running in backtest mode (unused)
950
951
  */
951
- const DEFAULT_GET_ORDER_BOOK_FN$1 = async (_symbol, _depth, _from, _to) => {
952
+ const DEFAULT_GET_ORDER_BOOK_FN$1 = async (_symbol, _depth, _from, _to, _backtest) => {
952
953
  throw new Error(`getOrderBook is not implemented for this exchange`);
953
954
  };
954
955
  /**
@@ -2253,6 +2254,48 @@ const toPlainString = (content) => {
2253
2254
  return text.trim();
2254
2255
  };
2255
2256
 
2257
+ /**
2258
+ * Wraps a function to execute it outside of the current execution context if one exists.
2259
+ *
2260
+ * This utility ensures that the wrapped function runs in isolation from any existing
2261
+ * ExecutionContext, preventing context leakage and unintended context sharing between
2262
+ * async operations.
2263
+ *
2264
+ * @template T - Function type with any parameters and return type
2265
+ * @param {T} run - The function to be wrapped and executed outside of context
2266
+ * @returns {Function} A curried function that accepts the original function's parameters
2267
+ * and executes it outside of the current context if one exists
2268
+ *
2269
+ * @example
2270
+ * ```ts
2271
+ * const myFunction = async (param: string) => {
2272
+ * // This code will run outside of any ExecutionContext
2273
+ * return param.toUpperCase();
2274
+ * };
2275
+ *
2276
+ * const wrappedFunction = beginTime(myFunction);
2277
+ * const result = wrappedFunction('hello'); // Returns 'HELLO'
2278
+ * ```
2279
+ *
2280
+ * @example
2281
+ * ```ts
2282
+ * // Usage with trycatch wrapper
2283
+ * const safeFunction = trycatch(
2284
+ * beginTime(async (id: number) => {
2285
+ * // Function body runs isolated from parent context
2286
+ * return await fetchData(id);
2287
+ * })
2288
+ * );
2289
+ * ```
2290
+ */
2291
+ const beginTime = (run) => (...args) => {
2292
+ let fn = () => run(...args);
2293
+ if (ExecutionContextService.hasContext()) {
2294
+ fn = ExecutionContextService.runOutOfContext(fn);
2295
+ }
2296
+ return fn();
2297
+ };
2298
+
2256
2299
  const INTERVAL_MINUTES$3 = {
2257
2300
  "1m": 1,
2258
2301
  "3m": 3,
@@ -3352,7 +3395,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
3352
3395
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, activationTime, self.params.execution.context.backtest);
3353
3396
  return result;
3354
3397
  };
3355
- const CALL_PING_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, scheduled, timestamp, backtest) => {
3398
+ const CALL_PING_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, scheduled, timestamp, backtest) => {
3356
3399
  await ExecutionContextService.runInContext(async () => {
3357
3400
  const publicSignal = TO_PUBLIC_SIGNAL(scheduled);
3358
3401
  // Call system onPing callback first (emits to pingSubject)
@@ -3366,7 +3409,7 @@ const CALL_PING_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, schedu
3366
3409
  symbol: symbol,
3367
3410
  backtest: backtest,
3368
3411
  });
3369
- }, {
3412
+ }), {
3370
3413
  fallback: (error) => {
3371
3414
  const message = "ClientStrategy CALL_PING_CALLBACKS_FN thrown";
3372
3415
  const payload = {
@@ -3378,7 +3421,7 @@ const CALL_PING_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, schedu
3378
3421
  errorEmitter.next(error);
3379
3422
  },
3380
3423
  });
3381
- const CALL_ACTIVE_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3424
+ const CALL_ACTIVE_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3382
3425
  await ExecutionContextService.runInContext(async () => {
3383
3426
  if (self.params.callbacks?.onActive) {
3384
3427
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
@@ -3389,7 +3432,7 @@ const CALL_ACTIVE_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, sign
3389
3432
  symbol: symbol,
3390
3433
  backtest: backtest,
3391
3434
  });
3392
- }, {
3435
+ }), {
3393
3436
  fallback: (error) => {
3394
3437
  const message = "ClientStrategy CALL_ACTIVE_CALLBACKS_FN thrown";
3395
3438
  const payload = {
@@ -3401,7 +3444,7 @@ const CALL_ACTIVE_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, sign
3401
3444
  errorEmitter.next(error);
3402
3445
  },
3403
3446
  });
3404
- const CALL_SCHEDULE_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3447
+ const CALL_SCHEDULE_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3405
3448
  await ExecutionContextService.runInContext(async () => {
3406
3449
  if (self.params.callbacks?.onSchedule) {
3407
3450
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
@@ -3412,7 +3455,7 @@ const CALL_SCHEDULE_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, si
3412
3455
  symbol: symbol,
3413
3456
  backtest: backtest,
3414
3457
  });
3415
- }, {
3458
+ }), {
3416
3459
  fallback: (error) => {
3417
3460
  const message = "ClientStrategy CALL_SCHEDULE_CALLBACKS_FN thrown";
3418
3461
  const payload = {
@@ -3424,7 +3467,7 @@ const CALL_SCHEDULE_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, si
3424
3467
  errorEmitter.next(error);
3425
3468
  },
3426
3469
  });
3427
- const CALL_CANCEL_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3470
+ const CALL_CANCEL_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3428
3471
  await ExecutionContextService.runInContext(async () => {
3429
3472
  if (self.params.callbacks?.onCancel) {
3430
3473
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
@@ -3435,7 +3478,7 @@ const CALL_CANCEL_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, sign
3435
3478
  symbol: symbol,
3436
3479
  backtest: backtest,
3437
3480
  });
3438
- }, {
3481
+ }), {
3439
3482
  fallback: (error) => {
3440
3483
  const message = "ClientStrategy CALL_CANCEL_CALLBACKS_FN thrown";
3441
3484
  const payload = {
@@ -3447,7 +3490,7 @@ const CALL_CANCEL_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, sign
3447
3490
  errorEmitter.next(error);
3448
3491
  },
3449
3492
  });
3450
- const CALL_OPEN_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, signal, priceOpen, timestamp, backtest) => {
3493
+ const CALL_OPEN_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, signal, priceOpen, timestamp, backtest) => {
3451
3494
  await ExecutionContextService.runInContext(async () => {
3452
3495
  if (self.params.callbacks?.onOpen) {
3453
3496
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
@@ -3458,7 +3501,7 @@ const CALL_OPEN_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, signal
3458
3501
  symbol: symbol,
3459
3502
  backtest: backtest,
3460
3503
  });
3461
- }, {
3504
+ }), {
3462
3505
  fallback: (error) => {
3463
3506
  const message = "ClientStrategy CALL_OPEN_CALLBACKS_FN thrown";
3464
3507
  const payload = {
@@ -3470,7 +3513,7 @@ const CALL_OPEN_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, signal
3470
3513
  errorEmitter.next(error);
3471
3514
  },
3472
3515
  });
3473
- const CALL_CLOSE_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3516
+ const CALL_CLOSE_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3474
3517
  await ExecutionContextService.runInContext(async () => {
3475
3518
  if (self.params.callbacks?.onClose) {
3476
3519
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
@@ -3481,7 +3524,7 @@ const CALL_CLOSE_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, signa
3481
3524
  symbol: symbol,
3482
3525
  backtest: backtest,
3483
3526
  });
3484
- }, {
3527
+ }), {
3485
3528
  fallback: (error) => {
3486
3529
  const message = "ClientStrategy CALL_CLOSE_CALLBACKS_FN thrown";
3487
3530
  const payload = {
@@ -3493,7 +3536,7 @@ const CALL_CLOSE_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, signa
3493
3536
  errorEmitter.next(error);
3494
3537
  },
3495
3538
  });
3496
- const CALL_TICK_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, result, timestamp, backtest) => {
3539
+ const CALL_TICK_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, result, timestamp, backtest) => {
3497
3540
  await ExecutionContextService.runInContext(async () => {
3498
3541
  if (self.params.callbacks?.onTick) {
3499
3542
  await self.params.callbacks.onTick(self.params.execution.context.symbol, result, self.params.execution.context.backtest);
@@ -3503,7 +3546,7 @@ const CALL_TICK_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, result
3503
3546
  symbol: symbol,
3504
3547
  backtest: backtest,
3505
3548
  });
3506
- }, {
3549
+ }), {
3507
3550
  fallback: (error) => {
3508
3551
  const message = "ClientStrategy CALL_TICK_CALLBACKS_FN thrown";
3509
3552
  const payload = {
@@ -3515,7 +3558,7 @@ const CALL_TICK_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, result
3515
3558
  errorEmitter.next(error);
3516
3559
  },
3517
3560
  });
3518
- const CALL_IDLE_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, currentPrice, timestamp, backtest) => {
3561
+ const CALL_IDLE_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, currentPrice, timestamp, backtest) => {
3519
3562
  await ExecutionContextService.runInContext(async () => {
3520
3563
  if (self.params.callbacks?.onIdle) {
3521
3564
  await self.params.callbacks.onIdle(self.params.execution.context.symbol, currentPrice, self.params.execution.context.backtest);
@@ -3525,7 +3568,7 @@ const CALL_IDLE_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, curren
3525
3568
  symbol: symbol,
3526
3569
  backtest: backtest,
3527
3570
  });
3528
- }, {
3571
+ }), {
3529
3572
  fallback: (error) => {
3530
3573
  const message = "ClientStrategy CALL_IDLE_CALLBACKS_FN thrown";
3531
3574
  const payload = {
@@ -3537,7 +3580,7 @@ const CALL_IDLE_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, curren
3537
3580
  errorEmitter.next(error);
3538
3581
  },
3539
3582
  });
3540
- const CALL_RISK_ADD_SIGNAL_FN = functoolsKit.trycatch(async (self, symbol, signal, timestamp, backtest) => {
3583
+ const CALL_RISK_ADD_SIGNAL_FN = functoolsKit.trycatch(beginTime(async (self, symbol, signal, timestamp, backtest) => {
3541
3584
  await ExecutionContextService.runInContext(async () => {
3542
3585
  await self.params.risk.addSignal(symbol, {
3543
3586
  strategyName: self.params.method.context.strategyName,
@@ -3557,7 +3600,7 @@ const CALL_RISK_ADD_SIGNAL_FN = functoolsKit.trycatch(async (self, symbol, signa
3557
3600
  symbol: symbol,
3558
3601
  backtest: backtest,
3559
3602
  });
3560
- }, {
3603
+ }), {
3561
3604
  fallback: (error) => {
3562
3605
  const message = "ClientStrategy CALL_RISK_ADD_SIGNAL_FN thrown";
3563
3606
  const payload = {
@@ -3569,7 +3612,7 @@ const CALL_RISK_ADD_SIGNAL_FN = functoolsKit.trycatch(async (self, symbol, signa
3569
3612
  errorEmitter.next(error);
3570
3613
  },
3571
3614
  });
3572
- const CALL_RISK_REMOVE_SIGNAL_FN = functoolsKit.trycatch(async (self, symbol, timestamp, backtest) => {
3615
+ const CALL_RISK_REMOVE_SIGNAL_FN = functoolsKit.trycatch(beginTime(async (self, symbol, timestamp, backtest) => {
3573
3616
  await ExecutionContextService.runInContext(async () => {
3574
3617
  await self.params.risk.removeSignal(symbol, {
3575
3618
  strategyName: self.params.method.context.strategyName,
@@ -3582,7 +3625,7 @@ const CALL_RISK_REMOVE_SIGNAL_FN = functoolsKit.trycatch(async (self, symbol, ti
3582
3625
  symbol: symbol,
3583
3626
  backtest: backtest,
3584
3627
  });
3585
- }, {
3628
+ }), {
3586
3629
  fallback: (error) => {
3587
3630
  const message = "ClientStrategy CALL_RISK_REMOVE_SIGNAL_FN thrown";
3588
3631
  const payload = {
@@ -3594,7 +3637,7 @@ const CALL_RISK_REMOVE_SIGNAL_FN = functoolsKit.trycatch(async (self, symbol, ti
3594
3637
  errorEmitter.next(error);
3595
3638
  },
3596
3639
  });
3597
- const CALL_PARTIAL_CLEAR_FN = functoolsKit.trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3640
+ const CALL_PARTIAL_CLEAR_FN = functoolsKit.trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3598
3641
  await ExecutionContextService.runInContext(async () => {
3599
3642
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
3600
3643
  await self.params.partial.clear(symbol, publicSignal, currentPrice, backtest);
@@ -3603,7 +3646,7 @@ const CALL_PARTIAL_CLEAR_FN = functoolsKit.trycatch(async (self, symbol, signal,
3603
3646
  symbol: symbol,
3604
3647
  backtest: backtest,
3605
3648
  });
3606
- }, {
3649
+ }), {
3607
3650
  fallback: (error) => {
3608
3651
  const message = "ClientStrategy CALL_PARTIAL_CLEAR_FN thrown";
3609
3652
  const payload = {
@@ -3615,7 +3658,7 @@ const CALL_PARTIAL_CLEAR_FN = functoolsKit.trycatch(async (self, symbol, signal,
3615
3658
  errorEmitter.next(error);
3616
3659
  },
3617
3660
  });
3618
- const CALL_RISK_CHECK_SIGNAL_FN = functoolsKit.trycatch(async (self, symbol, pendingSignal, currentPrice, timestamp, backtest) => {
3661
+ const CALL_RISK_CHECK_SIGNAL_FN = functoolsKit.trycatch(beginTime(async (self, symbol, pendingSignal, currentPrice, timestamp, backtest) => {
3619
3662
  return await ExecutionContextService.runInContext(async () => {
3620
3663
  return await self.params.risk.checkSignal({
3621
3664
  pendingSignal: TO_PUBLIC_SIGNAL(pendingSignal),
@@ -3632,7 +3675,7 @@ const CALL_RISK_CHECK_SIGNAL_FN = functoolsKit.trycatch(async (self, symbol, pen
3632
3675
  symbol: symbol,
3633
3676
  backtest: backtest,
3634
3677
  });
3635
- }, {
3678
+ }), {
3636
3679
  defaultValue: false,
3637
3680
  fallback: (error) => {
3638
3681
  const message = "ClientStrategy CALL_RISK_CHECK_SIGNAL_FN thrown";
@@ -3645,7 +3688,7 @@ const CALL_RISK_CHECK_SIGNAL_FN = functoolsKit.trycatch(async (self, symbol, pen
3645
3688
  errorEmitter.next(error);
3646
3689
  },
3647
3690
  });
3648
- const CALL_PARTIAL_PROFIT_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, signal, currentPrice, percentTp, timestamp, backtest) => {
3691
+ const CALL_PARTIAL_PROFIT_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, signal, currentPrice, percentTp, timestamp, backtest) => {
3649
3692
  await ExecutionContextService.runInContext(async () => {
3650
3693
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
3651
3694
  await self.params.partial.profit(symbol, publicSignal, currentPrice, percentTp, backtest, new Date(timestamp));
@@ -3657,7 +3700,7 @@ const CALL_PARTIAL_PROFIT_CALLBACKS_FN = functoolsKit.trycatch(async (self, symb
3657
3700
  symbol: symbol,
3658
3701
  backtest: backtest,
3659
3702
  });
3660
- }, {
3703
+ }), {
3661
3704
  fallback: (error) => {
3662
3705
  const message = "ClientStrategy CALL_PARTIAL_PROFIT_CALLBACKS_FN thrown";
3663
3706
  const payload = {
@@ -3669,7 +3712,7 @@ const CALL_PARTIAL_PROFIT_CALLBACKS_FN = functoolsKit.trycatch(async (self, symb
3669
3712
  errorEmitter.next(error);
3670
3713
  },
3671
3714
  });
3672
- const CALL_PARTIAL_LOSS_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, signal, currentPrice, percentSl, timestamp, backtest) => {
3715
+ const CALL_PARTIAL_LOSS_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, signal, currentPrice, percentSl, timestamp, backtest) => {
3673
3716
  await ExecutionContextService.runInContext(async () => {
3674
3717
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
3675
3718
  await self.params.partial.loss(symbol, publicSignal, currentPrice, percentSl, backtest, new Date(timestamp));
@@ -3681,7 +3724,7 @@ const CALL_PARTIAL_LOSS_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol
3681
3724
  symbol: symbol,
3682
3725
  backtest: backtest,
3683
3726
  });
3684
- }, {
3727
+ }), {
3685
3728
  fallback: (error) => {
3686
3729
  const message = "ClientStrategy CALL_PARTIAL_LOSS_CALLBACKS_FN thrown";
3687
3730
  const payload = {
@@ -3693,7 +3736,7 @@ const CALL_PARTIAL_LOSS_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol
3693
3736
  errorEmitter.next(error);
3694
3737
  },
3695
3738
  });
3696
- const CALL_BREAKEVEN_CHECK_FN = functoolsKit.trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3739
+ const CALL_BREAKEVEN_CHECK_FN = functoolsKit.trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3697
3740
  await ExecutionContextService.runInContext(async () => {
3698
3741
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
3699
3742
  const isBreakeven = await self.params.breakeven.check(symbol, publicSignal, currentPrice, backtest, new Date(timestamp));
@@ -3705,7 +3748,7 @@ const CALL_BREAKEVEN_CHECK_FN = functoolsKit.trycatch(async (self, symbol, signa
3705
3748
  symbol: symbol,
3706
3749
  backtest: backtest,
3707
3750
  });
3708
- }, {
3751
+ }), {
3709
3752
  fallback: (error) => {
3710
3753
  const message = "ClientStrategy CALL_BREAKEVEN_CHECK_FN thrown";
3711
3754
  const payload = {
@@ -3717,7 +3760,7 @@ const CALL_BREAKEVEN_CHECK_FN = functoolsKit.trycatch(async (self, symbol, signa
3717
3760
  errorEmitter.next(error);
3718
3761
  },
3719
3762
  });
3720
- const CALL_BREAKEVEN_CLEAR_FN = functoolsKit.trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3763
+ const CALL_BREAKEVEN_CLEAR_FN = functoolsKit.trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3721
3764
  await ExecutionContextService.runInContext(async () => {
3722
3765
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
3723
3766
  await self.params.breakeven.clear(symbol, publicSignal, currentPrice, backtest);
@@ -3726,7 +3769,7 @@ const CALL_BREAKEVEN_CLEAR_FN = functoolsKit.trycatch(async (self, symbol, signa
3726
3769
  symbol: symbol,
3727
3770
  backtest: backtest,
3728
3771
  });
3729
- }, {
3772
+ }), {
3730
3773
  fallback: (error) => {
3731
3774
  const message = "ClientStrategy CALL_BREAKEVEN_CLEAR_FN thrown";
3732
3775
  const payload = {
@@ -27171,25 +27214,35 @@ const EXCHANGE_METHOD_NAME_GET_AVERAGE_PRICE = "ExchangeUtils.getAveragePrice";
27171
27214
  const EXCHANGE_METHOD_NAME_FORMAT_QUANTITY = "ExchangeUtils.formatQuantity";
27172
27215
  const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
27173
27216
  const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
27217
+ /**
27218
+ * Gets backtest mode flag from execution context if available.
27219
+ * Returns false if no execution context exists (live mode).
27220
+ */
27221
+ const GET_BACKTEST_FN = async () => {
27222
+ if (ExecutionContextService.hasContext()) {
27223
+ return bt.executionContextService.context.backtest;
27224
+ }
27225
+ return false;
27226
+ };
27174
27227
  /**
27175
27228
  * Default implementation for getCandles.
27176
27229
  * Throws an error indicating the method is not implemented.
27177
27230
  */
27178
- const DEFAULT_GET_CANDLES_FN = async (_symbol, _interval, _since, _limit) => {
27231
+ const DEFAULT_GET_CANDLES_FN = async (_symbol, _interval, _since, _limit, _backtest) => {
27179
27232
  throw new Error(`getCandles is not implemented for this exchange`);
27180
27233
  };
27181
27234
  /**
27182
27235
  * Default implementation for formatQuantity.
27183
27236
  * Returns Bitcoin precision on Binance (8 decimal places).
27184
27237
  */
27185
- const DEFAULT_FORMAT_QUANTITY_FN = async (_symbol, quantity) => {
27238
+ const DEFAULT_FORMAT_QUANTITY_FN = async (_symbol, quantity, _backtest) => {
27186
27239
  return quantity.toFixed(8);
27187
27240
  };
27188
27241
  /**
27189
27242
  * Default implementation for formatPrice.
27190
27243
  * Returns Bitcoin precision on Binance (2 decimal places).
27191
27244
  */
27192
- const DEFAULT_FORMAT_PRICE_FN = async (_symbol, price) => {
27245
+ const DEFAULT_FORMAT_PRICE_FN = async (_symbol, price, _backtest) => {
27193
27246
  return price.toFixed(2);
27194
27247
  };
27195
27248
  /**
@@ -27200,8 +27253,9 @@ const DEFAULT_FORMAT_PRICE_FN = async (_symbol, price) => {
27200
27253
  * @param _depth - Maximum depth levels (unused)
27201
27254
  * @param _from - Start of time range (unused - can be ignored in live implementations)
27202
27255
  * @param _to - End of time range (unused - can be ignored in live implementations)
27256
+ * @param _backtest - Whether running in backtest mode (unused)
27203
27257
  */
27204
- const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to) => {
27258
+ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest) => {
27205
27259
  throw new Error(`getOrderBook is not implemented for this exchange`);
27206
27260
  };
27207
27261
  const INTERVAL_MINUTES$1 = {
@@ -27297,9 +27351,10 @@ class ExchangeInstance {
27297
27351
  if (limit > GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST) {
27298
27352
  let remaining = limit;
27299
27353
  let currentSince = new Date(since.getTime());
27354
+ const isBacktest = await GET_BACKTEST_FN();
27300
27355
  while (remaining > 0) {
27301
27356
  const chunkLimit = Math.min(remaining, GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST);
27302
- const chunkData = await getCandles(symbol, interval, currentSince, chunkLimit);
27357
+ const chunkData = await getCandles(symbol, interval, currentSince, chunkLimit, isBacktest);
27303
27358
  allData.push(...chunkData);
27304
27359
  remaining -= chunkLimit;
27305
27360
  if (remaining > 0) {
@@ -27309,7 +27364,8 @@ class ExchangeInstance {
27309
27364
  }
27310
27365
  }
27311
27366
  else {
27312
- allData = await getCandles(symbol, interval, since, limit);
27367
+ const isBacktest = await GET_BACKTEST_FN();
27368
+ allData = await getCandles(symbol, interval, since, limit, isBacktest);
27313
27369
  }
27314
27370
  // Filter candles to strictly match the requested range
27315
27371
  const whenTimestamp = when.getTime();
@@ -27385,7 +27441,8 @@ class ExchangeInstance {
27385
27441
  symbol,
27386
27442
  quantity,
27387
27443
  });
27388
- return await this._methods.formatQuantity(symbol, quantity);
27444
+ const isBacktest = await GET_BACKTEST_FN();
27445
+ return await this._methods.formatQuantity(symbol, quantity, isBacktest);
27389
27446
  };
27390
27447
  /**
27391
27448
  * Format price according to exchange precision rules.
@@ -27407,7 +27464,8 @@ class ExchangeInstance {
27407
27464
  symbol,
27408
27465
  price,
27409
27466
  });
27410
- return await this._methods.formatPrice(symbol, price);
27467
+ const isBacktest = await GET_BACKTEST_FN();
27468
+ return await this._methods.formatPrice(symbol, price, isBacktest);
27411
27469
  };
27412
27470
  /**
27413
27471
  * Fetch order book for a trading pair.
@@ -27437,7 +27495,8 @@ class ExchangeInstance {
27437
27495
  });
27438
27496
  const to = new Date(Date.now());
27439
27497
  const from = new Date(to.getTime() - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * 60 * 1000);
27440
- return await this._methods.getOrderBook(symbol, depth, from, to);
27498
+ const isBacktest = await GET_BACKTEST_FN();
27499
+ return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
27441
27500
  };
27442
27501
  const schema = bt.exchangeSchemaService.get(this.exchangeName);
27443
27502
  this._methods = CREATE_EXCHANGE_INSTANCE_FN(schema);
package/build/index.mjs CHANGED
@@ -631,7 +631,7 @@ const GET_CANDLES_FN = async (dto, since, self) => {
631
631
  let lastError;
632
632
  for (let i = 0; i !== GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT; i++) {
633
633
  try {
634
- const result = await self.params.getCandles(dto.symbol, dto.interval, since, dto.limit);
634
+ const result = await self.params.getCandles(dto.symbol, dto.interval, since, dto.limit, self.params.execution.context.backtest);
635
635
  VALIDATE_NO_INCOMPLETE_CANDLES_FN(result);
636
636
  return result;
637
637
  }
@@ -858,7 +858,7 @@ class ClientExchange {
858
858
  symbol,
859
859
  quantity,
860
860
  });
861
- return await this.params.formatQuantity(symbol, quantity);
861
+ return await this.params.formatQuantity(symbol, quantity, this.params.execution.context.backtest);
862
862
  }
863
863
  /**
864
864
  * Formats price according to exchange-specific rules for the given symbol.
@@ -873,7 +873,7 @@ class ClientExchange {
873
873
  symbol,
874
874
  price,
875
875
  });
876
- return await this.params.formatPrice(symbol, price);
876
+ return await this.params.formatPrice(symbol, price, this.params.execution.context.backtest);
877
877
  }
878
878
  /**
879
879
  * Fetches order book for a trading pair.
@@ -894,7 +894,7 @@ class ClientExchange {
894
894
  });
895
895
  const to = new Date(this.params.execution.context.when.getTime());
896
896
  const from = new Date(to.getTime() - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * 60 * 1000);
897
- return await this.params.getOrderBook(symbol, depth, from, to);
897
+ return await this.params.getOrderBook(symbol, depth, from, to, this.params.execution.context.backtest);
898
898
  }
899
899
  }
900
900
 
@@ -902,21 +902,21 @@ class ClientExchange {
902
902
  * Default implementation for getCandles.
903
903
  * Throws an error indicating the method is not implemented.
904
904
  */
905
- const DEFAULT_GET_CANDLES_FN$1 = async (_symbol, _interval, _since, _limit) => {
905
+ const DEFAULT_GET_CANDLES_FN$1 = async (_symbol, _interval, _since, _limit, _backtest) => {
906
906
  throw new Error(`getCandles is not implemented for this exchange`);
907
907
  };
908
908
  /**
909
909
  * Default implementation for formatQuantity.
910
910
  * Returns Bitcoin precision on Binance (8 decimal places).
911
911
  */
912
- const DEFAULT_FORMAT_QUANTITY_FN$1 = async (_symbol, quantity) => {
912
+ const DEFAULT_FORMAT_QUANTITY_FN$1 = async (_symbol, quantity, _backtest) => {
913
913
  return quantity.toFixed(8);
914
914
  };
915
915
  /**
916
916
  * Default implementation for formatPrice.
917
917
  * Returns Bitcoin precision on Binance (2 decimal places).
918
918
  */
919
- const DEFAULT_FORMAT_PRICE_FN$1 = async (_symbol, price) => {
919
+ const DEFAULT_FORMAT_PRICE_FN$1 = async (_symbol, price, _backtest) => {
920
920
  return price.toFixed(2);
921
921
  };
922
922
  /**
@@ -927,8 +927,9 @@ const DEFAULT_FORMAT_PRICE_FN$1 = async (_symbol, price) => {
927
927
  * @param _depth - Maximum depth levels (unused)
928
928
  * @param _from - Start of time range (unused - can be ignored in live implementations)
929
929
  * @param _to - End of time range (unused - can be ignored in live implementations)
930
+ * @param _backtest - Whether running in backtest mode (unused)
930
931
  */
931
- const DEFAULT_GET_ORDER_BOOK_FN$1 = async (_symbol, _depth, _from, _to) => {
932
+ const DEFAULT_GET_ORDER_BOOK_FN$1 = async (_symbol, _depth, _from, _to, _backtest) => {
932
933
  throw new Error(`getOrderBook is not implemented for this exchange`);
933
934
  };
934
935
  /**
@@ -2233,6 +2234,48 @@ const toPlainString = (content) => {
2233
2234
  return text.trim();
2234
2235
  };
2235
2236
 
2237
+ /**
2238
+ * Wraps a function to execute it outside of the current execution context if one exists.
2239
+ *
2240
+ * This utility ensures that the wrapped function runs in isolation from any existing
2241
+ * ExecutionContext, preventing context leakage and unintended context sharing between
2242
+ * async operations.
2243
+ *
2244
+ * @template T - Function type with any parameters and return type
2245
+ * @param {T} run - The function to be wrapped and executed outside of context
2246
+ * @returns {Function} A curried function that accepts the original function's parameters
2247
+ * and executes it outside of the current context if one exists
2248
+ *
2249
+ * @example
2250
+ * ```ts
2251
+ * const myFunction = async (param: string) => {
2252
+ * // This code will run outside of any ExecutionContext
2253
+ * return param.toUpperCase();
2254
+ * };
2255
+ *
2256
+ * const wrappedFunction = beginTime(myFunction);
2257
+ * const result = wrappedFunction('hello'); // Returns 'HELLO'
2258
+ * ```
2259
+ *
2260
+ * @example
2261
+ * ```ts
2262
+ * // Usage with trycatch wrapper
2263
+ * const safeFunction = trycatch(
2264
+ * beginTime(async (id: number) => {
2265
+ * // Function body runs isolated from parent context
2266
+ * return await fetchData(id);
2267
+ * })
2268
+ * );
2269
+ * ```
2270
+ */
2271
+ const beginTime = (run) => (...args) => {
2272
+ let fn = () => run(...args);
2273
+ if (ExecutionContextService.hasContext()) {
2274
+ fn = ExecutionContextService.runOutOfContext(fn);
2275
+ }
2276
+ return fn();
2277
+ };
2278
+
2236
2279
  const INTERVAL_MINUTES$3 = {
2237
2280
  "1m": 1,
2238
2281
  "3m": 3,
@@ -3332,7 +3375,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
3332
3375
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, activationTime, self.params.execution.context.backtest);
3333
3376
  return result;
3334
3377
  };
3335
- const CALL_PING_CALLBACKS_FN = trycatch(async (self, symbol, scheduled, timestamp, backtest) => {
3378
+ const CALL_PING_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, scheduled, timestamp, backtest) => {
3336
3379
  await ExecutionContextService.runInContext(async () => {
3337
3380
  const publicSignal = TO_PUBLIC_SIGNAL(scheduled);
3338
3381
  // Call system onPing callback first (emits to pingSubject)
@@ -3346,7 +3389,7 @@ const CALL_PING_CALLBACKS_FN = trycatch(async (self, symbol, scheduled, timestam
3346
3389
  symbol: symbol,
3347
3390
  backtest: backtest,
3348
3391
  });
3349
- }, {
3392
+ }), {
3350
3393
  fallback: (error) => {
3351
3394
  const message = "ClientStrategy CALL_PING_CALLBACKS_FN thrown";
3352
3395
  const payload = {
@@ -3358,7 +3401,7 @@ const CALL_PING_CALLBACKS_FN = trycatch(async (self, symbol, scheduled, timestam
3358
3401
  errorEmitter.next(error);
3359
3402
  },
3360
3403
  });
3361
- const CALL_ACTIVE_CALLBACKS_FN = trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3404
+ const CALL_ACTIVE_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3362
3405
  await ExecutionContextService.runInContext(async () => {
3363
3406
  if (self.params.callbacks?.onActive) {
3364
3407
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
@@ -3369,7 +3412,7 @@ const CALL_ACTIVE_CALLBACKS_FN = trycatch(async (self, symbol, signal, currentPr
3369
3412
  symbol: symbol,
3370
3413
  backtest: backtest,
3371
3414
  });
3372
- }, {
3415
+ }), {
3373
3416
  fallback: (error) => {
3374
3417
  const message = "ClientStrategy CALL_ACTIVE_CALLBACKS_FN thrown";
3375
3418
  const payload = {
@@ -3381,7 +3424,7 @@ const CALL_ACTIVE_CALLBACKS_FN = trycatch(async (self, symbol, signal, currentPr
3381
3424
  errorEmitter.next(error);
3382
3425
  },
3383
3426
  });
3384
- const CALL_SCHEDULE_CALLBACKS_FN = trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3427
+ const CALL_SCHEDULE_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3385
3428
  await ExecutionContextService.runInContext(async () => {
3386
3429
  if (self.params.callbacks?.onSchedule) {
3387
3430
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
@@ -3392,7 +3435,7 @@ const CALL_SCHEDULE_CALLBACKS_FN = trycatch(async (self, symbol, signal, current
3392
3435
  symbol: symbol,
3393
3436
  backtest: backtest,
3394
3437
  });
3395
- }, {
3438
+ }), {
3396
3439
  fallback: (error) => {
3397
3440
  const message = "ClientStrategy CALL_SCHEDULE_CALLBACKS_FN thrown";
3398
3441
  const payload = {
@@ -3404,7 +3447,7 @@ const CALL_SCHEDULE_CALLBACKS_FN = trycatch(async (self, symbol, signal, current
3404
3447
  errorEmitter.next(error);
3405
3448
  },
3406
3449
  });
3407
- const CALL_CANCEL_CALLBACKS_FN = trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3450
+ const CALL_CANCEL_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3408
3451
  await ExecutionContextService.runInContext(async () => {
3409
3452
  if (self.params.callbacks?.onCancel) {
3410
3453
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
@@ -3415,7 +3458,7 @@ const CALL_CANCEL_CALLBACKS_FN = trycatch(async (self, symbol, signal, currentPr
3415
3458
  symbol: symbol,
3416
3459
  backtest: backtest,
3417
3460
  });
3418
- }, {
3461
+ }), {
3419
3462
  fallback: (error) => {
3420
3463
  const message = "ClientStrategy CALL_CANCEL_CALLBACKS_FN thrown";
3421
3464
  const payload = {
@@ -3427,7 +3470,7 @@ const CALL_CANCEL_CALLBACKS_FN = trycatch(async (self, symbol, signal, currentPr
3427
3470
  errorEmitter.next(error);
3428
3471
  },
3429
3472
  });
3430
- const CALL_OPEN_CALLBACKS_FN = trycatch(async (self, symbol, signal, priceOpen, timestamp, backtest) => {
3473
+ const CALL_OPEN_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, signal, priceOpen, timestamp, backtest) => {
3431
3474
  await ExecutionContextService.runInContext(async () => {
3432
3475
  if (self.params.callbacks?.onOpen) {
3433
3476
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
@@ -3438,7 +3481,7 @@ const CALL_OPEN_CALLBACKS_FN = trycatch(async (self, symbol, signal, priceOpen,
3438
3481
  symbol: symbol,
3439
3482
  backtest: backtest,
3440
3483
  });
3441
- }, {
3484
+ }), {
3442
3485
  fallback: (error) => {
3443
3486
  const message = "ClientStrategy CALL_OPEN_CALLBACKS_FN thrown";
3444
3487
  const payload = {
@@ -3450,7 +3493,7 @@ const CALL_OPEN_CALLBACKS_FN = trycatch(async (self, symbol, signal, priceOpen,
3450
3493
  errorEmitter.next(error);
3451
3494
  },
3452
3495
  });
3453
- const CALL_CLOSE_CALLBACKS_FN = trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3496
+ const CALL_CLOSE_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3454
3497
  await ExecutionContextService.runInContext(async () => {
3455
3498
  if (self.params.callbacks?.onClose) {
3456
3499
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
@@ -3461,7 +3504,7 @@ const CALL_CLOSE_CALLBACKS_FN = trycatch(async (self, symbol, signal, currentPri
3461
3504
  symbol: symbol,
3462
3505
  backtest: backtest,
3463
3506
  });
3464
- }, {
3507
+ }), {
3465
3508
  fallback: (error) => {
3466
3509
  const message = "ClientStrategy CALL_CLOSE_CALLBACKS_FN thrown";
3467
3510
  const payload = {
@@ -3473,7 +3516,7 @@ const CALL_CLOSE_CALLBACKS_FN = trycatch(async (self, symbol, signal, currentPri
3473
3516
  errorEmitter.next(error);
3474
3517
  },
3475
3518
  });
3476
- const CALL_TICK_CALLBACKS_FN = trycatch(async (self, symbol, result, timestamp, backtest) => {
3519
+ const CALL_TICK_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, result, timestamp, backtest) => {
3477
3520
  await ExecutionContextService.runInContext(async () => {
3478
3521
  if (self.params.callbacks?.onTick) {
3479
3522
  await self.params.callbacks.onTick(self.params.execution.context.symbol, result, self.params.execution.context.backtest);
@@ -3483,7 +3526,7 @@ const CALL_TICK_CALLBACKS_FN = trycatch(async (self, symbol, result, timestamp,
3483
3526
  symbol: symbol,
3484
3527
  backtest: backtest,
3485
3528
  });
3486
- }, {
3529
+ }), {
3487
3530
  fallback: (error) => {
3488
3531
  const message = "ClientStrategy CALL_TICK_CALLBACKS_FN thrown";
3489
3532
  const payload = {
@@ -3495,7 +3538,7 @@ const CALL_TICK_CALLBACKS_FN = trycatch(async (self, symbol, result, timestamp,
3495
3538
  errorEmitter.next(error);
3496
3539
  },
3497
3540
  });
3498
- const CALL_IDLE_CALLBACKS_FN = trycatch(async (self, symbol, currentPrice, timestamp, backtest) => {
3541
+ const CALL_IDLE_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, currentPrice, timestamp, backtest) => {
3499
3542
  await ExecutionContextService.runInContext(async () => {
3500
3543
  if (self.params.callbacks?.onIdle) {
3501
3544
  await self.params.callbacks.onIdle(self.params.execution.context.symbol, currentPrice, self.params.execution.context.backtest);
@@ -3505,7 +3548,7 @@ const CALL_IDLE_CALLBACKS_FN = trycatch(async (self, symbol, currentPrice, times
3505
3548
  symbol: symbol,
3506
3549
  backtest: backtest,
3507
3550
  });
3508
- }, {
3551
+ }), {
3509
3552
  fallback: (error) => {
3510
3553
  const message = "ClientStrategy CALL_IDLE_CALLBACKS_FN thrown";
3511
3554
  const payload = {
@@ -3517,7 +3560,7 @@ const CALL_IDLE_CALLBACKS_FN = trycatch(async (self, symbol, currentPrice, times
3517
3560
  errorEmitter.next(error);
3518
3561
  },
3519
3562
  });
3520
- const CALL_RISK_ADD_SIGNAL_FN = trycatch(async (self, symbol, signal, timestamp, backtest) => {
3563
+ const CALL_RISK_ADD_SIGNAL_FN = trycatch(beginTime(async (self, symbol, signal, timestamp, backtest) => {
3521
3564
  await ExecutionContextService.runInContext(async () => {
3522
3565
  await self.params.risk.addSignal(symbol, {
3523
3566
  strategyName: self.params.method.context.strategyName,
@@ -3537,7 +3580,7 @@ const CALL_RISK_ADD_SIGNAL_FN = trycatch(async (self, symbol, signal, timestamp,
3537
3580
  symbol: symbol,
3538
3581
  backtest: backtest,
3539
3582
  });
3540
- }, {
3583
+ }), {
3541
3584
  fallback: (error) => {
3542
3585
  const message = "ClientStrategy CALL_RISK_ADD_SIGNAL_FN thrown";
3543
3586
  const payload = {
@@ -3549,7 +3592,7 @@ const CALL_RISK_ADD_SIGNAL_FN = trycatch(async (self, symbol, signal, timestamp,
3549
3592
  errorEmitter.next(error);
3550
3593
  },
3551
3594
  });
3552
- const CALL_RISK_REMOVE_SIGNAL_FN = trycatch(async (self, symbol, timestamp, backtest) => {
3595
+ const CALL_RISK_REMOVE_SIGNAL_FN = trycatch(beginTime(async (self, symbol, timestamp, backtest) => {
3553
3596
  await ExecutionContextService.runInContext(async () => {
3554
3597
  await self.params.risk.removeSignal(symbol, {
3555
3598
  strategyName: self.params.method.context.strategyName,
@@ -3562,7 +3605,7 @@ const CALL_RISK_REMOVE_SIGNAL_FN = trycatch(async (self, symbol, timestamp, back
3562
3605
  symbol: symbol,
3563
3606
  backtest: backtest,
3564
3607
  });
3565
- }, {
3608
+ }), {
3566
3609
  fallback: (error) => {
3567
3610
  const message = "ClientStrategy CALL_RISK_REMOVE_SIGNAL_FN thrown";
3568
3611
  const payload = {
@@ -3574,7 +3617,7 @@ const CALL_RISK_REMOVE_SIGNAL_FN = trycatch(async (self, symbol, timestamp, back
3574
3617
  errorEmitter.next(error);
3575
3618
  },
3576
3619
  });
3577
- const CALL_PARTIAL_CLEAR_FN = trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3620
+ const CALL_PARTIAL_CLEAR_FN = trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3578
3621
  await ExecutionContextService.runInContext(async () => {
3579
3622
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
3580
3623
  await self.params.partial.clear(symbol, publicSignal, currentPrice, backtest);
@@ -3583,7 +3626,7 @@ const CALL_PARTIAL_CLEAR_FN = trycatch(async (self, symbol, signal, currentPrice
3583
3626
  symbol: symbol,
3584
3627
  backtest: backtest,
3585
3628
  });
3586
- }, {
3629
+ }), {
3587
3630
  fallback: (error) => {
3588
3631
  const message = "ClientStrategy CALL_PARTIAL_CLEAR_FN thrown";
3589
3632
  const payload = {
@@ -3595,7 +3638,7 @@ const CALL_PARTIAL_CLEAR_FN = trycatch(async (self, symbol, signal, currentPrice
3595
3638
  errorEmitter.next(error);
3596
3639
  },
3597
3640
  });
3598
- const CALL_RISK_CHECK_SIGNAL_FN = trycatch(async (self, symbol, pendingSignal, currentPrice, timestamp, backtest) => {
3641
+ const CALL_RISK_CHECK_SIGNAL_FN = trycatch(beginTime(async (self, symbol, pendingSignal, currentPrice, timestamp, backtest) => {
3599
3642
  return await ExecutionContextService.runInContext(async () => {
3600
3643
  return await self.params.risk.checkSignal({
3601
3644
  pendingSignal: TO_PUBLIC_SIGNAL(pendingSignal),
@@ -3612,7 +3655,7 @@ const CALL_RISK_CHECK_SIGNAL_FN = trycatch(async (self, symbol, pendingSignal, c
3612
3655
  symbol: symbol,
3613
3656
  backtest: backtest,
3614
3657
  });
3615
- }, {
3658
+ }), {
3616
3659
  defaultValue: false,
3617
3660
  fallback: (error) => {
3618
3661
  const message = "ClientStrategy CALL_RISK_CHECK_SIGNAL_FN thrown";
@@ -3625,7 +3668,7 @@ const CALL_RISK_CHECK_SIGNAL_FN = trycatch(async (self, symbol, pendingSignal, c
3625
3668
  errorEmitter.next(error);
3626
3669
  },
3627
3670
  });
3628
- const CALL_PARTIAL_PROFIT_CALLBACKS_FN = trycatch(async (self, symbol, signal, currentPrice, percentTp, timestamp, backtest) => {
3671
+ const CALL_PARTIAL_PROFIT_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, signal, currentPrice, percentTp, timestamp, backtest) => {
3629
3672
  await ExecutionContextService.runInContext(async () => {
3630
3673
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
3631
3674
  await self.params.partial.profit(symbol, publicSignal, currentPrice, percentTp, backtest, new Date(timestamp));
@@ -3637,7 +3680,7 @@ const CALL_PARTIAL_PROFIT_CALLBACKS_FN = trycatch(async (self, symbol, signal, c
3637
3680
  symbol: symbol,
3638
3681
  backtest: backtest,
3639
3682
  });
3640
- }, {
3683
+ }), {
3641
3684
  fallback: (error) => {
3642
3685
  const message = "ClientStrategy CALL_PARTIAL_PROFIT_CALLBACKS_FN thrown";
3643
3686
  const payload = {
@@ -3649,7 +3692,7 @@ const CALL_PARTIAL_PROFIT_CALLBACKS_FN = trycatch(async (self, symbol, signal, c
3649
3692
  errorEmitter.next(error);
3650
3693
  },
3651
3694
  });
3652
- const CALL_PARTIAL_LOSS_CALLBACKS_FN = trycatch(async (self, symbol, signal, currentPrice, percentSl, timestamp, backtest) => {
3695
+ const CALL_PARTIAL_LOSS_CALLBACKS_FN = trycatch(beginTime(async (self, symbol, signal, currentPrice, percentSl, timestamp, backtest) => {
3653
3696
  await ExecutionContextService.runInContext(async () => {
3654
3697
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
3655
3698
  await self.params.partial.loss(symbol, publicSignal, currentPrice, percentSl, backtest, new Date(timestamp));
@@ -3661,7 +3704,7 @@ const CALL_PARTIAL_LOSS_CALLBACKS_FN = trycatch(async (self, symbol, signal, cur
3661
3704
  symbol: symbol,
3662
3705
  backtest: backtest,
3663
3706
  });
3664
- }, {
3707
+ }), {
3665
3708
  fallback: (error) => {
3666
3709
  const message = "ClientStrategy CALL_PARTIAL_LOSS_CALLBACKS_FN thrown";
3667
3710
  const payload = {
@@ -3673,7 +3716,7 @@ const CALL_PARTIAL_LOSS_CALLBACKS_FN = trycatch(async (self, symbol, signal, cur
3673
3716
  errorEmitter.next(error);
3674
3717
  },
3675
3718
  });
3676
- const CALL_BREAKEVEN_CHECK_FN = trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3719
+ const CALL_BREAKEVEN_CHECK_FN = trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3677
3720
  await ExecutionContextService.runInContext(async () => {
3678
3721
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
3679
3722
  const isBreakeven = await self.params.breakeven.check(symbol, publicSignal, currentPrice, backtest, new Date(timestamp));
@@ -3685,7 +3728,7 @@ const CALL_BREAKEVEN_CHECK_FN = trycatch(async (self, symbol, signal, currentPri
3685
3728
  symbol: symbol,
3686
3729
  backtest: backtest,
3687
3730
  });
3688
- }, {
3731
+ }), {
3689
3732
  fallback: (error) => {
3690
3733
  const message = "ClientStrategy CALL_BREAKEVEN_CHECK_FN thrown";
3691
3734
  const payload = {
@@ -3697,7 +3740,7 @@ const CALL_BREAKEVEN_CHECK_FN = trycatch(async (self, symbol, signal, currentPri
3697
3740
  errorEmitter.next(error);
3698
3741
  },
3699
3742
  });
3700
- const CALL_BREAKEVEN_CLEAR_FN = trycatch(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3743
+ const CALL_BREAKEVEN_CLEAR_FN = trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
3701
3744
  await ExecutionContextService.runInContext(async () => {
3702
3745
  const publicSignal = TO_PUBLIC_SIGNAL(signal);
3703
3746
  await self.params.breakeven.clear(symbol, publicSignal, currentPrice, backtest);
@@ -3706,7 +3749,7 @@ const CALL_BREAKEVEN_CLEAR_FN = trycatch(async (self, symbol, signal, currentPri
3706
3749
  symbol: symbol,
3707
3750
  backtest: backtest,
3708
3751
  });
3709
- }, {
3752
+ }), {
3710
3753
  fallback: (error) => {
3711
3754
  const message = "ClientStrategy CALL_BREAKEVEN_CLEAR_FN thrown";
3712
3755
  const payload = {
@@ -27151,25 +27194,35 @@ const EXCHANGE_METHOD_NAME_GET_AVERAGE_PRICE = "ExchangeUtils.getAveragePrice";
27151
27194
  const EXCHANGE_METHOD_NAME_FORMAT_QUANTITY = "ExchangeUtils.formatQuantity";
27152
27195
  const EXCHANGE_METHOD_NAME_FORMAT_PRICE = "ExchangeUtils.formatPrice";
27153
27196
  const EXCHANGE_METHOD_NAME_GET_ORDER_BOOK = "ExchangeUtils.getOrderBook";
27197
+ /**
27198
+ * Gets backtest mode flag from execution context if available.
27199
+ * Returns false if no execution context exists (live mode).
27200
+ */
27201
+ const GET_BACKTEST_FN = async () => {
27202
+ if (ExecutionContextService.hasContext()) {
27203
+ return bt.executionContextService.context.backtest;
27204
+ }
27205
+ return false;
27206
+ };
27154
27207
  /**
27155
27208
  * Default implementation for getCandles.
27156
27209
  * Throws an error indicating the method is not implemented.
27157
27210
  */
27158
- const DEFAULT_GET_CANDLES_FN = async (_symbol, _interval, _since, _limit) => {
27211
+ const DEFAULT_GET_CANDLES_FN = async (_symbol, _interval, _since, _limit, _backtest) => {
27159
27212
  throw new Error(`getCandles is not implemented for this exchange`);
27160
27213
  };
27161
27214
  /**
27162
27215
  * Default implementation for formatQuantity.
27163
27216
  * Returns Bitcoin precision on Binance (8 decimal places).
27164
27217
  */
27165
- const DEFAULT_FORMAT_QUANTITY_FN = async (_symbol, quantity) => {
27218
+ const DEFAULT_FORMAT_QUANTITY_FN = async (_symbol, quantity, _backtest) => {
27166
27219
  return quantity.toFixed(8);
27167
27220
  };
27168
27221
  /**
27169
27222
  * Default implementation for formatPrice.
27170
27223
  * Returns Bitcoin precision on Binance (2 decimal places).
27171
27224
  */
27172
- const DEFAULT_FORMAT_PRICE_FN = async (_symbol, price) => {
27225
+ const DEFAULT_FORMAT_PRICE_FN = async (_symbol, price, _backtest) => {
27173
27226
  return price.toFixed(2);
27174
27227
  };
27175
27228
  /**
@@ -27180,8 +27233,9 @@ const DEFAULT_FORMAT_PRICE_FN = async (_symbol, price) => {
27180
27233
  * @param _depth - Maximum depth levels (unused)
27181
27234
  * @param _from - Start of time range (unused - can be ignored in live implementations)
27182
27235
  * @param _to - End of time range (unused - can be ignored in live implementations)
27236
+ * @param _backtest - Whether running in backtest mode (unused)
27183
27237
  */
27184
- const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to) => {
27238
+ const DEFAULT_GET_ORDER_BOOK_FN = async (_symbol, _depth, _from, _to, _backtest) => {
27185
27239
  throw new Error(`getOrderBook is not implemented for this exchange`);
27186
27240
  };
27187
27241
  const INTERVAL_MINUTES$1 = {
@@ -27277,9 +27331,10 @@ class ExchangeInstance {
27277
27331
  if (limit > GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST) {
27278
27332
  let remaining = limit;
27279
27333
  let currentSince = new Date(since.getTime());
27334
+ const isBacktest = await GET_BACKTEST_FN();
27280
27335
  while (remaining > 0) {
27281
27336
  const chunkLimit = Math.min(remaining, GLOBAL_CONFIG.CC_MAX_CANDLES_PER_REQUEST);
27282
- const chunkData = await getCandles(symbol, interval, currentSince, chunkLimit);
27337
+ const chunkData = await getCandles(symbol, interval, currentSince, chunkLimit, isBacktest);
27283
27338
  allData.push(...chunkData);
27284
27339
  remaining -= chunkLimit;
27285
27340
  if (remaining > 0) {
@@ -27289,7 +27344,8 @@ class ExchangeInstance {
27289
27344
  }
27290
27345
  }
27291
27346
  else {
27292
- allData = await getCandles(symbol, interval, since, limit);
27347
+ const isBacktest = await GET_BACKTEST_FN();
27348
+ allData = await getCandles(symbol, interval, since, limit, isBacktest);
27293
27349
  }
27294
27350
  // Filter candles to strictly match the requested range
27295
27351
  const whenTimestamp = when.getTime();
@@ -27365,7 +27421,8 @@ class ExchangeInstance {
27365
27421
  symbol,
27366
27422
  quantity,
27367
27423
  });
27368
- return await this._methods.formatQuantity(symbol, quantity);
27424
+ const isBacktest = await GET_BACKTEST_FN();
27425
+ return await this._methods.formatQuantity(symbol, quantity, isBacktest);
27369
27426
  };
27370
27427
  /**
27371
27428
  * Format price according to exchange precision rules.
@@ -27387,7 +27444,8 @@ class ExchangeInstance {
27387
27444
  symbol,
27388
27445
  price,
27389
27446
  });
27390
- return await this._methods.formatPrice(symbol, price);
27447
+ const isBacktest = await GET_BACKTEST_FN();
27448
+ return await this._methods.formatPrice(symbol, price, isBacktest);
27391
27449
  };
27392
27450
  /**
27393
27451
  * Fetch order book for a trading pair.
@@ -27417,7 +27475,8 @@ class ExchangeInstance {
27417
27475
  });
27418
27476
  const to = new Date(Date.now());
27419
27477
  const from = new Date(to.getTime() - GLOBAL_CONFIG.CC_ORDER_BOOK_TIME_OFFSET_MINUTES * 60 * 1000);
27420
- return await this._methods.getOrderBook(symbol, depth, from, to);
27478
+ const isBacktest = await GET_BACKTEST_FN();
27479
+ return await this._methods.getOrderBook(symbol, depth, from, to, isBacktest);
27421
27480
  };
27422
27481
  const schema = bt.exchangeSchemaService.get(this.exchangeName);
27423
27482
  this._methods = CREATE_EXCHANGE_INSTANCE_FN(schema);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "1.11.9",
3
+ "version": "1.12.1",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
@@ -41,9 +41,9 @@
41
41
  "build": "rollup -c",
42
42
  "test": "npm run build && node ./test/index.mjs",
43
43
  "build:docs": "rimraf docs && mkdir docs && node ./scripts/dts-docs.cjs ./types.d.ts ./docs",
44
- "docs:gpt": "npm run build && node ./scripts/gpt-docs.mjs",
44
+ "docs:gpt": "npm run build && node ./tools/gpt-docs/index.mjs",
45
45
  "docs:uml": "npm run build && node ./scripts/uml.mjs",
46
- "docs:www": "rimraf docs/wwwroot && typedoc && node ./packages/typedoc-yandex-metrica/index.mjs",
46
+ "docs:www": "rimraf docs/wwwroot && typedoc && node ./tools/typedoc-yandex-metrica/index.mjs",
47
47
  "repl": "dotenv -e .env -- npm run build && node -e \"import('./scripts/repl.mjs')\" --interactive"
48
48
  },
49
49
  "main": "build/index.cjs",
@@ -77,5 +77,8 @@
77
77
  "di-scoped": "^1.0.20",
78
78
  "functools-kit": "^1.0.95",
79
79
  "get-moment-stamp": "^1.1.1"
80
+ },
81
+ "publishConfig": {
82
+ "access": "public"
80
83
  }
81
84
  }
package/types.d.ts CHANGED
@@ -454,13 +454,13 @@ interface IExchangeParams extends IExchangeSchema {
454
454
  /** Execution context service (symbol, when, backtest flag) */
455
455
  execution: TExecutionContextService;
456
456
  /** Fetch candles from data source (required, defaults applied) */
457
- getCandles: (symbol: string, interval: CandleInterval, since: Date, limit: number) => Promise<ICandleData[]>;
457
+ getCandles: (symbol: string, interval: CandleInterval, since: Date, limit: number, backtest: boolean) => Promise<ICandleData[]>;
458
458
  /** Format quantity according to exchange precision rules (required, defaults applied) */
459
- formatQuantity: (symbol: string, quantity: number) => Promise<string>;
459
+ formatQuantity: (symbol: string, quantity: number, backtest: boolean) => Promise<string>;
460
460
  /** Format price according to exchange precision rules (required, defaults applied) */
461
- formatPrice: (symbol: string, price: number) => Promise<string>;
461
+ formatPrice: (symbol: string, price: number, backtest: boolean) => Promise<string>;
462
462
  /** Fetch order book for a trading pair (required, defaults applied) */
463
- getOrderBook: (symbol: string, depth: number, from: Date, to: Date) => Promise<IOrderBookData>;
463
+ getOrderBook: (symbol: string, depth: number, from: Date, to: Date, backtest: boolean) => Promise<IOrderBookData>;
464
464
  }
465
465
  /**
466
466
  * Optional callbacks for exchange data events.
@@ -485,9 +485,10 @@ interface IExchangeSchema {
485
485
  * @param interval - Candle time interval (e.g., "1m", "1h")
486
486
  * @param since - Start date for candle fetching
487
487
  * @param limit - Maximum number of candles to fetch
488
+ * @param backtest - Whether running in backtest mode
488
489
  * @returns Promise resolving to array of OHLCV candle data
489
490
  */
490
- getCandles: (symbol: string, interval: CandleInterval, since: Date, limit: number) => Promise<ICandleData[]>;
491
+ getCandles: (symbol: string, interval: CandleInterval, since: Date, limit: number, backtest: boolean) => Promise<ICandleData[]>;
491
492
  /**
492
493
  * Format quantity according to exchange precision rules.
493
494
  *
@@ -495,9 +496,10 @@ interface IExchangeSchema {
495
496
  *
496
497
  * @param symbol - Trading pair symbol
497
498
  * @param quantity - Raw quantity value
499
+ * @param backtest - Whether running in backtest mode
498
500
  * @returns Promise resolving to formatted quantity string
499
501
  */
500
- formatQuantity?: (symbol: string, quantity: number) => Promise<string>;
502
+ formatQuantity?: (symbol: string, quantity: number, backtest: boolean) => Promise<string>;
501
503
  /**
502
504
  * Format price according to exchange precision rules.
503
505
  *
@@ -505,9 +507,10 @@ interface IExchangeSchema {
505
507
  *
506
508
  * @param symbol - Trading pair symbol
507
509
  * @param price - Raw price value
510
+ * @param backtest - Whether running in backtest mode
508
511
  * @returns Promise resolving to formatted price string
509
512
  */
510
- formatPrice?: (symbol: string, price: number) => Promise<string>;
513
+ formatPrice?: (symbol: string, price: number, backtest: boolean) => Promise<string>;
511
514
  /**
512
515
  * Fetch order book for a trading pair.
513
516
  *
@@ -517,22 +520,26 @@ interface IExchangeSchema {
517
520
  * @param depth - Maximum depth levels for both bids and asks (default: CC_ORDER_BOOK_MAX_DEPTH_LEVELS)
518
521
  * @param from - Start of time range (used in backtest for historical data, can be ignored in live)
519
522
  * @param to - End of time range (used in backtest for historical data, can be ignored in live)
523
+ * @param backtest - Whether running in backtest mode
520
524
  * @returns Promise resolving to order book data
521
525
  *
522
526
  * @example
523
527
  * ```typescript
524
528
  * // Backtest implementation: returns historical order book for the time range
525
- * const backtestOrderBook = async (symbol: string, depth: number, from: Date, to: Date) => {
526
- * return await database.getOrderBookSnapshot(symbol, depth, from, to);
529
+ * const backtestOrderBook = async (symbol: string, depth: number, from: Date, to: Date, backtest: boolean) => {
530
+ * if (backtest) {
531
+ * return await database.getOrderBookSnapshot(symbol, depth, from, to);
532
+ * }
533
+ * return await exchange.fetchOrderBook(symbol, depth);
527
534
  * };
528
535
  *
529
- * // Live implementation: ignores from/to and returns current snapshot
530
- * const liveOrderBook = async (symbol: string, depth: number, _from: Date, _to: Date) => {
536
+ * // Live implementation: ignores from/to when not in backtest mode
537
+ * const liveOrderBook = async (symbol: string, depth: number, _from: Date, _to: Date, backtest: boolean) => {
531
538
  * return await exchange.fetchOrderBook(symbol, depth);
532
539
  * };
533
540
  * ```
534
541
  */
535
- getOrderBook?: (symbol: string, depth: number, from: Date, to: Date) => Promise<IOrderBookData>;
542
+ getOrderBook?: (symbol: string, depth: number, from: Date, to: Date, backtest: boolean) => Promise<IOrderBookData>;
536
543
  /** Optional lifecycle event callbacks (onCandleData) */
537
544
  callbacks?: Partial<IExchangeCallbacks>;
538
545
  }