backtest-kit 1.0.3 → 1.1.0

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 +707 -153
  2. package/build/index.cjs +4132 -407
  3. package/build/index.mjs +4116 -401
  4. package/package.json +2 -9
  5. package/types.d.ts +2432 -69
package/README.md CHANGED
@@ -1,243 +1,616 @@
1
1
  # Backtest Kit
2
2
 
3
- A powerful TypeScript framework for backtesting trading strategies with clean architecture and real-time execution capabilities.
3
+ > A production-ready TypeScript framework for backtesting and live trading strategies with crash-safe state persistence, signal validation, and memory-optimized architecture.
4
+
5
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/tripolskypetr/backtest-kit)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)]()
7
+ [![Architecture](https://img.shields.io/badge/architecture-clean-orange)]()
4
8
 
5
9
  ## Features
6
10
 
7
- - 🚀 **Clean Architecture** - Separation of concerns with DI container
8
- - 📊 **Strategy Backtesting** - Test your trading strategies on historical data
9
- - 🔄 **Real-time Execution** - Run strategies live with configurable intervals
10
- - 📈 **VWAP Pricing** - Volume-weighted average price calculation
11
- - 🎯 **Signal Management** - Automatic signal lifecycle (open/close) with TP/SL
12
- - 📉 **PNL Calculation** - Accurate profit/loss with fees and slippage
13
- - 📝 **Beautiful Reports** - Markdown tables with statistics
14
- - 🔌 **Flexible Schema** - Plug your own data sources
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)
15
22
 
16
23
  ## Installation
17
24
 
18
25
  ```bash
19
- npm install
26
+ npm install backtest-kit
20
27
  ```
21
28
 
22
29
  ## Quick Start
23
30
 
24
- ### 1. Add Data Source (Exchange)
31
+ ### 1. Register Exchange Data Source
32
+
33
+ ```typescript
34
+ import { addExchange } from "backtest-kit";
35
+ import ccxt from "ccxt"; // Example using CCXT library
36
+
37
+ addExchange({
38
+ exchangeName: "binance",
39
+
40
+ // Fetch historical candles
41
+ getCandles: async (symbol, interval, since, limit) => {
42
+ const exchange = new ccxt.binance();
43
+ const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
44
+
45
+ return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
46
+ timestamp,
47
+ open,
48
+ high,
49
+ low,
50
+ close,
51
+ volume,
52
+ }));
53
+ },
54
+
55
+ // Format price according to exchange rules (e.g., 2 decimals for BTC)
56
+ formatPrice: async (symbol, price) => {
57
+ const exchange = new ccxt.binance();
58
+ const market = exchange.market(symbol);
59
+ return exchange.priceToPrecision(symbol, price);
60
+ },
61
+
62
+ // Format quantity according to exchange rules (e.g., 8 decimals)
63
+ formatQuantity: async (symbol, quantity) => {
64
+ const exchange = new ccxt.binance();
65
+ return exchange.amountToPrecision(symbol, quantity);
66
+ },
67
+ });
68
+ ```
69
+
70
+ **Alternative: Database implementation**
25
71
 
26
72
  ```typescript
27
- import { addExchange } from "./src/function/add";
73
+ import { addExchange } from "backtest-kit";
74
+ import { db } from "./database"; // Your database client
28
75
 
29
76
  addExchange({
77
+ exchangeName: "binance-db",
78
+
30
79
  getCandles: async (symbol, interval, since, limit) => {
31
- // Fetch candle data from your source (exchange API, database, etc.)
32
- return [
33
- {
34
- timestamp: Date.now(),
35
- open: 50000,
36
- high: 51000,
37
- low: 49000,
38
- close: 50500,
39
- volume: 1000,
40
- },
41
- ];
80
+ // Fetch from database for faster backtesting
81
+ return await db.query(`
82
+ SELECT timestamp, open, high, low, close, volume
83
+ FROM candles
84
+ WHERE symbol = $1 AND interval = $2 AND timestamp >= $3
85
+ ORDER BY timestamp ASC
86
+ LIMIT $4
87
+ `, [symbol, interval, since, limit]);
42
88
  },
89
+
90
+ formatPrice: async (symbol, price) => price.toFixed(2),
91
+ formatQuantity: async (symbol, quantity) => quantity.toFixed(8),
43
92
  });
44
93
  ```
45
94
 
46
- ### 2. Add Strategy
95
+ ### 2. Register Trading Strategy
47
96
 
48
97
  ```typescript
49
- import { addStrategy } from "./src/function/add";
98
+ import { addStrategy } from "backtest-kit";
50
99
 
51
100
  addStrategy({
101
+ strategyName: "my-strategy",
102
+ interval: "5m", // Throttling: signals generated max once per 5 minutes
52
103
  getSignal: async (symbol) => {
53
104
  // Your signal generation logic
105
+ // Validation happens automatically (prices, TP/SL logic, timestamps)
54
106
  return {
55
- id: "signal-1",
56
107
  position: "long",
57
108
  note: "BTC breakout",
58
109
  priceOpen: 50000,
59
- priceTakeProfit: 51000,
60
- priceStopLoss: 49000,
61
- minuteEstimatedTime: 60,
110
+ priceTakeProfit: 51000, // Must be > priceOpen for long
111
+ priceStopLoss: 49000, // Must be < priceOpen for long
112
+ minuteEstimatedTime: 60, // Signal duration in minutes
62
113
  timestamp: Date.now(),
63
114
  };
64
115
  },
65
116
  callbacks: {
66
- onOpen: (backtest, symbol, data) => {
67
- console.log("Signal opened:", data);
117
+ onOpen: (backtest, symbol, signal) => {
118
+ console.log(`[${backtest ? "BT" : "LIVE"}] Signal opened:`, signal.id);
68
119
  },
69
- onClose: (backtest, symbol, priceClose, data) => {
70
- console.log("Signal closed at:", priceClose);
120
+ onClose: (backtest, symbol, priceClose, signal) => {
121
+ console.log(`[${backtest ? "BT" : "LIVE"}] Signal closed:`, priceClose);
71
122
  },
72
123
  },
73
124
  });
74
125
  ```
75
126
 
76
- ### 3. Run Backtest
127
+ ### 3. Add Timeframe Generator
77
128
 
78
129
  ```typescript
79
- import { runBacktest, runBacktestGUI } from "./src/function/backtest";
130
+ import { addFrame } from "backtest-kit";
80
131
 
81
- // Generate timeframes (every minute for 24 hours)
82
- const timeframes = Array.from({ length: 1440 }, (_, i) => {
83
- const date = new Date("2024-01-01T00:00:00Z");
84
- date.setMinutes(date.getMinutes() + i);
85
- return date;
132
+ addFrame({
133
+ frameName: "1d-backtest",
134
+ interval: "1m",
135
+ startDate: new Date("2024-01-01T00:00:00Z"),
136
+ endDate: new Date("2024-01-02T00:00:00Z"),
137
+ callbacks: {
138
+ onTimeframe: (timeframe, startDate, endDate, interval) => {
139
+ console.log(`Generated ${timeframe.length} timeframes from ${startDate} to ${endDate}`);
140
+ },
141
+ },
86
142
  });
143
+ ```
87
144
 
88
- // Simple backtest (returns data only)
89
- const result = await runBacktest("BTCUSDT", timeframes);
90
- console.log(result.results); // Array of closed trades with PNL
145
+ ### 4. Run Backtest
91
146
 
92
- // Backtest with terminal output
93
- runBacktestGUI("BTCUSDT", timeframes);
94
- // Prints beautiful ASCII table to console
95
- ```
147
+ ```typescript
148
+ import { Backtest, listenSignalBacktest, listenError } from "backtest-kit";
96
149
 
97
- **Terminal Output:**
150
+ // Run backtest in background
151
+ await Backtest.background("BTCUSDT", {
152
+ strategyName: "my-strategy",
153
+ exchangeName: "binance",
154
+ frameName: "1d-backtest"
155
+ });
98
156
 
99
- ```
100
- ┌───┬──────────────────────────┬────────────┬───────────┬────────────┬──────────┐
101
- # Time │ Note │ Price │ Reason │ PNL % │
102
- ├───┼──────────────────────────┼────────────┼───────────┼────────────┼──────────┤
103
- │ 1 │ 2024-01-01T00:05:00.000Z │ BTC Long │ 51000.00 │ take_profit│ 🟢 +1.98%│
104
- │ 2 │ 2024-01-01T01:30:00.000Z │ BTC Short │ 50800.00 │ stop_loss │ 🔴 -0.42%│
105
- ├───┼──────────────────────────┼────────────┼───────────┼────────────┼──────────┤
106
- │ │ │ │ │ │ │
107
- ├───┼──────────────────────────┼────────────┼───────────┼────────────┼──────────┤
108
- │TOTAL│ 2 trades │ Win: 1 │ Loss: 1 │ - │ +1.56% │
109
- └───┴──────────────────────────┴────────────┴───────────┴────────────┴──────────┘
157
+ // Listen to closed signals
158
+ listenSignalBacktest((event) => {
159
+ if (event.action === "closed") {
160
+ console.log("PNL:", event.pnl.pnlPercentage);
161
+ }
162
+ });
163
+
164
+ // Listen to errors
165
+ listenError((error) => {
166
+ console.error("Error:", error.message);
167
+ });
168
+
169
+ // Generate and save report
170
+ const markdown = await Backtest.getReport("my-strategy");
171
+ await Backtest.dump("my-strategy"); // ./logs/backtest/my-strategy.md
110
172
  ```
111
173
 
112
- ### 4. Real-time Execution
174
+ ### 5. Run Live Trading (Crash-Safe)
113
175
 
114
176
  ```typescript
115
- import { startRun, stopRun, stopAll } from "./src/function/run";
177
+ import { Live, listenSignalLive, listenError } from "backtest-kit";
116
178
 
117
- // Start strategy for multiple symbols
118
- startRun({ symbol: "BTCUSDT", interval: 5 * 60 * 1000 }); // 5 minutes
119
- startRun({ symbol: "ETHUSDT", interval: 5 * 60 * 1000 });
179
+ // Run live trading in background (infinite loop, crash-safe)
180
+ const stop = await Live.background("BTCUSDT", {
181
+ strategyName: "my-strategy",
182
+ exchangeName: "binance"
183
+ });
120
184
 
121
- // Stop specific symbol
122
- stopRun("BTCUSDT");
185
+ // Listen to all signal events
186
+ listenSignalLive((event) => {
187
+ if (event.action === "opened") {
188
+ console.log("Signal opened:", event.signal.id);
189
+ }
190
+
191
+ if (event.action === "closed") {
192
+ console.log("Signal closed:", {
193
+ reason: event.closeReason,
194
+ pnl: event.pnl.pnlPercentage,
195
+ });
196
+
197
+ // Auto-save report
198
+ Live.dump(event.strategyName);
199
+ }
200
+ });
201
+
202
+ // Listen to errors
203
+ listenError((error) => {
204
+ console.error("Error:", error.message);
205
+ });
123
206
 
124
- // Stop all
125
- stopAll();
207
+ // Stop when needed: stop();
126
208
  ```
127
209
 
128
- ### 5. Advanced: Reduce Pattern
210
+ **Crash Recovery:** If process crashes, restart with same code - state automatically recovered from disk (no duplicate signals).
129
211
 
130
- Use the reduce pattern to iterate timeframes with custom logic:
212
+ ### 6. Alternative: Async Generators (Optional)
213
+
214
+ For manual control over execution flow:
131
215
 
132
216
  ```typescript
133
- import { reduce } from "./src/function/reduce";
217
+ import { Backtest, Live } from "backtest-kit";
218
+
219
+ // Manual backtest iteration
220
+ for await (const result of Backtest.run("BTCUSDT", {
221
+ strategyName: "my-strategy",
222
+ exchangeName: "binance",
223
+ frameName: "1d-backtest"
224
+ })) {
225
+ console.log("PNL:", result.pnl.pnlPercentage);
226
+ if (result.pnl.pnlPercentage < -5) break; // Early termination
227
+ }
134
228
 
135
- interface Context {
136
- count: number;
137
- timestamps: Date[];
138
- apiCalls: number;
229
+ // Manual live iteration (infinite loop)
230
+ for await (const result of Live.run("BTCUSDT", {
231
+ strategyName: "my-strategy",
232
+ exchangeName: "binance"
233
+ })) {
234
+ if (result.action === "closed") {
235
+ console.log("PNL:", result.pnl.pnlPercentage);
236
+ }
139
237
  }
238
+ ```
239
+
240
+ ## Architecture Overview
241
+
242
+ The framework follows **clean architecture** with:
140
243
 
141
- const result = await reduce<Context>(
142
- "BTCUSDT",
143
- timeframes,
144
- async (acc, index, when, symbol) => {
145
- acc.count++;
146
- acc.timestamps.push(when);
244
+ - **Client Layer** - Pure business logic without DI (ClientStrategy, ClientExchange, ClientFrame)
245
+ - **Service Layer** - DI-based services organized by responsibility
246
+ - **Schema Services** - Registry pattern for configuration
247
+ - **Connection Services** - Memoized client instance creators
248
+ - **Global Services** - Context wrappers for public API
249
+ - **Logic Services** - Async generator orchestration (backtest/live)
250
+ - **Persistence Layer** - Crash-safe atomic file writes with `PersistSignalAdaper`
147
251
 
148
- // Make your custom API calls, LLM requests, etc.
149
- const response = await fetch(`/api/analyze?symbol=${symbol}&when=${when}`);
150
- acc.apiCalls++;
252
+ See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed documentation.
253
+
254
+ ## Signal Validation
255
+
256
+ All signals are validated automatically before execution:
257
+
258
+ ```typescript
259
+ // ✅ Valid long signal
260
+ {
261
+ position: "long",
262
+ priceOpen: 50000,
263
+ priceTakeProfit: 51000, // ✅ 51000 > 50000
264
+ priceStopLoss: 49000, // ✅ 49000 < 50000
265
+ minuteEstimatedTime: 60, // ✅ positive
266
+ timestamp: Date.now(), // ✅ positive
267
+ }
151
268
 
152
- return acc;
269
+ // ❌ Invalid long signal - throws error
270
+ {
271
+ position: "long",
272
+ priceOpen: 50000,
273
+ priceTakeProfit: 49000, // ❌ 49000 < 50000 (must be higher for long)
274
+ priceStopLoss: 51000, // ❌ 51000 > 50000 (must be lower for long)
275
+ }
276
+
277
+ // ✅ Valid short signal
278
+ {
279
+ position: "short",
280
+ priceOpen: 50000,
281
+ priceTakeProfit: 49000, // ✅ 49000 < 50000 (profit goes down for short)
282
+ priceStopLoss: 51000, // ✅ 51000 > 50000 (stop loss goes up for short)
283
+ }
284
+ ```
285
+
286
+ Validation errors include detailed messages for debugging.
287
+
288
+ ## Interval Throttling
289
+
290
+ Prevent signal spam with automatic throttling:
291
+
292
+ ```typescript
293
+ addStrategy({
294
+ strategyName: "my-strategy",
295
+ interval: "5m", // Signals generated max once per 5 minutes
296
+ getSignal: async (symbol) => {
297
+ // This function will be called max once per 5 minutes
298
+ // Even if tick() is called every second
299
+ return signal;
153
300
  },
154
- { count: 0, timestamps: [], apiCalls: 0 }
155
- );
301
+ });
302
+ ```
303
+
304
+ Supported intervals: `"1m"`, `"3m"`, `"5m"`, `"15m"`, `"30m"`, `"1h"`
305
+
306
+ ## Markdown Reports
307
+
308
+ Generate detailed trading reports with statistics:
309
+
310
+ ### Backtest Reports
311
+
312
+ ```typescript
313
+ import { Backtest } from "backtest-kit";
314
+
315
+ // Run backtest
316
+ await Backtest.background("BTCUSDT", {
317
+ strategyName: "my-strategy",
318
+ exchangeName: "binance",
319
+ frameName: "1d-backtest"
320
+ });
321
+
322
+ // Generate markdown report
323
+ const markdown = await Backtest.getReport("my-strategy");
324
+ console.log(markdown);
325
+
326
+ // Save to disk (default: ./logs/backtest/my-strategy.md)
327
+ await Backtest.dump("my-strategy");
156
328
 
157
- // Use accumulated data
158
- console.log(result.accumulator);
159
- // { count: 1440, timestamps: [...], apiCalls: 1440 }
329
+ // Save to custom path
330
+ await Backtest.dump("my-strategy", "./custom/path");
160
331
  ```
161
332
 
162
- ## Architecture
333
+ **Report includes:**
334
+ - Total closed signals
335
+ - All signal details (prices, TP/SL, PNL, duration, close reason)
336
+ - Timestamps for each signal
337
+
338
+ ### Live Trading Reports
339
+
340
+ ```typescript
341
+ import { Live } from "backtest-kit";
163
342
 
343
+ // Generate live trading report
344
+ const markdown = await Live.getReport("my-strategy");
345
+
346
+ // Save to disk (default: ./logs/live/my-strategy.md)
347
+ await Live.dump("my-strategy");
164
348
  ```
165
- src/
166
- ├── function/ # High-level API functions
167
- │ ├── add.ts # Add schemas (strategy, exchange)
168
- │ ├── backtest.ts # Backtesting functions
169
- │ ├── reduce.ts # Reduce pattern for accumulation
170
- │ ├── run.ts # Real-time execution
171
- │ └── exchange.ts # Exchange data functions
172
- ├── client/ # Client implementations
173
- │ ├── ClientExchange.ts # Exchange client with VWAP
174
- │ └── ClientStrategy.ts # Strategy client with signal lifecycle
175
- ├── interfaces/ # TypeScript interfaces
176
- │ ├── Strategy.interface.ts
177
- │ └── Exchange.interface.ts
178
- └── lib/ # Core library with DI
179
- ├── core/ # Dependency injection
180
- └── services/ # Services (schema, connection, public)
349
+
350
+ **Report includes:**
351
+ - Total events (idle, opened, active, closed)
352
+ - Closed signals count
353
+ - Win rate (% wins, wins/losses)
354
+ - Average PNL percentage
355
+ - Signal-by-signal details with current state
356
+
357
+ **Report example:**
358
+ ```markdown
359
+ # Live Trading Report: my-strategy
360
+
361
+ Total events: 15
362
+ Closed signals: 5
363
+ Win rate: 60.00% (3W / 2L)
364
+ Average PNL: +1.23%
365
+
366
+ | Timestamp | Action | Symbol | Signal ID | Position | ... | PNL (net) | Close Reason |
367
+ |-----------|--------|--------|-----------|----------|-----|-----------|--------------|
368
+ | ... | CLOSED | BTCUSD | abc-123 | LONG | ... | +2.45% | take_profit |
181
369
  ```
182
370
 
183
- ## Configuration
371
+ ## Event Listeners
184
372
 
185
- ### Fee and Slippage
373
+ Subscribe to signal events with filtering support. Useful for running strategies in background while reacting to specific events.
186
374
 
187
- Configured in `src/interfaces/Strategy.interface.ts`:
375
+ ### Background Execution with Event Listeners
188
376
 
189
377
  ```typescript
190
- export const PERCENT_SLIPPAGE = 0.1; // 0.1%
191
- export const PERCENT_FEE = 0.1; // 0.1%
378
+ import { Backtest, listenSignalBacktest } from "backtest-kit";
379
+
380
+ // Run backtest in background (doesn't yield results)
381
+ Backtest.background("BTCUSDT", {
382
+ strategyName: "my-strategy",
383
+ exchangeName: "binance",
384
+ frameName: "1d-backtest"
385
+ });
386
+
387
+ // Listen to all backtest events
388
+ const unsubscribe = listenSignalBacktest((event) => {
389
+ if (event.action === "closed") {
390
+ console.log("Signal closed:", {
391
+ pnl: event.pnl.pnlPercentage,
392
+ reason: event.closeReason
393
+ });
394
+ }
395
+ });
396
+
397
+ // Stop listening when done
398
+ // unsubscribe();
192
399
  ```
193
400
 
194
- ### Signal Close Reasons
401
+ ### Listen Once with Filter
402
+
403
+ ```typescript
404
+ import { Backtest, listenSignalBacktestOnce } from "backtest-kit";
405
+
406
+ // Run backtest in background
407
+ Backtest.background("BTCUSDT", {
408
+ strategyName: "my-strategy",
409
+ exchangeName: "binance",
410
+ frameName: "1d-backtest"
411
+ });
195
412
 
196
- - `time_expired` - Signal duration exceeded
197
- - `take_profit` - Take profit target reached
198
- - `stop_loss` - Stop loss triggered
413
+ // Wait for first take profit event
414
+ listenSignalBacktestOnce(
415
+ (event) => event.action === "closed" && event.closeReason === "take_profit",
416
+ (event) => {
417
+ console.log("First take profit hit!", event.pnl.pnlPercentage);
418
+ // Automatically unsubscribes after first match
419
+ }
420
+ );
421
+ ```
422
+
423
+ ### Live Trading with Event Listeners
424
+
425
+ ```typescript
426
+ import { Live, listenSignalLive, listenSignalLiveOnce } from "backtest-kit";
427
+
428
+ // Run live trading in background (infinite loop)
429
+ const cancel = await Live.background("BTCUSDT", {
430
+ strategyName: "my-strategy",
431
+ exchangeName: "binance"
432
+ });
433
+
434
+ // Listen to all live events
435
+ listenSignalLive((event) => {
436
+ if (event.action === "opened") {
437
+ console.log("Signal opened:", event.signal.id);
438
+ }
439
+ if (event.action === "closed") {
440
+ console.log("Signal closed:", event.pnl.pnlPercentage);
441
+ }
442
+ });
443
+
444
+ // React to first stop loss once
445
+ listenSignalLiveOnce(
446
+ (event) => event.action === "closed" && event.closeReason === "stop_loss",
447
+ (event) => {
448
+ console.error("Stop loss hit!", event.pnl.pnlPercentage);
449
+ // Send alert, dump report, etc.
450
+ }
451
+ );
452
+
453
+ // Stop live trading after some condition
454
+ // cancel();
455
+ ```
456
+
457
+ ### Listen to All Signals (Backtest + Live)
458
+
459
+ ```typescript
460
+ import { listenSignal, listenSignalOnce, Backtest, Live } from "backtest-kit";
461
+
462
+ // Listen to both backtest and live events
463
+ listenSignal((event) => {
464
+ console.log("Event:", event.action, event.strategyName);
465
+ });
466
+
467
+ // Wait for first loss from any source
468
+ listenSignalOnce(
469
+ (event) => event.action === "closed" && event.pnl.pnlPercentage < 0,
470
+ (event) => {
471
+ console.log("First loss detected:", event.pnl.pnlPercentage);
472
+ }
473
+ );
474
+
475
+ // Run both modes
476
+ Backtest.background("BTCUSDT", {
477
+ strategyName: "my-strategy",
478
+ exchangeName: "binance",
479
+ frameName: "1d-backtest"
480
+ });
481
+
482
+ Live.background("BTCUSDT", {
483
+ strategyName: "my-strategy",
484
+ exchangeName: "binance"
485
+ });
486
+ ```
487
+
488
+ **Available event listeners:**
489
+
490
+ - `listenSignal(callback)` - Subscribe to all signal events (backtest + live)
491
+ - `listenSignalOnce(filter, callback)` - Subscribe once with filter predicate
492
+ - `listenSignalBacktest(callback)` - Subscribe to backtest signals only
493
+ - `listenSignalBacktestOnce(filter, callback)` - Subscribe to backtest signals once
494
+ - `listenSignalLive(callback)` - Subscribe to live signals only
495
+ - `listenSignalLiveOnce(filter, callback)` - Subscribe to live signals once
496
+
497
+ All listeners return an `unsubscribe` function. All callbacks are processed sequentially using queued async execution.
199
498
 
200
499
  ## API Reference
201
500
 
202
- ### Functions
501
+ ### High-Level Functions
203
502
 
204
- #### `addExchange(exchangeSchema: IExchangeSchema)`
205
- Add exchange data source for candles.
503
+ #### Schema Registration
206
504
 
207
- #### `addStrategy(strategySchema: IStrategySchema)`
208
- Add trading strategy.
505
+ ```typescript
506
+ // Register exchange
507
+ addExchange(exchangeSchema: IExchangeSchema): void
209
508
 
210
- #### `getCandles(symbol, interval, limit): Promise<ICandleData[]>`
211
- Get candle data from exchange.
509
+ // Register strategy
510
+ addStrategy(strategySchema: IStrategySchema): void
212
511
 
213
- #### `getAveragePrice(symbol): Promise<number>`
214
- Get VWAP average price based on last 5 1m candles.
512
+ // Register timeframe generator
513
+ addFrame(frameSchema: IFrameSchema): void
514
+ ```
215
515
 
216
- #### `runBacktest(symbol: string, timeframes: Date[]): Promise<IBacktestResult>`
217
- Run backtest and return closed trades only.
516
+ #### Exchange Data
218
517
 
219
- #### `runBacktestGUI(symbol: string, timeframes: Date[]): void`
220
- Run backtest and print beautiful ASCII table to terminal.
518
+ ```typescript
519
+ // Get historical candles
520
+ const candles = await getCandles("BTCUSDT", "1h", 5);
521
+ // Returns: [
522
+ // { timestamp: 1704067200000, open: 42150.5, high: 42380.2, low: 42100.0, close: 42250.8, volume: 125.43 },
523
+ // { timestamp: 1704070800000, open: 42250.8, high: 42500.0, low: 42200.0, close: 42450.3, volume: 98.76 },
524
+ // { timestamp: 1704074400000, open: 42450.3, high: 42600.0, low: 42400.0, close: 42580.5, volume: 110.22 },
525
+ // { timestamp: 1704078000000, open: 42580.5, high: 42700.0, low: 42550.0, close: 42650.0, volume: 95.18 },
526
+ // { timestamp: 1704081600000, open: 42650.0, high: 42750.0, low: 42600.0, close: 42720.0, volume: 102.35 }
527
+ // ]
528
+
529
+ // Get VWAP from last 5 1m candles
530
+ const vwap = await getAveragePrice("BTCUSDT");
531
+ // Returns: 42685.34
532
+
533
+ // Get current date in execution context
534
+ const date = await getDate();
535
+ // Returns: 2024-01-01T12:00:00.000Z (in backtest mode, returns frame's current timestamp)
536
+ // Returns: 2024-01-15T10:30:45.123Z (in live mode, returns current wall clock time)
537
+
538
+ // Get current mode
539
+ const mode = await getMode();
540
+ // Returns: "backtest" or "live"
541
+
542
+ // Format price/quantity for exchange
543
+ const price = await formatPrice("BTCUSDT", 42685.3456789);
544
+ // Returns: "42685.35" (formatted to exchange precision)
545
+
546
+ const quantity = await formatQuantity("BTCUSDT", 0.123456789);
547
+ // Returns: "0.12345" (formatted to exchange precision)
548
+ ```
221
549
 
222
- #### `reduce<T>(symbol, timeframes, callback, initialValue): Promise<IReduceResult<T>>`
223
- Iterate timeframes with accumulator pattern. Callback receives `(accumulator, index, when, symbol)`.
550
+ ### Service APIs
224
551
 
225
- #### `startRun(config: IRunConfig)`
226
- Start real-time strategy execution.
552
+ #### Backtest API
227
553
 
228
- #### `stopRun(symbol: string)`
229
- Stop specific symbol execution.
554
+ ```typescript
555
+ import { Backtest } from "backtest-kit";
556
+
557
+ // Stream backtest results
558
+ Backtest.run(
559
+ symbol: string,
560
+ context: {
561
+ strategyName: string;
562
+ exchangeName: string;
563
+ frameName: string;
564
+ }
565
+ ): AsyncIterableIterator<IStrategyTickResultClosed>
566
+
567
+ // Run in background without yielding results
568
+ Backtest.background(
569
+ symbol: string,
570
+ context: { strategyName, exchangeName, frameName }
571
+ ): Promise<() => void> // Returns cancellation function
572
+
573
+ // Generate markdown report
574
+ Backtest.getReport(strategyName: string): Promise<string>
575
+
576
+ // Save report to disk
577
+ Backtest.dump(strategyName: string, path?: string): Promise<void>
578
+ ```
230
579
 
231
- #### `stopAll()`
232
- Stop all running strategies.
580
+ #### Live Trading API
233
581
 
234
- ## Types
582
+ ```typescript
583
+ import { Live } from "backtest-kit";
584
+
585
+ // Stream live results (infinite)
586
+ Live.run(
587
+ symbol: string,
588
+ context: {
589
+ strategyName: string;
590
+ exchangeName: string;
591
+ }
592
+ ): AsyncIterableIterator<IStrategyTickResult>
593
+
594
+ // Run in background without yielding results
595
+ Live.background(
596
+ symbol: string,
597
+ context: { strategyName, exchangeName }
598
+ ): Promise<() => void> // Returns cancellation function
599
+
600
+ // Generate markdown report
601
+ Live.getReport(strategyName: string): Promise<string>
602
+
603
+ // Save report to disk
604
+ Live.dump(strategyName: string, path?: string): Promise<void>
605
+ ```
606
+
607
+ ## Type Definitions
235
608
 
236
609
  ### Signal Data
237
610
 
238
611
  ```typescript
239
- interface ISignalData {
240
- id: string;
612
+ interface ISignalRow {
613
+ id: string; // Auto-generated
241
614
  position: "long" | "short";
242
615
  note: string;
243
616
  priceOpen: number;
@@ -248,29 +621,210 @@ interface ISignalData {
248
621
  }
249
622
  ```
250
623
 
251
- ### Tick Results
624
+ ### Tick Results (Discriminated Union)
252
625
 
253
626
  ```typescript
254
627
  type IStrategyTickResult =
255
- | IStrategyTickResultIdle // No active signal
256
- | IStrategyTickResultOpened // Signal just opened
257
- | IStrategyTickResultActive // Signal is active
258
- | IStrategyTickResultClosed; // Signal closed with PNL
628
+ | { action: "idle"; signal: null }
629
+ | { action: "opened"; signal: ISignalRow }
630
+ | { action: "active"; signal: ISignalRow; currentPrice: number }
631
+ | {
632
+ action: "closed";
633
+ signal: ISignalRow;
634
+ currentPrice: number;
635
+ closeReason: "take_profit" | "stop_loss" | "time_expired";
636
+ closeTimestamp: number;
637
+ pnl: {
638
+ priceOpenWithCosts: number;
639
+ priceCloseWithCosts: number;
640
+ pnlPercentage: number;
641
+ };
642
+ };
643
+ ```
644
+
645
+ ### PNL Calculation
646
+
647
+ ```typescript
648
+ // Constants
649
+ PERCENT_SLIPPAGE = 0.1% // 0.001
650
+ PERCENT_FEE = 0.1% // 0.001
651
+
652
+ // LONG position
653
+ priceOpenWithCosts = priceOpen * (1 + slippage + fee)
654
+ priceCloseWithCosts = priceClose * (1 - slippage - fee)
655
+ pnl% = (priceCloseWithCosts - priceOpenWithCosts) / priceOpenWithCosts * 100
656
+
657
+ // SHORT position
658
+ priceOpenWithCosts = priceOpen * (1 - slippage + fee)
659
+ priceCloseWithCosts = priceClose * (1 + slippage + fee)
660
+ pnl% = (priceOpenWithCosts - priceCloseWithCosts) / priceOpenWithCosts * 100
661
+ ```
662
+
663
+ ## Production Readiness
664
+
665
+ ### ✅ Production-Ready Features
666
+
667
+ 1. **Crash-Safe Persistence** - Atomic file writes with automatic recovery
668
+ 2. **Signal Validation** - Comprehensive validation prevents invalid trades
669
+ 3. **Type Safety** - Discriminated unions eliminate runtime type errors
670
+ 4. **Memory Efficiency** - Prototype methods + async generators + memoization
671
+ 5. **Interval Throttling** - Prevents signal spam
672
+ 6. **Live Trading Ready** - Full implementation with real-time progression
673
+ 7. **Error Recovery** - Stateless process with disk-based state
674
+
675
+ ## File Structure
676
+
677
+ ```
678
+ src/
679
+ ├── client/ # Pure business logic (no DI)
680
+ │ ├── ClientStrategy.ts # Signal lifecycle + validation + persistence
681
+ │ ├── ClientExchange.ts # VWAP calculation
682
+ │ └── ClientFrame.ts # Timeframe generation
683
+ ├── classes/
684
+ │ └── Persist.ts # Atomic file persistence
685
+ ├── function/ # High-level API
686
+ │ ├── add.ts # addStrategy, addExchange, addFrame
687
+ │ ├── exchange.ts # getCandles, getAveragePrice, getDate, getMode
688
+ │ └── run.ts # DEPRECATED - use logic services instead
689
+ ├── interfaces/ # TypeScript interfaces
690
+ │ ├── Strategy.interface.ts
691
+ │ ├── Exchange.interface.ts
692
+ │ └── Frame.interface.ts
693
+ ├── lib/
694
+ │ ├── core/ # DI container
695
+ │ ├── services/
696
+ │ │ ├── base/ # LoggerService
697
+ │ │ ├── context/ # ExecutionContext, MethodContext
698
+ │ │ ├── connection/ # Client instance creators
699
+ │ │ ├── global/ # Context wrappers
700
+ │ │ ├── schema/ # Registry services
701
+ │ │ └── logic/
702
+ │ │ └── private/ # Async generator orchestration
703
+ │ │ ├── BacktestLogicPrivateService.ts
704
+ │ │ └── LiveLogicPrivateService.ts
705
+ │ └── index.ts # Public API
706
+ └── helpers/
707
+ └── toProfitLossDto.ts # PNL calculation
708
+ ```
709
+
710
+ ## Advanced Examples
711
+
712
+ ### Custom Persistence Adapter
713
+
714
+ ```typescript
715
+ import { PersistSignalAdaper, PersistBase } from "backtest-kit";
716
+
717
+ class RedisPersist extends PersistBase {
718
+ async readValue(entityId) {
719
+ return JSON.parse(await redis.get(entityId));
720
+ }
721
+ async writeValue(entityId, entity) {
722
+ await redis.set(entityId, JSON.stringify(entity));
723
+ }
724
+ }
725
+
726
+ PersistSignalAdaper.usePersistSignalAdapter(RedisPersist);
727
+ ```
728
+
729
+ ### Multi-Symbol Live Trading
730
+
731
+ ```typescript
732
+ import { Live } from "backtest-kit";
733
+
734
+ const symbols = ["BTCUSDT", "ETHUSDT", "SOLUSDT"];
735
+
736
+ // Run all symbols in parallel
737
+ await Promise.all(
738
+ symbols.map(async (symbol) => {
739
+ for await (const result of Live.run(symbol, {
740
+ strategyName: "my-strategy",
741
+ exchangeName: "binance"
742
+ })) {
743
+ console.log(`[${symbol}]`, result.action);
744
+
745
+ // Generate reports periodically
746
+ if (result.action === "closed") {
747
+ await Live.dump("my-strategy");
748
+ }
749
+ }
750
+ })
751
+ );
752
+ ```
753
+
754
+ ### Early Termination
755
+
756
+ **Using async generator with break:**
757
+
758
+ ```typescript
759
+ import { Backtest } from "backtest-kit";
760
+
761
+ for await (const result of Backtest.run("BTCUSDT", {
762
+ strategyName: "my-strategy",
763
+ exchangeName: "binance",
764
+ frameName: "1d-backtest"
765
+ })) {
766
+ if (result.closeReason === "stop_loss") {
767
+ console.log("Stop loss hit - terminating backtest");
768
+
769
+ // Save final report before exit
770
+ await Backtest.dump("my-strategy");
771
+ break; // Generator stops immediately
772
+ }
773
+ }
774
+ ```
775
+
776
+ **Using background mode with stop() function:**
777
+
778
+ ```typescript
779
+ import { Backtest, Live, listenSignalLiveOnce } from "backtest-kit";
780
+
781
+ // Backtest.background returns a stop function
782
+ const stopBacktest = await Backtest.background("BTCUSDT", {
783
+ strategyName: "my-strategy",
784
+ exchangeName: "binance",
785
+ frameName: "1d-backtest"
786
+ });
787
+
788
+ // Stop backtest after some condition
789
+ setTimeout(() => {
790
+ console.log("Stopping backtest...");
791
+ stopBacktest(); // Stops the background execution
792
+ }, 5000);
793
+
794
+ // Live.background also returns a stop function
795
+ const stopLive = await Live.background("BTCUSDT", {
796
+ strategyName: "my-strategy",
797
+ exchangeName: "binance"
798
+ });
799
+
800
+ // Stop live trading after detecting stop loss
801
+ listenSignalLiveOnce(
802
+ (event) => event.action === "closed" && event.closeReason === "stop_loss",
803
+ (event) => {
804
+ console.log("Stop loss detected - stopping live trading");
805
+ stopLive(); // Stops the infinite loop
806
+ }
807
+ );
259
808
  ```
260
809
 
261
810
  ## Use Cases
262
811
 
263
- The reduce pattern is perfect for:
264
- - **LLM Integration** - Feed historical data to AI models for analysis
265
- - **Custom Analytics** - Build your own metrics and statistics
266
- - **API Aggregation** - Collect data from multiple sources over time
267
- - **Data Processing** - Transform and accumulate timeframe data
268
- - **Real-time Trading** - Use `startRun` for live strategy execution
812
+ - **Algorithmic Trading** - Backtest and deploy strategies with crash recovery
813
+ - **Strategy Research** - Test hypotheses on historical data
814
+ - **Signal Generation** - Use with ML models or technical indicators
815
+ - **Portfolio Management** - Track multiple strategies across symbols
816
+ - **Educational Projects** - Learn trading system architecture
817
+
818
+ ## Contributing
819
+
820
+ Pull requests are welcome. For major changes, please open an issue first.
269
821
 
270
822
  ## License
271
823
 
272
824
  MIT
273
825
 
274
- ## Contributing
826
+ ## Links
275
827
 
276
- Pull requests are welcome. For major changes, please open an issue first.
828
+ - [Architecture Documentation](./ARCHITECTURE.md)
829
+ - [TypeScript Documentation](https://www.typescriptlang.org/)
830
+ - [Dependency Injection](https://github.com/tripolskypetr/di-kit)