backtest-kit 1.3.2 β 1.4.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 +841 -6
- package/build/index.cjs +3349 -183
- package/build/index.mjs +3339 -184
- package/package.json +3 -2
- package/types.d.ts +2260 -122
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Build sophisticated trading systems with confidence. Backtest Kit empowers you t
|
|
|
16
16
|
|
|
17
17
|
- π **Production-Ready Architecture**: Seamlessly switch between backtest and live modes with robust error recovery and graceful shutdown mechanisms. Your strategy code remains identical across environments.
|
|
18
18
|
|
|
19
|
-
- πΎ **Crash-Safe Persistence**: Atomic file writes with automatic state recovery ensure no duplicate signals or lost dataβeven after crashes. Resume execution exactly where you left off.
|
|
19
|
+
- πΎ **Crash-Safe Persistence**: Atomic file writes with automatic state recovery ensure no duplicate signals or lost dataβeven after crashes. Resume execution exactly where you left off.
|
|
20
20
|
|
|
21
21
|
- β
**Signal Validation**: Comprehensive validation prevents invalid trades before execution. Catches price logic errors (TP/SL), throttles signal spam, and ensures data integrity. π‘οΈ
|
|
22
22
|
|
|
@@ -44,11 +44,13 @@ Build sophisticated trading systems with confidence. Backtest Kit empowers you t
|
|
|
44
44
|
|
|
45
45
|
- πΎ **Zero Data Download**: Unlike Freqtrade, no need to download gigabytes of historical dataβplug any data source (CCXT, database, API). π
|
|
46
46
|
|
|
47
|
-
- π **Pluggable Persistence**: Replace default file-based persistence with custom adapters (Redis, MongoDB, PostgreSQL) for distributed systems and high-performance scenarios.
|
|
47
|
+
- π **Pluggable Persistence**: Replace default file-based persistence with custom adapters (Redis, MongoDB, PostgreSQL) for distributed systems and high-performance scenarios.
|
|
48
48
|
|
|
49
49
|
- π **Safe Math & Robustness**: All metrics protected against NaN/Infinity with unsafe numeric checks. Returns N/A for invalid calculations. β¨
|
|
50
50
|
|
|
51
|
-
-
|
|
51
|
+
- π€ **AI Strategy Optimizer**: LLM-powered strategy generation from historical data. Train multiple strategy variants, compare performance, and auto-generate executable code. Supports Ollama integration with multi-timeframe analysis. π§
|
|
52
|
+
|
|
53
|
+
- π§ͺ **Comprehensive Test Coverage**: Unit and integration tests covering validation, PNL, callbacks, reports, performance tracking, walker, heatmap, position sizing, risk management, scheduled signals, crash recovery, optimizer, and event system.
|
|
52
54
|
|
|
53
55
|
---
|
|
54
56
|
|
|
@@ -249,12 +251,15 @@ Backtest.background("BTCUSDT", {
|
|
|
249
251
|
- π **`addFrame`**: Configure timeframes for backtesting. π
|
|
250
252
|
- π **`Backtest` / `Live`**: Run strategies in backtest or live mode (generator or background). β‘
|
|
251
253
|
- π
**`Schedule`**: Track scheduled signals and cancellation rate for limit orders. π
|
|
254
|
+
- π **`Partial`**: Access partial profit/loss statistics and reports for risk management. Track signals reaching milestone levels (10%, 20%, 30%, etc.). πΉ
|
|
255
|
+
- π― **`Constant`**: Kelly Criterion-based constants for optimal take profit (TP_LEVEL1-3) and stop loss (SL_LEVEL1-2) levels. π
|
|
252
256
|
- π **`Walker`**: Compare multiple strategies in parallel with ranking. π
|
|
253
257
|
- π₯ **`Heat`**: Portfolio-wide performance analysis across multiple symbols. π
|
|
254
258
|
- π° **`PositionSize`**: Calculate position sizes with Fixed %, Kelly Criterion, or ATR-based methods. π΅
|
|
255
259
|
- π‘οΈ **`addRisk`**: Portfolio-level risk management with custom validation logic. π
|
|
256
|
-
- πΎ **`PersistBase`**: Base class for custom persistence adapters (Redis, MongoDB, PostgreSQL).
|
|
257
|
-
- π **`PersistSignalAdapter` / `PersistRiskAdapter`**: Register custom adapters for signal
|
|
260
|
+
- πΎ **`PersistBase`**: Base class for custom persistence adapters (Redis, MongoDB, PostgreSQL).
|
|
261
|
+
- π **`PersistSignalAdapter` / `PersistScheduleAdapter` / `PersistRiskAdapter` / `PersistPartialAdapter`**: Register custom adapters for signal, scheduled signal, risk, and partial state persistence.
|
|
262
|
+
- π€ **`Optimizer`**: AI-powered strategy generation with LLM integration. Auto-generate strategies from historical data and export executable code. π§
|
|
258
263
|
|
|
259
264
|
Check out the sections below for detailed examples! π
|
|
260
265
|
|
|
@@ -1071,6 +1076,777 @@ test("Custom Redis adapter works correctly", async ({ pass, fail }) => {
|
|
|
1071
1076
|
});
|
|
1072
1077
|
```
|
|
1073
1078
|
|
|
1079
|
+
### 10. Partial Profit/Loss Tracking
|
|
1080
|
+
|
|
1081
|
+
Partial Profit/Loss system tracks signal performance at fixed percentage levels (10%, 20%, 30%, etc.) for risk management and position scaling strategies.
|
|
1082
|
+
|
|
1083
|
+
#### Understanding Partial Levels
|
|
1084
|
+
|
|
1085
|
+
The system automatically monitors profit/loss milestones and emits events when signals reach specific levels:
|
|
1086
|
+
|
|
1087
|
+
```typescript
|
|
1088
|
+
import {
|
|
1089
|
+
listenPartialProfit,
|
|
1090
|
+
listenPartialLoss,
|
|
1091
|
+
listenPartialProfitOnce,
|
|
1092
|
+
listenPartialLossOnce,
|
|
1093
|
+
Constant
|
|
1094
|
+
} from "backtest-kit";
|
|
1095
|
+
|
|
1096
|
+
// Listen to all profit levels (10%, 20%, 30%, 40%, 50%, 60%, 70%, 80%, 90%, 100%)
|
|
1097
|
+
listenPartialProfit(({ symbol, signal, price, level, backtest }) => {
|
|
1098
|
+
console.log(`${symbol} profit: ${level}% at ${price}`);
|
|
1099
|
+
|
|
1100
|
+
// Close portions at Kelly-optimized levels
|
|
1101
|
+
if (level === Constant.TP_LEVEL3) {
|
|
1102
|
+
console.log("Close 33% at 25% profit");
|
|
1103
|
+
}
|
|
1104
|
+
if (level === Constant.TP_LEVEL2) {
|
|
1105
|
+
console.log("Close 33% at 50% profit");
|
|
1106
|
+
}
|
|
1107
|
+
if (level === Constant.TP_LEVEL1) {
|
|
1108
|
+
console.log("Close 34% at 100% profit");
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
// Listen to all loss levels (10%, 20%, 30%, 40%, 50%, 60%, 70%, 80%, 90%, 100%)
|
|
1113
|
+
listenPartialLoss(({ symbol, signal, price, level, backtest }) => {
|
|
1114
|
+
console.log(`${symbol} loss: -${level}% at ${price}`);
|
|
1115
|
+
|
|
1116
|
+
// Close portions at stop levels
|
|
1117
|
+
if (level === Constant.SL_LEVEL2) {
|
|
1118
|
+
console.log("Close 50% at -50% loss");
|
|
1119
|
+
}
|
|
1120
|
+
if (level === Constant.SL_LEVEL1) {
|
|
1121
|
+
console.log("Close 50% at -100% loss");
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
// Listen once to first profit level reached
|
|
1126
|
+
listenPartialProfitOnce(
|
|
1127
|
+
() => true, // Accept any profit event
|
|
1128
|
+
({ symbol, signal, price, level, backtest }) => {
|
|
1129
|
+
console.log(`First profit milestone: ${level}%`);
|
|
1130
|
+
}
|
|
1131
|
+
);
|
|
1132
|
+
|
|
1133
|
+
// Listen once to first loss level reached
|
|
1134
|
+
listenPartialLossOnce(
|
|
1135
|
+
() => true, // Accept any loss event
|
|
1136
|
+
({ symbol, signal, price, level, backtest }) => {
|
|
1137
|
+
console.log(`First loss milestone: -${level}%`);
|
|
1138
|
+
}
|
|
1139
|
+
);
|
|
1140
|
+
```
|
|
1141
|
+
|
|
1142
|
+
#### Constant Utility - Kelly-Optimized Levels
|
|
1143
|
+
|
|
1144
|
+
The `Constant` class provides predefined Kelly Criterion-based levels for optimal position sizing:
|
|
1145
|
+
|
|
1146
|
+
```typescript
|
|
1147
|
+
import { Constant } from "backtest-kit";
|
|
1148
|
+
|
|
1149
|
+
// Take Profit Levels
|
|
1150
|
+
console.log(Constant.TP_LEVEL1); // 100% (aggressive target)
|
|
1151
|
+
console.log(Constant.TP_LEVEL2); // 50% (moderate target)
|
|
1152
|
+
console.log(Constant.TP_LEVEL3); // 25% (conservative target)
|
|
1153
|
+
|
|
1154
|
+
// Stop Loss Levels
|
|
1155
|
+
console.log(Constant.SL_LEVEL1); // 100% (maximum risk)
|
|
1156
|
+
console.log(Constant.SL_LEVEL2); // 50% (standard stop)
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
**Use Case - Scale Out Strategy:**
|
|
1160
|
+
|
|
1161
|
+
```typescript
|
|
1162
|
+
// Strategy: Close position in 3 tranches at optimal levels
|
|
1163
|
+
listenPartialProfit(({ symbol, signal, price, level, backtest }) => {
|
|
1164
|
+
if (level === Constant.TP_LEVEL3) {
|
|
1165
|
+
// Close 33% at 25% profit (secure early gains)
|
|
1166
|
+
executePartialClose(symbol, signal.id, 0.33);
|
|
1167
|
+
}
|
|
1168
|
+
if (level === Constant.TP_LEVEL2) {
|
|
1169
|
+
// Close 33% at 50% profit (lock in medium gains)
|
|
1170
|
+
executePartialClose(symbol, signal.id, 0.33);
|
|
1171
|
+
}
|
|
1172
|
+
if (level === Constant.TP_LEVEL1) {
|
|
1173
|
+
// Close 34% at 100% profit (maximize winners)
|
|
1174
|
+
executePartialClose(symbol, signal.id, 0.34);
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
#### Partial Reports and Statistics
|
|
1180
|
+
|
|
1181
|
+
The `Partial` utility provides access to accumulated partial profit/loss data:
|
|
1182
|
+
|
|
1183
|
+
```typescript
|
|
1184
|
+
import { Partial } from "backtest-kit";
|
|
1185
|
+
|
|
1186
|
+
// Get statistical data
|
|
1187
|
+
const stats = await Partial.getData("BTCUSDT");
|
|
1188
|
+
console.log(stats);
|
|
1189
|
+
// Returns:
|
|
1190
|
+
// {
|
|
1191
|
+
// totalEvents: 15, // Total profit/loss events
|
|
1192
|
+
// totalProfit: 10, // Number of profit events
|
|
1193
|
+
// totalLoss: 5, // Number of loss events
|
|
1194
|
+
// eventList: [
|
|
1195
|
+
// {
|
|
1196
|
+
// timestamp: 1704370800000,
|
|
1197
|
+
// action: "PROFIT", // PROFIT or LOSS
|
|
1198
|
+
// symbol: "BTCUSDT",
|
|
1199
|
+
// signalId: "abc123",
|
|
1200
|
+
// position: "LONG", // or SHORT
|
|
1201
|
+
// level: 10, // Percentage level reached
|
|
1202
|
+
// price: 51500.00, // Current price at level
|
|
1203
|
+
// mode: "Backtest" // or Live
|
|
1204
|
+
// },
|
|
1205
|
+
// // ... more events
|
|
1206
|
+
// ]
|
|
1207
|
+
// }
|
|
1208
|
+
|
|
1209
|
+
// Generate markdown report
|
|
1210
|
+
const markdown = await Partial.getReport("BTCUSDT");
|
|
1211
|
+
console.log(markdown);
|
|
1212
|
+
|
|
1213
|
+
// Save report to disk (default: ./dump/partial/BTCUSDT.md)
|
|
1214
|
+
await Partial.dump("BTCUSDT");
|
|
1215
|
+
|
|
1216
|
+
// Custom output path
|
|
1217
|
+
await Partial.dump("BTCUSDT", "./reports/partial");
|
|
1218
|
+
```
|
|
1219
|
+
|
|
1220
|
+
**Partial Report Example:**
|
|
1221
|
+
|
|
1222
|
+
```markdown
|
|
1223
|
+
# Partial Profit/Loss Report: BTCUSDT
|
|
1224
|
+
|
|
1225
|
+
| Action | Symbol | Signal ID | Position | Level % | Current Price | Timestamp | Mode |
|
|
1226
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
1227
|
+
| PROFIT | BTCUSDT | abc123 | LONG | +10% | 51500.00000000 USD | 2024-01-15T10:30:00.000Z | Backtest |
|
|
1228
|
+
| PROFIT | BTCUSDT | abc123 | LONG | +20% | 53000.00000000 USD | 2024-01-15T11:15:00.000Z | Backtest |
|
|
1229
|
+
| LOSS | BTCUSDT | def456 | SHORT | -10% | 51500.00000000 USD | 2024-01-15T14:00:00.000Z | Backtest |
|
|
1230
|
+
|
|
1231
|
+
**Total events:** 15
|
|
1232
|
+
**Profit events:** 10
|
|
1233
|
+
**Loss events:** 5
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
#### Strategy Callbacks
|
|
1237
|
+
|
|
1238
|
+
Partial profit/loss callbacks can also be configured at the strategy level:
|
|
1239
|
+
|
|
1240
|
+
```typescript
|
|
1241
|
+
import { addStrategy } from "backtest-kit";
|
|
1242
|
+
|
|
1243
|
+
addStrategy({
|
|
1244
|
+
strategyName: "my-strategy",
|
|
1245
|
+
interval: "5m",
|
|
1246
|
+
getSignal: async (symbol) => { /* ... */ },
|
|
1247
|
+
callbacks: {
|
|
1248
|
+
onPartialProfit: (symbol, data, currentPrice, revenuePercent, backtest) => {
|
|
1249
|
+
console.log(`Signal ${data.id} at ${revenuePercent.toFixed(2)}% profit`);
|
|
1250
|
+
},
|
|
1251
|
+
onPartialLoss: (symbol, data, currentPrice, lossPercent, backtest) => {
|
|
1252
|
+
console.log(`Signal ${data.id} at ${lossPercent.toFixed(2)}% loss`);
|
|
1253
|
+
},
|
|
1254
|
+
},
|
|
1255
|
+
});
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1258
|
+
#### How Partial Levels Work
|
|
1259
|
+
|
|
1260
|
+
**Architecture:**
|
|
1261
|
+
|
|
1262
|
+
1. `ClientPartial` - Tracks levels using `Map<signalId, Set<level>>` to prevent duplicates
|
|
1263
|
+
2. `ClientStrategy` - Calls `partial.profit()` / `partial.loss()` on every tick
|
|
1264
|
+
3. `PartialMarkdownService` - Accumulates events (max 250 per symbol) for reports
|
|
1265
|
+
4. State persisted to disk: `./dump/data/partial/{symbol}/levels.json`
|
|
1266
|
+
|
|
1267
|
+
**Level Detection:**
|
|
1268
|
+
|
|
1269
|
+
```typescript
|
|
1270
|
+
// For LONG position at entry price 50000
|
|
1271
|
+
// Current price = 55000 β revenue = 10%
|
|
1272
|
+
// Levels triggered: 10%
|
|
1273
|
+
|
|
1274
|
+
// Current price = 61000 β revenue = 22%
|
|
1275
|
+
// Levels triggered: 10%, 20% (only 20% event emitted if 10% already triggered)
|
|
1276
|
+
|
|
1277
|
+
// For SHORT position at entry price 50000
|
|
1278
|
+
// Current price = 45000 β revenue = 10%
|
|
1279
|
+
// Levels triggered: 10%
|
|
1280
|
+
```
|
|
1281
|
+
|
|
1282
|
+
**Deduplication Guarantee:**
|
|
1283
|
+
|
|
1284
|
+
Each level is emitted **exactly once per signal**:
|
|
1285
|
+
|
|
1286
|
+
- Uses `Set<level>` to track reached levels
|
|
1287
|
+
- Persisted to disk for crash recovery
|
|
1288
|
+
- Restored on system restart
|
|
1289
|
+
|
|
1290
|
+
**Crash Recovery:**
|
|
1291
|
+
|
|
1292
|
+
```typescript
|
|
1293
|
+
// Before crash:
|
|
1294
|
+
// Signal opened at 50000, reached 10% and 20% profit
|
|
1295
|
+
// State: { profitLevels: [10, 20], lossLevels: [] }
|
|
1296
|
+
// Persisted to: ./dump/data/partial/BTCUSDT/levels.json
|
|
1297
|
+
|
|
1298
|
+
// After restart:
|
|
1299
|
+
// State restored from disk
|
|
1300
|
+
// Only new levels (30%, 40%, etc.) will emit events
|
|
1301
|
+
// 10% and 20% won't fire again
|
|
1302
|
+
```
|
|
1303
|
+
|
|
1304
|
+
#### Best Practices
|
|
1305
|
+
|
|
1306
|
+
1. **Use Constant for Kelly-Optimized Levels** - Don't hardcode profit/loss levels
|
|
1307
|
+
2. **Scale Out Gradually** - Close positions in tranches (25%, 50%, 100%)
|
|
1308
|
+
3. **Monitor Partial Statistics** - Use `Partial.getData()` to track scaling effectiveness
|
|
1309
|
+
4. **Filter Events** - Use `listenPartialProfitOnce` for first-level-only logic
|
|
1310
|
+
5. **Combine with Position Sizing** - Scale out inversely to volatility
|
|
1311
|
+
|
|
1312
|
+
```typescript
|
|
1313
|
+
import { Constant, listenPartialProfit } from "backtest-kit";
|
|
1314
|
+
|
|
1315
|
+
// Advanced: Dynamic scaling based on level
|
|
1316
|
+
listenPartialProfit(({ symbol, signal, price, level, backtest }) => {
|
|
1317
|
+
const percentToClose =
|
|
1318
|
+
level === Constant.TP_LEVEL3 ? 0.25 : // 25% at first level
|
|
1319
|
+
level === Constant.TP_LEVEL2 ? 0.35 : // 35% at second level
|
|
1320
|
+
level === Constant.TP_LEVEL1 ? 0.40 : // 40% at third level
|
|
1321
|
+
0;
|
|
1322
|
+
|
|
1323
|
+
if (percentToClose > 0) {
|
|
1324
|
+
executePartialClose(symbol, signal.id, percentToClose);
|
|
1325
|
+
}
|
|
1326
|
+
});
|
|
1327
|
+
```
|
|
1328
|
+
|
|
1329
|
+
---
|
|
1330
|
+
|
|
1331
|
+
### 11. Scheduled Signal Persistence
|
|
1332
|
+
|
|
1333
|
+
The framework includes a separate persistence system for scheduled signals (`PersistScheduleAdapter`) that works independently from pending/active signal persistence (`PersistSignalAdapter`). This separation ensures crash-safe recovery of both signal types.
|
|
1334
|
+
|
|
1335
|
+
#### Understanding the Dual Persistence System
|
|
1336
|
+
|
|
1337
|
+
The library uses **two independent persistence layers** for signals:
|
|
1338
|
+
|
|
1339
|
+
1. **PersistSignalAdapter** - Manages pending/active signals (signals that are already opened or waiting to reach TP/SL)
|
|
1340
|
+
2. **PersistScheduleAdapter** - Manages scheduled signals (signals waiting for entry price to activate)
|
|
1341
|
+
|
|
1342
|
+
This dual-layer architecture ensures that both signal types can be recovered independently after crashes, with proper callbacks (`onActive` for pending signals, `onSchedule` for scheduled signals).
|
|
1343
|
+
|
|
1344
|
+
#### Default Storage Structure
|
|
1345
|
+
|
|
1346
|
+
By default, scheduled signals are stored separately from pending signals:
|
|
1347
|
+
|
|
1348
|
+
```
|
|
1349
|
+
./dump/data/
|
|
1350
|
+
signal/
|
|
1351
|
+
my-strategy/
|
|
1352
|
+
BTCUSDT.json # Pending/active signal state
|
|
1353
|
+
ETHUSDT.json
|
|
1354
|
+
schedule/
|
|
1355
|
+
my-strategy/
|
|
1356
|
+
BTCUSDT.json # Scheduled signal state
|
|
1357
|
+
ETHUSDT.json
|
|
1358
|
+
```
|
|
1359
|
+
|
|
1360
|
+
#### How Scheduled Signal Persistence Works
|
|
1361
|
+
|
|
1362
|
+
**During Normal Operation:**
|
|
1363
|
+
|
|
1364
|
+
When a strategy generates a scheduled signal (limit order waiting for entry), the framework:
|
|
1365
|
+
|
|
1366
|
+
1. Stores the signal to disk using atomic writes: `./dump/data/schedule/{strategyName}/{symbol}.json`
|
|
1367
|
+
2. Monitors price movements for activation
|
|
1368
|
+
3. When price reaches entry point OR cancellation condition occurs:
|
|
1369
|
+
- Deletes scheduled signal from storage
|
|
1370
|
+
- Optionally creates pending signal in `PersistSignalAdapter`
|
|
1371
|
+
|
|
1372
|
+
**After System Crash:**
|
|
1373
|
+
|
|
1374
|
+
When the system restarts:
|
|
1375
|
+
|
|
1376
|
+
1. Framework checks for stored scheduled signals during initialization
|
|
1377
|
+
2. Validates exchange name and strategy name match (security protection)
|
|
1378
|
+
3. Restores scheduled signal to memory (`_scheduledSignal`)
|
|
1379
|
+
4. Calls `onSchedule()` callback to notify about restored signal
|
|
1380
|
+
5. Continues monitoring from where it left off
|
|
1381
|
+
|
|
1382
|
+
**Crash Recovery Flow:**
|
|
1383
|
+
|
|
1384
|
+
```typescript
|
|
1385
|
+
// Before crash:
|
|
1386
|
+
// 1. Strategy generates signal with priceOpen = 50000 (current price = 49500)
|
|
1387
|
+
// 2. Signal stored to ./dump/data/schedule/my-strategy/BTCUSDT.json
|
|
1388
|
+
// 3. System waits for price to reach 50000
|
|
1389
|
+
// 4. CRASH OCCURS at current price = 49800
|
|
1390
|
+
|
|
1391
|
+
// After restart:
|
|
1392
|
+
// 1. System reads ./dump/data/schedule/my-strategy/BTCUSDT.json
|
|
1393
|
+
// 2. Validates exchangeName and strategyName
|
|
1394
|
+
// 3. Restores signal to _scheduledSignal
|
|
1395
|
+
// 4. Calls onSchedule() callback with restored signal
|
|
1396
|
+
// 5. Continues monitoring for price = 50000
|
|
1397
|
+
// 6. When price reaches 50000, signal activates normally
|
|
1398
|
+
```
|
|
1399
|
+
|
|
1400
|
+
#### Scheduled Signal Data Structure
|
|
1401
|
+
|
|
1402
|
+
```typescript
|
|
1403
|
+
interface IScheduledSignalRow {
|
|
1404
|
+
id: string; // Unique signal ID
|
|
1405
|
+
position: "long" | "short";
|
|
1406
|
+
priceOpen: number; // Entry price (trigger price for scheduled signal)
|
|
1407
|
+
priceTakeProfit: number;
|
|
1408
|
+
priceStopLoss: number;
|
|
1409
|
+
minuteEstimatedTime: number;
|
|
1410
|
+
exchangeName: string; // Used for validation during restore
|
|
1411
|
+
strategyName: string; // Used for validation during restore
|
|
1412
|
+
timestamp: number;
|
|
1413
|
+
pendingAt: number;
|
|
1414
|
+
scheduledAt: number;
|
|
1415
|
+
symbol: string;
|
|
1416
|
+
_isScheduled: true; // Marker for scheduled signals
|
|
1417
|
+
note?: string;
|
|
1418
|
+
}
|
|
1419
|
+
```
|
|
1420
|
+
|
|
1421
|
+
#### Integration with ClientStrategy
|
|
1422
|
+
|
|
1423
|
+
The `ClientStrategy` class uses `setScheduledSignal()` method to ensure all scheduled signal changes are persisted:
|
|
1424
|
+
|
|
1425
|
+
```typescript
|
|
1426
|
+
// WRONG - Direct assignment (not persisted)
|
|
1427
|
+
this._scheduledSignal = newSignal;
|
|
1428
|
+
|
|
1429
|
+
// CORRECT - Using setScheduledSignal() method (persisted)
|
|
1430
|
+
await this.setScheduledSignal(newSignal);
|
|
1431
|
+
```
|
|
1432
|
+
|
|
1433
|
+
**Automatic Persistence Locations:**
|
|
1434
|
+
|
|
1435
|
+
All scheduled signal state changes are automatically persisted:
|
|
1436
|
+
|
|
1437
|
+
- Signal generation (new scheduled signal created)
|
|
1438
|
+
- Signal activation (scheduled β pending transition)
|
|
1439
|
+
- Signal cancellation (timeout or stop loss hit before activation)
|
|
1440
|
+
- Manual signal clearing
|
|
1441
|
+
|
|
1442
|
+
**BACKTEST Mode Exception:**
|
|
1443
|
+
|
|
1444
|
+
In backtest mode, persistence is **skipped** for performance reasons:
|
|
1445
|
+
|
|
1446
|
+
```typescript
|
|
1447
|
+
public async setScheduledSignal(scheduledSignal: IScheduledSignalRow | null) {
|
|
1448
|
+
this._scheduledSignal = scheduledSignal;
|
|
1449
|
+
|
|
1450
|
+
if (this.params.execution.context.backtest) {
|
|
1451
|
+
return; // Skip persistence in backtest mode
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
await PersistScheduleAdapter.writeScheduleData(
|
|
1455
|
+
this._scheduledSignal,
|
|
1456
|
+
this.params.strategyName,
|
|
1457
|
+
this.params.execution.context.symbol
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
```
|
|
1461
|
+
|
|
1462
|
+
#### Custom Scheduled Signal Adapters
|
|
1463
|
+
|
|
1464
|
+
You can replace file-based scheduled signal persistence with custom adapters (Redis, MongoDB, etc.):
|
|
1465
|
+
|
|
1466
|
+
```typescript
|
|
1467
|
+
import { PersistScheduleAdapter, PersistBase } from "backtest-kit";
|
|
1468
|
+
import Redis from "ioredis";
|
|
1469
|
+
|
|
1470
|
+
const redis = new Redis();
|
|
1471
|
+
|
|
1472
|
+
class RedisSchedulePersist extends PersistBase {
|
|
1473
|
+
async waitForInit(initial: boolean): Promise<void> {
|
|
1474
|
+
console.log(`Redis scheduled signal persistence initialized for ${this.entityName}`);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
async readValue<T>(entityId: string | number): Promise<T> {
|
|
1478
|
+
const key = `schedule:${this.entityName}:${entityId}`;
|
|
1479
|
+
const data = await redis.get(key);
|
|
1480
|
+
|
|
1481
|
+
if (!data) {
|
|
1482
|
+
throw new Error(`Scheduled signal ${this.entityName}:${entityId} not found`);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
return JSON.parse(data) as T;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
async hasValue(entityId: string | number): Promise<boolean> {
|
|
1489
|
+
const key = `schedule:${this.entityName}:${entityId}`;
|
|
1490
|
+
const exists = await redis.exists(key);
|
|
1491
|
+
return exists === 1;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
async writeValue<T>(entityId: string | number, entity: T): Promise<void> {
|
|
1495
|
+
const key = `schedule:${this.entityName}:${entityId}`;
|
|
1496
|
+
const serializedData = JSON.stringify(entity);
|
|
1497
|
+
await redis.set(key, serializedData);
|
|
1498
|
+
|
|
1499
|
+
// Optional: Set TTL for scheduled signals (e.g., 24 hours)
|
|
1500
|
+
await redis.expire(key, 86400);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
async removeValue(entityId: string | number): Promise<void> {
|
|
1504
|
+
const key = `schedule:${this.entityName}:${entityId}`;
|
|
1505
|
+
const result = await redis.del(key);
|
|
1506
|
+
|
|
1507
|
+
if (result === 0) {
|
|
1508
|
+
throw new Error(`Scheduled signal ${this.entityName}:${entityId} not found for deletion`);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
async removeAll(): Promise<void> {
|
|
1513
|
+
const pattern = `schedule:${this.entityName}:*`;
|
|
1514
|
+
const keys = await redis.keys(pattern);
|
|
1515
|
+
|
|
1516
|
+
if (keys.length > 0) {
|
|
1517
|
+
await redis.del(...keys);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
async *values<T>(): AsyncGenerator<T> {
|
|
1522
|
+
const pattern = `schedule:${this.entityName}:*`;
|
|
1523
|
+
const keys = await redis.keys(pattern);
|
|
1524
|
+
|
|
1525
|
+
keys.sort((a, b) => a.localeCompare(b, undefined, {
|
|
1526
|
+
numeric: true,
|
|
1527
|
+
sensitivity: "base"
|
|
1528
|
+
}));
|
|
1529
|
+
|
|
1530
|
+
for (const key of keys) {
|
|
1531
|
+
const data = await redis.get(key);
|
|
1532
|
+
if (data) {
|
|
1533
|
+
yield JSON.parse(data) as T;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
async *keys(): AsyncGenerator<string> {
|
|
1539
|
+
const pattern = `schedule:${this.entityName}:*`;
|
|
1540
|
+
const keys = await redis.keys(pattern);
|
|
1541
|
+
|
|
1542
|
+
keys.sort((a, b) => a.localeCompare(b, undefined, {
|
|
1543
|
+
numeric: true,
|
|
1544
|
+
sensitivity: "base"
|
|
1545
|
+
}));
|
|
1546
|
+
|
|
1547
|
+
for (const key of keys) {
|
|
1548
|
+
const entityId = key.slice(`schedule:${this.entityName}:`.length);
|
|
1549
|
+
yield entityId;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// Register Redis adapter for scheduled signal persistence
|
|
1555
|
+
PersistScheduleAdapter.usePersistScheduleAdapter(RedisSchedulePersist);
|
|
1556
|
+
```
|
|
1557
|
+
|
|
1558
|
+
#### Best Practices
|
|
1559
|
+
|
|
1560
|
+
1. **Always use `setScheduledSignal()`** - Never assign `_scheduledSignal` directly (except in `waitForInit` for restoration)
|
|
1561
|
+
|
|
1562
|
+
2. **Validate signal metadata** - Always store `exchangeName` and `strategyName` with signals for validation
|
|
1563
|
+
|
|
1564
|
+
3. **Handle empty storage gracefully** - Don't crash when `readScheduleData()` returns `null`
|
|
1565
|
+
|
|
1566
|
+
4. **Test crash recovery** - Write E2E tests that simulate system crashes and verify restoration
|
|
1567
|
+
|
|
1568
|
+
5. **Choose persistence adapter wisely**:
|
|
1569
|
+
- Use file-based for single-instance deployments
|
|
1570
|
+
- Use Redis for distributed systems with multiple instances
|
|
1571
|
+
- Use MongoDB for analytics and complex queries
|
|
1572
|
+
|
|
1573
|
+
6. **Monitor persistence operations** - Use callbacks to track storage operations:
|
|
1574
|
+
|
|
1575
|
+
```typescript
|
|
1576
|
+
addStrategy({
|
|
1577
|
+
strategyName: "my-strategy",
|
|
1578
|
+
interval: "5m",
|
|
1579
|
+
getSignal: async (symbol) => { /* ... */ },
|
|
1580
|
+
callbacks: {
|
|
1581
|
+
onSchedule: (symbol, signal, price, backtest) => {
|
|
1582
|
+
console.log(`Scheduled signal created/restored: ${signal.id}`);
|
|
1583
|
+
// Signal was either:
|
|
1584
|
+
// 1. Newly generated and persisted
|
|
1585
|
+
// 2. Restored from storage after crash
|
|
1586
|
+
},
|
|
1587
|
+
onCancel: (symbol, signal, price, backtest) => {
|
|
1588
|
+
console.log(`Scheduled signal cancelled: ${signal.id}`);
|
|
1589
|
+
// Signal was removed from storage
|
|
1590
|
+
},
|
|
1591
|
+
},
|
|
1592
|
+
});
|
|
1593
|
+
```
|
|
1594
|
+
|
|
1595
|
+
---
|
|
1596
|
+
|
|
1597
|
+
## π€ AI Strategy Optimizer
|
|
1598
|
+
|
|
1599
|
+
The Optimizer uses LLM (Large Language Models) to generate trading strategies from historical data. It automates the process of analyzing backtest results, generating strategy logic, and creating executable code.
|
|
1600
|
+
|
|
1601
|
+
### How It Works
|
|
1602
|
+
|
|
1603
|
+
1. **Data Collection**: Fetch historical data from multiple sources (backtest results, market data, indicators)
|
|
1604
|
+
2. **LLM Training**: Build conversation context with data for each training period
|
|
1605
|
+
3. **Strategy Generation**: LLM analyzes patterns and generates strategy logic
|
|
1606
|
+
4. **Code Export**: Auto-generate complete executable code with Walker for testing
|
|
1607
|
+
|
|
1608
|
+
### Basic Example
|
|
1609
|
+
|
|
1610
|
+
```typescript
|
|
1611
|
+
import { addOptimizer, Optimizer } from "backtest-kit";
|
|
1612
|
+
|
|
1613
|
+
// Register optimizer configuration
|
|
1614
|
+
addOptimizer({
|
|
1615
|
+
optimizerName: "btc-optimizer",
|
|
1616
|
+
|
|
1617
|
+
// Training periods (multiple strategies generated)
|
|
1618
|
+
rangeTrain: [
|
|
1619
|
+
{
|
|
1620
|
+
note: "Bull market Q1 2024",
|
|
1621
|
+
startDate: new Date("2024-01-01T00:00:00Z"),
|
|
1622
|
+
endDate: new Date("2024-03-31T23:59:59Z"),
|
|
1623
|
+
},
|
|
1624
|
+
{
|
|
1625
|
+
note: "Consolidation Q2 2024",
|
|
1626
|
+
startDate: new Date("2024-04-01T00:00:00Z"),
|
|
1627
|
+
endDate: new Date("2024-06-30T23:59:59Z"),
|
|
1628
|
+
},
|
|
1629
|
+
],
|
|
1630
|
+
|
|
1631
|
+
// Testing period (Walker validates strategies)
|
|
1632
|
+
rangeTest: {
|
|
1633
|
+
note: "Validation Q3 2024",
|
|
1634
|
+
startDate: new Date("2024-07-01T00:00:00Z"),
|
|
1635
|
+
endDate: new Date("2024-09-30T23:59:59Z"),
|
|
1636
|
+
},
|
|
1637
|
+
|
|
1638
|
+
// Data sources for strategy generation
|
|
1639
|
+
source: [
|
|
1640
|
+
{
|
|
1641
|
+
name: "backtest-results",
|
|
1642
|
+
fetch: async ({ symbol, startDate, endDate, limit, offset }) => {
|
|
1643
|
+
// Fetch closed signals from your backtest database
|
|
1644
|
+
return await db.getBacktestResults({
|
|
1645
|
+
symbol,
|
|
1646
|
+
startDate,
|
|
1647
|
+
endDate,
|
|
1648
|
+
limit,
|
|
1649
|
+
offset,
|
|
1650
|
+
});
|
|
1651
|
+
},
|
|
1652
|
+
},
|
|
1653
|
+
{
|
|
1654
|
+
name: "market-indicators",
|
|
1655
|
+
fetch: async ({ symbol, startDate, endDate, limit, offset }) => {
|
|
1656
|
+
// Fetch RSI, MACD, volume data, etc.
|
|
1657
|
+
return await db.getIndicators({
|
|
1658
|
+
symbol,
|
|
1659
|
+
startDate,
|
|
1660
|
+
endDate,
|
|
1661
|
+
limit,
|
|
1662
|
+
offset,
|
|
1663
|
+
});
|
|
1664
|
+
},
|
|
1665
|
+
},
|
|
1666
|
+
],
|
|
1667
|
+
|
|
1668
|
+
// LLM prompt generation from conversation history
|
|
1669
|
+
getPrompt: async (symbol, messages) => {
|
|
1670
|
+
// Analyze messages and create strategy prompt
|
|
1671
|
+
return `
|
|
1672
|
+
Based on the historical data, create a strategy that:
|
|
1673
|
+
- Uses multi-timeframe analysis (1h, 15m, 5m, 1m)
|
|
1674
|
+
- Identifies high-probability entry points
|
|
1675
|
+
- Uses proper risk/reward ratios (min 1.5:1)
|
|
1676
|
+
- Adapts to market conditions
|
|
1677
|
+
`;
|
|
1678
|
+
},
|
|
1679
|
+
});
|
|
1680
|
+
|
|
1681
|
+
// Generate strategies and export code
|
|
1682
|
+
await Optimizer.dump("BTCUSDT", {
|
|
1683
|
+
optimizerName: "btc-optimizer"
|
|
1684
|
+
}, "./generated");
|
|
1685
|
+
|
|
1686
|
+
// Output: ./generated/btc-optimizer_BTCUSDT.mjs
|
|
1687
|
+
```
|
|
1688
|
+
|
|
1689
|
+
### Generated Code Structure
|
|
1690
|
+
|
|
1691
|
+
The Optimizer auto-generates a complete executable file with:
|
|
1692
|
+
|
|
1693
|
+
1. **Imports** - All necessary dependencies (backtest-kit, ollama, ccxt)
|
|
1694
|
+
2. **Helper Functions**:
|
|
1695
|
+
- `text()` - LLM text generation for analysis
|
|
1696
|
+
- `json()` - Structured signal generation with schema validation
|
|
1697
|
+
- `dumpJson()` - Debug logging to ./dump/strategy
|
|
1698
|
+
3. **Exchange Configuration** - CCXT Binance integration
|
|
1699
|
+
4. **Frame Definitions** - Training and testing timeframes
|
|
1700
|
+
5. **Strategy Implementations** - One per training range with multi-timeframe analysis
|
|
1701
|
+
6. **Walker Setup** - Automatic strategy comparison on test period
|
|
1702
|
+
7. **Event Listeners** - Progress tracking and result collection
|
|
1703
|
+
|
|
1704
|
+
### Advanced Configuration
|
|
1705
|
+
|
|
1706
|
+
#### Custom Message Templates
|
|
1707
|
+
|
|
1708
|
+
Override default LLM message formatting:
|
|
1709
|
+
|
|
1710
|
+
```typescript
|
|
1711
|
+
addOptimizer({
|
|
1712
|
+
optimizerName: "custom-optimizer",
|
|
1713
|
+
rangeTrain: [...],
|
|
1714
|
+
rangeTest: {...},
|
|
1715
|
+
source: [...],
|
|
1716
|
+
getPrompt: async (symbol, messages) => "...",
|
|
1717
|
+
|
|
1718
|
+
// Custom templates
|
|
1719
|
+
template: {
|
|
1720
|
+
getUserMessage: async (symbol, data, sourceName) => {
|
|
1721
|
+
return `Analyze ${sourceName} data for ${symbol}:\n${JSON.stringify(data, null, 2)}`;
|
|
1722
|
+
},
|
|
1723
|
+
getAssistantMessage: async (symbol, data, sourceName) => {
|
|
1724
|
+
return `Data from ${sourceName} analyzed successfully`;
|
|
1725
|
+
},
|
|
1726
|
+
},
|
|
1727
|
+
});
|
|
1728
|
+
```
|
|
1729
|
+
|
|
1730
|
+
#### Lifecycle Callbacks
|
|
1731
|
+
|
|
1732
|
+
Monitor optimizer operations:
|
|
1733
|
+
|
|
1734
|
+
```typescript
|
|
1735
|
+
addOptimizer({
|
|
1736
|
+
optimizerName: "monitored-optimizer",
|
|
1737
|
+
rangeTrain: [...],
|
|
1738
|
+
rangeTest: {...},
|
|
1739
|
+
source: [...],
|
|
1740
|
+
getPrompt: async (symbol, messages) => "...",
|
|
1741
|
+
|
|
1742
|
+
callbacks: {
|
|
1743
|
+
onSourceData: async (symbol, sourceName, data, startDate, endDate) => {
|
|
1744
|
+
console.log(`Fetched ${data.length} rows from ${sourceName}`);
|
|
1745
|
+
},
|
|
1746
|
+
onData: async (symbol, strategyData) => {
|
|
1747
|
+
console.log(`Generated ${strategyData.length} strategies for ${symbol}`);
|
|
1748
|
+
},
|
|
1749
|
+
onCode: async (symbol, code) => {
|
|
1750
|
+
console.log(`Generated ${code.length} bytes of code`);
|
|
1751
|
+
},
|
|
1752
|
+
onDump: async (symbol, filepath) => {
|
|
1753
|
+
console.log(`Saved strategy to ${filepath}`);
|
|
1754
|
+
},
|
|
1755
|
+
},
|
|
1756
|
+
});
|
|
1757
|
+
```
|
|
1758
|
+
|
|
1759
|
+
#### Multiple Data Sources
|
|
1760
|
+
|
|
1761
|
+
Combine different data types for comprehensive analysis:
|
|
1762
|
+
|
|
1763
|
+
```typescript
|
|
1764
|
+
addOptimizer({
|
|
1765
|
+
optimizerName: "multi-source-optimizer",
|
|
1766
|
+
rangeTrain: [...],
|
|
1767
|
+
rangeTest: {...},
|
|
1768
|
+
|
|
1769
|
+
source: [
|
|
1770
|
+
// Source 1: Backtest results
|
|
1771
|
+
{
|
|
1772
|
+
name: "backtest-signals",
|
|
1773
|
+
fetch: async (args) => await getBacktestSignals(args),
|
|
1774
|
+
},
|
|
1775
|
+
|
|
1776
|
+
// Source 2: Market indicators
|
|
1777
|
+
{
|
|
1778
|
+
name: "technical-indicators",
|
|
1779
|
+
fetch: async (args) => await getTechnicalIndicators(args),
|
|
1780
|
+
},
|
|
1781
|
+
|
|
1782
|
+
// Source 3: Volume profile
|
|
1783
|
+
{
|
|
1784
|
+
name: "volume-analysis",
|
|
1785
|
+
fetch: async (args) => await getVolumeProfile(args),
|
|
1786
|
+
},
|
|
1787
|
+
|
|
1788
|
+
// Source 4: Order book depth
|
|
1789
|
+
{
|
|
1790
|
+
name: "order-book",
|
|
1791
|
+
fetch: async (args) => await getOrderBookData(args),
|
|
1792
|
+
},
|
|
1793
|
+
],
|
|
1794
|
+
|
|
1795
|
+
getPrompt: async (symbol, messages) => {
|
|
1796
|
+
// LLM has full context from all sources
|
|
1797
|
+
return `Create strategy using all available data sources`;
|
|
1798
|
+
},
|
|
1799
|
+
});
|
|
1800
|
+
```
|
|
1801
|
+
|
|
1802
|
+
### API Methods
|
|
1803
|
+
|
|
1804
|
+
```typescript
|
|
1805
|
+
// Get strategy metadata (no code generation)
|
|
1806
|
+
const strategies = await Optimizer.getData("BTCUSDT", {
|
|
1807
|
+
optimizerName: "my-optimizer"
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1810
|
+
// strategies[0].messages - LLM conversation history
|
|
1811
|
+
// strategies[0].strategy - Generated strategy prompt
|
|
1812
|
+
|
|
1813
|
+
// Generate executable code
|
|
1814
|
+
const code = await Optimizer.getCode("BTCUSDT", {
|
|
1815
|
+
optimizerName: "my-optimizer"
|
|
1816
|
+
});
|
|
1817
|
+
|
|
1818
|
+
// Save to file
|
|
1819
|
+
await Optimizer.dump("BTCUSDT", {
|
|
1820
|
+
optimizerName: "my-optimizer"
|
|
1821
|
+
}, "./output"); // Default: "./"
|
|
1822
|
+
```
|
|
1823
|
+
|
|
1824
|
+
### LLM Integration
|
|
1825
|
+
|
|
1826
|
+
The Optimizer uses Ollama for LLM inference:
|
|
1827
|
+
|
|
1828
|
+
```bash
|
|
1829
|
+
# Set up Ollama API
|
|
1830
|
+
export OLLAMA_API_KEY=your-api-key
|
|
1831
|
+
|
|
1832
|
+
# Run generated strategy
|
|
1833
|
+
node generated/btc-optimizer_BTCUSDT.mjs
|
|
1834
|
+
```
|
|
1835
|
+
|
|
1836
|
+
Generated strategies use:
|
|
1837
|
+
- **Model**: `gpt-oss:20b` (configurable in templates)
|
|
1838
|
+
- **Multi-timeframe analysis**: 1h, 15m, 5m, 1m candles
|
|
1839
|
+
- **Structured output**: JSON schema with signal validation
|
|
1840
|
+
- **Debug logging**: Saves conversations to ./dump/strategy
|
|
1841
|
+
|
|
1842
|
+
### Best Practices
|
|
1843
|
+
|
|
1844
|
+
1. **Training Periods**: Use 2-4 diverse market conditions (bull, bear, sideways)
|
|
1845
|
+
2. **Data Quality**: Ensure data sources have unique IDs for deduplication
|
|
1846
|
+
3. **Pagination**: Sources automatically paginated (25 records per request)
|
|
1847
|
+
4. **Testing**: Always validate generated strategies on unseen data (rangeTest)
|
|
1848
|
+
5. **Monitoring**: Use callbacks to track data fetching and code generation
|
|
1849
|
+
|
|
1074
1850
|
---
|
|
1075
1851
|
|
|
1076
1852
|
## π Architecture Overview
|
|
@@ -1273,6 +2049,63 @@ listenSignal((event) => {
|
|
|
1273
2049
|
});
|
|
1274
2050
|
```
|
|
1275
2051
|
|
|
2052
|
+
### Listen to Partial Profit/Loss Events
|
|
2053
|
+
|
|
2054
|
+
```typescript
|
|
2055
|
+
import {
|
|
2056
|
+
listenPartialProfit,
|
|
2057
|
+
listenPartialLoss,
|
|
2058
|
+
listenPartialProfitOnce,
|
|
2059
|
+
listenPartialLossOnce,
|
|
2060
|
+
Constant
|
|
2061
|
+
} from "backtest-kit";
|
|
2062
|
+
|
|
2063
|
+
// Listen to all profit milestones
|
|
2064
|
+
listenPartialProfit(({ symbol, signal, price, level, backtest }) => {
|
|
2065
|
+
console.log(`${symbol} reached ${level}% profit at ${price}`);
|
|
2066
|
+
|
|
2067
|
+
// Scale out at Kelly-optimized levels
|
|
2068
|
+
if (level === Constant.TP_LEVEL3) {
|
|
2069
|
+
console.log("Close 33% at 25% profit");
|
|
2070
|
+
}
|
|
2071
|
+
if (level === Constant.TP_LEVEL2) {
|
|
2072
|
+
console.log("Close 33% at 50% profit");
|
|
2073
|
+
}
|
|
2074
|
+
if (level === Constant.TP_LEVEL1) {
|
|
2075
|
+
console.log("Close 34% at 100% profit");
|
|
2076
|
+
}
|
|
2077
|
+
});
|
|
2078
|
+
|
|
2079
|
+
// Listen to all loss milestones
|
|
2080
|
+
listenPartialLoss(({ symbol, signal, price, level, backtest }) => {
|
|
2081
|
+
console.log(`${symbol} reached -${level}% loss at ${price}`);
|
|
2082
|
+
|
|
2083
|
+
// Scale out at stop levels
|
|
2084
|
+
if (level === Constant.SL_LEVEL2) {
|
|
2085
|
+
console.log("Close 50% at -50% loss");
|
|
2086
|
+
}
|
|
2087
|
+
if (level === Constant.SL_LEVEL1) {
|
|
2088
|
+
console.log("Close 50% at -100% loss");
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
|
|
2092
|
+
// Listen once to first profit level
|
|
2093
|
+
listenPartialProfitOnce(
|
|
2094
|
+
() => true, // Accept any profit event
|
|
2095
|
+
({ symbol, signal, price, level, backtest }) => {
|
|
2096
|
+
console.log(`First profit milestone: ${level}%`);
|
|
2097
|
+
}
|
|
2098
|
+
);
|
|
2099
|
+
|
|
2100
|
+
// Listen once to first loss level
|
|
2101
|
+
listenPartialLossOnce(
|
|
2102
|
+
() => true, // Accept any loss event
|
|
2103
|
+
({ symbol, signal, price, level, backtest }) => {
|
|
2104
|
+
console.log(`First loss milestone: -${level}%`);
|
|
2105
|
+
}
|
|
2106
|
+
);
|
|
2107
|
+
```
|
|
2108
|
+
|
|
1276
2109
|
### Listen Once with Filter
|
|
1277
2110
|
|
|
1278
2111
|
```typescript
|
|
@@ -1432,11 +2265,13 @@ await setConfig({
|
|
|
1432
2265
|
|
|
1433
2266
|
## β
Tested & Reliable
|
|
1434
2267
|
|
|
1435
|
-
`backtest-kit` comes with **
|
|
2268
|
+
`backtest-kit` comes with **217 unit and integration tests** covering:
|
|
1436
2269
|
|
|
1437
2270
|
- Signal validation and throttling
|
|
1438
2271
|
- PNL calculation with fees and slippage
|
|
1439
2272
|
- Crash recovery and state persistence
|
|
2273
|
+
- Dual-layer persistence (pending signals and scheduled signals)
|
|
2274
|
+
- Crash recovery validation (exchange/strategy name mismatch protection)
|
|
1440
2275
|
- Callback execution order (onSchedule, onOpen, onActive, onClose, onCancel)
|
|
1441
2276
|
- Markdown report generation (backtest, live, scheduled signals)
|
|
1442
2277
|
- Walker strategy comparison
|