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 +64 -6
- package/build/index.cjs +108 -49
- package/build/index.mjs +108 -49
- package/package.json +6 -3
- package/types.d.ts +19 -12
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**:
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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 ./
|
|
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 ./
|
|
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
|
-
*
|
|
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
|
|
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
|
}
|