backtest-kit 1.1.3 → 1.1.5
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 +90 -52
- package/build/index.cjs +341 -54
- package/build/index.mjs +339 -56
- package/package.json +2 -1
- package/types.d.ts +232 -2
package/build/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createActivator } from 'di-kit';
|
|
2
2
|
import { scoped } from 'di-scoped';
|
|
3
|
-
import { memoize, makeExtendable, singleshot, getErrorMessage, not, trycatch, retry, randomString,
|
|
3
|
+
import { memoize, makeExtendable, singleshot, getErrorMessage, not, trycatch, retry, Subject, randomString, errorData, ToolRegistry, sleep, str, queued } from 'functools-kit';
|
|
4
4
|
import fs, { mkdir, writeFile } from 'fs/promises';
|
|
5
5
|
import path, { join } from 'path';
|
|
6
6
|
import crypto from 'crypto';
|
|
@@ -1102,6 +1102,37 @@ class PersistSignalUtils {
|
|
|
1102
1102
|
*/
|
|
1103
1103
|
const PersistSignalAdaper = new PersistSignalUtils();
|
|
1104
1104
|
|
|
1105
|
+
/**
|
|
1106
|
+
* Global signal emitter for all trading events (live + backtest).
|
|
1107
|
+
* Emits all signal events regardless of execution mode.
|
|
1108
|
+
*/
|
|
1109
|
+
const signalEmitter = new Subject();
|
|
1110
|
+
/**
|
|
1111
|
+
* Live trading signal emitter.
|
|
1112
|
+
* Emits only signals from live trading execution.
|
|
1113
|
+
*/
|
|
1114
|
+
const signalLiveEmitter = new Subject();
|
|
1115
|
+
/**
|
|
1116
|
+
* Backtest signal emitter.
|
|
1117
|
+
* Emits only signals from backtest execution.
|
|
1118
|
+
*/
|
|
1119
|
+
const signalBacktestEmitter = new Subject();
|
|
1120
|
+
/**
|
|
1121
|
+
* Error emitter for background execution errors.
|
|
1122
|
+
* Emits errors caught in background tasks (Live.background, Backtest.background).
|
|
1123
|
+
*/
|
|
1124
|
+
const errorEmitter = new Subject();
|
|
1125
|
+
/**
|
|
1126
|
+
* Done emitter for background execution completion.
|
|
1127
|
+
* Emits when background tasks complete (Live.background, Backtest.background).
|
|
1128
|
+
*/
|
|
1129
|
+
const doneEmitter = new Subject();
|
|
1130
|
+
/**
|
|
1131
|
+
* Progress emitter for backtest execution progress.
|
|
1132
|
+
* Emits progress updates during backtest execution.
|
|
1133
|
+
*/
|
|
1134
|
+
const progressEmitter = new Subject();
|
|
1135
|
+
|
|
1105
1136
|
const INTERVAL_MINUTES$1 = {
|
|
1106
1137
|
"1m": 1,
|
|
1107
1138
|
"3m": 3,
|
|
@@ -1153,6 +1184,9 @@ const VALIDATE_SIGNAL_FN = (signal) => {
|
|
|
1153
1184
|
}
|
|
1154
1185
|
};
|
|
1155
1186
|
const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
1187
|
+
if (self._isStopped) {
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1156
1190
|
const currentTime = self.params.execution.context.when.getTime();
|
|
1157
1191
|
{
|
|
1158
1192
|
const intervalMinutes = INTERVAL_MINUTES$1[self.params.interval];
|
|
@@ -1181,6 +1215,13 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
1181
1215
|
return signalRow;
|
|
1182
1216
|
}, {
|
|
1183
1217
|
defaultValue: null,
|
|
1218
|
+
fallback: (error) => {
|
|
1219
|
+
backtest$1.loggerService.warn("ClientStrategy exception thrown", {
|
|
1220
|
+
error: errorData(error),
|
|
1221
|
+
message: getErrorMessage(error),
|
|
1222
|
+
});
|
|
1223
|
+
errorEmitter.next(error);
|
|
1224
|
+
},
|
|
1184
1225
|
});
|
|
1185
1226
|
const GET_AVG_PRICE_FN = (candles) => {
|
|
1186
1227
|
const sumPriceVolume = candles.reduce((acc, c) => {
|
|
@@ -1239,6 +1280,7 @@ const WAIT_FOR_INIT_FN = async (self) => {
|
|
|
1239
1280
|
class ClientStrategy {
|
|
1240
1281
|
constructor(params) {
|
|
1241
1282
|
this.params = params;
|
|
1283
|
+
this._isStopped = false;
|
|
1242
1284
|
this._pendingSignal = null;
|
|
1243
1285
|
this._lastSignalTimestamp = null;
|
|
1244
1286
|
/**
|
|
@@ -1561,34 +1603,32 @@ class ClientStrategy {
|
|
|
1561
1603
|
}
|
|
1562
1604
|
return result;
|
|
1563
1605
|
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Stops the strategy from generating new signals.
|
|
1608
|
+
*
|
|
1609
|
+
* Sets internal flag to prevent getSignal from being called.
|
|
1610
|
+
* Does NOT close active pending signals - they continue monitoring until TP/SL/time_expired.
|
|
1611
|
+
*
|
|
1612
|
+
* Use case: Graceful shutdown in live trading without forcing position closure.
|
|
1613
|
+
*
|
|
1614
|
+
* @returns Promise that resolves immediately when stop flag is set
|
|
1615
|
+
*
|
|
1616
|
+
* @example
|
|
1617
|
+
* ```typescript
|
|
1618
|
+
* // In Live.background() cancellation
|
|
1619
|
+
* await strategy.stop();
|
|
1620
|
+
* // Existing signal will continue until natural close
|
|
1621
|
+
* ```
|
|
1622
|
+
*/
|
|
1623
|
+
stop() {
|
|
1624
|
+
this.params.logger.debug("ClientStrategy stop", {
|
|
1625
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
1626
|
+
});
|
|
1627
|
+
this._isStopped = true;
|
|
1628
|
+
return Promise.resolve();
|
|
1629
|
+
}
|
|
1564
1630
|
}
|
|
1565
1631
|
|
|
1566
|
-
/**
|
|
1567
|
-
* Global signal emitter for all trading events (live + backtest).
|
|
1568
|
-
* Emits all signal events regardless of execution mode.
|
|
1569
|
-
*/
|
|
1570
|
-
const signalEmitter = new Subject();
|
|
1571
|
-
/**
|
|
1572
|
-
* Live trading signal emitter.
|
|
1573
|
-
* Emits only signals from live trading execution.
|
|
1574
|
-
*/
|
|
1575
|
-
const signalLiveEmitter = new Subject();
|
|
1576
|
-
/**
|
|
1577
|
-
* Backtest signal emitter.
|
|
1578
|
-
* Emits only signals from backtest execution.
|
|
1579
|
-
*/
|
|
1580
|
-
const signalBacktestEmitter = new Subject();
|
|
1581
|
-
/**
|
|
1582
|
-
* Error emitter for background execution errors.
|
|
1583
|
-
* Emits errors caught in background tasks (Live.background, Backtest.background).
|
|
1584
|
-
*/
|
|
1585
|
-
const errorEmitter = new Subject();
|
|
1586
|
-
/**
|
|
1587
|
-
* Done emitter for background execution completion.
|
|
1588
|
-
* Emits when background tasks complete (Live.background, Backtest.background).
|
|
1589
|
-
*/
|
|
1590
|
-
const doneEmitter = new Subject();
|
|
1591
|
-
|
|
1592
1632
|
/**
|
|
1593
1633
|
* Connection service routing strategy operations to correct ClientStrategy instance.
|
|
1594
1634
|
*
|
|
@@ -1687,6 +1727,36 @@ class StrategyConnectionService {
|
|
|
1687
1727
|
}
|
|
1688
1728
|
return tick;
|
|
1689
1729
|
};
|
|
1730
|
+
/**
|
|
1731
|
+
* Stops the specified strategy from generating new signals.
|
|
1732
|
+
*
|
|
1733
|
+
* Delegates to ClientStrategy.stop() which sets internal flag to prevent
|
|
1734
|
+
* getSignal from being called on subsequent ticks.
|
|
1735
|
+
*
|
|
1736
|
+
* @param strategyName - Name of strategy to stop
|
|
1737
|
+
* @returns Promise that resolves when stop flag is set
|
|
1738
|
+
*/
|
|
1739
|
+
this.stop = async (strategyName) => {
|
|
1740
|
+
this.loggerService.log("strategyConnectionService stop", {
|
|
1741
|
+
strategyName,
|
|
1742
|
+
});
|
|
1743
|
+
const strategy = this.getStrategy(strategyName);
|
|
1744
|
+
await strategy.stop();
|
|
1745
|
+
};
|
|
1746
|
+
/**
|
|
1747
|
+
* Clears the memoized ClientStrategy instance from cache.
|
|
1748
|
+
*
|
|
1749
|
+
* Forces re-initialization of strategy on next getStrategy call.
|
|
1750
|
+
* Useful for resetting strategy state or releasing resources.
|
|
1751
|
+
*
|
|
1752
|
+
* @param strategyName - Name of strategy to clear from cache
|
|
1753
|
+
*/
|
|
1754
|
+
this.clear = async (strategyName) => {
|
|
1755
|
+
this.loggerService.log("strategyConnectionService clear", {
|
|
1756
|
+
strategyName,
|
|
1757
|
+
});
|
|
1758
|
+
this.getStrategy.clear(strategyName);
|
|
1759
|
+
};
|
|
1690
1760
|
}
|
|
1691
1761
|
}
|
|
1692
1762
|
|
|
@@ -2030,6 +2100,35 @@ class StrategyGlobalService {
|
|
|
2030
2100
|
backtest,
|
|
2031
2101
|
});
|
|
2032
2102
|
};
|
|
2103
|
+
/**
|
|
2104
|
+
* Stops the strategy from generating new signals.
|
|
2105
|
+
*
|
|
2106
|
+
* Delegates to StrategyConnectionService.stop() to set internal flag.
|
|
2107
|
+
* Does not require execution context.
|
|
2108
|
+
*
|
|
2109
|
+
* @param strategyName - Name of strategy to stop
|
|
2110
|
+
* @returns Promise that resolves when stop flag is set
|
|
2111
|
+
*/
|
|
2112
|
+
this.stop = async (strategyName) => {
|
|
2113
|
+
this.loggerService.log("strategyGlobalService stop", {
|
|
2114
|
+
strategyName,
|
|
2115
|
+
});
|
|
2116
|
+
return await this.strategyConnectionService.stop(strategyName);
|
|
2117
|
+
};
|
|
2118
|
+
/**
|
|
2119
|
+
* Clears the memoized ClientStrategy instance from cache.
|
|
2120
|
+
*
|
|
2121
|
+
* Delegates to StrategyConnectionService.clear() to remove strategy from cache.
|
|
2122
|
+
* Forces re-initialization of strategy on next operation.
|
|
2123
|
+
*
|
|
2124
|
+
* @param strategyName - Name of strategy to clear from cache
|
|
2125
|
+
*/
|
|
2126
|
+
this.clear = async (strategyName) => {
|
|
2127
|
+
this.loggerService.log("strategyGlobalService clear", {
|
|
2128
|
+
strategyName,
|
|
2129
|
+
});
|
|
2130
|
+
return await this.strategyConnectionService.clear(strategyName);
|
|
2131
|
+
};
|
|
2033
2132
|
}
|
|
2034
2133
|
}
|
|
2035
2134
|
|
|
@@ -2260,7 +2359,7 @@ class FrameSchemaService {
|
|
|
2260
2359
|
register(key, value) {
|
|
2261
2360
|
this.loggerService.log(`frameSchemaService register`, { key });
|
|
2262
2361
|
this.validateShallow(value);
|
|
2263
|
-
this._registry.register(key, value);
|
|
2362
|
+
this._registry = this._registry.register(key, value);
|
|
2264
2363
|
}
|
|
2265
2364
|
/**
|
|
2266
2365
|
* Overrides an existing frame schema with partial updates.
|
|
@@ -2271,7 +2370,8 @@ class FrameSchemaService {
|
|
|
2271
2370
|
*/
|
|
2272
2371
|
override(key, value) {
|
|
2273
2372
|
this.loggerService.log(`frameSchemaService override`, { key });
|
|
2274
|
-
this._registry.override(key, value);
|
|
2373
|
+
this._registry = this._registry.override(key, value);
|
|
2374
|
+
return this._registry.get(key);
|
|
2275
2375
|
}
|
|
2276
2376
|
/**
|
|
2277
2377
|
* Retrieves a frame schema by name.
|
|
@@ -2305,6 +2405,7 @@ class BacktestLogicPrivateService {
|
|
|
2305
2405
|
this.strategyGlobalService = inject(TYPES.strategyGlobalService);
|
|
2306
2406
|
this.exchangeGlobalService = inject(TYPES.exchangeGlobalService);
|
|
2307
2407
|
this.frameGlobalService = inject(TYPES.frameGlobalService);
|
|
2408
|
+
this.methodContextService = inject(TYPES.methodContextService);
|
|
2308
2409
|
}
|
|
2309
2410
|
/**
|
|
2310
2411
|
* Runs backtest for a symbol, streaming closed signals as async generator.
|
|
@@ -2325,9 +2426,21 @@ class BacktestLogicPrivateService {
|
|
|
2325
2426
|
symbol,
|
|
2326
2427
|
});
|
|
2327
2428
|
const timeframes = await this.frameGlobalService.getTimeframe(symbol);
|
|
2429
|
+
const totalFrames = timeframes.length;
|
|
2328
2430
|
let i = 0;
|
|
2329
2431
|
while (i < timeframes.length) {
|
|
2330
2432
|
const when = timeframes[i];
|
|
2433
|
+
// Emit progress event if context is available
|
|
2434
|
+
{
|
|
2435
|
+
await progressEmitter.next({
|
|
2436
|
+
exchangeName: this.methodContextService.context.exchangeName,
|
|
2437
|
+
strategyName: this.methodContextService.context.strategyName,
|
|
2438
|
+
symbol,
|
|
2439
|
+
totalFrames,
|
|
2440
|
+
processedFrames: i,
|
|
2441
|
+
progress: totalFrames > 0 ? i / totalFrames : 0,
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2331
2444
|
const result = await this.strategyGlobalService.tick(symbol, when, true);
|
|
2332
2445
|
// Если сигнал открыт, вызываем backtest
|
|
2333
2446
|
if (result.action === "opened") {
|
|
@@ -2364,6 +2477,17 @@ class BacktestLogicPrivateService {
|
|
|
2364
2477
|
}
|
|
2365
2478
|
i++;
|
|
2366
2479
|
}
|
|
2480
|
+
// Emit final progress event (100%)
|
|
2481
|
+
{
|
|
2482
|
+
await progressEmitter.next({
|
|
2483
|
+
exchangeName: this.methodContextService.context.exchangeName,
|
|
2484
|
+
strategyName: this.methodContextService.context.strategyName,
|
|
2485
|
+
symbol,
|
|
2486
|
+
totalFrames,
|
|
2487
|
+
processedFrames: totalFrames,
|
|
2488
|
+
progress: 1.0,
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2367
2491
|
}
|
|
2368
2492
|
}
|
|
2369
2493
|
|
|
@@ -2718,10 +2842,17 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
2718
2842
|
return str.newline(`# Backtest Report: ${strategyName}`, "", "No signals closed yet.");
|
|
2719
2843
|
}
|
|
2720
2844
|
const header = columns$1.map((col) => col.label);
|
|
2845
|
+
const separator = columns$1.map(() => "---");
|
|
2721
2846
|
const rows = this._signalList.map((closedSignal) => columns$1.map((col) => col.format(closedSignal)));
|
|
2722
|
-
const tableData = [header, ...rows];
|
|
2723
|
-
const table = str.
|
|
2724
|
-
|
|
2847
|
+
const tableData = [header, separator, ...rows];
|
|
2848
|
+
const table = str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
|
|
2849
|
+
// Calculate statistics
|
|
2850
|
+
const totalSignals = this._signalList.length;
|
|
2851
|
+
const winCount = this._signalList.filter((s) => s.pnl.pnlPercentage > 0).length;
|
|
2852
|
+
const lossCount = this._signalList.filter((s) => s.pnl.pnlPercentage < 0).length;
|
|
2853
|
+
const avgPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0) / totalSignals;
|
|
2854
|
+
const totalPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0);
|
|
2855
|
+
return str.newline(`# Backtest Report: ${strategyName}`, "", table, "", `**Total signals:** ${totalSignals}`, `**Closed signals:** ${totalSignals}`, `**Win rate:** ${((winCount / totalSignals) * 100).toFixed(2)}% (${winCount}W / ${lossCount}L)`, `**Average PNL:** ${avgPnl > 0 ? "+" : ""}${avgPnl.toFixed(2)}%`, `**Total PNL:** ${totalPnl > 0 ? "+" : ""}${totalPnl.toFixed(2)}%`);
|
|
2725
2856
|
}
|
|
2726
2857
|
/**
|
|
2727
2858
|
* Saves strategy report to disk.
|
|
@@ -3116,9 +3247,10 @@ class ReportStorage {
|
|
|
3116
3247
|
return str.newline(`# Live Trading Report: ${strategyName}`, "", "No events recorded yet.");
|
|
3117
3248
|
}
|
|
3118
3249
|
const header = columns.map((col) => col.label);
|
|
3250
|
+
const separator = columns.map(() => "---");
|
|
3119
3251
|
const rows = this._eventList.map((event) => columns.map((col) => col.format(event)));
|
|
3120
|
-
const tableData = [header, ...rows];
|
|
3121
|
-
const table = str.
|
|
3252
|
+
const tableData = [header, separator, ...rows];
|
|
3253
|
+
const table = str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
|
|
3122
3254
|
// Calculate statistics
|
|
3123
3255
|
const closedEvents = this._eventList.filter((e) => e.action === "closed");
|
|
3124
3256
|
const totalClosed = closedEvents.length;
|
|
@@ -3127,11 +3259,14 @@ class ReportStorage {
|
|
|
3127
3259
|
const avgPnl = totalClosed > 0
|
|
3128
3260
|
? closedEvents.reduce((sum, e) => sum + (e.pnl || 0), 0) / totalClosed
|
|
3129
3261
|
: 0;
|
|
3130
|
-
|
|
3131
|
-
|
|
3262
|
+
const totalPnl = closedEvents.reduce((sum, e) => sum + (e.pnl || 0), 0);
|
|
3263
|
+
return str.newline(`# Live Trading Report: ${strategyName}`, "", table, "", `**Total events:** ${this._eventList.length}`, `**Closed signals:** ${totalClosed}`, totalClosed > 0
|
|
3264
|
+
? `**Win rate:** ${((winCount / totalClosed) * 100).toFixed(2)}% (${winCount}W / ${lossCount}L)`
|
|
3265
|
+
: "", totalClosed > 0
|
|
3266
|
+
? `**Average PNL:** ${avgPnl > 0 ? "+" : ""}${avgPnl.toFixed(2)}%`
|
|
3132
3267
|
: "", totalClosed > 0
|
|
3133
|
-
?
|
|
3134
|
-
: ""
|
|
3268
|
+
? `**Total PNL:** ${totalPnl > 0 ? "+" : ""}${totalPnl.toFixed(2)}%`
|
|
3269
|
+
: "");
|
|
3135
3270
|
}
|
|
3136
3271
|
/**
|
|
3137
3272
|
* Saves strategy report to disk.
|
|
@@ -3371,6 +3506,15 @@ class ExchangeValidationService {
|
|
|
3371
3506
|
}
|
|
3372
3507
|
return true;
|
|
3373
3508
|
});
|
|
3509
|
+
/**
|
|
3510
|
+
* Returns a list of all registered exchange schemas
|
|
3511
|
+
* @public
|
|
3512
|
+
* @returns Array of exchange schemas with their configurations
|
|
3513
|
+
*/
|
|
3514
|
+
this.list = async () => {
|
|
3515
|
+
this.loggerService.log("exchangeValidationService list");
|
|
3516
|
+
return Array.from(this._exchangeMap.values());
|
|
3517
|
+
};
|
|
3374
3518
|
}
|
|
3375
3519
|
}
|
|
3376
3520
|
|
|
@@ -3423,6 +3567,15 @@ class StrategyValidationService {
|
|
|
3423
3567
|
}
|
|
3424
3568
|
return true;
|
|
3425
3569
|
});
|
|
3570
|
+
/**
|
|
3571
|
+
* Returns a list of all registered strategy schemas
|
|
3572
|
+
* @public
|
|
3573
|
+
* @returns Array of strategy schemas with their configurations
|
|
3574
|
+
*/
|
|
3575
|
+
this.list = async () => {
|
|
3576
|
+
this.loggerService.log("strategyValidationService list");
|
|
3577
|
+
return Array.from(this._strategyMap.values());
|
|
3578
|
+
};
|
|
3426
3579
|
}
|
|
3427
3580
|
}
|
|
3428
3581
|
|
|
@@ -3475,6 +3628,15 @@ class FrameValidationService {
|
|
|
3475
3628
|
}
|
|
3476
3629
|
return true;
|
|
3477
3630
|
});
|
|
3631
|
+
/**
|
|
3632
|
+
* Returns a list of all registered frame schemas
|
|
3633
|
+
* @public
|
|
3634
|
+
* @returns Array of frame schemas with their configurations
|
|
3635
|
+
*/
|
|
3636
|
+
this.list = async () => {
|
|
3637
|
+
this.loggerService.log("frameValidationService list");
|
|
3638
|
+
return Array.from(this._frameMap.values());
|
|
3639
|
+
};
|
|
3478
3640
|
}
|
|
3479
3641
|
}
|
|
3480
3642
|
|
|
@@ -3720,6 +3882,102 @@ function addFrame(frameSchema) {
|
|
|
3720
3882
|
backtest$1.frameSchemaService.register(frameSchema.frameName, frameSchema);
|
|
3721
3883
|
}
|
|
3722
3884
|
|
|
3885
|
+
const LIST_EXCHANGES_METHOD_NAME = "list.listExchanges";
|
|
3886
|
+
const LIST_STRATEGIES_METHOD_NAME = "list.listStrategies";
|
|
3887
|
+
const LIST_FRAMES_METHOD_NAME = "list.listFrames";
|
|
3888
|
+
/**
|
|
3889
|
+
* Returns a list of all registered exchange schemas.
|
|
3890
|
+
*
|
|
3891
|
+
* Retrieves all exchanges that have been registered via addExchange().
|
|
3892
|
+
* Useful for debugging, documentation, or building dynamic UIs.
|
|
3893
|
+
*
|
|
3894
|
+
* @returns Array of exchange schemas with their configurations
|
|
3895
|
+
*
|
|
3896
|
+
* @example
|
|
3897
|
+
* ```typescript
|
|
3898
|
+
* import { listExchanges, addExchange } from "backtest-kit";
|
|
3899
|
+
*
|
|
3900
|
+
* addExchange({
|
|
3901
|
+
* exchangeName: "binance",
|
|
3902
|
+
* note: "Binance cryptocurrency exchange",
|
|
3903
|
+
* getCandles: async (symbol, interval, since, limit) => [...],
|
|
3904
|
+
* formatPrice: async (symbol, price) => price.toFixed(2),
|
|
3905
|
+
* formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
|
|
3906
|
+
* });
|
|
3907
|
+
*
|
|
3908
|
+
* const exchanges = listExchanges();
|
|
3909
|
+
* console.log(exchanges);
|
|
3910
|
+
* // [{ exchangeName: "binance", note: "Binance cryptocurrency exchange", ... }]
|
|
3911
|
+
* ```
|
|
3912
|
+
*/
|
|
3913
|
+
async function listExchanges() {
|
|
3914
|
+
backtest$1.loggerService.log(LIST_EXCHANGES_METHOD_NAME);
|
|
3915
|
+
return await backtest$1.exchangeValidationService.list();
|
|
3916
|
+
}
|
|
3917
|
+
/**
|
|
3918
|
+
* Returns a list of all registered strategy schemas.
|
|
3919
|
+
*
|
|
3920
|
+
* Retrieves all strategies that have been registered via addStrategy().
|
|
3921
|
+
* Useful for debugging, documentation, or building dynamic UIs.
|
|
3922
|
+
*
|
|
3923
|
+
* @returns Array of strategy schemas with their configurations
|
|
3924
|
+
*
|
|
3925
|
+
* @example
|
|
3926
|
+
* ```typescript
|
|
3927
|
+
* import { listStrategies, addStrategy } from "backtest-kit";
|
|
3928
|
+
*
|
|
3929
|
+
* addStrategy({
|
|
3930
|
+
* strategyName: "my-strategy",
|
|
3931
|
+
* note: "Simple moving average crossover strategy",
|
|
3932
|
+
* interval: "5m",
|
|
3933
|
+
* getSignal: async (symbol) => ({
|
|
3934
|
+
* position: "long",
|
|
3935
|
+
* priceOpen: 50000,
|
|
3936
|
+
* priceTakeProfit: 51000,
|
|
3937
|
+
* priceStopLoss: 49000,
|
|
3938
|
+
* minuteEstimatedTime: 60,
|
|
3939
|
+
* }),
|
|
3940
|
+
* });
|
|
3941
|
+
*
|
|
3942
|
+
* const strategies = listStrategies();
|
|
3943
|
+
* console.log(strategies);
|
|
3944
|
+
* // [{ strategyName: "my-strategy", note: "Simple moving average...", ... }]
|
|
3945
|
+
* ```
|
|
3946
|
+
*/
|
|
3947
|
+
async function listStrategies() {
|
|
3948
|
+
backtest$1.loggerService.log(LIST_STRATEGIES_METHOD_NAME);
|
|
3949
|
+
return await backtest$1.strategyValidationService.list();
|
|
3950
|
+
}
|
|
3951
|
+
/**
|
|
3952
|
+
* Returns a list of all registered frame schemas.
|
|
3953
|
+
*
|
|
3954
|
+
* Retrieves all frames that have been registered via addFrame().
|
|
3955
|
+
* Useful for debugging, documentation, or building dynamic UIs.
|
|
3956
|
+
*
|
|
3957
|
+
* @returns Array of frame schemas with their configurations
|
|
3958
|
+
*
|
|
3959
|
+
* @example
|
|
3960
|
+
* ```typescript
|
|
3961
|
+
* import { listFrames, addFrame } from "backtest-kit";
|
|
3962
|
+
*
|
|
3963
|
+
* addFrame({
|
|
3964
|
+
* frameName: "1d-backtest",
|
|
3965
|
+
* note: "One day backtest period for testing",
|
|
3966
|
+
* interval: "1m",
|
|
3967
|
+
* startDate: new Date("2024-01-01T00:00:00Z"),
|
|
3968
|
+
* endDate: new Date("2024-01-02T00:00:00Z"),
|
|
3969
|
+
* });
|
|
3970
|
+
*
|
|
3971
|
+
* const frames = listFrames();
|
|
3972
|
+
* console.log(frames);
|
|
3973
|
+
* // [{ frameName: "1d-backtest", note: "One day backtest...", ... }]
|
|
3974
|
+
* ```
|
|
3975
|
+
*/
|
|
3976
|
+
async function listFrames() {
|
|
3977
|
+
backtest$1.loggerService.log(LIST_FRAMES_METHOD_NAME);
|
|
3978
|
+
return await backtest$1.frameValidationService.list();
|
|
3979
|
+
}
|
|
3980
|
+
|
|
3723
3981
|
const LISTEN_SIGNAL_METHOD_NAME = "event.listenSignal";
|
|
3724
3982
|
const LISTEN_SIGNAL_ONCE_METHOD_NAME = "event.listenSignalOnce";
|
|
3725
3983
|
const LISTEN_SIGNAL_LIVE_METHOD_NAME = "event.listenSignalLive";
|
|
@@ -3729,6 +3987,7 @@ const LISTEN_SIGNAL_BACKTEST_ONCE_METHOD_NAME = "event.listenSignalBacktestOnce"
|
|
|
3729
3987
|
const LISTEN_ERROR_METHOD_NAME = "event.listenError";
|
|
3730
3988
|
const LISTEN_DONE_METHOD_NAME = "event.listenDone";
|
|
3731
3989
|
const LISTEN_DONE_ONCE_METHOD_NAME = "event.listenDoneOnce";
|
|
3990
|
+
const LISTEN_PROGRESS_METHOD_NAME = "event.listenProgress";
|
|
3732
3991
|
/**
|
|
3733
3992
|
* Subscribes to all signal events with queued async processing.
|
|
3734
3993
|
*
|
|
@@ -3984,6 +4243,40 @@ function listenDoneOnce(filterFn, fn) {
|
|
|
3984
4243
|
backtest$1.loggerService.log(LISTEN_DONE_ONCE_METHOD_NAME);
|
|
3985
4244
|
return doneEmitter.filter(filterFn).once(fn);
|
|
3986
4245
|
}
|
|
4246
|
+
/**
|
|
4247
|
+
* Subscribes to backtest progress events with queued async processing.
|
|
4248
|
+
*
|
|
4249
|
+
* Emits during Backtest.background() execution to track progress.
|
|
4250
|
+
* Events are processed sequentially in order received, even if callback is async.
|
|
4251
|
+
* Uses queued wrapper to prevent concurrent execution of the callback.
|
|
4252
|
+
*
|
|
4253
|
+
* @param fn - Callback function to handle progress events
|
|
4254
|
+
* @returns Unsubscribe function to stop listening to events
|
|
4255
|
+
*
|
|
4256
|
+
* @example
|
|
4257
|
+
* ```typescript
|
|
4258
|
+
* import { listenProgress, Backtest } from "backtest-kit";
|
|
4259
|
+
*
|
|
4260
|
+
* const unsubscribe = listenProgress((event) => {
|
|
4261
|
+
* console.log(`Progress: ${(event.progress * 100).toFixed(2)}%`);
|
|
4262
|
+
* console.log(`${event.processedFrames} / ${event.totalFrames} frames`);
|
|
4263
|
+
* console.log(`Strategy: ${event.strategyName}, Symbol: ${event.symbol}`);
|
|
4264
|
+
* });
|
|
4265
|
+
*
|
|
4266
|
+
* Backtest.background("BTCUSDT", {
|
|
4267
|
+
* strategyName: "my-strategy",
|
|
4268
|
+
* exchangeName: "binance",
|
|
4269
|
+
* frameName: "1d-backtest"
|
|
4270
|
+
* });
|
|
4271
|
+
*
|
|
4272
|
+
* // Later: stop listening
|
|
4273
|
+
* unsubscribe();
|
|
4274
|
+
* ```
|
|
4275
|
+
*/
|
|
4276
|
+
function listenProgress(fn) {
|
|
4277
|
+
backtest$1.loggerService.log(LISTEN_PROGRESS_METHOD_NAME);
|
|
4278
|
+
return progressEmitter.subscribe(queued(async (event) => fn(event)));
|
|
4279
|
+
}
|
|
3987
4280
|
|
|
3988
4281
|
const GET_CANDLES_METHOD_NAME = "exchange.getCandles";
|
|
3989
4282
|
const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
|
|
@@ -4162,6 +4455,7 @@ class BacktestUtils {
|
|
|
4162
4455
|
context,
|
|
4163
4456
|
});
|
|
4164
4457
|
backtest$1.backtestMarkdownService.clear(context.strategyName);
|
|
4458
|
+
backtest$1.strategyGlobalService.clear(context.strategyName);
|
|
4165
4459
|
return backtest$1.backtestGlobalService.run(symbol, context);
|
|
4166
4460
|
};
|
|
4167
4461
|
/**
|
|
@@ -4190,14 +4484,9 @@ class BacktestUtils {
|
|
|
4190
4484
|
symbol,
|
|
4191
4485
|
context,
|
|
4192
4486
|
});
|
|
4193
|
-
const iterator = this.run(symbol, context);
|
|
4194
4487
|
let isStopped = false;
|
|
4195
4488
|
const task = async () => {
|
|
4196
|
-
|
|
4197
|
-
const { done } = await iterator.next();
|
|
4198
|
-
if (done) {
|
|
4199
|
-
break;
|
|
4200
|
-
}
|
|
4489
|
+
for await (const _ of this.run(symbol, context)) {
|
|
4201
4490
|
if (isStopped) {
|
|
4202
4491
|
break;
|
|
4203
4492
|
}
|
|
@@ -4211,6 +4500,7 @@ class BacktestUtils {
|
|
|
4211
4500
|
};
|
|
4212
4501
|
task().catch((error) => errorEmitter.next(new Error(getErrorMessage(error))));
|
|
4213
4502
|
return () => {
|
|
4503
|
+
backtest$1.strategyGlobalService.stop(context.strategyName);
|
|
4214
4504
|
isStopped = true;
|
|
4215
4505
|
};
|
|
4216
4506
|
};
|
|
@@ -4327,6 +4617,7 @@ class LiveUtils {
|
|
|
4327
4617
|
context,
|
|
4328
4618
|
});
|
|
4329
4619
|
backtest$1.liveMarkdownService.clear(context.strategyName);
|
|
4620
|
+
backtest$1.strategyGlobalService.clear(context.strategyName);
|
|
4330
4621
|
return backtest$1.liveGlobalService.run(symbol, context);
|
|
4331
4622
|
};
|
|
4332
4623
|
/**
|
|
@@ -4355,19 +4646,10 @@ class LiveUtils {
|
|
|
4355
4646
|
symbol,
|
|
4356
4647
|
context,
|
|
4357
4648
|
});
|
|
4358
|
-
const iterator = this.run(symbol, context);
|
|
4359
4649
|
let isStopped = false;
|
|
4360
|
-
let lastValue = null;
|
|
4361
4650
|
const task = async () => {
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
if (value) {
|
|
4365
|
-
lastValue = value;
|
|
4366
|
-
}
|
|
4367
|
-
if (done) {
|
|
4368
|
-
break;
|
|
4369
|
-
}
|
|
4370
|
-
if (lastValue?.action === "closed" && isStopped) {
|
|
4651
|
+
for await (const signal of this.run(symbol, context)) {
|
|
4652
|
+
if (signal?.action === "closed" && isStopped) {
|
|
4371
4653
|
break;
|
|
4372
4654
|
}
|
|
4373
4655
|
}
|
|
@@ -4380,6 +4662,7 @@ class LiveUtils {
|
|
|
4380
4662
|
};
|
|
4381
4663
|
task().catch((error) => errorEmitter.next(new Error(getErrorMessage(error))));
|
|
4382
4664
|
return () => {
|
|
4665
|
+
backtest$1.strategyGlobalService.stop(context.strategyName);
|
|
4383
4666
|
isStopped = true;
|
|
4384
4667
|
};
|
|
4385
4668
|
};
|
|
@@ -4442,4 +4725,4 @@ class LiveUtils {
|
|
|
4442
4725
|
*/
|
|
4443
4726
|
const Live = new LiveUtils();
|
|
4444
4727
|
|
|
4445
|
-
export { Backtest, ExecutionContextService, Live, MethodContextService, PersistBase, PersistSignalAdaper, addExchange, addFrame, addStrategy, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listenDone, listenDoneOnce, listenError, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, setLogger };
|
|
4728
|
+
export { Backtest, ExecutionContextService, Live, MethodContextService, PersistBase, PersistSignalAdaper, addExchange, addFrame, addStrategy, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listExchanges, listFrames, listStrategies, listenDone, listenDoneOnce, listenError, listenProgress, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, setLogger };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backtest-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "A TypeScript library for trading system backtest",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Petr Tripolsky",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "rollup -c",
|
|
42
|
+
"test": "npm run build && node ./test/index.mjs",
|
|
42
43
|
"build:docs": "rimraf docs && mkdir docs && node ./scripts/dts-docs.cjs ./types.d.ts ./docs",
|
|
43
44
|
"docs:gpt": "npm run build && node ./scripts/gpt-docs.mjs",
|
|
44
45
|
"docs:uml": "npm run build && node ./scripts/uml.mjs",
|