backtest-kit 1.1.7 β†’ 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.
Files changed (5) hide show
  1. package/README.md +809 -841
  2. package/build/index.cjs +4404 -577
  3. package/build/index.mjs +4383 -577
  4. package/package.json +2 -2
  5. package/types.d.ts +3259 -486
package/README.md CHANGED
@@ -1,89 +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
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/tripolskypetr/backtest-kit)
6
+ [![npm](https://img.shields.io/npm/v/backtest-kit.svg?style=flat-square)](https://npmjs.org/package/backtest-kit)
6
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)]()
7
- [![Architecture](https://img.shields.io/badge/architecture-clean-orange)]()
8
-
9
- ## Features
10
-
11
- - πŸš€ **Production-Ready Architecture** - Backtest/live mode, robust error recovery
12
- - πŸ’Ύ **Crash-Safe Persistence** - Atomic file writes with automatic state recovery
13
- - βœ… **Signal Validation** - Comprehensive validation prevents invalid trades
14
- - πŸ”„ **Async Generators** - Memory-efficient streaming for backtest and live execution
15
- - πŸ“Š **VWAP Pricing** - Volume-weighted average price from last 5 1m candles
16
- - 🎯 **Signal Lifecycle** - Type-safe state machine (idle β†’ opened β†’ active β†’ closed)
17
- - πŸ“ˆ **Accurate PNL** - Calculation with fees (0.1%) and slippage (0.1%)
18
- - 🧠 **Interval Throttling** - Prevents signal spam at strategy level
19
- - ⚑ **Memory Optimized** - Prototype methods + memoization + streaming
20
- - πŸ”Œ **Flexible Architecture** - Plug your own exchanges and strategies
21
- - πŸ“ **Markdown Reports** - Auto-generated trading reports with statistics (win rate, avg PNL, Sharpe Ratio, Standard Deviation, Certainty Ratio, Expected Yearly Returns, Risk-Adjusted Returns)
22
- - πŸ›‘ **Graceful Shutdown** - Live.background() waits for open positions to close before stopping
23
- - πŸ’‰ **Strategy Dependency Injection** - addStrategy() enables DI pattern for trading strategies
24
- - πŸ” **Schema Reflection API** - listExchanges(), listStrategies(), listFrames() for runtime introspection
25
- - πŸ§ͺ **Comprehensive Test Coverage** - 50+ unit tests covering validation, PNL, callbacks, reports, and event system
26
- - πŸ’Ύ **Zero Data Download** - Unlike Freqtrade, no need to download gigabytes of historical data - plug any data source (CCXT, database, API)
27
- - πŸ”’ **Safe Math & Robustness** - All metrics protected against NaN/Infinity with unsafe numeric checks, returns N/A for invalid calculations
28
-
29
- ## Installation
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:
30
91
 
31
92
  ```bash
32
93
  npm install backtest-kit
33
94
  ```
34
95
 
35
- ## Quick Start
96
+ ### Quick Example
36
97
 
37
- ### 1. Register Exchange Data Source
98
+ Here's a taste of what `backtest-kit` can doβ€”create a simple moving average crossover strategy with crash-safe persistence:
38
99
 
39
100
  ```typescript
40
- import { addExchange } from "backtest-kit";
41
- import ccxt from "ccxt"; // Example using CCXT library
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";
42
111
 
112
+ // 1. Register exchange data source
43
113
  addExchange({
44
114
  exchangeName: "binance",
45
-
46
- // Fetch historical candles
47
115
  getCandles: async (symbol, interval, since, limit) => {
48
116
  const exchange = new ccxt.binance();
49
117
  const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
50
-
51
118
  return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
52
- timestamp,
53
- open,
54
- high,
55
- low,
56
- close,
57
- volume,
119
+ timestamp, open, high, low, close, volume
58
120
  }));
59
121
  },
122
+ formatPrice: async (symbol, price) => price.toFixed(2),
123
+ formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
124
+ });
60
125
 
61
- // Format price according to exchange rules (e.g., 2 decimals for BTC)
62
- formatPrice: async (symbol, price) => {
63
- const exchange = new ccxt.binance();
64
- const market = exchange.market(symbol);
65
- return exchange.priceToPrecision(symbol, price);
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
+ };
66
140
  },
67
-
68
- // Format quantity according to exchange rules (e.g., 8 decimals)
69
- formatQuantity: async (symbol, quantity) => {
70
- const exchange = new ccxt.binance();
71
- return exchange.amountToPrecision(symbol, quantity);
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
+ },
72
148
  },
73
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
+ });
178
+ ```
179
+
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
+ });
74
211
  ```
75
212
 
76
- **Alternative: Database implementation**
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:
77
257
 
78
258
  ```typescript
79
259
  import { addExchange } from "backtest-kit";
80
- import { db } from "./database"; // Your database client
260
+ import ccxt from "ccxt";
81
261
 
262
+ // Option 1: CCXT (live or historical)
82
263
  addExchange({
83
- exchangeName: "binance-db",
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
+ });
275
+
276
+ // Option 2: Database (faster backtesting)
277
+ import { db } from "./database";
84
278
 
279
+ addExchange({
280
+ exchangeName: "binance-db",
85
281
  getCandles: async (symbol, interval, since, limit) => {
86
- // Fetch from database for faster backtesting
87
282
  return await db.query(`
88
283
  SELECT timestamp, open, high, low, close, volume
89
284
  FROM candles
@@ -92,7 +287,6 @@ addExchange({
92
287
  LIMIT $4
93
288
  `, [symbol, interval, since, limit]);
94
289
  },
95
-
96
290
  formatPrice: async (symbol, price) => price.toFixed(2),
97
291
  formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
98
292
  });
@@ -100,6 +294,8 @@ addExchange({
100
294
 
101
295
  ### 2. Register Trading Strategy
102
296
 
297
+ Define your signal generation logic with automatic validation:
298
+
103
299
  ```typescript
104
300
  import { addStrategy } from "backtest-kit";
105
301
 
@@ -108,7 +304,6 @@ addStrategy({
108
304
  interval: "5m", // Throttling: signals generated max once per 5 minutes
109
305
  getSignal: async (symbol) => {
110
306
  // Your signal generation logic
111
- // Validation happens automatically (prices, TP/SL logic)
112
307
  return {
113
308
  position: "long",
114
309
  note: "BTC breakout",
@@ -129,62 +324,48 @@ addStrategy({
129
324
  });
130
325
  ```
131
326
 
132
- ### 3. Add Timeframe Generator
327
+ ### 3. Run Backtest
133
328
 
134
- ```typescript
135
- import { addFrame } from "backtest-kit";
136
-
137
- addFrame({
138
- frameName: "1d-backtest",
139
- interval: "1m",
140
- startDate: new Date("2024-01-01T00:00:00Z"),
141
- endDate: new Date("2024-01-02T00:00:00Z"),
142
- callbacks: {
143
- onTimeframe: (timeframe, startDate, endDate, interval) => {
144
- console.log(`Generated ${timeframe.length} timeframes from ${startDate} to ${endDate}`);
145
- },
146
- },
147
- });
148
- ```
149
-
150
- ### 4. Run Backtest
329
+ Run strategies in background mode (infinite loop) or manually iterate with async generators:
151
330
 
152
331
  ```typescript
153
- import { Backtest, listenSignalBacktest, listenError, listenDone } from "backtest-kit";
332
+ import { Backtest, listenSignalBacktest, listenDoneBacktest } from "backtest-kit";
154
333
 
155
- // Run backtest in background
334
+ // Option 1: Background mode (recommended)
156
335
  const stopBacktest = Backtest.background("BTCUSDT", {
157
336
  strategyName: "my-strategy",
158
337
  exchangeName: "binance",
159
338
  frameName: "1d-backtest"
160
339
  });
161
340
 
162
- // Listen to closed signals
163
341
  listenSignalBacktest((event) => {
164
342
  if (event.action === "closed") {
165
343
  console.log("PNL:", event.pnl.pnlPercentage);
166
344
  }
167
345
  });
168
346
 
169
- // Listen to errors
170
- listenError((error) => {
171
- console.error("Error:", error.message);
347
+ listenDoneBacktest((event) => {
348
+ console.log("Backtest completed:", event.symbol);
349
+ Backtest.dump(event.strategyName); // ./logs/backtest/my-strategy.md
172
350
  });
173
351
 
174
- // Listen to completion
175
- listenDone((event) => {
176
- if (event.backtest) {
177
- console.log("Backtest completed:", event.symbol);
178
- // Generate and save report
179
- Backtest.dump(event.strategyName); // ./logs/backtest/my-strategy.md
180
- }
181
- });
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
+ }
182
361
  ```
183
362
 
184
- ### 5. Run Live Trading (Crash-Safe)
363
+ ### 4. Run Live Trading (Crash-Safe)
364
+
365
+ Live mode automatically persists state to disk with atomic writes:
185
366
 
186
367
  ```typescript
187
- import { Live, listenSignalLive, listenError, listenDone } from "backtest-kit";
368
+ import { Live, listenSignalLive } from "backtest-kit";
188
369
 
189
370
  // Run live trading in background (infinite loop, crash-safe)
190
371
  const stop = Live.background("BTCUSDT", {
@@ -192,7 +373,6 @@ const stop = Live.background("BTCUSDT", {
192
373
  exchangeName: "binance"
193
374
  });
194
375
 
195
- // Listen to all signal events
196
376
  listenSignalLive((event) => {
197
377
  if (event.action === "opened") {
198
378
  console.log("Signal opened:", event.signal.id);
@@ -203,923 +383,711 @@ listenSignalLive((event) => {
203
383
  reason: event.closeReason,
204
384
  pnl: event.pnl.pnlPercentage,
205
385
  });
206
-
207
- // Auto-save report
208
- Live.dump(event.strategyName);
209
- }
210
- });
211
-
212
- // Listen to errors
213
- listenError((error) => {
214
- console.error("Error:", error.message);
215
- });
216
-
217
- // Listen to completion
218
- listenDone((event) => {
219
- if (!event.backtest) {
220
- console.log("Live trading stopped:", event.symbol);
386
+ Live.dump(event.strategyName); // Auto-save report
221
387
  }
222
388
  });
223
389
 
224
390
  // Stop when needed: stop();
225
391
  ```
226
392
 
227
- **Crash Recovery:** If process crashes, restart with same code - state automatically recovered from disk (no duplicate signals).
228
-
229
- ### 6. Alternative: Async Generators (Optional)
230
-
231
- For manual control over execution flow:
232
-
233
- ```typescript
234
- import { Backtest, Live } from "backtest-kit";
393
+ **Crash Recovery:** If process crashes, restart with same codeβ€”state automatically recovered from disk (no duplicate signals).
235
394
 
236
- // Manual backtest iteration
237
- for await (const result of Backtest.run("BTCUSDT", {
238
- strategyName: "my-strategy",
239
- exchangeName: "binance",
240
- frameName: "1d-backtest"
241
- })) {
242
- console.log("PNL:", result.pnl.pnlPercentage);
243
- if (result.pnl.pnlPercentage < -5) break; // Early termination
244
- }
395
+ ### 5. Strategy Comparison with Walker
245
396
 
246
- // Manual live iteration (infinite loop)
247
- for await (const result of Live.run("BTCUSDT", {
248
- strategyName: "my-strategy",
249
- exchangeName: "binance"
250
- })) {
251
- if (result.action === "closed") {
252
- console.log("PNL:", result.pnl.pnlPercentage);
253
- }
254
- }
255
- ```
256
-
257
- ### 7. Schema Reflection API (Optional)
258
-
259
- Retrieve registered schemas at runtime for debugging, documentation, or building dynamic UIs:
397
+ Walker runs multiple strategies in parallel and ranks them by a selected metric:
260
398
 
261
399
  ```typescript
262
- import {
263
- addExchange,
264
- addStrategy,
265
- addFrame,
266
- listExchanges,
267
- listStrategies,
268
- listFrames
269
- } from "backtest-kit";
400
+ import { addWalker, Walker, listenWalkerComplete } from "backtest-kit";
270
401
 
271
- // Register schemas with notes
272
- addExchange({
402
+ // Register walker schema
403
+ addWalker({
404
+ walkerName: "btc-walker",
273
405
  exchangeName: "binance",
274
- note: "Binance cryptocurrency exchange with database backend",
275
- getCandles: async (symbol, interval, since, limit) => [...],
276
- formatPrice: async (symbol, price) => price.toFixed(2),
277
- formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
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
+ },
278
421
  });
279
422
 
280
- addStrategy({
281
- strategyName: "sma-crossover",
282
- note: "Simple moving average crossover strategy (50/200)",
283
- interval: "5m",
284
- getSignal: async (symbol) => ({...}),
423
+ // Run walker in background
424
+ Walker.background("BTCUSDT", {
425
+ walkerName: "btc-walker"
285
426
  });
286
427
 
287
- addFrame({
288
- frameName: "january-2024",
289
- note: "Full month backtest for January 2024",
290
- interval: "1m",
291
- startDate: new Date("2024-01-01"),
292
- 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
293
432
  });
294
433
 
295
- // List all registered schemas
296
- const exchanges = await listExchanges();
297
- console.log("Available exchanges:", exchanges.map(e => ({
298
- name: e.exchangeName,
299
- note: e.note
300
- })));
301
- // Output: [{ name: "binance", note: "Binance cryptocurrency exchange..." }]
302
-
303
- const strategies = await listStrategies();
304
- console.log("Available strategies:", strategies.map(s => ({
305
- name: s.strategyName,
306
- note: s.note,
307
- interval: s.interval
308
- })));
309
- // Output: [{ name: "sma-crossover", note: "Simple moving average...", interval: "5m" }]
310
-
311
- const frames = await listFrames();
312
- console.log("Available frames:", frames.map(f => ({
313
- name: f.frameName,
314
- note: f.note,
315
- period: `${f.startDate.toISOString()} - ${f.endDate.toISOString()}`
316
- })));
317
- // 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);
318
451
  ```
319
452
 
320
- **Use cases:**
321
- - Generate documentation automatically from registered schemas
322
- - Build admin dashboards showing available strategies and exchanges
323
- - Create CLI tools with auto-completion based on registered schemas
324
- - Validate configuration files against registered schemas
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|
325
459
 
326
- ## Architecture Overview
460
+ ### 6. Portfolio Heatmap
327
461
 
328
- The framework follows **clean architecture** with:
462
+ Heat provides portfolio-wide performance analysis across multiple symbols:
329
463
 
330
- - **Client Layer** - Pure business logic without DI (ClientStrategy, ClientExchange, ClientFrame)
331
- - **Service Layer** - DI-based services organized by responsibility
332
- - **Schema Services** - Registry pattern for configuration
333
- - **Connection Services** - Memoized client instance creators
334
- - **Global Services** - Context wrappers for public API
335
- - **Logic Services** - Async generator orchestration (backtest/live)
336
- - **Persistence Layer** - Crash-safe atomic file writes with `PersistSignalAdaper`
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
+ }
337
475
 
338
- See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed documentation.
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
+ // }
339
507
 
340
- ## Signal Validation
508
+ // Generate markdown report
509
+ const markdown = await Heat.getReport("my-strategy");
510
+ console.log(markdown);
341
511
 
342
- All signals are validated automatically before execution:
512
+ // Save to disk (default: ./logs/heatmap/my-strategy.md)
513
+ await Heat.dump("my-strategy");
514
+ ```
343
515
 
344
- ```typescript
345
- // βœ… Valid long signal
346
- {
347
- position: "long",
348
- priceOpen: 50000,
349
- priceTakeProfit: 51000, // βœ… 51000 > 50000
350
- priceStopLoss: 49000, // βœ… 49000 < 50000
351
- minuteEstimatedTime: 60, // βœ… positive
352
- }
516
+ **Heatmap Report Example:**
517
+ ```markdown
518
+ # Portfolio Heatmap: my-strategy
353
519
 
354
- // ❌ Invalid long signal - throws error
355
- {
356
- position: "long",
357
- priceOpen: 50000,
358
- priceTakeProfit: 49000, // ❌ 49000 < 50000 (must be higher for long)
359
- priceStopLoss: 51000, // ❌ 51000 > 50000 (must be lower for long)
360
- }
520
+ **Total Symbols:** 4 | **Portfolio PNL:** +45.30% | **Portfolio Sharpe:** 1.85 | **Total Trades:** 120
361
521
 
362
- // βœ… Valid short signal
363
- {
364
- position: "short",
365
- priceOpen: 50000,
366
- priceTakeProfit: 49000, // βœ… 49000 < 50000 (profit goes down for short)
367
- priceStopLoss: 51000, // βœ… 51000 > 50000 (stop loss goes up for short)
368
- }
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 |
369
528
  ```
370
529
 
371
- Validation errors include detailed messages for debugging.
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
372
542
 
373
- ## Custom Persistence Adapter
543
+ ### 7. Position Sizing Calculator
374
544
 
375
- By default, signals are persisted to disk using atomic file writes (`./logs/data/signal/`). You can override the persistence layer with a custom adapter (e.g., Redis, MongoDB):
545
+ Position Sizing Calculator helps determine optimal position sizes based on risk management rules:
376
546
 
377
547
  ```typescript
378
- import { PersistBase, PersistSignalAdaper, ISignalData, EntityId } from "backtest-kit";
379
- import Redis from "ioredis";
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
+ });
380
560
 
381
- // Create custom Redis adapter
382
- class RedisPersist extends PersistBase {
383
- private redis = new Redis({
384
- host: "localhost",
385
- port: 6379,
386
- });
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
+ });
387
571
 
388
- async waitForInit(initial: boolean): Promise<void> {
389
- // Initialize Redis connection if needed
390
- await this.redis.ping();
391
- }
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
+ });
392
583
 
393
- async readValue(entityId: EntityId): Promise<ISignalData> {
394
- const key = `${this.entityName}:${entityId}`;
395
- const data = await this.redis.get(key);
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
+ ```
396
613
 
397
- if (!data) {
398
- throw new Error(`Entity ${this.entityName}:${entityId} not found`);
399
- }
614
+ **When to Use Each Method:**
400
615
 
401
- return JSON.parse(data);
402
- }
616
+ 1. **Fixed Percentage** - Simple risk management, consistent risk per trade
617
+ - Best for: Beginners, conservative strategies
618
+ - Risk: Fixed 1-2% per trade
403
619
 
404
- async hasValue(entityId: EntityId): Promise<boolean> {
405
- const key = `${this.entityName}:${entityId}`;
406
- const exists = await this.redis.exists(key);
407
- return exists === 1;
408
- }
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
409
623
 
410
- async writeValue(entityId: EntityId, entity: ISignalData): Promise<void> {
411
- const key = `${this.entityName}:${entityId}`;
412
- await this.redis.set(key, JSON.stringify(entity));
413
- }
414
- }
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
415
627
 
416
- // Register custom adapter
417
- PersistSignalAdaper.usePersistSignalAdapter(RedisPersist);
628
+ ### 8. Risk Management
418
629
 
419
- // Now all signal persistence uses Redis
420
- Live.background("BTCUSDT", {
421
- strategyName: "my-strategy",
422
- exchangeName: "binance"
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
+ },
423
654
  });
424
- ```
425
655
 
426
- **Key methods to implement:**
427
- - `waitForInit(initial)` - Initialize storage connection
428
- - `readValue(entityId)` - Read entity from storage
429
- - `hasValue(entityId)` - Check if entity exists
430
- - `writeValue(entityId, entity)` - Write entity to storage
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
+ });
431
669
 
432
- The adapter is registered globally and applies to all strategies.
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
+ });
433
685
 
434
- ## Interval Throttling
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
+ }
435
700
 
436
- Prevent signal spam with automatic throttling:
701
+ // Check if we already have a position on this symbol
702
+ const symbolPositions = activePositions.filter(
703
+ (pos) => pos.symbol === symbol
704
+ );
437
705
 
438
- ```typescript
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
439
714
  addStrategy({
440
715
  strategyName: "my-strategy",
441
- interval: "5m", // Signals generated max once per 5 minutes
716
+ interval: "5m",
717
+ riskName: "conservative", // Apply risk profile
442
718
  getSignal: async (symbol) => {
443
- // This function will be called max once per 5 minutes
444
- // Even if tick() is called every second
445
- return signal;
719
+ // Signal generation logic
720
+ return { /* ... */ };
446
721
  },
447
722
  });
448
723
  ```
449
724
 
450
- Supported intervals: `"1m"`, `"3m"`, `"5m"`, `"15m"`, `"30m"`, `"1h"`
725
+ ### 9. Custom Persistence Adapters (Optional)
451
726
 
452
- ## Markdown Reports
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.
453
728
 
454
- Generate detailed trading reports with statistics:
729
+ #### Understanding the Persistence System
455
730
 
456
- ### Backtest Reports
457
-
458
- ```typescript
459
- import { Backtest } from "backtest-kit";
460
-
461
- // Run backtest
462
- const stopBacktest = Backtest.background("BTCUSDT", {
463
- strategyName: "my-strategy",
464
- exchangeName: "binance",
465
- frameName: "1d-backtest"
466
- });
731
+ The library uses three persistence layers:
467
732
 
468
- // Get raw statistical data (Controller)
469
- const stats = await Backtest.getData("my-strategy");
470
- console.log(stats);
471
- // Returns:
472
- // {
473
- // signalList: [...], // All closed signals
474
- // totalSignals: 10,
475
- // winCount: 7,
476
- // lossCount: 3,
477
- // winRate: 70.0, // Percentage (higher is better)
478
- // avgPnl: 1.23, // Average PNL % (higher is better)
479
- // totalPnl: 12.30, // Total PNL % (higher is better)
480
- // stdDev: 2.45, // Standard deviation (lower is better)
481
- // sharpeRatio: 0.50, // Risk-adjusted return (higher is better)
482
- // annualizedSharpeRatio: 9.55, // Sharpe Γ— √365 (higher is better)
483
- // certaintyRatio: 1.75, // avgWin / |avgLoss| (higher is better)
484
- // expectedYearlyReturns: 156 // Estimated yearly trades (higher is better)
485
- // }
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
486
736
 
487
- // Generate markdown report (View)
488
- const markdown = await Backtest.getReport("my-strategy");
489
- console.log(markdown);
737
+ #### Default File-Based Persistence
490
738
 
491
- // Save to disk (default: ./logs/backtest/my-strategy.md)
492
- await Backtest.dump("my-strategy");
739
+ By default, data is stored in JSON files:
493
740
 
494
- // Save to custom path
495
- await Backtest.dump("my-strategy", "./custom/path");
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
496
750
  ```
497
751
 
498
- **getData() returns BacktestStatistics:**
499
- - `signalList` - Array of all closed signals
500
- - `totalSignals` - Total number of closed signals
501
- - `winCount` / `lossCount` - Number of winning/losing trades
502
- - `winRate` - Win percentage (higher is better)
503
- - `avgPnl` - Average PNL percentage (higher is better)
504
- - `totalPnl` - Total PNL percentage (higher is better)
505
- - `stdDev` - Standard deviation / volatility (lower is better)
506
- - `sharpeRatio` - Risk-adjusted return (higher is better)
507
- - `annualizedSharpeRatio` - Sharpe Ratio Γ— √365 (higher is better)
508
- - `certaintyRatio` - avgWin / |avgLoss| (higher is better)
509
- - `expectedYearlyReturns` - Estimated number of trades per year (higher is better)
510
-
511
- **getReport() includes:**
512
- - All metrics from getData() formatted as markdown
513
- - All signal details (prices, TP/SL, PNL, duration, close reason)
514
- - Timestamps for each signal
515
- - "Higher is better" / "Lower is better" annotations
516
-
517
- ### Live Trading Reports
752
+ #### Create Custom Adapter (Redis Example)
518
753
 
519
754
  ```typescript
520
- import { Live } from "backtest-kit";
755
+ import { PersistBase, PersistSignalAdaper, PersistRiskAdapter } from "backtest-kit";
756
+ import Redis from "ioredis";
521
757
 
522
- // Get raw statistical data (Controller)
523
- const stats = await Live.getData("my-strategy");
524
- console.log(stats);
525
- // Returns:
526
- // {
527
- // eventList: [...], // All events (idle, opened, active, closed)
528
- // totalEvents: 15,
529
- // totalClosed: 5,
530
- // winCount: 3,
531
- // lossCount: 2,
532
- // winRate: 60.0, // Percentage (higher is better)
533
- // avgPnl: 1.23, // Average PNL % (higher is better)
534
- // totalPnl: 6.15, // Total PNL % (higher is better)
535
- // stdDev: 1.85, // Standard deviation (lower is better)
536
- // sharpeRatio: 0.66, // Risk-adjusted return (higher is better)
537
- // annualizedSharpeRatio: 12.61,// Sharpe Γ— √365 (higher is better)
538
- // certaintyRatio: 2.10, // avgWin / |avgLoss| (higher is better)
539
- // expectedYearlyReturns: 365 // Estimated yearly trades (higher is better)
540
- // }
758
+ const redis = new Redis();
541
759
 
542
- // Generate markdown report (View)
543
- const markdown = await Live.getReport("my-strategy");
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
+ }
544
767
 
545
- // Save to disk (default: ./logs/live/my-strategy.md)
546
- await Live.dump("my-strategy");
547
- ```
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);
548
772
 
549
- **getData() returns LiveStatistics:**
550
- - `eventList` - Array of all events (idle, opened, active, closed)
551
- - `totalEvents` - Total number of events
552
- - `totalClosed` - Total number of closed signals
553
- - `winCount` / `lossCount` - Number of winning/losing trades
554
- - `winRate` - Win percentage (higher is better)
555
- - `avgPnl` - Average PNL percentage (higher is better)
556
- - `totalPnl` - Total PNL percentage (higher is better)
557
- - `stdDev` - Standard deviation / volatility (lower is better)
558
- - `sharpeRatio` - Risk-adjusted return (higher is better)
559
- - `annualizedSharpeRatio` - Sharpe Ratio Γ— √365 (higher is better)
560
- - `certaintyRatio` - avgWin / |avgLoss| (higher is better)
561
- - `expectedYearlyReturns` - Estimated number of trades per year (higher is better)
562
-
563
- **getReport() includes:**
564
- - All metrics from getData() formatted as markdown
565
- - Signal-by-signal details with current state
566
- - "Higher is better" / "Lower is better" annotations
567
-
568
- **Report example:**
569
- ```markdown
570
- # Live Trading Report: my-strategy
571
-
572
- Total events: 15
573
- Closed signals: 5
574
- Win rate: 60.00% (3W / 2L) (higher is better)
575
- Average PNL: +1.23% (higher is better)
576
- Total PNL: +6.15% (higher is better)
577
- Standard Deviation: 1.85% (lower is better)
578
- Sharpe Ratio: 0.66 (higher is better)
579
- Annualized Sharpe Ratio: 12.61 (higher is better)
580
- Certainty Ratio: 2.10 (higher is better)
581
- Expected Yearly Returns: 365 trades (higher is better)
582
-
583
- | Timestamp | Action | Symbol | Signal ID | Position | ... | PNL (net) | Close Reason |
584
- |-----------|--------|--------|-----------|----------|-----|-----------|--------------|
585
- | ... | CLOSED | BTCUSD | abc-123 | LONG | ... | +2.45% | take_profit |
586
- ```
773
+ if (!data) {
774
+ throw new Error(`Entity ${this.entityName}:${entityId} not found`);
775
+ }
587
776
 
588
- ## Event Listeners
777
+ return JSON.parse(data) as T;
778
+ }
589
779
 
590
- Subscribe to signal events with filtering support. Useful for running strategies in background while reacting to specific events.
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
+ }
591
786
 
592
- ### Background Execution with Event Listeners
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);
593
792
 
594
- ```typescript
595
- import { Backtest, listenSignalBacktest } from "backtest-kit";
793
+ // Optional: Set TTL (time to live)
794
+ // await redis.expire(key, 86400); // 24 hours
795
+ }
596
796
 
597
- // Run backtest in background (doesn't yield results)
598
- Backtest.background("BTCUSDT", {
599
- strategyName: "my-strategy",
600
- exchangeName: "binance",
601
- frameName: "1d-backtest"
602
- });
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);
603
801
 
604
- // Listen to all backtest events
605
- const unsubscribe = listenSignalBacktest((event) => {
606
- if (event.action === "closed") {
607
- console.log("Signal closed:", {
608
- pnl: event.pnl.pnlPercentage,
609
- reason: event.closeReason
610
- });
802
+ if (result === 0) {
803
+ throw new Error(`Entity ${this.entityName}:${entityId} not found for deletion`);
804
+ }
611
805
  }
612
- });
613
806
 
614
- // Stop listening when done
615
- // unsubscribe();
616
- ```
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);
617
811
 
618
- ### Listen Once with Filter
812
+ if (keys.length > 0) {
813
+ await redis.del(...keys);
814
+ }
815
+ }
619
816
 
620
- ```typescript
621
- import { Backtest, listenSignalBacktestOnce } from "backtest-kit";
817
+ // Iterate over all entity values
818
+ async *values<T>(): AsyncGenerator<T> {
819
+ const pattern = `${this.entityName}:*`;
820
+ const keys = await redis.keys(pattern);
622
821
 
623
- // Run backtest in background
624
- Backtest.background("BTCUSDT", {
625
- strategyName: "my-strategy",
626
- exchangeName: "binance",
627
- frameName: "1d-backtest"
628
- });
822
+ // Sort keys alphanumerically
823
+ keys.sort((a, b) => a.localeCompare(b, undefined, {
824
+ numeric: true,
825
+ sensitivity: "base"
826
+ }));
629
827
 
630
- // Wait for first take profit event
631
- listenSignalBacktestOnce(
632
- (event) => event.action === "closed" && event.closeReason === "take_profit",
633
- (event) => {
634
- console.log("First take profit hit!", event.pnl.pnlPercentage);
635
- // Automatically unsubscribes after first match
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
+ }
636
834
  }
637
- );
638
- ```
639
835
 
640
- ### Live Trading with Event Listeners
836
+ // Iterate over all entity IDs
837
+ async *keys(): AsyncGenerator<string> {
838
+ const pattern = `${this.entityName}:*`;
839
+ const keys = await redis.keys(pattern);
641
840
 
642
- ```typescript
643
- import { Live, listenSignalLive, listenSignalLiveOnce } from "backtest-kit";
644
-
645
- // Run live trading in background (infinite loop)
646
- const cancel = Live.background("BTCUSDT", {
647
- strategyName: "my-strategy",
648
- exchangeName: "binance"
649
- });
841
+ // Sort keys alphanumerically
842
+ keys.sort((a, b) => a.localeCompare(b, undefined, {
843
+ numeric: true,
844
+ sensitivity: "base"
845
+ }));
650
846
 
651
- // Listen to all live events
652
- listenSignalLive((event) => {
653
- if (event.action === "opened") {
654
- console.log("Signal opened:", event.signal.id);
655
- }
656
- if (event.action === "closed") {
657
- 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
+ }
658
852
  }
659
- });
853
+ }
660
854
 
661
- // React to first stop loss once
662
- listenSignalLiveOnce(
663
- (event) => event.action === "closed" && event.closeReason === "stop_loss",
664
- (event) => {
665
- console.error("Stop loss hit!", event.pnl.pnlPercentage);
666
- // Send alert, dump report, etc.
667
- }
668
- );
855
+ // Register Redis adapter for signal persistence
856
+ PersistSignalAdaper.usePersistSignalAdapter(RedisPersist);
669
857
 
670
- // Stop live trading after some condition
671
- // cancel();
858
+ // Register Redis adapter for risk persistence
859
+ PersistRiskAdapter.usePersistRiskAdapter(RedisPersist);
672
860
  ```
673
861
 
674
- ### Listen to All Signals (Backtest + Live)
862
+ #### Custom Adapter Registration (Before Running Strategies)
675
863
 
676
864
  ```typescript
677
- import { listenSignal, listenSignalOnce, Backtest, Live } from "backtest-kit";
678
-
679
- // Listen to both backtest and live events
680
- listenSignal((event) => {
681
- console.log("Event:", event.action, event.strategyName);
682
- });
683
-
684
- // Wait for first loss from any source
685
- listenSignalOnce(
686
- (event) => event.action === "closed" && event.pnl.pnlPercentage < 0,
687
- (event) => {
688
- console.log("First loss detected:", event.pnl.pnlPercentage);
689
- }
690
- );
865
+ import { PersistSignalAdaper, PersistRiskAdapter, Live } from "backtest-kit";
691
866
 
692
- // Run both modes
693
- Backtest.background("BTCUSDT", {
694
- strategyName: "my-strategy",
695
- exchangeName: "binance",
696
- frameName: "1d-backtest"
697
- });
867
+ // IMPORTANT: Register adapters BEFORE running any strategies
868
+ PersistSignalAdaper.usePersistSignalAdapter(RedisPersist);
869
+ PersistRiskAdapter.usePersistRiskAdapter(RedisPersist);
698
870
 
871
+ // Now run live trading with Redis persistence
699
872
  Live.background("BTCUSDT", {
700
873
  strategyName: "my-strategy",
701
874
  exchangeName: "binance"
702
875
  });
703
876
  ```
704
877
 
705
- **Available event listeners:**
878
+ #### MongoDB Adapter Example
706
879
 
707
- - `listenSignal(callback)` - Subscribe to all signal events (backtest + live)
708
- - `listenSignalOnce(filter, callback)` - Subscribe once with filter predicate
709
- - `listenSignalBacktest(callback)` - Subscribe to backtest signals only
710
- - `listenSignalBacktestOnce(filter, callback)` - Subscribe to backtest signals once
711
- - `listenSignalLive(callback)` - Subscribe to live signals only
712
- - `listenSignalLiveOnce(filter, callback)` - Subscribe to live signals once
713
- - `listenError(callback)` - Subscribe to background execution errors
714
- - `listenDone(callback)` - Subscribe to background completion events
715
- - `listenDoneOnce(filter, callback)` - Subscribe to background completion once
880
+ ```typescript
881
+ import { PersistBase } from "backtest-kit";
882
+ import { MongoClient, Collection } from "mongodb";
716
883
 
717
- All listeners return an `unsubscribe` function. All callbacks are processed sequentially using queued async execution.
884
+ const client = new MongoClient("mongodb://localhost:27017");
885
+ const db = client.db("backtest-kit");
718
886
 
719
- ### Listen to Background Completion
887
+ class MongoPersist extends PersistBase {
888
+ private collection: Collection;
720
889
 
721
- ```typescript
722
- import { listenDone, listenDoneOnce, Backtest, Live } from "backtest-kit";
723
-
724
- // Listen to all completion events
725
- listenDone((event) => {
726
- console.log("Execution completed:", {
727
- mode: event.backtest ? "backtest" : "live",
728
- symbol: event.symbol,
729
- strategy: event.strategyName,
730
- exchange: event.exchangeName,
731
- });
732
-
733
- // Auto-generate report on completion
734
- if (event.backtest) {
735
- Backtest.dump(event.strategyName);
736
- } else {
737
- Live.dump(event.strategyName);
890
+ constructor(entityName: string, baseDir: string) {
891
+ super(entityName, baseDir);
892
+ this.collection = db.collection(this.entityName);
738
893
  }
739
- });
740
894
 
741
- // Wait for specific backtest to complete
742
- listenDoneOnce(
743
- (event) => event.backtest && event.symbol === "BTCUSDT",
744
- (event) => {
745
- console.log("BTCUSDT backtest finished");
746
- // Start next backtest or live trading
747
- Live.background(event.symbol, {
748
- strategyName: event.strategyName,
749
- exchangeName: event.exchangeName,
750
- });
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}`);
751
900
  }
752
- );
753
901
 
754
- // Run backtests
755
- Backtest.background("BTCUSDT", {
756
- strategyName: "my-strategy",
757
- exchangeName: "binance",
758
- frameName: "1d-backtest"
759
- });
760
- ```
761
-
762
- ## API Reference
763
-
764
- ### High-Level Functions
902
+ async readValue<T>(entityId: string | number): Promise<T> {
903
+ const doc = await this.collection.findOne({ entityId });
765
904
 
766
- #### Schema Registration
905
+ if (!doc) {
906
+ throw new Error(`Entity ${this.entityName}:${entityId} not found`);
907
+ }
767
908
 
768
- ```typescript
769
- // Register exchange
770
- addExchange(exchangeSchema: IExchangeSchema): void
909
+ return doc.data as T;
910
+ }
771
911
 
772
- // Register strategy
773
- addStrategy(strategySchema: IStrategySchema): void
912
+ async hasValue(entityId: string | number): Promise<boolean> {
913
+ const count = await this.collection.countDocuments({ entityId });
914
+ return count > 0;
915
+ }
774
916
 
775
- // Register timeframe generator
776
- addFrame(frameSchema: IFrameSchema): void
777
- ```
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
+ }
778
924
 
779
- #### Exchange Data
925
+ async removeValue(entityId: string | number): Promise<void> {
926
+ const result = await this.collection.deleteOne({ entityId });
780
927
 
781
- ```typescript
782
- // Get historical candles
783
- const candles = await getCandles("BTCUSDT", "1h", 5);
784
- // Returns: [
785
- // { timestamp: 1704067200000, open: 42150.5, high: 42380.2, low: 42100.0, close: 42250.8, volume: 125.43 },
786
- // { timestamp: 1704070800000, open: 42250.8, high: 42500.0, low: 42200.0, close: 42450.3, volume: 98.76 },
787
- // { timestamp: 1704074400000, open: 42450.3, high: 42600.0, low: 42400.0, close: 42580.5, volume: 110.22 },
788
- // { timestamp: 1704078000000, open: 42580.5, high: 42700.0, low: 42550.0, close: 42650.0, volume: 95.18 },
789
- // { timestamp: 1704081600000, open: 42650.0, high: 42750.0, low: 42600.0, close: 42720.0, volume: 102.35 }
790
- // ]
791
-
792
- // Get VWAP from last 5 1m candles
793
- const vwap = await getAveragePrice("BTCUSDT");
794
- // Returns: 42685.34
795
-
796
- // Get current date in execution context
797
- const date = await getDate();
798
- // Returns: 2024-01-01T12:00:00.000Z (in backtest mode, returns frame's current timestamp)
799
- // Returns: 2024-01-15T10:30:45.123Z (in live mode, returns current wall clock time)
800
-
801
- // Get current mode
802
- const mode = await getMode();
803
- // Returns: "backtest" or "live"
804
-
805
- // Format price/quantity for exchange
806
- const price = await formatPrice("BTCUSDT", 42685.3456789);
807
- // Returns: "42685.35" (formatted to exchange precision)
808
-
809
- const quantity = await formatQuantity("BTCUSDT", 0.123456789);
810
- // Returns: "0.12345" (formatted to exchange precision)
811
- ```
928
+ if (result.deletedCount === 0) {
929
+ throw new Error(`Entity ${this.entityName}:${entityId} not found for deletion`);
930
+ }
931
+ }
812
932
 
813
- ### Service APIs
933
+ async removeAll(): Promise<void> {
934
+ await this.collection.deleteMany({});
935
+ }
814
936
 
815
- #### Backtest API
937
+ async *values<T>(): AsyncGenerator<T> {
938
+ const cursor = this.collection.find({}).sort({ entityId: 1 });
816
939
 
817
- ```typescript
818
- import { Backtest, BacktestStatistics } from "backtest-kit";
819
-
820
- // Stream backtest results
821
- Backtest.run(
822
- symbol: string,
823
- context: {
824
- strategyName: string;
825
- exchangeName: string;
826
- frameName: string;
940
+ for await (const doc of cursor) {
941
+ yield doc.data as T;
942
+ }
827
943
  }
828
- ): AsyncIterableIterator<IStrategyTickResultClosed>
829
944
 
830
- // Run in background without yielding results
831
- Backtest.background(
832
- symbol: string,
833
- context: { strategyName, exchangeName, frameName }
834
- ): Promise<() => void> // Returns cancellation function
945
+ async *keys(): AsyncGenerator<string> {
946
+ const cursor = this.collection.find({}, { projection: { entityId: 1 } }).sort({ entityId: 1 });
835
947
 
836
- // Get raw statistical data (Controller)
837
- Backtest.getData(strategyName: string): Promise<BacktestStatistics>
838
-
839
- // Generate markdown report (View)
840
- Backtest.getReport(strategyName: string): Promise<string>
948
+ for await (const doc of cursor) {
949
+ yield String(doc.entityId);
950
+ }
951
+ }
952
+ }
841
953
 
842
- // Save report to disk
843
- Backtest.dump(strategyName: string, path?: string): Promise<void>
954
+ // Register MongoDB adapter
955
+ PersistSignalAdaper.usePersistSignalAdapter(MongoPersist);
956
+ PersistRiskAdapter.usePersistRiskAdapter(MongoPersist);
844
957
  ```
845
958
 
846
- #### Live Trading API
959
+ #### Direct Persistence API Usage (Advanced)
847
960
 
848
- ```typescript
849
- import { Live, LiveStatistics } from "backtest-kit";
850
-
851
- // Stream live results (infinite)
852
- Live.run(
853
- symbol: string,
854
- context: {
855
- strategyName: string;
856
- exchangeName: string;
857
- }
858
- ): AsyncIterableIterator<IStrategyTickResult>
961
+ You can also use PersistBase directly for custom data storage:
859
962
 
860
- // Run in background without yielding results
861
- Live.background(
862
- symbol: string,
863
- context: { strategyName, exchangeName }
864
- ): Promise<() => void> // Returns cancellation function
963
+ ```typescript
964
+ import { PersistBase } from "backtest-kit";
865
965
 
866
- // Get raw statistical data (Controller)
867
- Live.getData(strategyName: string): Promise<LiveStatistics>
966
+ // Create custom persistence for trading logs
967
+ const tradingLogs = new PersistBase("trading-logs", "./logs/custom");
868
968
 
869
- // Generate markdown report (View)
870
- Live.getReport(strategyName: string): Promise<string>
969
+ // Initialize
970
+ await tradingLogs.waitForInit(true);
871
971
 
872
- // Save report to disk
873
- Live.dump(strategyName: string, path?: string): Promise<void>
874
- ```
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" }
977
+ });
875
978
 
876
- ## Type Definitions
979
+ // Read log entry
980
+ const log = await tradingLogs.readValue("log-1");
981
+ console.log(log);
877
982
 
878
- ### Statistics Types
983
+ // Check if log exists
984
+ const exists = await tradingLogs.hasValue("log-1");
985
+ console.log(`Log exists: ${exists}`);
879
986
 
880
- ```typescript
881
- // Backtest statistics (exported from "backtest-kit")
882
- interface BacktestStatistics {
883
- signalList: IStrategyTickResultClosed[]; // All closed signals
884
- totalSignals: number;
885
- winCount: number;
886
- lossCount: number;
887
- winRate: number | null; // Win percentage (higher is better)
888
- avgPnl: number | null; // Average PNL % (higher is better)
889
- totalPnl: number | null; // Total PNL % (higher is better)
890
- stdDev: number | null; // Standard deviation (lower is better)
891
- sharpeRatio: number | null; // Risk-adjusted return (higher is better)
892
- annualizedSharpeRatio: number | null; // Sharpe Γ— √365 (higher is better)
893
- certaintyRatio: number | null; // avgWin / |avgLoss| (higher is better)
894
- 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);
895
990
  }
896
991
 
897
- // Live statistics (exported from "backtest-kit")
898
- interface LiveStatistics {
899
- eventList: TickEvent[]; // All events (idle, opened, active, closed)
900
- totalEvents: number;
901
- totalClosed: number;
902
- winCount: number;
903
- lossCount: number;
904
- winRate: number | null; // Win percentage (higher is better)
905
- avgPnl: number | null; // Average PNL % (higher is better)
906
- totalPnl: number | null; // Total PNL % (higher is better)
907
- stdDev: number | null; // Standard deviation (lower is better)
908
- sharpeRatio: number | null; // Risk-adjusted return (higher is better)
909
- annualizedSharpeRatio: number | null; // Sharpe Γ— √365 (higher is better)
910
- certaintyRatio: number | null; // avgWin / |avgLoss| (higher is better)
911
- 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);
912
995
  }
913
- ```
914
996
 
915
- ### Signal Data
916
-
917
- ```typescript
918
- interface ISignalRow {
919
- id: string; // UUID v4 auto-generated
920
- position: "long" | "short";
921
- note?: string;
922
- priceOpen: number;
923
- priceTakeProfit: number;
924
- priceStopLoss: number;
925
- minuteEstimatedTime: number;
926
- exchangeName: string;
927
- strategyName: string;
928
- timestamp: number; // Signal creation timestamp
929
- symbol: string; // Trading pair (e.g., "BTCUSDT")
997
+ // Filter logs
998
+ for await (const log of tradingLogs.filter((l: any) => l.metadata.symbol === "BTCUSDT")) {
999
+ console.log("BTC Log:", log);
930
1000
  }
931
- ```
932
1001
 
933
- ### Tick Results (Discriminated Union)
934
-
935
- ```typescript
936
- type IStrategyTickResult =
937
- | {
938
- action: "idle";
939
- signal: null;
940
- strategyName: string;
941
- exchangeName: string;
942
- currentPrice: number;
943
- }
944
- | {
945
- action: "opened";
946
- signal: ISignalRow;
947
- strategyName: string;
948
- exchangeName: string;
949
- currentPrice: number;
950
- }
951
- | {
952
- action: "active";
953
- signal: ISignalRow;
954
- currentPrice: number;
955
- strategyName: string;
956
- exchangeName: string;
957
- }
958
- | {
959
- action: "closed";
960
- signal: ISignalRow;
961
- currentPrice: number;
962
- closeReason: "take_profit" | "stop_loss" | "time_expired";
963
- closeTimestamp: number;
964
- pnl: {
965
- pnlPercentage: number;
966
- priceOpen: number; // Entry price adjusted with slippage and fees
967
- priceClose: number; // Exit price adjusted with slippage and fees
968
- };
969
- strategyName: string;
970
- exchangeName: string;
971
- };
972
- ```
1002
+ // Take first 5 logs
1003
+ for await (const log of tradingLogs.take(5)) {
1004
+ console.log("Recent Log:", log);
1005
+ }
973
1006
 
974
- ### PNL Calculation
1007
+ // Remove specific log
1008
+ await tradingLogs.removeValue("log-1");
975
1009
 
976
- ```typescript
977
- // Constants
978
- PERCENT_SLIPPAGE = 0.1% // 0.001
979
- PERCENT_FEE = 0.1% // 0.001
980
-
981
- // LONG position
982
- priceOpenWithCosts = priceOpen * (1 + slippage + fee)
983
- priceCloseWithCosts = priceClose * (1 - slippage - fee)
984
- pnl% = (priceCloseWithCosts - priceOpenWithCosts) / priceOpenWithCosts * 100
985
-
986
- // SHORT position
987
- priceOpenWithCosts = priceOpen * (1 - slippage + fee)
988
- priceCloseWithCosts = priceClose * (1 + slippage + fee)
989
- pnl% = (priceOpenWithCosts - priceCloseWithCosts) / priceOpenWithCosts * 100
1010
+ // Remove all logs
1011
+ await tradingLogs.removeAll();
990
1012
  ```
991
1013
 
992
- ## Production Readiness
1014
+ #### When to Use Custom Adapters
993
1015
 
994
- ### βœ… Production-Ready Features
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
995
1020
 
996
- 1. **Crash-Safe Persistence** - Atomic file writes with automatic recovery
997
- 2. **Signal Validation** - Comprehensive validation prevents invalid trades
998
- 3. **Type Safety** - Discriminated unions eliminate runtime type errors
999
- 4. **Memory Efficiency** - Prototype methods + async generators + memoization
1000
- 5. **Interval Throttling** - Prevents signal spam
1001
- 6. **Live Trading Ready** - Full implementation with real-time progression
1002
- 7. **Error Recovery** - Stateless process with disk-based state
1021
+ 2. **MongoDB** - Best for complex queries and analytics
1022
+ - Rich query language
1023
+ - Aggregation pipelines
1024
+ - Scalable for large datasets
1003
1025
 
1004
- ## Advanced Examples
1026
+ 3. **PostgreSQL** - Best for ACID transactions and relational data
1027
+ - Strong consistency guarantees
1028
+ - Complex joins and queries
1029
+ - Mature ecosystem
1005
1030
 
1006
- ### Multi-Symbol Live Trading
1007
-
1008
- ```typescript
1009
- import { Live } from "backtest-kit";
1010
-
1011
- const symbols = ["BTCUSDT", "ETHUSDT", "SOLUSDT"];
1012
-
1013
- // Run all symbols in parallel
1014
- await Promise.all(
1015
- symbols.map(async (symbol) => {
1016
- for await (const result of Live.run(symbol, {
1017
- strategyName: "my-strategy",
1018
- exchangeName: "binance"
1019
- })) {
1020
- console.log(`[${symbol}]`, result.action);
1021
-
1022
- // Generate reports periodically
1023
- if (result.action === "closed") {
1024
- await Live.dump("my-strategy");
1025
- }
1026
- }
1027
- })
1028
- );
1029
- ```
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
1030
1035
 
1031
- ### Backtest Progress Listener
1036
+ #### Testing Custom Adapters
1032
1037
 
1033
1038
  ```typescript
1034
- import { listenProgress, Backtest } from "backtest-kit";
1035
-
1036
- listenProgress((event) => {
1037
- console.log(`Progress: ${(event.progress * 100).toFixed(2)}%`);
1038
- console.log(`${event.processedFrames} / ${event.totalFrames} frames`);
1039
- console.log(`Strategy: ${event.strategyName}, Symbol: ${event.symbol}`);
1040
- });
1041
-
1042
- Backtest.background("BTCUSDT", {
1043
- strategyName: "my-strategy",
1044
- exchangeName: "binance",
1045
- frameName: "1d-backtest"
1046
- });
1047
- ```
1039
+ import { test } from "worker-testbed";
1040
+ import { PersistBase } from "backtest-kit";
1048
1041
 
1049
- ### Early Termination
1042
+ test("Custom Redis adapter works correctly", async ({ pass, fail }) => {
1043
+ const persist = new RedisPersist("test-entity", "./logs/test");
1050
1044
 
1051
- **Using async generator with break:**
1045
+ await persist.waitForInit(true);
1052
1046
 
1053
- ```typescript
1054
- import { Backtest } from "backtest-kit";
1055
-
1056
- for await (const result of Backtest.run("BTCUSDT", {
1057
- strategyName: "my-strategy",
1058
- exchangeName: "binance",
1059
- frameName: "1d-backtest"
1060
- })) {
1061
- if (result.closeReason === "stop_loss") {
1062
- console.log("Stop loss hit - terminating backtest");
1047
+ // Write
1048
+ await persist.writeValue("key1", { data: "value1" });
1063
1049
 
1064
- // Save final report before exit
1065
- await Backtest.dump("my-strategy");
1066
- 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");
1067
1056
  }
1068
- }
1069
- ```
1070
-
1071
- **Using background mode with stop() function:**
1072
1057
 
1073
- ```typescript
1074
- import { Backtest, Live, listenSignalLiveOnce } from "backtest-kit";
1075
-
1076
- // Backtest.background returns a stop function
1077
- const stopBacktest = await Backtest.background("BTCUSDT", {
1078
- strategyName: "my-strategy",
1079
- exchangeName: "binance",
1080
- frameName: "1d-backtest"
1081
- });
1082
-
1083
- // Stop backtest after some condition
1084
- setTimeout(() => {
1085
- console.log("Stopping backtest...");
1086
- stopBacktest(); // Stops the background execution
1087
- }, 5000);
1088
-
1089
- // Live.background also returns a stop function
1090
- const stopLive = Live.background("BTCUSDT", {
1091
- strategyName: "my-strategy",
1092
- exchangeName: "binance"
1058
+ // Cleanup
1059
+ await persist.removeValue("key1");
1093
1060
  });
1094
-
1095
- // Stop live trading after detecting stop loss
1096
- listenSignalLiveOnce(
1097
- (event) => event.action === "closed" && event.closeReason === "stop_loss",
1098
- (event) => {
1099
- console.log("Stop loss detected - stopping live trading");
1100
- stopLive(); // Stops the infinite loop
1101
- }
1102
- );
1103
1061
  ```
1104
1062
 
1105
- ## Use Cases
1063
+ ---
1064
+
1065
+ ## βœ… Tested & Reliable
1106
1066
 
1107
- - **Algorithmic Trading** - Backtest and deploy strategies with crash recovery
1108
- - **Strategy Research** - Test hypotheses on historical data
1109
- - **Signal Generation** - Use with ML models or technical indicators
1110
- - **Portfolio Management** - Track multiple strategies across symbols
1111
- - **Educational Projects** - Learn trading system architecture
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. πŸ“ˆ
1112
1072
 
1113
- ## Contributing
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
1114
1084
 
1115
- Pull requests are welcome. For major changes, please open an issue first.
1085
+ ---
1116
1086
 
1117
- ## License
1087
+ ## 🀝 Contribute
1118
1088
 
1119
- MIT
1089
+ We'd love your input! Fork the repo, submit a PR, or open an issue on **[GitHub](https://github.com/tripolskypetr/backtest-kit)**. πŸ™Œ
1120
1090
 
1121
- ## Links
1091
+ ## πŸ“œ License
1122
1092
 
1123
- - [Architecture Documentation](./ARCHITECTURE.md)
1124
- - [TypeScript Documentation](https://www.typescriptlang.org/)
1125
- - [Dependency Injection](https://github.com/tripolskypetr/di-kit)
1093
+ MIT Β© [tripolskypetr](https://github.com/tripolskypetr) πŸ–‹οΈ