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.cjs
CHANGED
|
@@ -1104,6 +1104,37 @@ class PersistSignalUtils {
|
|
|
1104
1104
|
*/
|
|
1105
1105
|
const PersistSignalAdaper = new PersistSignalUtils();
|
|
1106
1106
|
|
|
1107
|
+
/**
|
|
1108
|
+
* Global signal emitter for all trading events (live + backtest).
|
|
1109
|
+
* Emits all signal events regardless of execution mode.
|
|
1110
|
+
*/
|
|
1111
|
+
const signalEmitter = new functoolsKit.Subject();
|
|
1112
|
+
/**
|
|
1113
|
+
* Live trading signal emitter.
|
|
1114
|
+
* Emits only signals from live trading execution.
|
|
1115
|
+
*/
|
|
1116
|
+
const signalLiveEmitter = new functoolsKit.Subject();
|
|
1117
|
+
/**
|
|
1118
|
+
* Backtest signal emitter.
|
|
1119
|
+
* Emits only signals from backtest execution.
|
|
1120
|
+
*/
|
|
1121
|
+
const signalBacktestEmitter = new functoolsKit.Subject();
|
|
1122
|
+
/**
|
|
1123
|
+
* Error emitter for background execution errors.
|
|
1124
|
+
* Emits errors caught in background tasks (Live.background, Backtest.background).
|
|
1125
|
+
*/
|
|
1126
|
+
const errorEmitter = new functoolsKit.Subject();
|
|
1127
|
+
/**
|
|
1128
|
+
* Done emitter for background execution completion.
|
|
1129
|
+
* Emits when background tasks complete (Live.background, Backtest.background).
|
|
1130
|
+
*/
|
|
1131
|
+
const doneEmitter = new functoolsKit.Subject();
|
|
1132
|
+
/**
|
|
1133
|
+
* Progress emitter for backtest execution progress.
|
|
1134
|
+
* Emits progress updates during backtest execution.
|
|
1135
|
+
*/
|
|
1136
|
+
const progressEmitter = new functoolsKit.Subject();
|
|
1137
|
+
|
|
1107
1138
|
const INTERVAL_MINUTES$1 = {
|
|
1108
1139
|
"1m": 1,
|
|
1109
1140
|
"3m": 3,
|
|
@@ -1155,6 +1186,9 @@ const VALIDATE_SIGNAL_FN = (signal) => {
|
|
|
1155
1186
|
}
|
|
1156
1187
|
};
|
|
1157
1188
|
const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
1189
|
+
if (self._isStopped) {
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1158
1192
|
const currentTime = self.params.execution.context.when.getTime();
|
|
1159
1193
|
{
|
|
1160
1194
|
const intervalMinutes = INTERVAL_MINUTES$1[self.params.interval];
|
|
@@ -1183,6 +1217,13 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
1183
1217
|
return signalRow;
|
|
1184
1218
|
}, {
|
|
1185
1219
|
defaultValue: null,
|
|
1220
|
+
fallback: (error) => {
|
|
1221
|
+
backtest$1.loggerService.warn("ClientStrategy exception thrown", {
|
|
1222
|
+
error: functoolsKit.errorData(error),
|
|
1223
|
+
message: functoolsKit.getErrorMessage(error),
|
|
1224
|
+
});
|
|
1225
|
+
errorEmitter.next(error);
|
|
1226
|
+
},
|
|
1186
1227
|
});
|
|
1187
1228
|
const GET_AVG_PRICE_FN = (candles) => {
|
|
1188
1229
|
const sumPriceVolume = candles.reduce((acc, c) => {
|
|
@@ -1241,6 +1282,7 @@ const WAIT_FOR_INIT_FN = async (self) => {
|
|
|
1241
1282
|
class ClientStrategy {
|
|
1242
1283
|
constructor(params) {
|
|
1243
1284
|
this.params = params;
|
|
1285
|
+
this._isStopped = false;
|
|
1244
1286
|
this._pendingSignal = null;
|
|
1245
1287
|
this._lastSignalTimestamp = null;
|
|
1246
1288
|
/**
|
|
@@ -1563,34 +1605,32 @@ class ClientStrategy {
|
|
|
1563
1605
|
}
|
|
1564
1606
|
return result;
|
|
1565
1607
|
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Stops the strategy from generating new signals.
|
|
1610
|
+
*
|
|
1611
|
+
* Sets internal flag to prevent getSignal from being called.
|
|
1612
|
+
* Does NOT close active pending signals - they continue monitoring until TP/SL/time_expired.
|
|
1613
|
+
*
|
|
1614
|
+
* Use case: Graceful shutdown in live trading without forcing position closure.
|
|
1615
|
+
*
|
|
1616
|
+
* @returns Promise that resolves immediately when stop flag is set
|
|
1617
|
+
*
|
|
1618
|
+
* @example
|
|
1619
|
+
* ```typescript
|
|
1620
|
+
* // In Live.background() cancellation
|
|
1621
|
+
* await strategy.stop();
|
|
1622
|
+
* // Existing signal will continue until natural close
|
|
1623
|
+
* ```
|
|
1624
|
+
*/
|
|
1625
|
+
stop() {
|
|
1626
|
+
this.params.logger.debug("ClientStrategy stop", {
|
|
1627
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
1628
|
+
});
|
|
1629
|
+
this._isStopped = true;
|
|
1630
|
+
return Promise.resolve();
|
|
1631
|
+
}
|
|
1566
1632
|
}
|
|
1567
1633
|
|
|
1568
|
-
/**
|
|
1569
|
-
* Global signal emitter for all trading events (live + backtest).
|
|
1570
|
-
* Emits all signal events regardless of execution mode.
|
|
1571
|
-
*/
|
|
1572
|
-
const signalEmitter = new functoolsKit.Subject();
|
|
1573
|
-
/**
|
|
1574
|
-
* Live trading signal emitter.
|
|
1575
|
-
* Emits only signals from live trading execution.
|
|
1576
|
-
*/
|
|
1577
|
-
const signalLiveEmitter = new functoolsKit.Subject();
|
|
1578
|
-
/**
|
|
1579
|
-
* Backtest signal emitter.
|
|
1580
|
-
* Emits only signals from backtest execution.
|
|
1581
|
-
*/
|
|
1582
|
-
const signalBacktestEmitter = new functoolsKit.Subject();
|
|
1583
|
-
/**
|
|
1584
|
-
* Error emitter for background execution errors.
|
|
1585
|
-
* Emits errors caught in background tasks (Live.background, Backtest.background).
|
|
1586
|
-
*/
|
|
1587
|
-
const errorEmitter = new functoolsKit.Subject();
|
|
1588
|
-
/**
|
|
1589
|
-
* Done emitter for background execution completion.
|
|
1590
|
-
* Emits when background tasks complete (Live.background, Backtest.background).
|
|
1591
|
-
*/
|
|
1592
|
-
const doneEmitter = new functoolsKit.Subject();
|
|
1593
|
-
|
|
1594
1634
|
/**
|
|
1595
1635
|
* Connection service routing strategy operations to correct ClientStrategy instance.
|
|
1596
1636
|
*
|
|
@@ -1689,6 +1729,36 @@ class StrategyConnectionService {
|
|
|
1689
1729
|
}
|
|
1690
1730
|
return tick;
|
|
1691
1731
|
};
|
|
1732
|
+
/**
|
|
1733
|
+
* Stops the specified strategy from generating new signals.
|
|
1734
|
+
*
|
|
1735
|
+
* Delegates to ClientStrategy.stop() which sets internal flag to prevent
|
|
1736
|
+
* getSignal from being called on subsequent ticks.
|
|
1737
|
+
*
|
|
1738
|
+
* @param strategyName - Name of strategy to stop
|
|
1739
|
+
* @returns Promise that resolves when stop flag is set
|
|
1740
|
+
*/
|
|
1741
|
+
this.stop = async (strategyName) => {
|
|
1742
|
+
this.loggerService.log("strategyConnectionService stop", {
|
|
1743
|
+
strategyName,
|
|
1744
|
+
});
|
|
1745
|
+
const strategy = this.getStrategy(strategyName);
|
|
1746
|
+
await strategy.stop();
|
|
1747
|
+
};
|
|
1748
|
+
/**
|
|
1749
|
+
* Clears the memoized ClientStrategy instance from cache.
|
|
1750
|
+
*
|
|
1751
|
+
* Forces re-initialization of strategy on next getStrategy call.
|
|
1752
|
+
* Useful for resetting strategy state or releasing resources.
|
|
1753
|
+
*
|
|
1754
|
+
* @param strategyName - Name of strategy to clear from cache
|
|
1755
|
+
*/
|
|
1756
|
+
this.clear = async (strategyName) => {
|
|
1757
|
+
this.loggerService.log("strategyConnectionService clear", {
|
|
1758
|
+
strategyName,
|
|
1759
|
+
});
|
|
1760
|
+
this.getStrategy.clear(strategyName);
|
|
1761
|
+
};
|
|
1692
1762
|
}
|
|
1693
1763
|
}
|
|
1694
1764
|
|
|
@@ -2032,6 +2102,35 @@ class StrategyGlobalService {
|
|
|
2032
2102
|
backtest,
|
|
2033
2103
|
});
|
|
2034
2104
|
};
|
|
2105
|
+
/**
|
|
2106
|
+
* Stops the strategy from generating new signals.
|
|
2107
|
+
*
|
|
2108
|
+
* Delegates to StrategyConnectionService.stop() to set internal flag.
|
|
2109
|
+
* Does not require execution context.
|
|
2110
|
+
*
|
|
2111
|
+
* @param strategyName - Name of strategy to stop
|
|
2112
|
+
* @returns Promise that resolves when stop flag is set
|
|
2113
|
+
*/
|
|
2114
|
+
this.stop = async (strategyName) => {
|
|
2115
|
+
this.loggerService.log("strategyGlobalService stop", {
|
|
2116
|
+
strategyName,
|
|
2117
|
+
});
|
|
2118
|
+
return await this.strategyConnectionService.stop(strategyName);
|
|
2119
|
+
};
|
|
2120
|
+
/**
|
|
2121
|
+
* Clears the memoized ClientStrategy instance from cache.
|
|
2122
|
+
*
|
|
2123
|
+
* Delegates to StrategyConnectionService.clear() to remove strategy from cache.
|
|
2124
|
+
* Forces re-initialization of strategy on next operation.
|
|
2125
|
+
*
|
|
2126
|
+
* @param strategyName - Name of strategy to clear from cache
|
|
2127
|
+
*/
|
|
2128
|
+
this.clear = async (strategyName) => {
|
|
2129
|
+
this.loggerService.log("strategyGlobalService clear", {
|
|
2130
|
+
strategyName,
|
|
2131
|
+
});
|
|
2132
|
+
return await this.strategyConnectionService.clear(strategyName);
|
|
2133
|
+
};
|
|
2035
2134
|
}
|
|
2036
2135
|
}
|
|
2037
2136
|
|
|
@@ -2262,7 +2361,7 @@ class FrameSchemaService {
|
|
|
2262
2361
|
register(key, value) {
|
|
2263
2362
|
this.loggerService.log(`frameSchemaService register`, { key });
|
|
2264
2363
|
this.validateShallow(value);
|
|
2265
|
-
this._registry.register(key, value);
|
|
2364
|
+
this._registry = this._registry.register(key, value);
|
|
2266
2365
|
}
|
|
2267
2366
|
/**
|
|
2268
2367
|
* Overrides an existing frame schema with partial updates.
|
|
@@ -2273,7 +2372,8 @@ class FrameSchemaService {
|
|
|
2273
2372
|
*/
|
|
2274
2373
|
override(key, value) {
|
|
2275
2374
|
this.loggerService.log(`frameSchemaService override`, { key });
|
|
2276
|
-
this._registry.override(key, value);
|
|
2375
|
+
this._registry = this._registry.override(key, value);
|
|
2376
|
+
return this._registry.get(key);
|
|
2277
2377
|
}
|
|
2278
2378
|
/**
|
|
2279
2379
|
* Retrieves a frame schema by name.
|
|
@@ -2307,6 +2407,7 @@ class BacktestLogicPrivateService {
|
|
|
2307
2407
|
this.strategyGlobalService = inject(TYPES.strategyGlobalService);
|
|
2308
2408
|
this.exchangeGlobalService = inject(TYPES.exchangeGlobalService);
|
|
2309
2409
|
this.frameGlobalService = inject(TYPES.frameGlobalService);
|
|
2410
|
+
this.methodContextService = inject(TYPES.methodContextService);
|
|
2310
2411
|
}
|
|
2311
2412
|
/**
|
|
2312
2413
|
* Runs backtest for a symbol, streaming closed signals as async generator.
|
|
@@ -2327,9 +2428,21 @@ class BacktestLogicPrivateService {
|
|
|
2327
2428
|
symbol,
|
|
2328
2429
|
});
|
|
2329
2430
|
const timeframes = await this.frameGlobalService.getTimeframe(symbol);
|
|
2431
|
+
const totalFrames = timeframes.length;
|
|
2330
2432
|
let i = 0;
|
|
2331
2433
|
while (i < timeframes.length) {
|
|
2332
2434
|
const when = timeframes[i];
|
|
2435
|
+
// Emit progress event if context is available
|
|
2436
|
+
{
|
|
2437
|
+
await progressEmitter.next({
|
|
2438
|
+
exchangeName: this.methodContextService.context.exchangeName,
|
|
2439
|
+
strategyName: this.methodContextService.context.strategyName,
|
|
2440
|
+
symbol,
|
|
2441
|
+
totalFrames,
|
|
2442
|
+
processedFrames: i,
|
|
2443
|
+
progress: totalFrames > 0 ? i / totalFrames : 0,
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2333
2446
|
const result = await this.strategyGlobalService.tick(symbol, when, true);
|
|
2334
2447
|
// Если сигнал открыт, вызываем backtest
|
|
2335
2448
|
if (result.action === "opened") {
|
|
@@ -2366,6 +2479,17 @@ class BacktestLogicPrivateService {
|
|
|
2366
2479
|
}
|
|
2367
2480
|
i++;
|
|
2368
2481
|
}
|
|
2482
|
+
// Emit final progress event (100%)
|
|
2483
|
+
{
|
|
2484
|
+
await progressEmitter.next({
|
|
2485
|
+
exchangeName: this.methodContextService.context.exchangeName,
|
|
2486
|
+
strategyName: this.methodContextService.context.strategyName,
|
|
2487
|
+
symbol,
|
|
2488
|
+
totalFrames,
|
|
2489
|
+
processedFrames: totalFrames,
|
|
2490
|
+
progress: 1.0,
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2369
2493
|
}
|
|
2370
2494
|
}
|
|
2371
2495
|
|
|
@@ -2720,10 +2844,17 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
2720
2844
|
return functoolsKit.str.newline(`# Backtest Report: ${strategyName}`, "", "No signals closed yet.");
|
|
2721
2845
|
}
|
|
2722
2846
|
const header = columns$1.map((col) => col.label);
|
|
2847
|
+
const separator = columns$1.map(() => "---");
|
|
2723
2848
|
const rows = this._signalList.map((closedSignal) => columns$1.map((col) => col.format(closedSignal)));
|
|
2724
|
-
const tableData = [header, ...rows];
|
|
2725
|
-
const table = functoolsKit.str.
|
|
2726
|
-
|
|
2849
|
+
const tableData = [header, separator, ...rows];
|
|
2850
|
+
const table = functoolsKit.str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
|
|
2851
|
+
// Calculate statistics
|
|
2852
|
+
const totalSignals = this._signalList.length;
|
|
2853
|
+
const winCount = this._signalList.filter((s) => s.pnl.pnlPercentage > 0).length;
|
|
2854
|
+
const lossCount = this._signalList.filter((s) => s.pnl.pnlPercentage < 0).length;
|
|
2855
|
+
const avgPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0) / totalSignals;
|
|
2856
|
+
const totalPnl = this._signalList.reduce((sum, s) => sum + s.pnl.pnlPercentage, 0);
|
|
2857
|
+
return functoolsKit.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)}%`);
|
|
2727
2858
|
}
|
|
2728
2859
|
/**
|
|
2729
2860
|
* Saves strategy report to disk.
|
|
@@ -3118,9 +3249,10 @@ class ReportStorage {
|
|
|
3118
3249
|
return functoolsKit.str.newline(`# Live Trading Report: ${strategyName}`, "", "No events recorded yet.");
|
|
3119
3250
|
}
|
|
3120
3251
|
const header = columns.map((col) => col.label);
|
|
3252
|
+
const separator = columns.map(() => "---");
|
|
3121
3253
|
const rows = this._eventList.map((event) => columns.map((col) => col.format(event)));
|
|
3122
|
-
const tableData = [header, ...rows];
|
|
3123
|
-
const table = functoolsKit.str.
|
|
3254
|
+
const tableData = [header, separator, ...rows];
|
|
3255
|
+
const table = functoolsKit.str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
|
|
3124
3256
|
// Calculate statistics
|
|
3125
3257
|
const closedEvents = this._eventList.filter((e) => e.action === "closed");
|
|
3126
3258
|
const totalClosed = closedEvents.length;
|
|
@@ -3129,11 +3261,14 @@ class ReportStorage {
|
|
|
3129
3261
|
const avgPnl = totalClosed > 0
|
|
3130
3262
|
? closedEvents.reduce((sum, e) => sum + (e.pnl || 0), 0) / totalClosed
|
|
3131
3263
|
: 0;
|
|
3132
|
-
|
|
3133
|
-
|
|
3264
|
+
const totalPnl = closedEvents.reduce((sum, e) => sum + (e.pnl || 0), 0);
|
|
3265
|
+
return functoolsKit.str.newline(`# Live Trading Report: ${strategyName}`, "", table, "", `**Total events:** ${this._eventList.length}`, `**Closed signals:** ${totalClosed}`, totalClosed > 0
|
|
3266
|
+
? `**Win rate:** ${((winCount / totalClosed) * 100).toFixed(2)}% (${winCount}W / ${lossCount}L)`
|
|
3267
|
+
: "", totalClosed > 0
|
|
3268
|
+
? `**Average PNL:** ${avgPnl > 0 ? "+" : ""}${avgPnl.toFixed(2)}%`
|
|
3134
3269
|
: "", totalClosed > 0
|
|
3135
|
-
?
|
|
3136
|
-
: ""
|
|
3270
|
+
? `**Total PNL:** ${totalPnl > 0 ? "+" : ""}${totalPnl.toFixed(2)}%`
|
|
3271
|
+
: "");
|
|
3137
3272
|
}
|
|
3138
3273
|
/**
|
|
3139
3274
|
* Saves strategy report to disk.
|
|
@@ -3373,6 +3508,15 @@ class ExchangeValidationService {
|
|
|
3373
3508
|
}
|
|
3374
3509
|
return true;
|
|
3375
3510
|
});
|
|
3511
|
+
/**
|
|
3512
|
+
* Returns a list of all registered exchange schemas
|
|
3513
|
+
* @public
|
|
3514
|
+
* @returns Array of exchange schemas with their configurations
|
|
3515
|
+
*/
|
|
3516
|
+
this.list = async () => {
|
|
3517
|
+
this.loggerService.log("exchangeValidationService list");
|
|
3518
|
+
return Array.from(this._exchangeMap.values());
|
|
3519
|
+
};
|
|
3376
3520
|
}
|
|
3377
3521
|
}
|
|
3378
3522
|
|
|
@@ -3425,6 +3569,15 @@ class StrategyValidationService {
|
|
|
3425
3569
|
}
|
|
3426
3570
|
return true;
|
|
3427
3571
|
});
|
|
3572
|
+
/**
|
|
3573
|
+
* Returns a list of all registered strategy schemas
|
|
3574
|
+
* @public
|
|
3575
|
+
* @returns Array of strategy schemas with their configurations
|
|
3576
|
+
*/
|
|
3577
|
+
this.list = async () => {
|
|
3578
|
+
this.loggerService.log("strategyValidationService list");
|
|
3579
|
+
return Array.from(this._strategyMap.values());
|
|
3580
|
+
};
|
|
3428
3581
|
}
|
|
3429
3582
|
}
|
|
3430
3583
|
|
|
@@ -3477,6 +3630,15 @@ class FrameValidationService {
|
|
|
3477
3630
|
}
|
|
3478
3631
|
return true;
|
|
3479
3632
|
});
|
|
3633
|
+
/**
|
|
3634
|
+
* Returns a list of all registered frame schemas
|
|
3635
|
+
* @public
|
|
3636
|
+
* @returns Array of frame schemas with their configurations
|
|
3637
|
+
*/
|
|
3638
|
+
this.list = async () => {
|
|
3639
|
+
this.loggerService.log("frameValidationService list");
|
|
3640
|
+
return Array.from(this._frameMap.values());
|
|
3641
|
+
};
|
|
3480
3642
|
}
|
|
3481
3643
|
}
|
|
3482
3644
|
|
|
@@ -3722,6 +3884,102 @@ function addFrame(frameSchema) {
|
|
|
3722
3884
|
backtest$1.frameSchemaService.register(frameSchema.frameName, frameSchema);
|
|
3723
3885
|
}
|
|
3724
3886
|
|
|
3887
|
+
const LIST_EXCHANGES_METHOD_NAME = "list.listExchanges";
|
|
3888
|
+
const LIST_STRATEGIES_METHOD_NAME = "list.listStrategies";
|
|
3889
|
+
const LIST_FRAMES_METHOD_NAME = "list.listFrames";
|
|
3890
|
+
/**
|
|
3891
|
+
* Returns a list of all registered exchange schemas.
|
|
3892
|
+
*
|
|
3893
|
+
* Retrieves all exchanges that have been registered via addExchange().
|
|
3894
|
+
* Useful for debugging, documentation, or building dynamic UIs.
|
|
3895
|
+
*
|
|
3896
|
+
* @returns Array of exchange schemas with their configurations
|
|
3897
|
+
*
|
|
3898
|
+
* @example
|
|
3899
|
+
* ```typescript
|
|
3900
|
+
* import { listExchanges, addExchange } from "backtest-kit";
|
|
3901
|
+
*
|
|
3902
|
+
* addExchange({
|
|
3903
|
+
* exchangeName: "binance",
|
|
3904
|
+
* note: "Binance cryptocurrency exchange",
|
|
3905
|
+
* getCandles: async (symbol, interval, since, limit) => [...],
|
|
3906
|
+
* formatPrice: async (symbol, price) => price.toFixed(2),
|
|
3907
|
+
* formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
|
|
3908
|
+
* });
|
|
3909
|
+
*
|
|
3910
|
+
* const exchanges = listExchanges();
|
|
3911
|
+
* console.log(exchanges);
|
|
3912
|
+
* // [{ exchangeName: "binance", note: "Binance cryptocurrency exchange", ... }]
|
|
3913
|
+
* ```
|
|
3914
|
+
*/
|
|
3915
|
+
async function listExchanges() {
|
|
3916
|
+
backtest$1.loggerService.log(LIST_EXCHANGES_METHOD_NAME);
|
|
3917
|
+
return await backtest$1.exchangeValidationService.list();
|
|
3918
|
+
}
|
|
3919
|
+
/**
|
|
3920
|
+
* Returns a list of all registered strategy schemas.
|
|
3921
|
+
*
|
|
3922
|
+
* Retrieves all strategies that have been registered via addStrategy().
|
|
3923
|
+
* Useful for debugging, documentation, or building dynamic UIs.
|
|
3924
|
+
*
|
|
3925
|
+
* @returns Array of strategy schemas with their configurations
|
|
3926
|
+
*
|
|
3927
|
+
* @example
|
|
3928
|
+
* ```typescript
|
|
3929
|
+
* import { listStrategies, addStrategy } from "backtest-kit";
|
|
3930
|
+
*
|
|
3931
|
+
* addStrategy({
|
|
3932
|
+
* strategyName: "my-strategy",
|
|
3933
|
+
* note: "Simple moving average crossover strategy",
|
|
3934
|
+
* interval: "5m",
|
|
3935
|
+
* getSignal: async (symbol) => ({
|
|
3936
|
+
* position: "long",
|
|
3937
|
+
* priceOpen: 50000,
|
|
3938
|
+
* priceTakeProfit: 51000,
|
|
3939
|
+
* priceStopLoss: 49000,
|
|
3940
|
+
* minuteEstimatedTime: 60,
|
|
3941
|
+
* }),
|
|
3942
|
+
* });
|
|
3943
|
+
*
|
|
3944
|
+
* const strategies = listStrategies();
|
|
3945
|
+
* console.log(strategies);
|
|
3946
|
+
* // [{ strategyName: "my-strategy", note: "Simple moving average...", ... }]
|
|
3947
|
+
* ```
|
|
3948
|
+
*/
|
|
3949
|
+
async function listStrategies() {
|
|
3950
|
+
backtest$1.loggerService.log(LIST_STRATEGIES_METHOD_NAME);
|
|
3951
|
+
return await backtest$1.strategyValidationService.list();
|
|
3952
|
+
}
|
|
3953
|
+
/**
|
|
3954
|
+
* Returns a list of all registered frame schemas.
|
|
3955
|
+
*
|
|
3956
|
+
* Retrieves all frames that have been registered via addFrame().
|
|
3957
|
+
* Useful for debugging, documentation, or building dynamic UIs.
|
|
3958
|
+
*
|
|
3959
|
+
* @returns Array of frame schemas with their configurations
|
|
3960
|
+
*
|
|
3961
|
+
* @example
|
|
3962
|
+
* ```typescript
|
|
3963
|
+
* import { listFrames, addFrame } from "backtest-kit";
|
|
3964
|
+
*
|
|
3965
|
+
* addFrame({
|
|
3966
|
+
* frameName: "1d-backtest",
|
|
3967
|
+
* note: "One day backtest period for testing",
|
|
3968
|
+
* interval: "1m",
|
|
3969
|
+
* startDate: new Date("2024-01-01T00:00:00Z"),
|
|
3970
|
+
* endDate: new Date("2024-01-02T00:00:00Z"),
|
|
3971
|
+
* });
|
|
3972
|
+
*
|
|
3973
|
+
* const frames = listFrames();
|
|
3974
|
+
* console.log(frames);
|
|
3975
|
+
* // [{ frameName: "1d-backtest", note: "One day backtest...", ... }]
|
|
3976
|
+
* ```
|
|
3977
|
+
*/
|
|
3978
|
+
async function listFrames() {
|
|
3979
|
+
backtest$1.loggerService.log(LIST_FRAMES_METHOD_NAME);
|
|
3980
|
+
return await backtest$1.frameValidationService.list();
|
|
3981
|
+
}
|
|
3982
|
+
|
|
3725
3983
|
const LISTEN_SIGNAL_METHOD_NAME = "event.listenSignal";
|
|
3726
3984
|
const LISTEN_SIGNAL_ONCE_METHOD_NAME = "event.listenSignalOnce";
|
|
3727
3985
|
const LISTEN_SIGNAL_LIVE_METHOD_NAME = "event.listenSignalLive";
|
|
@@ -3731,6 +3989,7 @@ const LISTEN_SIGNAL_BACKTEST_ONCE_METHOD_NAME = "event.listenSignalBacktestOnce"
|
|
|
3731
3989
|
const LISTEN_ERROR_METHOD_NAME = "event.listenError";
|
|
3732
3990
|
const LISTEN_DONE_METHOD_NAME = "event.listenDone";
|
|
3733
3991
|
const LISTEN_DONE_ONCE_METHOD_NAME = "event.listenDoneOnce";
|
|
3992
|
+
const LISTEN_PROGRESS_METHOD_NAME = "event.listenProgress";
|
|
3734
3993
|
/**
|
|
3735
3994
|
* Subscribes to all signal events with queued async processing.
|
|
3736
3995
|
*
|
|
@@ -3986,6 +4245,40 @@ function listenDoneOnce(filterFn, fn) {
|
|
|
3986
4245
|
backtest$1.loggerService.log(LISTEN_DONE_ONCE_METHOD_NAME);
|
|
3987
4246
|
return doneEmitter.filter(filterFn).once(fn);
|
|
3988
4247
|
}
|
|
4248
|
+
/**
|
|
4249
|
+
* Subscribes to backtest progress events with queued async processing.
|
|
4250
|
+
*
|
|
4251
|
+
* Emits during Backtest.background() execution to track progress.
|
|
4252
|
+
* Events are processed sequentially in order received, even if callback is async.
|
|
4253
|
+
* Uses queued wrapper to prevent concurrent execution of the callback.
|
|
4254
|
+
*
|
|
4255
|
+
* @param fn - Callback function to handle progress events
|
|
4256
|
+
* @returns Unsubscribe function to stop listening to events
|
|
4257
|
+
*
|
|
4258
|
+
* @example
|
|
4259
|
+
* ```typescript
|
|
4260
|
+
* import { listenProgress, Backtest } from "backtest-kit";
|
|
4261
|
+
*
|
|
4262
|
+
* const unsubscribe = listenProgress((event) => {
|
|
4263
|
+
* console.log(`Progress: ${(event.progress * 100).toFixed(2)}%`);
|
|
4264
|
+
* console.log(`${event.processedFrames} / ${event.totalFrames} frames`);
|
|
4265
|
+
* console.log(`Strategy: ${event.strategyName}, Symbol: ${event.symbol}`);
|
|
4266
|
+
* });
|
|
4267
|
+
*
|
|
4268
|
+
* Backtest.background("BTCUSDT", {
|
|
4269
|
+
* strategyName: "my-strategy",
|
|
4270
|
+
* exchangeName: "binance",
|
|
4271
|
+
* frameName: "1d-backtest"
|
|
4272
|
+
* });
|
|
4273
|
+
*
|
|
4274
|
+
* // Later: stop listening
|
|
4275
|
+
* unsubscribe();
|
|
4276
|
+
* ```
|
|
4277
|
+
*/
|
|
4278
|
+
function listenProgress(fn) {
|
|
4279
|
+
backtest$1.loggerService.log(LISTEN_PROGRESS_METHOD_NAME);
|
|
4280
|
+
return progressEmitter.subscribe(functoolsKit.queued(async (event) => fn(event)));
|
|
4281
|
+
}
|
|
3989
4282
|
|
|
3990
4283
|
const GET_CANDLES_METHOD_NAME = "exchange.getCandles";
|
|
3991
4284
|
const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
|
|
@@ -4164,6 +4457,7 @@ class BacktestUtils {
|
|
|
4164
4457
|
context,
|
|
4165
4458
|
});
|
|
4166
4459
|
backtest$1.backtestMarkdownService.clear(context.strategyName);
|
|
4460
|
+
backtest$1.strategyGlobalService.clear(context.strategyName);
|
|
4167
4461
|
return backtest$1.backtestGlobalService.run(symbol, context);
|
|
4168
4462
|
};
|
|
4169
4463
|
/**
|
|
@@ -4192,14 +4486,9 @@ class BacktestUtils {
|
|
|
4192
4486
|
symbol,
|
|
4193
4487
|
context,
|
|
4194
4488
|
});
|
|
4195
|
-
const iterator = this.run(symbol, context);
|
|
4196
4489
|
let isStopped = false;
|
|
4197
4490
|
const task = async () => {
|
|
4198
|
-
|
|
4199
|
-
const { done } = await iterator.next();
|
|
4200
|
-
if (done) {
|
|
4201
|
-
break;
|
|
4202
|
-
}
|
|
4491
|
+
for await (const _ of this.run(symbol, context)) {
|
|
4203
4492
|
if (isStopped) {
|
|
4204
4493
|
break;
|
|
4205
4494
|
}
|
|
@@ -4213,6 +4502,7 @@ class BacktestUtils {
|
|
|
4213
4502
|
};
|
|
4214
4503
|
task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
4215
4504
|
return () => {
|
|
4505
|
+
backtest$1.strategyGlobalService.stop(context.strategyName);
|
|
4216
4506
|
isStopped = true;
|
|
4217
4507
|
};
|
|
4218
4508
|
};
|
|
@@ -4329,6 +4619,7 @@ class LiveUtils {
|
|
|
4329
4619
|
context,
|
|
4330
4620
|
});
|
|
4331
4621
|
backtest$1.liveMarkdownService.clear(context.strategyName);
|
|
4622
|
+
backtest$1.strategyGlobalService.clear(context.strategyName);
|
|
4332
4623
|
return backtest$1.liveGlobalService.run(symbol, context);
|
|
4333
4624
|
};
|
|
4334
4625
|
/**
|
|
@@ -4357,19 +4648,10 @@ class LiveUtils {
|
|
|
4357
4648
|
symbol,
|
|
4358
4649
|
context,
|
|
4359
4650
|
});
|
|
4360
|
-
const iterator = this.run(symbol, context);
|
|
4361
4651
|
let isStopped = false;
|
|
4362
|
-
let lastValue = null;
|
|
4363
4652
|
const task = async () => {
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
if (value) {
|
|
4367
|
-
lastValue = value;
|
|
4368
|
-
}
|
|
4369
|
-
if (done) {
|
|
4370
|
-
break;
|
|
4371
|
-
}
|
|
4372
|
-
if (lastValue?.action === "closed" && isStopped) {
|
|
4653
|
+
for await (const signal of this.run(symbol, context)) {
|
|
4654
|
+
if (signal?.action === "closed" && isStopped) {
|
|
4373
4655
|
break;
|
|
4374
4656
|
}
|
|
4375
4657
|
}
|
|
@@ -4382,6 +4664,7 @@ class LiveUtils {
|
|
|
4382
4664
|
};
|
|
4383
4665
|
task().catch((error) => errorEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
4384
4666
|
return () => {
|
|
4667
|
+
backtest$1.strategyGlobalService.stop(context.strategyName);
|
|
4385
4668
|
isStopped = true;
|
|
4386
4669
|
};
|
|
4387
4670
|
};
|
|
@@ -4460,9 +4743,13 @@ exports.getCandles = getCandles;
|
|
|
4460
4743
|
exports.getDate = getDate;
|
|
4461
4744
|
exports.getMode = getMode;
|
|
4462
4745
|
exports.lib = backtest;
|
|
4746
|
+
exports.listExchanges = listExchanges;
|
|
4747
|
+
exports.listFrames = listFrames;
|
|
4748
|
+
exports.listStrategies = listStrategies;
|
|
4463
4749
|
exports.listenDone = listenDone;
|
|
4464
4750
|
exports.listenDoneOnce = listenDoneOnce;
|
|
4465
4751
|
exports.listenError = listenError;
|
|
4752
|
+
exports.listenProgress = listenProgress;
|
|
4466
4753
|
exports.listenSignal = listenSignal;
|
|
4467
4754
|
exports.listenSignalBacktest = listenSignalBacktest;
|
|
4468
4755
|
exports.listenSignalBacktestOnce = listenSignalBacktestOnce;
|