backtest-kit 1.1.8 β 1.1.9
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 +806 -970
- package/build/index.cjs +3588 -275
- package/build/index.mjs +3569 -275
- package/package.json +1 -1
- package/types.d.ts +2955 -520
package/README.md
CHANGED
|
@@ -1,90 +1,284 @@
|
|
|
1
1
|
# π§Ώ Backtest Kit
|
|
2
2
|
|
|
3
|
-
> A production-ready TypeScript framework for backtesting and live trading strategies with crash-safe state persistence, signal validation, and memory-optimized architecture
|
|
3
|
+
> **A production-ready TypeScript framework for backtesting and live trading strategies with crash-safe state persistence, signal validation, and memory-optimized architecture.**
|
|
4
4
|
|
|
5
5
|
[](https://deepwiki.com/tripolskypetr/backtest-kit)
|
|
6
|
+
[](https://npmjs.org/package/backtest-kit)
|
|
6
7
|
[]()
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
8
|
+
|
|
9
|
+
Build sophisticated trading systems with confidence. Backtest Kit empowers you to develop, test, and deploy algorithmic trading strategies with enterprise-grade reliabilityβfeaturing atomic state persistence, comprehensive validation, and memory-efficient execution. Whether you're backtesting historical data or running live strategies, this framework provides the tools you need to trade with precision.
|
|
10
|
+
|
|
11
|
+
π **[API Reference](https://github.com/tripolskypetr/backtest-kit)** | π **[Quick Start](#quick-start)**
|
|
12
|
+
|
|
13
|
+
## π― Supported Order Types
|
|
14
|
+
|
|
15
|
+
Backtest Kit supports multiple execution styles to match real trading behavior:
|
|
16
|
+
|
|
17
|
+
## β¨ Why Choose Backtest Kit?
|
|
18
|
+
|
|
19
|
+
- π **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. β
|
|
20
|
+
|
|
21
|
+
- πΎ **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. π
|
|
22
|
+
|
|
23
|
+
- β
**Signal Validation**: Comprehensive validation prevents invalid trades before execution. Catches price logic errors (TP/SL), throttles signal spam, and ensures data integrity. π‘οΈ
|
|
24
|
+
|
|
25
|
+
- π **Async Generator Architecture**: Memory-efficient streaming for backtest and live execution. Process years of historical data without loading everything into memory. β‘
|
|
26
|
+
|
|
27
|
+
- π **VWAP Pricing**: Volume-weighted average price from last 5 1-minute candles ensures realistic backtest results that match live execution. π
|
|
28
|
+
|
|
29
|
+
- π― **Type-Safe Signal Lifecycle**: State machine with compile-time guarantees (idle β opened β active β closed). No runtime state confusion. π
|
|
30
|
+
|
|
31
|
+
- π **Accurate PNL Calculation**: Realistic profit/loss with configurable fees (0.1%) and slippage (0.1%). Track gross and net returns separately. π°
|
|
32
|
+
|
|
33
|
+
- β° **Time-Travel Context**: Async context propagation allows same strategy code to run in backtest (with historical time) and live (with real-time) without modifications. π
|
|
34
|
+
|
|
35
|
+
- π **Auto-Generated Reports**: Markdown reports with statistics (win rate, avg PNL, Sharpe Ratio, standard deviation, certainty ratio, expected yearly returns, risk-adjusted returns). π
|
|
36
|
+
|
|
37
|
+
- π **Revenue Profiling**: Built-in performance tracking with aggregated statistics (avg, min, max, stdDev, P95, P99) for bottleneck analysis. β‘
|
|
38
|
+
|
|
39
|
+
- π **Strategy Comparison (Walker)**: Compare multiple strategies in parallel with automatic ranking and statistical analysis. Find your best performer. π
|
|
40
|
+
|
|
41
|
+
- π₯ **Portfolio Heatmap**: Multi-symbol performance analysis with extended metrics (Profit Factor, Expectancy, Win/Loss Streaks, Avg Win/Loss) sorted by Sharpe Ratio. π
|
|
42
|
+
|
|
43
|
+
- π° **Position Sizing Calculator**: Built-in position sizing methods (Fixed Percentage, Kelly Criterion, ATR-based) with risk management constraints. π΅
|
|
44
|
+
|
|
45
|
+
- π‘οΈ **Risk Management System**: Portfolio-level risk controls with custom validation logic, concurrent position limits, and cross-strategy coordination. π
|
|
46
|
+
|
|
47
|
+
- πΎ **Zero Data Download**: Unlike Freqtrade, no need to download gigabytes of historical dataβplug any data source (CCXT, database, API). π
|
|
48
|
+
|
|
49
|
+
- π **Pluggable Persistence**: Replace default file-based persistence with custom adapters (Redis, MongoDB, PostgreSQL) for distributed systems and high-performance scenarios. πΎ
|
|
50
|
+
|
|
51
|
+
- π **Safe Math & Robustness**: All metrics protected against NaN/Infinity with unsafe numeric checks. Returns N/A for invalid calculations. β¨
|
|
52
|
+
|
|
53
|
+
- π§ͺ **Comprehensive Test Coverage**: 109 unit and integration tests covering validation, PNL, callbacks, reports, performance tracking, walker, heatmap, position sizing, risk management, and event system. β
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
### β
Built-in Order Types
|
|
58
|
+
|
|
59
|
+
- **Market** β instant execution using current VWAP
|
|
60
|
+
|
|
61
|
+
- **Limit** β entry at a specified `priceOpen`
|
|
62
|
+
|
|
63
|
+
- **Take Profit (TP)** β automatic exit at the target price
|
|
64
|
+
|
|
65
|
+
- **Stop Loss (SL)** β protective exit at the stop level
|
|
66
|
+
|
|
67
|
+
- **OCO (TP + SL)** β linked exits; one cancels the other
|
|
68
|
+
|
|
69
|
+
- **Time-Expired** β automatic closure after `minuteEstimatedTime` β±οΈ
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
### β Extendable Order Types
|
|
73
|
+
|
|
74
|
+
Easy to add without modifying the core:
|
|
75
|
+
|
|
76
|
+
- **Stop / Stop-Limit** β entry triggered by `triggerPrice`
|
|
77
|
+
|
|
78
|
+
- **Trailing Stop** β dynamic SL based on market movement
|
|
79
|
+
|
|
80
|
+
- **Conditional Entry** β enter only if price breaks a level (`above` / `below`)
|
|
81
|
+
|
|
82
|
+
- **Post-Only / Reduce-Only** β exchange-level execution flags
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## π Getting Started
|
|
87
|
+
|
|
88
|
+
### Installation
|
|
89
|
+
|
|
90
|
+
Get up and running in seconds:
|
|
31
91
|
|
|
32
92
|
```bash
|
|
33
93
|
npm install backtest-kit
|
|
34
94
|
```
|
|
35
95
|
|
|
36
|
-
|
|
96
|
+
### Quick Example
|
|
37
97
|
|
|
38
|
-
|
|
98
|
+
Here's a taste of what `backtest-kit` can doβcreate a simple moving average crossover strategy with crash-safe persistence:
|
|
39
99
|
|
|
40
100
|
```typescript
|
|
41
|
-
import {
|
|
42
|
-
|
|
101
|
+
import {
|
|
102
|
+
addExchange,
|
|
103
|
+
addStrategy,
|
|
104
|
+
addFrame,
|
|
105
|
+
Backtest,
|
|
106
|
+
listenSignalBacktest,
|
|
107
|
+
listenError,
|
|
108
|
+
listenDoneBacktest
|
|
109
|
+
} from "backtest-kit";
|
|
110
|
+
import ccxt from "ccxt";
|
|
43
111
|
|
|
112
|
+
// 1. Register exchange data source
|
|
44
113
|
addExchange({
|
|
45
114
|
exchangeName: "binance",
|
|
46
|
-
|
|
47
|
-
// Fetch historical candles
|
|
48
115
|
getCandles: async (symbol, interval, since, limit) => {
|
|
49
116
|
const exchange = new ccxt.binance();
|
|
50
117
|
const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
|
|
51
|
-
|
|
52
118
|
return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
|
|
53
|
-
timestamp,
|
|
54
|
-
open,
|
|
55
|
-
high,
|
|
56
|
-
low,
|
|
57
|
-
close,
|
|
58
|
-
volume,
|
|
119
|
+
timestamp, open, high, low, close, volume
|
|
59
120
|
}));
|
|
60
121
|
},
|
|
122
|
+
formatPrice: async (symbol, price) => price.toFixed(2),
|
|
123
|
+
formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
|
|
124
|
+
});
|
|
61
125
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
126
|
+
// 2. Register trading strategy
|
|
127
|
+
addStrategy({
|
|
128
|
+
strategyName: "sma-crossover",
|
|
129
|
+
interval: "5m", // Throttling: signals generated max once per 5 minutes
|
|
130
|
+
getSignal: async (symbol) => {
|
|
131
|
+
// Your signal generation logic
|
|
132
|
+
return {
|
|
133
|
+
position: "long",
|
|
134
|
+
note: "BTC breakout",
|
|
135
|
+
priceOpen: 50000,
|
|
136
|
+
priceTakeProfit: 51000, // Must be > priceOpen for long
|
|
137
|
+
priceStopLoss: 49000, // Must be < priceOpen for long
|
|
138
|
+
minuteEstimatedTime: 60, // Signal duration in minutes
|
|
139
|
+
};
|
|
67
140
|
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
141
|
+
callbacks: {
|
|
142
|
+
onOpen: (symbol, signal, currentPrice, backtest) => {
|
|
143
|
+
console.log(`[${backtest ? "BT" : "LIVE"}] Signal opened:`, signal.id);
|
|
144
|
+
},
|
|
145
|
+
onClose: (symbol, signal, priceClose, backtest) => {
|
|
146
|
+
console.log(`[${backtest ? "BT" : "LIVE"}] Signal closed:`, priceClose);
|
|
147
|
+
},
|
|
73
148
|
},
|
|
74
149
|
});
|
|
150
|
+
|
|
151
|
+
// 3. Add timeframe generator
|
|
152
|
+
addFrame({
|
|
153
|
+
frameName: "1d-backtest",
|
|
154
|
+
interval: "1m",
|
|
155
|
+
startDate: new Date("2024-01-01T00:00:00Z"),
|
|
156
|
+
endDate: new Date("2024-01-02T00:00:00Z"),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// 4. Run backtest in background
|
|
160
|
+
Backtest.background("BTCUSDT", {
|
|
161
|
+
strategyName: "sma-crossover",
|
|
162
|
+
exchangeName: "binance",
|
|
163
|
+
frameName: "1d-backtest"
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Listen to closed signals
|
|
167
|
+
listenSignalBacktest((event) => {
|
|
168
|
+
if (event.action === "closed") {
|
|
169
|
+
console.log("PNL:", event.pnl.pnlPercentage);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Listen to backtest completion
|
|
174
|
+
listenDoneBacktest((event) => {
|
|
175
|
+
console.log("Backtest completed:", event.symbol);
|
|
176
|
+
Backtest.dump(event.strategyName); // ./logs/backtest/sma-crossover.md
|
|
177
|
+
});
|
|
75
178
|
```
|
|
76
179
|
|
|
77
|
-
|
|
180
|
+
The feature of this library is dependency inversion for component injection. Exchanges, strategies, frames, and risk profiles are lazy-loaded during runtime, so you can declare them in separate modules and connect them with string constants π§©
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
export enum ExchangeName {
|
|
184
|
+
Binance = "binance",
|
|
185
|
+
Bybit = "bybit",
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export enum StrategyName {
|
|
189
|
+
SMACrossover = "sma-crossover",
|
|
190
|
+
RSIStrategy = "rsi-strategy",
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export enum FrameName {
|
|
194
|
+
OneDay = "1d-backtest",
|
|
195
|
+
OneWeek = "1w-backtest",
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ...
|
|
199
|
+
|
|
200
|
+
addStrategy({
|
|
201
|
+
strategyName: StrategyName.SMACrossover,
|
|
202
|
+
interval: "5m",
|
|
203
|
+
// ...
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
Backtest.background("BTCUSDT", {
|
|
207
|
+
strategyName: StrategyName.SMACrossover,
|
|
208
|
+
exchangeName: ExchangeName.Binance,
|
|
209
|
+
frameName: FrameName.OneDay
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## π Key Features
|
|
216
|
+
|
|
217
|
+
- π€ **Mode Switching**: Seamlessly switch between backtest and live modes with identical strategy code. π
|
|
218
|
+
- π **Crash Recovery**: Atomic persistence ensures state recovery after crashesβno duplicate signals. ποΈ
|
|
219
|
+
- π οΈ **Custom Validators**: Define validation rules with strategy-level throttling and price logic checks. π§
|
|
220
|
+
- π‘οΈ **Signal Lifecycle**: Type-safe state machine prevents invalid state transitions. π
|
|
221
|
+
- π¦ **Dependency Inversion**: Lazy-load components at runtime for modular, scalable designs. π§©
|
|
222
|
+
- π **Schema Reflection**: Runtime introspection with `listExchanges()`, `listStrategies()`, `listFrames()`. π
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## π― Use Cases
|
|
227
|
+
|
|
228
|
+
- π **Algorithmic Trading**: Backtest and deploy systematic trading strategies with confidence. πΉ
|
|
229
|
+
- π€ **Strategy Development**: Rapid prototyping with automatic validation and PNL tracking. π οΈ
|
|
230
|
+
- π **Performance Analysis**: Compare strategies with Walker and analyze portfolios with Heatmap. π
|
|
231
|
+
- πΌ **Portfolio Management**: Multi-symbol trading with risk controls and position sizing. π¦
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## π API Highlights
|
|
236
|
+
|
|
237
|
+
- π οΈ **`addExchange`**: Define exchange data sources (CCXT, database, API). π‘
|
|
238
|
+
- π€ **`addStrategy`**: Create trading strategies with custom signals and callbacks. π‘
|
|
239
|
+
- π **`addFrame`**: Configure timeframes for backtesting. π
|
|
240
|
+
- π **`Backtest` / `Live`**: Run strategies in backtest or live mode (generator or background). β‘
|
|
241
|
+
- π **`Walker`**: Compare multiple strategies in parallel with ranking. π
|
|
242
|
+
- π₯ **`Heat`**: Portfolio-wide performance analysis across multiple symbols. π
|
|
243
|
+
- π° **`PositionSize`**: Calculate position sizes with Fixed %, Kelly Criterion, or ATR-based methods. π΅
|
|
244
|
+
- π‘οΈ **`addRisk`**: Portfolio-level risk management with custom validation logic. π
|
|
245
|
+
- πΎ **`PersistBase`**: Base class for custom persistence adapters (Redis, MongoDB, PostgreSQL). ποΈ
|
|
246
|
+
- π **`PersistSignalAdapter` / `PersistRiskAdapter`**: Register custom adapters for signal and risk persistence. π
|
|
247
|
+
|
|
248
|
+
Check out the sections below for detailed examples! π
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## π Advanced Features
|
|
253
|
+
|
|
254
|
+
### 1. Register Exchange Data Source
|
|
255
|
+
|
|
256
|
+
You can plug any data sourceβCCXT for live data or a database for faster backtesting:
|
|
78
257
|
|
|
79
258
|
```typescript
|
|
80
259
|
import { addExchange } from "backtest-kit";
|
|
81
|
-
import
|
|
260
|
+
import ccxt from "ccxt";
|
|
82
261
|
|
|
262
|
+
// Option 1: CCXT (live or historical)
|
|
83
263
|
addExchange({
|
|
84
|
-
exchangeName: "binance
|
|
264
|
+
exchangeName: "binance",
|
|
265
|
+
getCandles: async (symbol, interval, since, limit) => {
|
|
266
|
+
const exchange = new ccxt.binance();
|
|
267
|
+
const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
|
|
268
|
+
return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
|
|
269
|
+
timestamp, open, high, low, close, volume
|
|
270
|
+
}));
|
|
271
|
+
},
|
|
272
|
+
formatPrice: async (symbol, price) => price.toFixed(2),
|
|
273
|
+
formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
|
|
274
|
+
});
|
|
85
275
|
|
|
276
|
+
// Option 2: Database (faster backtesting)
|
|
277
|
+
import { db } from "./database";
|
|
278
|
+
|
|
279
|
+
addExchange({
|
|
280
|
+
exchangeName: "binance-db",
|
|
86
281
|
getCandles: async (symbol, interval, since, limit) => {
|
|
87
|
-
// Fetch from database for faster backtesting
|
|
88
282
|
return await db.query(`
|
|
89
283
|
SELECT timestamp, open, high, low, close, volume
|
|
90
284
|
FROM candles
|
|
@@ -93,7 +287,6 @@ addExchange({
|
|
|
93
287
|
LIMIT $4
|
|
94
288
|
`, [symbol, interval, since, limit]);
|
|
95
289
|
},
|
|
96
|
-
|
|
97
290
|
formatPrice: async (symbol, price) => price.toFixed(2),
|
|
98
291
|
formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
|
|
99
292
|
});
|
|
@@ -101,6 +294,8 @@ addExchange({
|
|
|
101
294
|
|
|
102
295
|
### 2. Register Trading Strategy
|
|
103
296
|
|
|
297
|
+
Define your signal generation logic with automatic validation:
|
|
298
|
+
|
|
104
299
|
```typescript
|
|
105
300
|
import { addStrategy } from "backtest-kit";
|
|
106
301
|
|
|
@@ -109,7 +304,6 @@ addStrategy({
|
|
|
109
304
|
interval: "5m", // Throttling: signals generated max once per 5 minutes
|
|
110
305
|
getSignal: async (symbol) => {
|
|
111
306
|
// Your signal generation logic
|
|
112
|
-
// Validation happens automatically (prices, TP/SL logic)
|
|
113
307
|
return {
|
|
114
308
|
position: "long",
|
|
115
309
|
note: "BTC breakout",
|
|
@@ -130,62 +324,48 @@ addStrategy({
|
|
|
130
324
|
});
|
|
131
325
|
```
|
|
132
326
|
|
|
133
|
-
### 3.
|
|
327
|
+
### 3. Run Backtest
|
|
134
328
|
|
|
135
|
-
|
|
136
|
-
import { addFrame } from "backtest-kit";
|
|
137
|
-
|
|
138
|
-
addFrame({
|
|
139
|
-
frameName: "1d-backtest",
|
|
140
|
-
interval: "1m",
|
|
141
|
-
startDate: new Date("2024-01-01T00:00:00Z"),
|
|
142
|
-
endDate: new Date("2024-01-02T00:00:00Z"),
|
|
143
|
-
callbacks: {
|
|
144
|
-
onTimeframe: (timeframe, startDate, endDate, interval) => {
|
|
145
|
-
console.log(`Generated ${timeframe.length} timeframes from ${startDate} to ${endDate}`);
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### 4. Run Backtest
|
|
329
|
+
Run strategies in background mode (infinite loop) or manually iterate with async generators:
|
|
152
330
|
|
|
153
331
|
```typescript
|
|
154
|
-
import { Backtest, listenSignalBacktest,
|
|
332
|
+
import { Backtest, listenSignalBacktest, listenDoneBacktest } from "backtest-kit";
|
|
155
333
|
|
|
156
|
-
//
|
|
334
|
+
// Option 1: Background mode (recommended)
|
|
157
335
|
const stopBacktest = Backtest.background("BTCUSDT", {
|
|
158
336
|
strategyName: "my-strategy",
|
|
159
337
|
exchangeName: "binance",
|
|
160
338
|
frameName: "1d-backtest"
|
|
161
339
|
});
|
|
162
340
|
|
|
163
|
-
// Listen to closed signals
|
|
164
341
|
listenSignalBacktest((event) => {
|
|
165
342
|
if (event.action === "closed") {
|
|
166
343
|
console.log("PNL:", event.pnl.pnlPercentage);
|
|
167
344
|
}
|
|
168
345
|
});
|
|
169
346
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
347
|
+
listenDoneBacktest((event) => {
|
|
348
|
+
console.log("Backtest completed:", event.symbol);
|
|
349
|
+
Backtest.dump(event.strategyName); // ./logs/backtest/my-strategy.md
|
|
173
350
|
});
|
|
174
351
|
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
352
|
+
// Option 2: Manual iteration (for custom control)
|
|
353
|
+
for await (const result of Backtest.run("BTCUSDT", {
|
|
354
|
+
strategyName: "my-strategy",
|
|
355
|
+
exchangeName: "binance",
|
|
356
|
+
frameName: "1d-backtest"
|
|
357
|
+
})) {
|
|
358
|
+
console.log("PNL:", result.pnl.pnlPercentage);
|
|
359
|
+
if (result.pnl.pnlPercentage < -5) break; // Early termination
|
|
360
|
+
}
|
|
183
361
|
```
|
|
184
362
|
|
|
185
|
-
###
|
|
363
|
+
### 4. Run Live Trading (Crash-Safe)
|
|
364
|
+
|
|
365
|
+
Live mode automatically persists state to disk with atomic writes:
|
|
186
366
|
|
|
187
367
|
```typescript
|
|
188
|
-
import { Live, listenSignalLive
|
|
368
|
+
import { Live, listenSignalLive } from "backtest-kit";
|
|
189
369
|
|
|
190
370
|
// Run live trading in background (infinite loop, crash-safe)
|
|
191
371
|
const stop = Live.background("BTCUSDT", {
|
|
@@ -193,7 +373,6 @@ const stop = Live.background("BTCUSDT", {
|
|
|
193
373
|
exchangeName: "binance"
|
|
194
374
|
});
|
|
195
375
|
|
|
196
|
-
// Listen to all signal events
|
|
197
376
|
listenSignalLive((event) => {
|
|
198
377
|
if (event.action === "opened") {
|
|
199
378
|
console.log("Signal opened:", event.signal.id);
|
|
@@ -204,1054 +383,711 @@ listenSignalLive((event) => {
|
|
|
204
383
|
reason: event.closeReason,
|
|
205
384
|
pnl: event.pnl.pnlPercentage,
|
|
206
385
|
});
|
|
207
|
-
|
|
208
|
-
// Auto-save report
|
|
209
|
-
Live.dump(event.strategyName);
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// Listen to errors
|
|
214
|
-
listenError((error) => {
|
|
215
|
-
console.error("Error:", error.message);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
// Listen to completion
|
|
219
|
-
listenDone((event) => {
|
|
220
|
-
if (!event.backtest) {
|
|
221
|
-
console.log("Live trading stopped:", event.symbol);
|
|
386
|
+
Live.dump(event.strategyName); // Auto-save report
|
|
222
387
|
}
|
|
223
388
|
});
|
|
224
389
|
|
|
225
390
|
// Stop when needed: stop();
|
|
226
391
|
```
|
|
227
392
|
|
|
228
|
-
**Crash Recovery:** If process crashes, restart with same code
|
|
229
|
-
|
|
230
|
-
### 6. Alternative: Async Generators (Optional)
|
|
231
|
-
|
|
232
|
-
For manual control over execution flow:
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
import { Backtest, Live } from "backtest-kit";
|
|
236
|
-
|
|
237
|
-
// Manual backtest iteration
|
|
238
|
-
for await (const result of Backtest.run("BTCUSDT", {
|
|
239
|
-
strategyName: "my-strategy",
|
|
240
|
-
exchangeName: "binance",
|
|
241
|
-
frameName: "1d-backtest"
|
|
242
|
-
})) {
|
|
243
|
-
console.log("PNL:", result.pnl.pnlPercentage);
|
|
244
|
-
if (result.pnl.pnlPercentage < -5) break; // Early termination
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Manual live iteration (infinite loop)
|
|
248
|
-
for await (const result of Live.run("BTCUSDT", {
|
|
249
|
-
strategyName: "my-strategy",
|
|
250
|
-
exchangeName: "binance"
|
|
251
|
-
})) {
|
|
252
|
-
if (result.action === "closed") {
|
|
253
|
-
console.log("PNL:", result.pnl.pnlPercentage);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
```
|
|
393
|
+
**Crash Recovery:** If process crashes, restart with same codeβstate automatically recovered from disk (no duplicate signals).
|
|
257
394
|
|
|
258
|
-
###
|
|
395
|
+
### 5. Strategy Comparison with Walker
|
|
259
396
|
|
|
260
|
-
|
|
397
|
+
Walker runs multiple strategies in parallel and ranks them by a selected metric:
|
|
261
398
|
|
|
262
399
|
```typescript
|
|
263
|
-
import {
|
|
264
|
-
addExchange,
|
|
265
|
-
addStrategy,
|
|
266
|
-
addFrame,
|
|
267
|
-
listExchanges,
|
|
268
|
-
listStrategies,
|
|
269
|
-
listFrames
|
|
270
|
-
} from "backtest-kit";
|
|
400
|
+
import { addWalker, Walker, listenWalkerComplete } from "backtest-kit";
|
|
271
401
|
|
|
272
|
-
// Register
|
|
273
|
-
|
|
402
|
+
// Register walker schema
|
|
403
|
+
addWalker({
|
|
404
|
+
walkerName: "btc-walker",
|
|
274
405
|
exchangeName: "binance",
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
406
|
+
frameName: "1d-backtest",
|
|
407
|
+
strategies: ["strategy-a", "strategy-b", "strategy-c"],
|
|
408
|
+
metric: "sharpeRatio", // Metric to compare strategies
|
|
409
|
+
callbacks: {
|
|
410
|
+
onStrategyStart: (strategyName, symbol) => {
|
|
411
|
+
console.log(`Starting strategy: ${strategyName}`);
|
|
412
|
+
},
|
|
413
|
+
onStrategyComplete: (strategyName, symbol, stats) => {
|
|
414
|
+
console.log(`${strategyName} completed:`, stats.sharpeRatio);
|
|
415
|
+
},
|
|
416
|
+
onComplete: (results) => {
|
|
417
|
+
console.log("Best strategy:", results.bestStrategy);
|
|
418
|
+
console.log("Best metric:", results.bestMetric);
|
|
419
|
+
},
|
|
420
|
+
},
|
|
279
421
|
});
|
|
280
422
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
interval: "5m",
|
|
285
|
-
getSignal: async (symbol) => ({...}),
|
|
423
|
+
// Run walker in background
|
|
424
|
+
Walker.background("BTCUSDT", {
|
|
425
|
+
walkerName: "btc-walker"
|
|
286
426
|
});
|
|
287
427
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
startDate: new Date("2024-01-01"),
|
|
293
|
-
endDate: new Date("2024-02-01"),
|
|
428
|
+
// Listen to walker completion
|
|
429
|
+
listenWalkerComplete((results) => {
|
|
430
|
+
console.log("Walker completed:", results.bestStrategy);
|
|
431
|
+
Walker.dump("BTCUSDT", results.walkerName); // Save report
|
|
294
432
|
});
|
|
295
433
|
|
|
296
|
-
//
|
|
297
|
-
const
|
|
298
|
-
console.log(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
console.log("Available frames:", frames.map(f => ({
|
|
314
|
-
name: f.frameName,
|
|
315
|
-
note: f.note,
|
|
316
|
-
period: `${f.startDate.toISOString()} - ${f.endDate.toISOString()}`
|
|
317
|
-
})));
|
|
318
|
-
// Output: [{ name: "january-2024", note: "Full month backtest...", period: "2024-01-01..." }]
|
|
434
|
+
// Get raw comparison data
|
|
435
|
+
const results = await Walker.getData("BTCUSDT", "btc-walker");
|
|
436
|
+
console.log(results);
|
|
437
|
+
// Returns:
|
|
438
|
+
// {
|
|
439
|
+
// bestStrategy: "strategy-b",
|
|
440
|
+
// bestMetric: 1.85,
|
|
441
|
+
// strategies: [
|
|
442
|
+
// { strategyName: "strategy-a", stats: { sharpeRatio: 1.23, ... }, metric: 1.23 },
|
|
443
|
+
// { strategyName: "strategy-b", stats: { sharpeRatio: 1.85, ... }, metric: 1.85 },
|
|
444
|
+
// { strategyName: "strategy-c", stats: { sharpeRatio: 0.98, ... }, metric: 0.98 }
|
|
445
|
+
// ]
|
|
446
|
+
// }
|
|
447
|
+
|
|
448
|
+
// Generate markdown report
|
|
449
|
+
const markdown = await Walker.getReport("BTCUSDT", "btc-walker");
|
|
450
|
+
console.log(markdown);
|
|
319
451
|
```
|
|
320
452
|
|
|
321
|
-
**
|
|
322
|
-
-
|
|
323
|
-
-
|
|
324
|
-
-
|
|
325
|
-
-
|
|
453
|
+
**Available metrics for comparison:**
|
|
454
|
+
- `sharpeRatio` - Risk-adjusted return (default)
|
|
455
|
+
- `winRate` - Win percentage
|
|
456
|
+
- `avgPnl` - Average PNL percentage
|
|
457
|
+
- `totalPnl` - Total PNL percentage
|
|
458
|
+
- `certaintyRatio` - avgWin / |avgLoss|
|
|
326
459
|
|
|
327
|
-
|
|
460
|
+
### 6. Portfolio Heatmap
|
|
328
461
|
|
|
329
|
-
|
|
462
|
+
Heat provides portfolio-wide performance analysis across multiple symbols:
|
|
330
463
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
464
|
+
```typescript
|
|
465
|
+
import { Heat, Backtest } from "backtest-kit";
|
|
466
|
+
|
|
467
|
+
// Run backtests for multiple symbols
|
|
468
|
+
for (const symbol of ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT"]) {
|
|
469
|
+
for await (const _ of Backtest.run(symbol, {
|
|
470
|
+
strategyName: "my-strategy",
|
|
471
|
+
exchangeName: "binance",
|
|
472
|
+
frameName: "2024-backtest"
|
|
473
|
+
})) {}
|
|
474
|
+
}
|
|
338
475
|
|
|
339
|
-
|
|
476
|
+
// Get raw heatmap data
|
|
477
|
+
const stats = await Heat.getData("my-strategy");
|
|
478
|
+
console.log(stats);
|
|
479
|
+
// Returns:
|
|
480
|
+
// {
|
|
481
|
+
// symbols: [
|
|
482
|
+
// {
|
|
483
|
+
// symbol: "BTCUSDT",
|
|
484
|
+
// totalPnl: 15.5, // Total profit/loss %
|
|
485
|
+
// sharpeRatio: 2.10, // Risk-adjusted return
|
|
486
|
+
// profitFactor: 2.50, // Wins / Losses ratio
|
|
487
|
+
// expectancy: 1.85, // Expected value per trade
|
|
488
|
+
// winRate: 72.3, // Win percentage
|
|
489
|
+
// avgWin: 2.45, // Average win %
|
|
490
|
+
// avgLoss: -0.95, // Average loss %
|
|
491
|
+
// maxDrawdown: -2.5, // Maximum drawdown %
|
|
492
|
+
// maxWinStreak: 5, // Consecutive wins
|
|
493
|
+
// maxLossStreak: 2, // Consecutive losses
|
|
494
|
+
// totalTrades: 45,
|
|
495
|
+
// winCount: 32,
|
|
496
|
+
// lossCount: 13,
|
|
497
|
+
// avgPnl: 0.34,
|
|
498
|
+
// stdDev: 1.62
|
|
499
|
+
// },
|
|
500
|
+
// // ... more symbols sorted by Sharpe Ratio
|
|
501
|
+
// ],
|
|
502
|
+
// totalSymbols: 4,
|
|
503
|
+
// portfolioTotalPnl: 45.3, // Portfolio-wide total PNL
|
|
504
|
+
// portfolioSharpeRatio: 1.85, // Portfolio-wide Sharpe
|
|
505
|
+
// portfolioTotalTrades: 120
|
|
506
|
+
// }
|
|
340
507
|
|
|
341
|
-
|
|
508
|
+
// Generate markdown report
|
|
509
|
+
const markdown = await Heat.getReport("my-strategy");
|
|
510
|
+
console.log(markdown);
|
|
342
511
|
|
|
343
|
-
|
|
512
|
+
// Save to disk (default: ./logs/heatmap/my-strategy.md)
|
|
513
|
+
await Heat.dump("my-strategy");
|
|
514
|
+
```
|
|
344
515
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
position: "long",
|
|
349
|
-
priceOpen: 50000,
|
|
350
|
-
priceTakeProfit: 51000, // β
51000 > 50000
|
|
351
|
-
priceStopLoss: 49000, // β
49000 < 50000
|
|
352
|
-
minuteEstimatedTime: 60, // β
positive
|
|
353
|
-
}
|
|
516
|
+
**Heatmap Report Example:**
|
|
517
|
+
```markdown
|
|
518
|
+
# Portfolio Heatmap: my-strategy
|
|
354
519
|
|
|
355
|
-
|
|
356
|
-
{
|
|
357
|
-
position: "long",
|
|
358
|
-
priceOpen: 50000,
|
|
359
|
-
priceTakeProfit: 49000, // β 49000 < 50000 (must be higher for long)
|
|
360
|
-
priceStopLoss: 51000, // β 51000 > 50000 (must be lower for long)
|
|
361
|
-
}
|
|
520
|
+
**Total Symbols:** 4 | **Portfolio PNL:** +45.30% | **Portfolio Sharpe:** 1.85 | **Total Trades:** 120
|
|
362
521
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
522
|
+
| Symbol | Total PNL | Sharpe | PF | Expect | WR | Avg Win | Avg Loss | Max DD | W Streak | L Streak | Trades |
|
|
523
|
+
|--------|-----------|--------|-------|--------|-----|---------|----------|--------|----------|----------|--------|
|
|
524
|
+
| BTCUSDT | +15.50% | 2.10 | 2.50 | +1.85% | 72.3% | +2.45% | -0.95% | -2.50% | 5 | 2 | 45 |
|
|
525
|
+
| ETHUSDT | +12.30% | 1.85 | 2.15 | +1.45% | 68.5% | +2.10% | -1.05% | -3.10% | 4 | 2 | 38 |
|
|
526
|
+
| SOLUSDT | +10.20% | 1.65 | 1.95 | +1.20% | 65.2% | +1.95% | -1.15% | -4.20% | 3 | 3 | 25 |
|
|
527
|
+
| BNBUSDT | +7.30% | 1.40 | 1.75 | +0.95% | 62.5% | +1.75% | -1.20% | -3.80% | 3 | 2 | 12 |
|
|
370
528
|
```
|
|
371
529
|
|
|
372
|
-
|
|
530
|
+
**Column Descriptions:**
|
|
531
|
+
- **Total PNL** - Total profit/loss percentage across all trades
|
|
532
|
+
- **Sharpe** - Risk-adjusted return (higher is better)
|
|
533
|
+
- **PF** - Profit Factor: sum of wins / sum of losses (>1.0 is profitable)
|
|
534
|
+
- **Expect** - Expectancy: expected value per trade
|
|
535
|
+
- **WR** - Win Rate: percentage of winning trades
|
|
536
|
+
- **Avg Win** - Average profit on winning trades
|
|
537
|
+
- **Avg Loss** - Average loss on losing trades
|
|
538
|
+
- **Max DD** - Maximum drawdown (largest peak-to-trough decline)
|
|
539
|
+
- **W Streak** - Maximum consecutive winning trades
|
|
540
|
+
- **L Streak** - Maximum consecutive losing trades
|
|
541
|
+
- **Trades** - Total number of trades for this symbol
|
|
373
542
|
|
|
374
|
-
|
|
543
|
+
### 7. Position Sizing Calculator
|
|
375
544
|
|
|
376
|
-
|
|
545
|
+
Position Sizing Calculator helps determine optimal position sizes based on risk management rules:
|
|
377
546
|
|
|
378
547
|
```typescript
|
|
379
|
-
import {
|
|
380
|
-
|
|
548
|
+
import { addSizing, PositionSize } from "backtest-kit";
|
|
549
|
+
|
|
550
|
+
// Fixed Percentage Risk - risk fixed % of account per trade
|
|
551
|
+
addSizing({
|
|
552
|
+
sizingName: "conservative",
|
|
553
|
+
note: "Conservative 2% risk per trade",
|
|
554
|
+
method: "fixed-percentage",
|
|
555
|
+
riskPercentage: 2, // Risk 2% of account per trade
|
|
556
|
+
maxPositionPercentage: 10, // Max 10% of account in single position (optional)
|
|
557
|
+
minPositionSize: 0.001, // Min 0.001 BTC position (optional)
|
|
558
|
+
maxPositionSize: 1.0, // Max 1.0 BTC position (optional)
|
|
559
|
+
});
|
|
381
560
|
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
561
|
+
// Kelly Criterion - optimal bet sizing based on edge
|
|
562
|
+
addSizing({
|
|
563
|
+
sizingName: "kelly-quarter",
|
|
564
|
+
note: "Kelly Criterion with 25% multiplier for safety",
|
|
565
|
+
method: "kelly-criterion",
|
|
566
|
+
kellyMultiplier: 0.25, // Use 25% of full Kelly (recommended for safety)
|
|
567
|
+
maxPositionPercentage: 15, // Cap position at 15% of account (optional)
|
|
568
|
+
minPositionSize: 0.001, // Min 0.001 BTC position (optional)
|
|
569
|
+
maxPositionSize: 2.0, // Max 2.0 BTC position (optional)
|
|
570
|
+
});
|
|
388
571
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
572
|
+
// ATR-based - volatility-adjusted position sizing
|
|
573
|
+
addSizing({
|
|
574
|
+
sizingName: "atr-dynamic",
|
|
575
|
+
note: "ATR-based sizing with 2x multiplier",
|
|
576
|
+
method: "atr-based",
|
|
577
|
+
riskPercentage: 2, // Risk 2% of account
|
|
578
|
+
atrMultiplier: 2, // Use 2x ATR as stop distance
|
|
579
|
+
maxPositionPercentage: 12, // Max 12% of account (optional)
|
|
580
|
+
minPositionSize: 0.001, // Min 0.001 BTC position (optional)
|
|
581
|
+
maxPositionSize: 1.5, // Max 1.5 BTC position (optional)
|
|
582
|
+
});
|
|
393
583
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
584
|
+
// Calculate position sizes
|
|
585
|
+
const quantity1 = await PositionSize.fixedPercentage(
|
|
586
|
+
"BTCUSDT",
|
|
587
|
+
10000, // Account balance: $10,000
|
|
588
|
+
50000, // Entry price: $50,000
|
|
589
|
+
49000, // Stop loss: $49,000
|
|
590
|
+
{ sizingName: "conservative" }
|
|
591
|
+
);
|
|
592
|
+
console.log(`Position size: ${quantity1} BTC`);
|
|
593
|
+
|
|
594
|
+
const quantity2 = await PositionSize.kellyCriterion(
|
|
595
|
+
"BTCUSDT",
|
|
596
|
+
10000, // Account balance: $10,000
|
|
597
|
+
50000, // Entry price: $50,000
|
|
598
|
+
0.55, // Win rate: 55%
|
|
599
|
+
1.5, // Win/loss ratio: 1.5
|
|
600
|
+
{ sizingName: "kelly-quarter" }
|
|
601
|
+
);
|
|
602
|
+
console.log(`Position size: ${quantity2} BTC`);
|
|
603
|
+
|
|
604
|
+
const quantity3 = await PositionSize.atrBased(
|
|
605
|
+
"BTCUSDT",
|
|
606
|
+
10000, // Account balance: $10,000
|
|
607
|
+
50000, // Entry price: $50,000
|
|
608
|
+
500, // ATR: $500
|
|
609
|
+
{ sizingName: "atr-dynamic" }
|
|
610
|
+
);
|
|
611
|
+
console.log(`Position size: ${quantity3} BTC`);
|
|
612
|
+
```
|
|
397
613
|
|
|
398
|
-
|
|
399
|
-
throw new Error(`Entity ${this.entityName}:${entityId} not found`);
|
|
400
|
-
}
|
|
614
|
+
**When to Use Each Method:**
|
|
401
615
|
|
|
402
|
-
|
|
403
|
-
|
|
616
|
+
1. **Fixed Percentage** - Simple risk management, consistent risk per trade
|
|
617
|
+
- Best for: Beginners, conservative strategies
|
|
618
|
+
- Risk: Fixed 1-2% per trade
|
|
404
619
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
return exists === 1;
|
|
409
|
-
}
|
|
620
|
+
2. **Kelly Criterion** - Optimal bet sizing based on win rate and win/loss ratio
|
|
621
|
+
- Best for: Strategies with known edge, statistical advantage
|
|
622
|
+
- Risk: Use fractional Kelly (0.25-0.5) to reduce volatility
|
|
410
623
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
}
|
|
624
|
+
3. **ATR-based** - Volatility-adjusted sizing, accounts for market conditions
|
|
625
|
+
- Best for: Swing trading, volatile markets
|
|
626
|
+
- Risk: Position size scales with volatility
|
|
416
627
|
|
|
417
|
-
|
|
418
|
-
PersistSignalAdaper.usePersistSignalAdapter(RedisPersist);
|
|
628
|
+
### 8. Risk Management
|
|
419
629
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
630
|
+
Risk Management provides portfolio-level risk controls across strategies:
|
|
631
|
+
|
|
632
|
+
```typescript
|
|
633
|
+
import { addRisk } from "backtest-kit";
|
|
634
|
+
|
|
635
|
+
// Simple concurrent position limit
|
|
636
|
+
addRisk({
|
|
637
|
+
riskName: "conservative",
|
|
638
|
+
note: "Conservative risk profile with max 3 concurrent positions",
|
|
639
|
+
validations: [
|
|
640
|
+
({ activePositionCount }) => {
|
|
641
|
+
if (activePositionCount >= 3) {
|
|
642
|
+
throw new Error("Maximum 3 concurrent positions allowed");
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
],
|
|
646
|
+
callbacks: {
|
|
647
|
+
onRejected: (symbol, params) => {
|
|
648
|
+
console.warn(`Signal rejected for ${symbol}:`, params);
|
|
649
|
+
},
|
|
650
|
+
onAllowed: (symbol, params) => {
|
|
651
|
+
console.log(`Signal allowed for ${symbol}`);
|
|
652
|
+
},
|
|
653
|
+
},
|
|
424
654
|
});
|
|
425
|
-
```
|
|
426
655
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
656
|
+
// Symbol-based filtering
|
|
657
|
+
addRisk({
|
|
658
|
+
riskName: "no-meme-coins",
|
|
659
|
+
note: "Block meme coins from trading",
|
|
660
|
+
validations: [
|
|
661
|
+
({ symbol }) => {
|
|
662
|
+
const memeCoins = ["DOGEUSDT", "SHIBUSDT", "PEPEUSDT"];
|
|
663
|
+
if (memeCoins.includes(symbol)) {
|
|
664
|
+
throw new Error(`Meme coin ${symbol} not allowed`);
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
],
|
|
668
|
+
});
|
|
432
669
|
|
|
433
|
-
|
|
670
|
+
// Time-based trading windows
|
|
671
|
+
addRisk({
|
|
672
|
+
riskName: "trading-hours",
|
|
673
|
+
note: "Only trade during market hours (9 AM - 5 PM UTC)",
|
|
674
|
+
validations: [
|
|
675
|
+
({ timestamp }) => {
|
|
676
|
+
const date = new Date(timestamp);
|
|
677
|
+
const hour = date.getUTCHours();
|
|
678
|
+
|
|
679
|
+
if (hour < 9 || hour >= 17) {
|
|
680
|
+
throw new Error("Trading only allowed 9 AM - 5 PM UTC");
|
|
681
|
+
}
|
|
682
|
+
},
|
|
683
|
+
],
|
|
684
|
+
});
|
|
434
685
|
|
|
435
|
-
|
|
686
|
+
// Multi-strategy coordination with position inspection
|
|
687
|
+
addRisk({
|
|
688
|
+
riskName: "strategy-coordinator",
|
|
689
|
+
note: "Limit exposure per strategy and inspect active positions",
|
|
690
|
+
validations: [
|
|
691
|
+
({ activePositions, strategyName, symbol }) => {
|
|
692
|
+
// Count positions for this specific strategy
|
|
693
|
+
const strategyPositions = activePositions.filter(
|
|
694
|
+
(pos) => pos.strategyName === strategyName
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
if (strategyPositions.length >= 2) {
|
|
698
|
+
throw new Error(`Strategy ${strategyName} already has 2 positions`);
|
|
699
|
+
}
|
|
436
700
|
|
|
437
|
-
|
|
701
|
+
// Check if we already have a position on this symbol
|
|
702
|
+
const symbolPositions = activePositions.filter(
|
|
703
|
+
(pos) => pos.symbol === symbol
|
|
704
|
+
);
|
|
438
705
|
|
|
439
|
-
|
|
706
|
+
if (symbolPositions.length > 0) {
|
|
707
|
+
throw new Error(`Already have position on ${symbol}`);
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
],
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
// Use risk profile in strategy
|
|
440
714
|
addStrategy({
|
|
441
715
|
strategyName: "my-strategy",
|
|
442
|
-
interval: "5m",
|
|
716
|
+
interval: "5m",
|
|
717
|
+
riskName: "conservative", // Apply risk profile
|
|
443
718
|
getSignal: async (symbol) => {
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
return signal;
|
|
719
|
+
// Signal generation logic
|
|
720
|
+
return { /* ... */ };
|
|
447
721
|
},
|
|
448
722
|
});
|
|
449
723
|
```
|
|
450
724
|
|
|
451
|
-
|
|
725
|
+
### 9. Custom Persistence Adapters (Optional)
|
|
452
726
|
|
|
453
|
-
|
|
727
|
+
By default, backtest-kit uses file-based persistence with atomic writes. You can replace this with custom adapters (e.g., Redis, MongoDB, PostgreSQL) for distributed systems or high-performance scenarios.
|
|
454
728
|
|
|
455
|
-
|
|
729
|
+
#### Understanding the Persistence System
|
|
456
730
|
|
|
457
|
-
|
|
731
|
+
The library uses three persistence layers:
|
|
458
732
|
|
|
459
|
-
|
|
460
|
-
|
|
733
|
+
1. **PersistBase** - Base class for all persistence operations (file-based by default)
|
|
734
|
+
2. **PersistSignalAdapter** - Manages signal state persistence (used by Live mode)
|
|
735
|
+
3. **PersistRiskAdapter** - Manages active positions for risk management
|
|
461
736
|
|
|
462
|
-
|
|
463
|
-
const stopBacktest = Backtest.background("BTCUSDT", {
|
|
464
|
-
strategyName: "my-strategy",
|
|
465
|
-
exchangeName: "binance",
|
|
466
|
-
frameName: "1d-backtest"
|
|
467
|
-
});
|
|
737
|
+
#### Default File-Based Persistence
|
|
468
738
|
|
|
469
|
-
|
|
470
|
-
const stats = await Backtest.getData("my-strategy");
|
|
471
|
-
console.log(stats);
|
|
472
|
-
// Returns:
|
|
473
|
-
// {
|
|
474
|
-
// signalList: [...], // All closed signals
|
|
475
|
-
// totalSignals: 10,
|
|
476
|
-
// winCount: 7,
|
|
477
|
-
// lossCount: 3,
|
|
478
|
-
// winRate: 70.0, // Percentage (higher is better)
|
|
479
|
-
// avgPnl: 1.23, // Average PNL % (higher is better)
|
|
480
|
-
// totalPnl: 12.30, // Total PNL % (higher is better)
|
|
481
|
-
// stdDev: 2.45, // Standard deviation (lower is better)
|
|
482
|
-
// sharpeRatio: 0.50, // Risk-adjusted return (higher is better)
|
|
483
|
-
// annualizedSharpeRatio: 9.55, // Sharpe Γ β365 (higher is better)
|
|
484
|
-
// certaintyRatio: 1.75, // avgWin / |avgLoss| (higher is better)
|
|
485
|
-
// expectedYearlyReturns: 156 // Estimated yearly trades (higher is better)
|
|
486
|
-
// }
|
|
739
|
+
By default, data is stored in JSON files:
|
|
487
740
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
741
|
+
```
|
|
742
|
+
./logs/data/
|
|
743
|
+
signal/
|
|
744
|
+
my-strategy/
|
|
745
|
+
BTCUSDT.json # Signal state for BTCUSDT
|
|
746
|
+
ETHUSDT.json # Signal state for ETHUSDT
|
|
747
|
+
risk/
|
|
748
|
+
conservative/
|
|
749
|
+
positions.json # Active positions for risk profile
|
|
497
750
|
```
|
|
498
751
|
|
|
499
|
-
|
|
500
|
-
- `signalList` - Array of all closed signals
|
|
501
|
-
- `totalSignals` - Total number of closed signals
|
|
502
|
-
- `winCount` / `lossCount` - Number of winning/losing trades
|
|
503
|
-
- `winRate` - Win percentage (higher is better)
|
|
504
|
-
- `avgPnl` - Average PNL percentage (higher is better)
|
|
505
|
-
- `totalPnl` - Total PNL percentage (higher is better)
|
|
506
|
-
- `stdDev` - Standard deviation / volatility (lower is better)
|
|
507
|
-
- `sharpeRatio` - Risk-adjusted return (higher is better)
|
|
508
|
-
- `annualizedSharpeRatio` - Sharpe Ratio Γ β365 (higher is better)
|
|
509
|
-
- `certaintyRatio` - avgWin / |avgLoss| (higher is better)
|
|
510
|
-
- `expectedYearlyReturns` - Estimated number of trades per year (higher is better)
|
|
511
|
-
|
|
512
|
-
**getReport() includes:**
|
|
513
|
-
- All metrics from getData() formatted as markdown
|
|
514
|
-
- All signal details (prices, TP/SL, PNL, duration, close reason)
|
|
515
|
-
- Timestamps for each signal
|
|
516
|
-
- "Higher is better" / "Lower is better" annotations
|
|
517
|
-
|
|
518
|
-
### Live Trading Reports
|
|
752
|
+
#### Create Custom Adapter (Redis Example)
|
|
519
753
|
|
|
520
754
|
```typescript
|
|
521
|
-
import {
|
|
755
|
+
import { PersistBase, PersistSignalAdaper, PersistRiskAdapter } from "backtest-kit";
|
|
756
|
+
import Redis from "ioredis";
|
|
522
757
|
|
|
523
|
-
|
|
524
|
-
const stats = await Live.getData("my-strategy");
|
|
525
|
-
console.log(stats);
|
|
526
|
-
// Returns:
|
|
527
|
-
// {
|
|
528
|
-
// eventList: [...], // All events (idle, opened, active, closed)
|
|
529
|
-
// totalEvents: 15,
|
|
530
|
-
// totalClosed: 5,
|
|
531
|
-
// winCount: 3,
|
|
532
|
-
// lossCount: 2,
|
|
533
|
-
// winRate: 60.0, // Percentage (higher is better)
|
|
534
|
-
// avgPnl: 1.23, // Average PNL % (higher is better)
|
|
535
|
-
// totalPnl: 6.15, // Total PNL % (higher is better)
|
|
536
|
-
// stdDev: 1.85, // Standard deviation (lower is better)
|
|
537
|
-
// sharpeRatio: 0.66, // Risk-adjusted return (higher is better)
|
|
538
|
-
// annualizedSharpeRatio: 12.61,// Sharpe Γ β365 (higher is better)
|
|
539
|
-
// certaintyRatio: 2.10, // avgWin / |avgLoss| (higher is better)
|
|
540
|
-
// expectedYearlyReturns: 365 // Estimated yearly trades (higher is better)
|
|
541
|
-
// }
|
|
758
|
+
const redis = new Redis();
|
|
542
759
|
|
|
543
|
-
//
|
|
544
|
-
|
|
760
|
+
// Custom Redis-based persistence adapter
|
|
761
|
+
class RedisPersist extends PersistBase {
|
|
762
|
+
// Initialize Redis connection
|
|
763
|
+
async waitForInit(initial: boolean): Promise<void> {
|
|
764
|
+
// Redis connection is already established
|
|
765
|
+
console.log(`Redis persistence initialized for ${this.entityName}`);
|
|
766
|
+
}
|
|
545
767
|
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
768
|
+
// Read entity from Redis
|
|
769
|
+
async readValue<T>(entityId: string | number): Promise<T> {
|
|
770
|
+
const key = `${this.entityName}:${entityId}`;
|
|
771
|
+
const data = await redis.get(key);
|
|
549
772
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
- `totalClosed` - Total number of closed signals
|
|
554
|
-
- `winCount` / `lossCount` - Number of winning/losing trades
|
|
555
|
-
- `winRate` - Win percentage (higher is better)
|
|
556
|
-
- `avgPnl` - Average PNL percentage (higher is better)
|
|
557
|
-
- `totalPnl` - Total PNL percentage (higher is better)
|
|
558
|
-
- `stdDev` - Standard deviation / volatility (lower is better)
|
|
559
|
-
- `sharpeRatio` - Risk-adjusted return (higher is better)
|
|
560
|
-
- `annualizedSharpeRatio` - Sharpe Ratio Γ β365 (higher is better)
|
|
561
|
-
- `certaintyRatio` - avgWin / |avgLoss| (higher is better)
|
|
562
|
-
- `expectedYearlyReturns` - Estimated number of trades per year (higher is better)
|
|
563
|
-
|
|
564
|
-
**getReport() includes:**
|
|
565
|
-
- All metrics from getData() formatted as markdown
|
|
566
|
-
- Signal-by-signal details with current state
|
|
567
|
-
- "Higher is better" / "Lower is better" annotations
|
|
568
|
-
|
|
569
|
-
**Report example:**
|
|
570
|
-
```markdown
|
|
571
|
-
# Live Trading Report: my-strategy
|
|
572
|
-
|
|
573
|
-
Total events: 15
|
|
574
|
-
Closed signals: 5
|
|
575
|
-
Win rate: 60.00% (3W / 2L) (higher is better)
|
|
576
|
-
Average PNL: +1.23% (higher is better)
|
|
577
|
-
Total PNL: +6.15% (higher is better)
|
|
578
|
-
Standard Deviation: 1.85% (lower is better)
|
|
579
|
-
Sharpe Ratio: 0.66 (higher is better)
|
|
580
|
-
Annualized Sharpe Ratio: 12.61 (higher is better)
|
|
581
|
-
Certainty Ratio: 2.10 (higher is better)
|
|
582
|
-
Expected Yearly Returns: 365 trades (higher is better)
|
|
583
|
-
|
|
584
|
-
| Timestamp | Action | Symbol | Signal ID | Position | ... | PNL (net) | Close Reason |
|
|
585
|
-
|-----------|--------|--------|-----------|----------|-----|-----------|--------------|
|
|
586
|
-
| ... | CLOSED | BTCUSD | abc-123 | LONG | ... | +2.45% | take_profit |
|
|
587
|
-
```
|
|
773
|
+
if (!data) {
|
|
774
|
+
throw new Error(`Entity ${this.entityName}:${entityId} not found`);
|
|
775
|
+
}
|
|
588
776
|
|
|
589
|
-
|
|
777
|
+
return JSON.parse(data) as T;
|
|
778
|
+
}
|
|
590
779
|
|
|
591
|
-
|
|
780
|
+
// Check if entity exists in Redis
|
|
781
|
+
async hasValue(entityId: string | number): Promise<boolean> {
|
|
782
|
+
const key = `${this.entityName}:${entityId}`;
|
|
783
|
+
const exists = await redis.exists(key);
|
|
784
|
+
return exists === 1;
|
|
785
|
+
}
|
|
592
786
|
|
|
593
|
-
|
|
787
|
+
// Write entity to Redis
|
|
788
|
+
async writeValue<T>(entityId: string | number, entity: T): Promise<void> {
|
|
789
|
+
const key = `${this.entityName}:${entityId}`;
|
|
790
|
+
const serializedData = JSON.stringify(entity);
|
|
791
|
+
await redis.set(key, serializedData);
|
|
594
792
|
|
|
595
|
-
|
|
596
|
-
|
|
793
|
+
// Optional: Set TTL (time to live)
|
|
794
|
+
// await redis.expire(key, 86400); // 24 hours
|
|
795
|
+
}
|
|
597
796
|
|
|
598
|
-
//
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
frameName: "1d-backtest"
|
|
603
|
-
});
|
|
797
|
+
// Remove entity from Redis
|
|
798
|
+
async removeValue(entityId: string | number): Promise<void> {
|
|
799
|
+
const key = `${this.entityName}:${entityId}`;
|
|
800
|
+
const result = await redis.del(key);
|
|
604
801
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
console.log("Signal closed:", {
|
|
609
|
-
pnl: event.pnl.pnlPercentage,
|
|
610
|
-
reason: event.closeReason
|
|
611
|
-
});
|
|
802
|
+
if (result === 0) {
|
|
803
|
+
throw new Error(`Entity ${this.entityName}:${entityId} not found for deletion`);
|
|
804
|
+
}
|
|
612
805
|
}
|
|
613
|
-
});
|
|
614
806
|
|
|
615
|
-
//
|
|
616
|
-
|
|
617
|
-
|
|
807
|
+
// Remove all entities for this entity type
|
|
808
|
+
async removeAll(): Promise<void> {
|
|
809
|
+
const pattern = `${this.entityName}:*`;
|
|
810
|
+
const keys = await redis.keys(pattern);
|
|
618
811
|
|
|
619
|
-
|
|
812
|
+
if (keys.length > 0) {
|
|
813
|
+
await redis.del(...keys);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
620
816
|
|
|
621
|
-
|
|
622
|
-
|
|
817
|
+
// Iterate over all entity values
|
|
818
|
+
async *values<T>(): AsyncGenerator<T> {
|
|
819
|
+
const pattern = `${this.entityName}:*`;
|
|
820
|
+
const keys = await redis.keys(pattern);
|
|
623
821
|
|
|
624
|
-
//
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
});
|
|
822
|
+
// Sort keys alphanumerically
|
|
823
|
+
keys.sort((a, b) => a.localeCompare(b, undefined, {
|
|
824
|
+
numeric: true,
|
|
825
|
+
sensitivity: "base"
|
|
826
|
+
}));
|
|
630
827
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
828
|
+
for (const key of keys) {
|
|
829
|
+
const data = await redis.get(key);
|
|
830
|
+
if (data) {
|
|
831
|
+
yield JSON.parse(data) as T;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
637
834
|
}
|
|
638
|
-
);
|
|
639
|
-
```
|
|
640
835
|
|
|
641
|
-
|
|
836
|
+
// Iterate over all entity IDs
|
|
837
|
+
async *keys(): AsyncGenerator<string> {
|
|
838
|
+
const pattern = `${this.entityName}:*`;
|
|
839
|
+
const keys = await redis.keys(pattern);
|
|
642
840
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
strategyName: "my-strategy",
|
|
649
|
-
exchangeName: "binance"
|
|
650
|
-
});
|
|
841
|
+
// Sort keys alphanumerically
|
|
842
|
+
keys.sort((a, b) => a.localeCompare(b, undefined, {
|
|
843
|
+
numeric: true,
|
|
844
|
+
sensitivity: "base"
|
|
845
|
+
}));
|
|
651
846
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
if (event.action === "closed") {
|
|
658
|
-
console.log("Signal closed:", event.pnl.pnlPercentage);
|
|
847
|
+
for (const key of keys) {
|
|
848
|
+
// Extract entity ID from key (remove prefix)
|
|
849
|
+
const entityId = key.slice(this.entityName.length + 1);
|
|
850
|
+
yield entityId;
|
|
851
|
+
}
|
|
659
852
|
}
|
|
660
|
-
}
|
|
853
|
+
}
|
|
661
854
|
|
|
662
|
-
//
|
|
663
|
-
|
|
664
|
-
(event) => event.action === "closed" && event.closeReason === "stop_loss",
|
|
665
|
-
(event) => {
|
|
666
|
-
console.error("Stop loss hit!", event.pnl.pnlPercentage);
|
|
667
|
-
// Send alert, dump report, etc.
|
|
668
|
-
}
|
|
669
|
-
);
|
|
855
|
+
// Register Redis adapter for signal persistence
|
|
856
|
+
PersistSignalAdaper.usePersistSignalAdapter(RedisPersist);
|
|
670
857
|
|
|
671
|
-
//
|
|
672
|
-
|
|
858
|
+
// Register Redis adapter for risk persistence
|
|
859
|
+
PersistRiskAdapter.usePersistRiskAdapter(RedisPersist);
|
|
673
860
|
```
|
|
674
861
|
|
|
675
|
-
|
|
862
|
+
#### Custom Adapter Registration (Before Running Strategies)
|
|
676
863
|
|
|
677
864
|
```typescript
|
|
678
|
-
import {
|
|
679
|
-
|
|
680
|
-
// Listen to both backtest and live events
|
|
681
|
-
listenSignal((event) => {
|
|
682
|
-
console.log("Event:", event.action, event.strategyName);
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
// Wait for first loss from any source
|
|
686
|
-
listenSignalOnce(
|
|
687
|
-
(event) => event.action === "closed" && event.pnl.pnlPercentage < 0,
|
|
688
|
-
(event) => {
|
|
689
|
-
console.log("First loss detected:", event.pnl.pnlPercentage);
|
|
690
|
-
}
|
|
691
|
-
);
|
|
865
|
+
import { PersistSignalAdaper, PersistRiskAdapter, Live } from "backtest-kit";
|
|
692
866
|
|
|
693
|
-
//
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
exchangeName: "binance",
|
|
697
|
-
frameName: "1d-backtest"
|
|
698
|
-
});
|
|
867
|
+
// IMPORTANT: Register adapters BEFORE running any strategies
|
|
868
|
+
PersistSignalAdaper.usePersistSignalAdapter(RedisPersist);
|
|
869
|
+
PersistRiskAdapter.usePersistRiskAdapter(RedisPersist);
|
|
699
870
|
|
|
871
|
+
// Now run live trading with Redis persistence
|
|
700
872
|
Live.background("BTCUSDT", {
|
|
701
873
|
strategyName: "my-strategy",
|
|
702
874
|
exchangeName: "binance"
|
|
703
875
|
});
|
|
704
876
|
```
|
|
705
877
|
|
|
706
|
-
|
|
878
|
+
#### MongoDB Adapter Example
|
|
707
879
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
- `listenSignalBacktestOnce(filter, callback)` - Subscribe to backtest signals once
|
|
712
|
-
- `listenSignalLive(callback)` - Subscribe to live signals only
|
|
713
|
-
- `listenSignalLiveOnce(filter, callback)` - Subscribe to live signals once
|
|
714
|
-
- `listenPerformance(callback)` - Subscribe to performance metrics (backtest + live)
|
|
715
|
-
- `listenProgress(callback)` - Subscribe to backtest progress events
|
|
716
|
-
- `listenError(callback)` - Subscribe to background execution errors
|
|
717
|
-
- `listenDone(callback)` - Subscribe to background completion events
|
|
718
|
-
- `listenDoneOnce(filter, callback)` - Subscribe to background completion once
|
|
880
|
+
```typescript
|
|
881
|
+
import { PersistBase } from "backtest-kit";
|
|
882
|
+
import { MongoClient, Collection } from "mongodb";
|
|
719
883
|
|
|
720
|
-
|
|
884
|
+
const client = new MongoClient("mongodb://localhost:27017");
|
|
885
|
+
const db = client.db("backtest-kit");
|
|
721
886
|
|
|
722
|
-
|
|
887
|
+
class MongoPersist extends PersistBase {
|
|
888
|
+
private collection: Collection;
|
|
723
889
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
// Listen to all completion events
|
|
728
|
-
listenDone((event) => {
|
|
729
|
-
console.log("Execution completed:", {
|
|
730
|
-
mode: event.backtest ? "backtest" : "live",
|
|
731
|
-
symbol: event.symbol,
|
|
732
|
-
strategy: event.strategyName,
|
|
733
|
-
exchange: event.exchangeName,
|
|
734
|
-
});
|
|
735
|
-
|
|
736
|
-
// Auto-generate report on completion
|
|
737
|
-
if (event.backtest) {
|
|
738
|
-
Backtest.dump(event.strategyName);
|
|
739
|
-
} else {
|
|
740
|
-
Live.dump(event.strategyName);
|
|
890
|
+
constructor(entityName: string, baseDir: string) {
|
|
891
|
+
super(entityName, baseDir);
|
|
892
|
+
this.collection = db.collection(this.entityName);
|
|
741
893
|
}
|
|
742
|
-
});
|
|
743
894
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
console.log(
|
|
749
|
-
// Start next backtest or live trading
|
|
750
|
-
Live.background(event.symbol, {
|
|
751
|
-
strategyName: event.strategyName,
|
|
752
|
-
exchangeName: event.exchangeName,
|
|
753
|
-
});
|
|
895
|
+
async waitForInit(initial: boolean): Promise<void> {
|
|
896
|
+
await client.connect();
|
|
897
|
+
// Create index for faster lookups
|
|
898
|
+
await this.collection.createIndex({ entityId: 1 }, { unique: true });
|
|
899
|
+
console.log(`MongoDB persistence initialized for ${this.entityName}`);
|
|
754
900
|
}
|
|
755
|
-
);
|
|
756
|
-
|
|
757
|
-
// Run backtests
|
|
758
|
-
Backtest.background("BTCUSDT", {
|
|
759
|
-
strategyName: "my-strategy",
|
|
760
|
-
exchangeName: "binance",
|
|
761
|
-
frameName: "1d-backtest"
|
|
762
|
-
});
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
## API Reference
|
|
766
|
-
|
|
767
|
-
### High-Level Functions
|
|
768
|
-
|
|
769
|
-
#### Schema Registration
|
|
770
|
-
|
|
771
|
-
```typescript
|
|
772
|
-
// Register exchange
|
|
773
|
-
addExchange(exchangeSchema: IExchangeSchema): void
|
|
774
901
|
|
|
775
|
-
|
|
776
|
-
|
|
902
|
+
async readValue<T>(entityId: string | number): Promise<T> {
|
|
903
|
+
const doc = await this.collection.findOne({ entityId });
|
|
777
904
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
#### Exchange Data
|
|
783
|
-
|
|
784
|
-
```typescript
|
|
785
|
-
// Get historical candles
|
|
786
|
-
const candles = await getCandles("BTCUSDT", "1h", 5);
|
|
787
|
-
// Returns: [
|
|
788
|
-
// { timestamp: 1704067200000, open: 42150.5, high: 42380.2, low: 42100.0, close: 42250.8, volume: 125.43 },
|
|
789
|
-
// { timestamp: 1704070800000, open: 42250.8, high: 42500.0, low: 42200.0, close: 42450.3, volume: 98.76 },
|
|
790
|
-
// { timestamp: 1704074400000, open: 42450.3, high: 42600.0, low: 42400.0, close: 42580.5, volume: 110.22 },
|
|
791
|
-
// { timestamp: 1704078000000, open: 42580.5, high: 42700.0, low: 42550.0, close: 42650.0, volume: 95.18 },
|
|
792
|
-
// { timestamp: 1704081600000, open: 42650.0, high: 42750.0, low: 42600.0, close: 42720.0, volume: 102.35 }
|
|
793
|
-
// ]
|
|
794
|
-
|
|
795
|
-
// Get VWAP from last 5 1m candles
|
|
796
|
-
const vwap = await getAveragePrice("BTCUSDT");
|
|
797
|
-
// Returns: 42685.34
|
|
798
|
-
|
|
799
|
-
// Get current date in execution context
|
|
800
|
-
const date = await getDate();
|
|
801
|
-
// Returns: 2024-01-01T12:00:00.000Z (in backtest mode, returns frame's current timestamp)
|
|
802
|
-
// Returns: 2024-01-15T10:30:45.123Z (in live mode, returns current wall clock time)
|
|
803
|
-
|
|
804
|
-
// Get current mode
|
|
805
|
-
const mode = await getMode();
|
|
806
|
-
// Returns: "backtest" or "live"
|
|
807
|
-
|
|
808
|
-
// Format price/quantity for exchange
|
|
809
|
-
const price = await formatPrice("BTCUSDT", 42685.3456789);
|
|
810
|
-
// Returns: "42685.35" (formatted to exchange precision)
|
|
811
|
-
|
|
812
|
-
const quantity = await formatQuantity("BTCUSDT", 0.123456789);
|
|
813
|
-
// Returns: "0.12345" (formatted to exchange precision)
|
|
814
|
-
```
|
|
815
|
-
|
|
816
|
-
### Service APIs
|
|
905
|
+
if (!doc) {
|
|
906
|
+
throw new Error(`Entity ${this.entityName}:${entityId} not found`);
|
|
907
|
+
}
|
|
817
908
|
|
|
818
|
-
|
|
909
|
+
return doc.data as T;
|
|
910
|
+
}
|
|
819
911
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
// Stream backtest results
|
|
824
|
-
Backtest.run(
|
|
825
|
-
symbol: string,
|
|
826
|
-
context: {
|
|
827
|
-
strategyName: string;
|
|
828
|
-
exchangeName: string;
|
|
829
|
-
frameName: string;
|
|
912
|
+
async hasValue(entityId: string | number): Promise<boolean> {
|
|
913
|
+
const count = await this.collection.countDocuments({ entityId });
|
|
914
|
+
return count > 0;
|
|
830
915
|
}
|
|
831
|
-
): AsyncIterableIterator<IStrategyTickResultClosed>
|
|
832
916
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
917
|
+
async writeValue<T>(entityId: string | number, entity: T): Promise<void> {
|
|
918
|
+
await this.collection.updateOne(
|
|
919
|
+
{ entityId },
|
|
920
|
+
{ $set: { entityId, data: entity, updatedAt: new Date() } },
|
|
921
|
+
{ upsert: true }
|
|
922
|
+
);
|
|
923
|
+
}
|
|
838
924
|
|
|
839
|
-
|
|
840
|
-
|
|
925
|
+
async removeValue(entityId: string | number): Promise<void> {
|
|
926
|
+
const result = await this.collection.deleteOne({ entityId });
|
|
841
927
|
|
|
842
|
-
|
|
843
|
-
|
|
928
|
+
if (result.deletedCount === 0) {
|
|
929
|
+
throw new Error(`Entity ${this.entityName}:${entityId} not found for deletion`);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
844
932
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
933
|
+
async removeAll(): Promise<void> {
|
|
934
|
+
await this.collection.deleteMany({});
|
|
935
|
+
}
|
|
848
936
|
|
|
849
|
-
|
|
937
|
+
async *values<T>(): AsyncGenerator<T> {
|
|
938
|
+
const cursor = this.collection.find({}).sort({ entityId: 1 });
|
|
850
939
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
// Stream live results (infinite)
|
|
855
|
-
Live.run(
|
|
856
|
-
symbol: string,
|
|
857
|
-
context: {
|
|
858
|
-
strategyName: string;
|
|
859
|
-
exchangeName: string;
|
|
940
|
+
for await (const doc of cursor) {
|
|
941
|
+
yield doc.data as T;
|
|
942
|
+
}
|
|
860
943
|
}
|
|
861
|
-
): AsyncIterableIterator<IStrategyTickResult>
|
|
862
944
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
symbol: string,
|
|
866
|
-
context: { strategyName, exchangeName }
|
|
867
|
-
): Promise<() => void> // Returns cancellation function
|
|
945
|
+
async *keys(): AsyncGenerator<string> {
|
|
946
|
+
const cursor = this.collection.find({}, { projection: { entityId: 1 } }).sort({ entityId: 1 });
|
|
868
947
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
948
|
+
for await (const doc of cursor) {
|
|
949
|
+
yield String(doc.entityId);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
874
953
|
|
|
875
|
-
//
|
|
876
|
-
|
|
954
|
+
// Register MongoDB adapter
|
|
955
|
+
PersistSignalAdaper.usePersistSignalAdapter(MongoPersist);
|
|
956
|
+
PersistRiskAdapter.usePersistRiskAdapter(MongoPersist);
|
|
877
957
|
```
|
|
878
958
|
|
|
879
|
-
####
|
|
880
|
-
|
|
881
|
-
```typescript
|
|
882
|
-
import { Performance, PerformanceStatistics, listenPerformance } from "backtest-kit";
|
|
959
|
+
#### Direct Persistence API Usage (Advanced)
|
|
883
960
|
|
|
884
|
-
|
|
885
|
-
Performance.getData(strategyName: string): Promise<PerformanceStatistics>
|
|
961
|
+
You can also use PersistBase directly for custom data storage:
|
|
886
962
|
|
|
887
|
-
|
|
888
|
-
|
|
963
|
+
```typescript
|
|
964
|
+
import { PersistBase } from "backtest-kit";
|
|
889
965
|
|
|
890
|
-
//
|
|
891
|
-
|
|
966
|
+
// Create custom persistence for trading logs
|
|
967
|
+
const tradingLogs = new PersistBase("trading-logs", "./logs/custom");
|
|
892
968
|
|
|
893
|
-
//
|
|
894
|
-
|
|
969
|
+
// Initialize
|
|
970
|
+
await tradingLogs.waitForInit(true);
|
|
895
971
|
|
|
896
|
-
//
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
972
|
+
// Write log entry
|
|
973
|
+
await tradingLogs.writeValue("log-1", {
|
|
974
|
+
timestamp: Date.now(),
|
|
975
|
+
message: "Strategy started",
|
|
976
|
+
metadata: { symbol: "BTCUSDT", strategy: "sma-crossover" }
|
|
901
977
|
});
|
|
902
|
-
```
|
|
903
978
|
|
|
904
|
-
|
|
979
|
+
// Read log entry
|
|
980
|
+
const log = await tradingLogs.readValue("log-1");
|
|
981
|
+
console.log(log);
|
|
905
982
|
|
|
906
|
-
|
|
983
|
+
// Check if log exists
|
|
984
|
+
const exists = await tradingLogs.hasValue("log-1");
|
|
985
|
+
console.log(`Log exists: ${exists}`);
|
|
907
986
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
signalList: IStrategyTickResultClosed[]; // All closed signals
|
|
912
|
-
totalSignals: number;
|
|
913
|
-
winCount: number;
|
|
914
|
-
lossCount: number;
|
|
915
|
-
winRate: number | null; // Win percentage (higher is better)
|
|
916
|
-
avgPnl: number | null; // Average PNL % (higher is better)
|
|
917
|
-
totalPnl: number | null; // Total PNL % (higher is better)
|
|
918
|
-
stdDev: number | null; // Standard deviation (lower is better)
|
|
919
|
-
sharpeRatio: number | null; // Risk-adjusted return (higher is better)
|
|
920
|
-
annualizedSharpeRatio: number | null; // Sharpe Γ β365 (higher is better)
|
|
921
|
-
certaintyRatio: number | null; // avgWin / |avgLoss| (higher is better)
|
|
922
|
-
expectedYearlyReturns: number | null; // Estimated yearly trades (higher is better)
|
|
987
|
+
// Iterate over all logs
|
|
988
|
+
for await (const log of tradingLogs.values()) {
|
|
989
|
+
console.log("Log:", log);
|
|
923
990
|
}
|
|
924
991
|
|
|
925
|
-
//
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
totalEvents: number;
|
|
929
|
-
totalClosed: number;
|
|
930
|
-
winCount: number;
|
|
931
|
-
lossCount: number;
|
|
932
|
-
winRate: number | null; // Win percentage (higher is better)
|
|
933
|
-
avgPnl: number | null; // Average PNL % (higher is better)
|
|
934
|
-
totalPnl: number | null; // Total PNL % (higher is better)
|
|
935
|
-
stdDev: number | null; // Standard deviation (lower is better)
|
|
936
|
-
sharpeRatio: number | null; // Risk-adjusted return (higher is better)
|
|
937
|
-
annualizedSharpeRatio: number | null; // Sharpe Γ β365 (higher is better)
|
|
938
|
-
certaintyRatio: number | null; // avgWin / |avgLoss| (higher is better)
|
|
939
|
-
expectedYearlyReturns: number | null; // Estimated yearly trades (higher is better)
|
|
992
|
+
// Get all log IDs
|
|
993
|
+
for await (const logId of tradingLogs.keys()) {
|
|
994
|
+
console.log("Log ID:", logId);
|
|
940
995
|
}
|
|
941
996
|
|
|
942
|
-
//
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
totalEvents: number; // Total number of performance events
|
|
946
|
-
totalDuration: number; // Total execution time (ms)
|
|
947
|
-
metricStats: Record<string, { // Statistics by metric type
|
|
948
|
-
metricType: PerformanceMetricType; // backtest_total | backtest_timeframe | backtest_signal | live_tick
|
|
949
|
-
count: number; // Number of samples
|
|
950
|
-
totalDuration: number; // Total duration (ms)
|
|
951
|
-
avgDuration: number; // Average duration (ms)
|
|
952
|
-
minDuration: number; // Minimum duration (ms)
|
|
953
|
-
maxDuration: number; // Maximum duration (ms)
|
|
954
|
-
stdDev: number; // Standard deviation (ms)
|
|
955
|
-
median: number; // Median duration (ms)
|
|
956
|
-
p95: number; // 95th percentile (ms)
|
|
957
|
-
p99: number; // 99th percentile (ms)
|
|
958
|
-
}>;
|
|
959
|
-
events: PerformanceContract[]; // All raw performance events
|
|
997
|
+
// Filter logs
|
|
998
|
+
for await (const log of tradingLogs.filter((l: any) => l.metadata.symbol === "BTCUSDT")) {
|
|
999
|
+
console.log("BTC Log:", log);
|
|
960
1000
|
}
|
|
961
1001
|
|
|
962
|
-
//
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
metricType: PerformanceMetricType; // Type of operation measured
|
|
966
|
-
duration: number; // Operation duration (ms)
|
|
967
|
-
strategyName: string; // Strategy name
|
|
968
|
-
exchangeName: string; // Exchange name
|
|
969
|
-
symbol: string; // Trading symbol
|
|
970
|
-
backtest: boolean; // true = backtest, false = live
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
// Performance metric types (exported from "backtest-kit")
|
|
974
|
-
type PerformanceMetricType =
|
|
975
|
-
| "backtest_total" // Total backtest duration
|
|
976
|
-
| "backtest_timeframe" // Single timeframe processing
|
|
977
|
-
| "backtest_signal" // Signal processing (tick + getNextCandles + backtest)
|
|
978
|
-
| "live_tick"; // Single live tick duration
|
|
979
|
-
```
|
|
980
|
-
|
|
981
|
-
### Signal Data
|
|
982
|
-
|
|
983
|
-
```typescript
|
|
984
|
-
interface ISignalRow {
|
|
985
|
-
id: string; // UUID v4 auto-generated
|
|
986
|
-
position: "long" | "short";
|
|
987
|
-
note?: string;
|
|
988
|
-
priceOpen: number;
|
|
989
|
-
priceTakeProfit: number;
|
|
990
|
-
priceStopLoss: number;
|
|
991
|
-
minuteEstimatedTime: number;
|
|
992
|
-
exchangeName: string;
|
|
993
|
-
strategyName: string;
|
|
994
|
-
timestamp: number; // Signal creation timestamp
|
|
995
|
-
symbol: string; // Trading pair (e.g., "BTCUSDT")
|
|
1002
|
+
// Take first 5 logs
|
|
1003
|
+
for await (const log of tradingLogs.take(5)) {
|
|
1004
|
+
console.log("Recent Log:", log);
|
|
996
1005
|
}
|
|
997
|
-
```
|
|
998
1006
|
|
|
999
|
-
|
|
1007
|
+
// Remove specific log
|
|
1008
|
+
await tradingLogs.removeValue("log-1");
|
|
1000
1009
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
| {
|
|
1004
|
-
action: "idle";
|
|
1005
|
-
signal: null;
|
|
1006
|
-
strategyName: string;
|
|
1007
|
-
exchangeName: string;
|
|
1008
|
-
currentPrice: number;
|
|
1009
|
-
}
|
|
1010
|
-
| {
|
|
1011
|
-
action: "opened";
|
|
1012
|
-
signal: ISignalRow;
|
|
1013
|
-
strategyName: string;
|
|
1014
|
-
exchangeName: string;
|
|
1015
|
-
currentPrice: number;
|
|
1016
|
-
}
|
|
1017
|
-
| {
|
|
1018
|
-
action: "active";
|
|
1019
|
-
signal: ISignalRow;
|
|
1020
|
-
currentPrice: number;
|
|
1021
|
-
strategyName: string;
|
|
1022
|
-
exchangeName: string;
|
|
1023
|
-
}
|
|
1024
|
-
| {
|
|
1025
|
-
action: "closed";
|
|
1026
|
-
signal: ISignalRow;
|
|
1027
|
-
currentPrice: number;
|
|
1028
|
-
closeReason: "take_profit" | "stop_loss" | "time_expired";
|
|
1029
|
-
closeTimestamp: number;
|
|
1030
|
-
pnl: {
|
|
1031
|
-
pnlPercentage: number;
|
|
1032
|
-
priceOpen: number; // Entry price adjusted with slippage and fees
|
|
1033
|
-
priceClose: number; // Exit price adjusted with slippage and fees
|
|
1034
|
-
};
|
|
1035
|
-
strategyName: string;
|
|
1036
|
-
exchangeName: string;
|
|
1037
|
-
};
|
|
1010
|
+
// Remove all logs
|
|
1011
|
+
await tradingLogs.removeAll();
|
|
1038
1012
|
```
|
|
1039
1013
|
|
|
1040
|
-
|
|
1014
|
+
#### When to Use Custom Adapters
|
|
1041
1015
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
// LONG position
|
|
1048
|
-
priceOpenWithCosts = priceOpen * (1 + slippage + fee)
|
|
1049
|
-
priceCloseWithCosts = priceClose * (1 - slippage - fee)
|
|
1050
|
-
pnl% = (priceCloseWithCosts - priceOpenWithCosts) / priceOpenWithCosts * 100
|
|
1051
|
-
|
|
1052
|
-
// SHORT position
|
|
1053
|
-
priceOpenWithCosts = priceOpen * (1 - slippage + fee)
|
|
1054
|
-
priceCloseWithCosts = priceClose * (1 + slippage + fee)
|
|
1055
|
-
pnl% = (priceOpenWithCosts - priceCloseWithCosts) / priceOpenWithCosts * 100
|
|
1056
|
-
```
|
|
1016
|
+
1. **Redis** - Best for high-performance distributed systems with multiple instances
|
|
1017
|
+
- Fast read/write operations
|
|
1018
|
+
- Built-in TTL (automatic cleanup)
|
|
1019
|
+
- Pub/sub for real-time updates
|
|
1057
1020
|
|
|
1058
|
-
|
|
1021
|
+
2. **MongoDB** - Best for complex queries and analytics
|
|
1022
|
+
- Rich query language
|
|
1023
|
+
- Aggregation pipelines
|
|
1024
|
+
- Scalable for large datasets
|
|
1059
1025
|
|
|
1060
|
-
|
|
1026
|
+
3. **PostgreSQL** - Best for ACID transactions and relational data
|
|
1027
|
+
- Strong consistency guarantees
|
|
1028
|
+
- Complex joins and queries
|
|
1029
|
+
- Mature ecosystem
|
|
1061
1030
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
5. **Interval Throttling** - Prevents signal spam
|
|
1067
|
-
6. **Live Trading Ready** - Full implementation with real-time progression
|
|
1068
|
-
7. **Error Recovery** - Stateless process with disk-based state
|
|
1031
|
+
4. **File-based (default)** - Best for single-instance deployments
|
|
1032
|
+
- No dependencies
|
|
1033
|
+
- Simple debugging (inspect JSON files)
|
|
1034
|
+
- Sufficient for most use cases
|
|
1069
1035
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
### Multi-Symbol Live Trading
|
|
1036
|
+
#### Testing Custom Adapters
|
|
1073
1037
|
|
|
1074
1038
|
```typescript
|
|
1075
|
-
import {
|
|
1076
|
-
|
|
1077
|
-
const symbols = ["BTCUSDT", "ETHUSDT", "SOLUSDT"];
|
|
1078
|
-
|
|
1079
|
-
// Run all symbols in parallel
|
|
1080
|
-
await Promise.all(
|
|
1081
|
-
symbols.map(async (symbol) => {
|
|
1082
|
-
for await (const result of Live.run(symbol, {
|
|
1083
|
-
strategyName: "my-strategy",
|
|
1084
|
-
exchangeName: "binance"
|
|
1085
|
-
})) {
|
|
1086
|
-
console.log(`[${symbol}]`, result.action);
|
|
1087
|
-
|
|
1088
|
-
// Generate reports periodically
|
|
1089
|
-
if (result.action === "closed") {
|
|
1090
|
-
await Live.dump("my-strategy");
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
})
|
|
1094
|
-
);
|
|
1095
|
-
```
|
|
1039
|
+
import { test } from "worker-testbed";
|
|
1040
|
+
import { PersistBase } from "backtest-kit";
|
|
1096
1041
|
|
|
1097
|
-
|
|
1042
|
+
test("Custom Redis adapter works correctly", async ({ pass, fail }) => {
|
|
1043
|
+
const persist = new RedisPersist("test-entity", "./logs/test");
|
|
1098
1044
|
|
|
1099
|
-
|
|
1100
|
-
import { listenProgress, Backtest } from "backtest-kit";
|
|
1101
|
-
|
|
1102
|
-
listenProgress((event) => {
|
|
1103
|
-
console.log(`Progress: ${(event.progress * 100).toFixed(2)}%`);
|
|
1104
|
-
console.log(`${event.processedFrames} / ${event.totalFrames} frames`);
|
|
1105
|
-
console.log(`Strategy: ${event.strategyName}, Symbol: ${event.symbol}`);
|
|
1106
|
-
});
|
|
1107
|
-
|
|
1108
|
-
Backtest.background("BTCUSDT", {
|
|
1109
|
-
strategyName: "my-strategy",
|
|
1110
|
-
exchangeName: "binance",
|
|
1111
|
-
frameName: "1d-backtest"
|
|
1112
|
-
});
|
|
1113
|
-
```
|
|
1114
|
-
|
|
1115
|
-
### Performance Profiling
|
|
1116
|
-
|
|
1117
|
-
```typescript
|
|
1118
|
-
import { Performance, listenPerformance, Backtest } from "backtest-kit";
|
|
1119
|
-
|
|
1120
|
-
// Listen to real-time performance metrics
|
|
1121
|
-
listenPerformance((event) => {
|
|
1122
|
-
console.log(`[${event.metricType}] ${event.duration.toFixed(2)}ms`);
|
|
1123
|
-
console.log(` Strategy: ${event.strategyName}`);
|
|
1124
|
-
console.log(` Symbol: ${event.symbol}, Backtest: ${event.backtest}`);
|
|
1125
|
-
});
|
|
1126
|
-
|
|
1127
|
-
// Run backtest
|
|
1128
|
-
await Backtest.background("BTCUSDT", {
|
|
1129
|
-
strategyName: "my-strategy",
|
|
1130
|
-
exchangeName: "binance",
|
|
1131
|
-
frameName: "1d-backtest"
|
|
1132
|
-
});
|
|
1133
|
-
|
|
1134
|
-
// Get aggregated performance statistics
|
|
1135
|
-
const perfStats = await Performance.getData("my-strategy");
|
|
1136
|
-
console.log("Performance Statistics:");
|
|
1137
|
-
console.log(` Total events: ${perfStats.totalEvents}`);
|
|
1138
|
-
console.log(` Total duration: ${perfStats.totalDuration.toFixed(2)}ms`);
|
|
1139
|
-
console.log(` Metrics tracked: ${Object.keys(perfStats.metricStats).join(", ")}`);
|
|
1140
|
-
|
|
1141
|
-
// Analyze bottlenecks
|
|
1142
|
-
for (const [type, stats] of Object.entries(perfStats.metricStats)) {
|
|
1143
|
-
console.log(`\n${type}:`);
|
|
1144
|
-
console.log(` Count: ${stats.count}`);
|
|
1145
|
-
console.log(` Average: ${stats.avgDuration.toFixed(2)}ms`);
|
|
1146
|
-
console.log(` Min/Max: ${stats.minDuration.toFixed(2)}ms / ${stats.maxDuration.toFixed(2)}ms`);
|
|
1147
|
-
console.log(` P95/P99: ${stats.p95.toFixed(2)}ms / ${stats.p99.toFixed(2)}ms`);
|
|
1148
|
-
console.log(` Std Dev: ${stats.stdDev.toFixed(2)}ms`);
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
// Generate and save performance report
|
|
1152
|
-
const markdown = await Performance.getReport("my-strategy");
|
|
1153
|
-
await Performance.dump("my-strategy"); // Saves to ./logs/performance/my-strategy.md
|
|
1154
|
-
```
|
|
1155
|
-
|
|
1156
|
-
**Performance Report Example:**
|
|
1157
|
-
```markdown
|
|
1158
|
-
# Performance Report: my-strategy
|
|
1159
|
-
|
|
1160
|
-
**Total events:** 1440
|
|
1161
|
-
**Total execution time:** 12345.67ms
|
|
1162
|
-
**Number of metric types:** 3
|
|
1163
|
-
|
|
1164
|
-
## Time Distribution
|
|
1165
|
-
|
|
1166
|
-
- **backtest_timeframe**: 65.4% (8074.32ms total)
|
|
1167
|
-
- **backtest_signal**: 28.3% (3493.85ms total)
|
|
1168
|
-
- **backtest_total**: 6.3% (777.50ms total)
|
|
1169
|
-
|
|
1170
|
-
## Detailed Metrics
|
|
1045
|
+
await persist.waitForInit(true);
|
|
1171
1046
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
| backtest_timeframe | 1440 | 8074.32 | 5.61 | 2.10 | 12.45 | 1.85 | 5.20 | 8.90 | 10.50 |
|
|
1175
|
-
| backtest_signal | 45 | 3493.85 | 77.64 | 45.20 | 125.80 | 18.32 | 75.10 | 110.20 | 120.15 |
|
|
1176
|
-
| backtest_total | 1 | 777.50 | 777.50 | 777.50 | 777.50 | 0.00 | 777.50 | 777.50 | 777.50 |
|
|
1047
|
+
// Write
|
|
1048
|
+
await persist.writeValue("key1", { data: "value1" });
|
|
1177
1049
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
```typescript
|
|
1186
|
-
import { Backtest } from "backtest-kit";
|
|
1187
|
-
|
|
1188
|
-
for await (const result of Backtest.run("BTCUSDT", {
|
|
1189
|
-
strategyName: "my-strategy",
|
|
1190
|
-
exchangeName: "binance",
|
|
1191
|
-
frameName: "1d-backtest"
|
|
1192
|
-
})) {
|
|
1193
|
-
if (result.closeReason === "stop_loss") {
|
|
1194
|
-
console.log("Stop loss hit - terminating backtest");
|
|
1195
|
-
|
|
1196
|
-
// Save final report before exit
|
|
1197
|
-
await Backtest.dump("my-strategy");
|
|
1198
|
-
break; // Generator stops immediately
|
|
1050
|
+
// Read
|
|
1051
|
+
const value = await persist.readValue("key1");
|
|
1052
|
+
if (value.data === "value1") {
|
|
1053
|
+
pass("Redis adapter read/write works");
|
|
1054
|
+
} else {
|
|
1055
|
+
fail("Redis adapter failed");
|
|
1199
1056
|
}
|
|
1200
|
-
}
|
|
1201
|
-
```
|
|
1202
1057
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
```typescript
|
|
1206
|
-
import { Backtest, Live, listenSignalLiveOnce } from "backtest-kit";
|
|
1207
|
-
|
|
1208
|
-
// Backtest.background returns a stop function
|
|
1209
|
-
const stopBacktest = await Backtest.background("BTCUSDT", {
|
|
1210
|
-
strategyName: "my-strategy",
|
|
1211
|
-
exchangeName: "binance",
|
|
1212
|
-
frameName: "1d-backtest"
|
|
1058
|
+
// Cleanup
|
|
1059
|
+
await persist.removeValue("key1");
|
|
1213
1060
|
});
|
|
1214
|
-
|
|
1215
|
-
// Stop backtest after some condition
|
|
1216
|
-
setTimeout(() => {
|
|
1217
|
-
console.log("Stopping backtest...");
|
|
1218
|
-
stopBacktest(); // Stops the background execution
|
|
1219
|
-
}, 5000);
|
|
1220
|
-
|
|
1221
|
-
// Live.background also returns a stop function
|
|
1222
|
-
const stopLive = Live.background("BTCUSDT", {
|
|
1223
|
-
strategyName: "my-strategy",
|
|
1224
|
-
exchangeName: "binance"
|
|
1225
|
-
});
|
|
1226
|
-
|
|
1227
|
-
// Stop live trading after detecting stop loss
|
|
1228
|
-
listenSignalLiveOnce(
|
|
1229
|
-
(event) => event.action === "closed" && event.closeReason === "stop_loss",
|
|
1230
|
-
(event) => {
|
|
1231
|
-
console.log("Stop loss detected - stopping live trading");
|
|
1232
|
-
stopLive(); // Stops the infinite loop
|
|
1233
|
-
}
|
|
1234
|
-
);
|
|
1235
1061
|
```
|
|
1236
1062
|
|
|
1237
|
-
|
|
1063
|
+
---
|
|
1064
|
+
|
|
1065
|
+
## β
Tested & Reliable
|
|
1238
1066
|
|
|
1239
|
-
-
|
|
1240
|
-
-
|
|
1241
|
-
-
|
|
1242
|
-
-
|
|
1243
|
-
-
|
|
1067
|
+
`backtest-kit` comes with a robust test suite covering:
|
|
1068
|
+
- π‘οΈ **Validation**: Ensures all components (exchanges, strategies, frames, risk profiles) are properly configured. β
|
|
1069
|
+
- π **Recovery**: Handles edge cases like invalid signals or empty outputs. π οΈ
|
|
1070
|
+
- π **Navigation**: Smoothly switches between backtest and live modes without errors. π
|
|
1071
|
+
- β‘ **Performance**: Efficient memory usage and history management. π
|
|
1244
1072
|
|
|
1245
|
-
|
|
1073
|
+
**109 unit and integration tests** covering:
|
|
1074
|
+
- Signal validation and throttling
|
|
1075
|
+
- PNL calculation with fees and slippage
|
|
1076
|
+
- Crash recovery and state persistence
|
|
1077
|
+
- Callback execution order
|
|
1078
|
+
- Markdown report generation
|
|
1079
|
+
- Walker strategy comparison
|
|
1080
|
+
- Heatmap portfolio analysis
|
|
1081
|
+
- Position sizing calculations
|
|
1082
|
+
- Risk management validation
|
|
1083
|
+
- Event system
|
|
1246
1084
|
|
|
1247
|
-
|
|
1085
|
+
---
|
|
1248
1086
|
|
|
1249
|
-
##
|
|
1087
|
+
## π€ Contribute
|
|
1250
1088
|
|
|
1251
|
-
|
|
1089
|
+
We'd love your input! Fork the repo, submit a PR, or open an issue on **[GitHub](https://github.com/tripolskypetr/backtest-kit)**. π
|
|
1252
1090
|
|
|
1253
|
-
##
|
|
1091
|
+
## π License
|
|
1254
1092
|
|
|
1255
|
-
|
|
1256
|
-
- [TypeScript Documentation](https://www.typescriptlang.org/)
|
|
1257
|
-
- [Dependency Injection](https://github.com/tripolskypetr/di-kit)
|
|
1093
|
+
MIT Β© [tripolskypetr](https://github.com/tripolskypetr) ποΈ
|