tradelab 0.4.0 → 1.0.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 (54) hide show
  1. package/README.md +121 -52
  2. package/bin/tradelab.js +340 -49
  3. package/dist/cjs/data.cjs +210 -155
  4. package/dist/cjs/index.cjs +1782 -274
  5. package/dist/cjs/live.cjs +3350 -0
  6. package/docs/README.md +26 -9
  7. package/docs/api-reference.md +89 -26
  8. package/docs/backtest-engine.md +74 -60
  9. package/docs/data-reporting-cli.md +66 -36
  10. package/docs/examples.md +275 -0
  11. package/docs/live-trading.md +186 -0
  12. package/examples/yahooEmaCross.js +1 -6
  13. package/package.json +18 -3
  14. package/src/data/csv.js +24 -14
  15. package/src/data/index.js +1 -5
  16. package/src/data/yahoo.js +6 -19
  17. package/src/engine/backtest.js +137 -144
  18. package/src/engine/backtestTicks.js +481 -0
  19. package/src/engine/barSystemRunner.js +1027 -0
  20. package/src/engine/execution.js +11 -39
  21. package/src/engine/portfolio.js +237 -66
  22. package/src/engine/walkForward.js +132 -13
  23. package/src/index.js +3 -11
  24. package/src/live/broker/alpaca.js +254 -0
  25. package/src/live/broker/binance.js +351 -0
  26. package/src/live/broker/coinbase.js +339 -0
  27. package/src/live/broker/interactiveBrokers.js +123 -0
  28. package/src/live/broker/interface.js +74 -0
  29. package/src/live/clock.js +56 -0
  30. package/src/live/engine/candleAggregator.js +154 -0
  31. package/src/live/engine/liveEngine.js +694 -0
  32. package/src/live/engine/paperEngine.js +453 -0
  33. package/src/live/engine/riskManager.js +185 -0
  34. package/src/live/engine/stateManager.js +112 -0
  35. package/src/live/events.js +48 -0
  36. package/src/live/feed/brokerFeed.js +35 -0
  37. package/src/live/feed/interface.js +28 -0
  38. package/src/live/feed/pollingFeed.js +105 -0
  39. package/src/live/index.js +27 -0
  40. package/src/live/logger.js +82 -0
  41. package/src/live/orchestrator.js +133 -0
  42. package/src/live/storage/interface.js +36 -0
  43. package/src/live/storage/jsonFileStorage.js +112 -0
  44. package/src/metrics/buildMetrics.js +103 -100
  45. package/src/reporting/exportBacktestArtifacts.js +1 -4
  46. package/src/reporting/exportTradesCsv.js +2 -7
  47. package/src/reporting/renderHtmlReport.js +8 -13
  48. package/src/utils/indicators.js +1 -2
  49. package/src/utils/positionSizing.js +16 -2
  50. package/src/utils/time.js +4 -12
  51. package/templates/report.html +23 -9
  52. package/templates/report.js +83 -69
  53. package/types/index.d.ts +98 -4
  54. package/types/live.d.ts +382 -0
package/README.md CHANGED
@@ -3,19 +3,19 @@
3
3
 
4
4
  <p><strong>A Node.js backtesting toolkit for serious trading strategy research.</strong></p>
5
5
 
6
- [![npm version](https://img.shields.io/npm/v/tradelab?color=0f172a&label=npm&logo=npm)](https://www.npmjs.com/package/tradelab)
7
- [![GitHub](https://img.shields.io/badge/github-ishsharm0/tradelab-0f172a?logo=github)](https://github.com/ishsharm0/tradelab)
8
- [![License: MIT](https://img.shields.io/badge/license-MIT-0f172a)](https://github.com/ishsharm0/tradelab/blob/main/LICENSE)
9
- [![Node.js](https://img.shields.io/badge/node-%3E%3D18-0f172a?logo=node.js)](https://nodejs.org)
10
- [![TypeScript](https://img.shields.io/badge/TypeScript-ready-0f172a?logo=typescript)](https://github.com/ishsharm0/tradelab/blob/main/types/index.d.ts)
6
+ [![npm version](https://img.shields.io/npm/v/tradelab?color=0f172a&label=npm&logo=npm)](https://www.npmjs.com/package/tradelab)
7
+ [![GitHub](https://img.shields.io/badge/github-ishsharm0/tradelab-0f172a?logo=github)](https://github.com/ishsharm0/tradelab)
8
+ [![License: MIT](https://img.shields.io/badge/license-MIT-0f172a)](https://github.com/ishsharm0/tradelab/blob/main/LICENSE)
9
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-0f172a?logo=node.js)](https://nodejs.org)
10
+ [![TypeScript](https://img.shields.io/badge/TypeScript-ready-0f172a?logo=typescript)](https://github.com/ishsharm0/tradelab/blob/main/types/index.d.ts)
11
11
 
12
12
  </div>
13
13
 
14
14
  ---
15
15
 
16
- **tradelab** handles the simulation, sizing, exits, costs, and result exports you bring the candles and signal logic.
16
+ **tradelab** handles strategy research and execution workflows in one package.
17
17
 
18
- It works cleanly for a single-strategy backtest and scales up to portfolio runs, walk-forward testing, and detailed execution modeling. It is not a broker connector or a live trading tool.
18
+ Use it for backtests, portfolio and walk-forward validation, and live or paper execution through broker adapters while keeping the same `signal()` contract.
19
19
 
20
20
  ```bash
21
21
  npm install tradelab
@@ -31,6 +31,8 @@ npm install tradelab
31
31
  - [Core concepts](#core-concepts)
32
32
  - [Portfolio mode](#portfolio-mode)
33
33
  - [Walk-forward optimization](#walk-forward-optimization)
34
+ - [Tick backtests](#tick-backtests)
35
+ - [Live trading](#live-trading)
34
36
  - [Execution and cost modeling](#execution-and-cost-modeling)
35
37
  - [Exports and reporting](#exports-and-reporting)
36
38
  - [CLI](#cli)
@@ -41,15 +43,16 @@ npm install tradelab
41
43
 
42
44
  ## What it includes
43
45
 
44
- | Area | What you get |
45
- |---|---|
46
- | **Engine** | Candle-based backtests with position sizing, exits, risk controls, replay capture |
47
- | **Portfolio** | Multi-symbol aggregation with weight-based capital allocation |
48
- | **Walk-forward** | Rolling train/test validation with parameter search |
49
- | **Data** | Yahoo Finance downloads, CSV import, and local cache helpers |
50
- | **Costs** | Slippage, spread, and commission modeling |
51
- | **Exports** | HTML reports, metrics JSON, and trade CSV |
52
- | **Dev experience** | TypeScript definitions, ESM/CJS support, CLI for quick runs |
46
+ | Area | What you get |
47
+ | ------------------ | ---------------------------------------------------------------------------------------- |
48
+ | **Engine** | Candle and tick backtests with position sizing, exits, replay capture, and cost models |
49
+ | **Portfolio** | Multi-system shared-capital simulation with live capital locking and daily loss halts |
50
+ | **Walk-forward** | Rolling and anchored train/test validation with parameter search and stability summaries |
51
+ | **Live execution** | Live and paper engines with broker adapters, state persistence, and orchestration |
52
+ | **Data** | Yahoo Finance downloads, CSV import, and local cache helpers |
53
+ | **Costs** | Slippage, spread, and commission modeling |
54
+ | **Exports** | HTML reports, metrics JSON, and trade CSV |
55
+ | **Dev experience** | TypeScript definitions, ESM/CJS support, CLI for quick runs |
53
56
 
54
57
  ---
55
58
 
@@ -107,7 +110,7 @@ const candles = await getHistoricalCandles({
107
110
  symbol: "SPY",
108
111
  interval: "1d",
109
112
  period: "2y",
110
- cache: true, // reuses local copy on repeated runs
113
+ cache: true, // reuses local copy on repeated runs
111
114
  });
112
115
 
113
116
  const result = backtest({ candles, symbol: "SPY", interval: "1d", range: "2y", signal });
@@ -160,16 +163,16 @@ The minimum viable signal is just `side`, `stop`, and `rr`. Start there and add
160
163
 
161
164
  ### Key backtest options
162
165
 
163
- | Option | Purpose |
164
- |---|---|
165
- | `equity` | Starting equity (default `10000`) |
166
- | `riskPct` | Percent of equity risked per trade |
167
- | `warmupBars` | Bars skipped before signal evaluation starts |
168
- | `flattenAtClose` | Forces end-of-day exit when enabled |
169
- | `costs` | Slippage, spread, and commission model |
170
- | `strict` | Throws on lookahead access |
171
- | `collectEqSeries` | Enables equity curve output |
172
- | `collectReplay` | Enables visualization payload |
166
+ | Option | Purpose |
167
+ | ----------------- | -------------------------------------------- |
168
+ | `equity` | Starting equity (default `10000`) |
169
+ | `riskPct` | Percent of equity risked per trade |
170
+ | `warmupBars` | Bars skipped before signal evaluation starts |
171
+ | `flattenAtClose` | Forces end-of-day exit when enabled |
172
+ | `costs` | Slippage, spread, and commission model |
173
+ | `strict` | Throws on lookahead access |
174
+ | `collectEqSeries` | Enables equity curve output |
175
+ | `collectReplay` | Enables visualization payload |
173
176
 
174
177
  ### Result shape
175
178
 
@@ -177,19 +180,19 @@ The minimum viable signal is just `side`, `stop`, and `rr`. Start there and add
177
180
  {
178
181
  symbol, interval, range,
179
182
  trades, // every realized leg, including partial exits
180
- positions, // completed positions start here for analysis
183
+ positions, // completed positions - start here for analysis
181
184
  metrics, // winRate, profitFactor, maxDrawdown, sharpe, ...
182
- eqSeries, // [{ time, timestamp, equity }] equity curve
185
+ eqSeries, // [{ time, timestamp, equity }] - equity curve
183
186
  replay, // visualization frames and events
184
187
  }
185
188
  ```
186
189
 
187
190
  **First checks after any run:**
188
191
 
189
- - `metrics.trades` enough sample size to trust the numbers?
190
- - `metrics.profitFactor` do winners beat losers gross of costs?
191
- - `metrics.maxDrawdown` is the equity path survivable?
192
- - `metrics.sideBreakdown` does one side carry the whole result?
192
+ - `metrics.trades` - enough sample size to trust the numbers?
193
+ - `metrics.profitFactor` - do winners beat losers gross of costs?
194
+ - `metrics.maxDrawdown` - is the equity path survivable?
195
+ - `metrics.sideBreakdown` - does one side carry the whole result?
193
196
 
194
197
  ---
195
198
 
@@ -209,25 +212,26 @@ const result = backtestPortfolio({
209
212
  });
210
213
  ```
211
214
 
212
- Capital is allocated up front by weight. Each system runs through the normal single-symbol engine, and the portfolio result merges trades, positions, replay events, and the equity series.
215
+ Weights now act as default per-system allocation caps rather than pre-funded sleeves. Capital is locked only when a fill happens, `eqSeries` includes `lockedCapital` and `availableCapital`, later systems size against remaining live capital, and `maxDailyLossPct` on `backtestPortfolio()` can halt the whole book for the rest of the day.
213
216
 
214
217
  ---
215
218
 
216
219
  ## Walk-forward optimization
217
220
 
218
- Use `walkForwardOptimize()` when one in-sample backtest is not enough. It runs rolling train/test windows across the full candle history.
221
+ Use `walkForwardOptimize()` when one in-sample backtest is not enough. It supports rolling and anchored train/test windows across the full candle history.
219
222
 
220
223
  ```js
221
224
  import { walkForwardOptimize } from "tradelab";
222
225
 
223
226
  const wf = walkForwardOptimize({
224
227
  candles,
228
+ mode: "anchored",
225
229
  trainBars: 180,
226
230
  testBars: 60,
227
231
  stepBars: 60,
228
232
  scoreBy: "profitFactor",
229
233
  parameterSets: [
230
- { fast: 8, slow: 21, rr: 2 },
234
+ { fast: 8, slow: 21, rr: 2 },
231
235
  { fast: 10, slow: 30, rr: 2 },
232
236
  ],
233
237
  signalFactory(params) {
@@ -236,7 +240,59 @@ const wf = walkForwardOptimize({
236
240
  });
237
241
  ```
238
242
 
239
- Each window picks the best parameter set in training, then runs it blind on the test slice. The `windows` array in the result shows per-window winners. If the winning parameters swing wildly from window to window, that is a real signal not a formatting detail.
243
+ Each window picks the best parameter set in training, then runs it blind on the test slice. The `windows` array now includes out-of-sample trade count, profitability, and a per-window stability score. `bestParamsSummary` reports how stable the winners were across the full run.
244
+
245
+ ---
246
+
247
+ ## Tick backtests
248
+
249
+ Use `backtestTicks()` when you want event-driven fills on tick or quote data without changing the result shape used by metrics, exports, or replay.
250
+
251
+ ```js
252
+ import { backtestTicks } from "tradelab";
253
+
254
+ const result = backtestTicks({
255
+ ticks,
256
+ queueFillProbability: 0.35,
257
+ signal,
258
+ });
259
+ ```
260
+
261
+ Market entries fill on the next tick, limit orders can fill at the touch with configurable queue probability, and stop exits use the existing cost model with stop-specific slippage if you provide it in `costs.slippageByKind.stop`.
262
+
263
+ ---
264
+
265
+ ## Live trading
266
+
267
+ `tradelab/live` provides the live stack:
268
+
269
+ - `LiveEngine` for single-system live/paper execution
270
+ - `LiveOrchestrator` for multi-system execution with shared broker state
271
+ - `PaperEngine` implementing the broker interface for deterministic simulation
272
+ - broker adapters for Alpaca, Binance, Coinbase, and Interactive Brokers
273
+ - JSON state/trade/equity persistence via `JsonFileStorage`
274
+
275
+ Use the same signal contract from backtesting in live mode:
276
+
277
+ ```js
278
+ import { LiveEngine, PaperEngine, JsonFileStorage } from "tradelab/live";
279
+
280
+ const engine = new LiveEngine({
281
+ id: "aapl-1m",
282
+ symbol: "AAPL",
283
+ interval: "1m",
284
+ broker: new PaperEngine({ equity: 25_000 }),
285
+ storage: new JsonFileStorage({ baseDir: "./output/live-state" }),
286
+ signal({ bar, openPosition }) {
287
+ if (openPosition) return null;
288
+ return { side: "long", stop: bar.close - 1, rr: 2 };
289
+ },
290
+ });
291
+
292
+ await engine.start();
293
+ ```
294
+
295
+ See [docs/live-trading.md](docs/live-trading.md) for API and CLI workflows.
240
296
 
241
297
  ---
242
298
 
@@ -282,12 +338,12 @@ exportBacktestArtifacts({ result, outDir: "./output" });
282
338
 
283
339
  Or use the narrower helpers:
284
340
 
285
- | Helper | Output |
286
- |---|---|
287
- | `exportHtmlReport(options)` | Interactive HTML report written to disk |
288
- | `renderHtmlReport(options)` | HTML report returned as a string |
289
- | `exportTradesCsv(trades, options)` | Flat trade ledger for spreadsheets or pandas |
290
- | `exportMetricsJSON(options)` | Machine-readable metrics for dashboards or automation |
341
+ | Helper | Output |
342
+ | ---------------------------------- | ----------------------------------------------------- |
343
+ | `exportHtmlReport(options)` | Interactive HTML report written to disk |
344
+ | `renderHtmlReport(options)` | HTML report returned as a string |
345
+ | `exportTradesCsv(trades, options)` | Flat trade ledger for spreadsheets or pandas |
346
+ | `exportMetricsJSON(options)` | Machine-readable metrics for dashboards or automation |
291
347
 
292
348
  For programmatic pipelines, `exportMetricsJSON` is usually the most useful format to build on.
293
349
 
@@ -313,7 +369,16 @@ npx tradelab portfolio \
313
369
  # Walk-forward validation
314
370
  npx tradelab walk-forward \
315
371
  --source yahoo --symbol QQQ --interval 1d --period 2y \
316
- --trainBars 180 --testBars 60
372
+ --trainBars 180 --testBars 60 --mode anchored
373
+
374
+ # Live paper engine (single system)
375
+ npx tradelab paper --symbol AAPL --interval 1m --mode polling --once true
376
+
377
+ # Live orchestrator from config
378
+ npx tradelab live --config ./live-portfolio.json --paper --mode polling --once true
379
+
380
+ # Inspect persisted live state
381
+ npx tradelab status --dir ./output/live-state
317
382
 
318
383
  # Prefetch and cache data
319
384
  npx tradelab prefetch --symbol SPY --interval 1d --period 1y
@@ -322,7 +387,7 @@ npx tradelab import-csv --csvPath ./data/spy.csv --symbol SPY --interval 1d
322
387
 
323
388
  **Built-in strategies:** `ema-cross` · `buy-hold`
324
389
 
325
- You can also point `--strategy` at a local module that exports `default(args)`, `createSignal(args)`, or `signal`.
390
+ You can also point `--strategy` at a local module that exports `default(args)`, `createSignal(args)`, or `signal` for `backtest`, or `signalFactory(params, args)` plus `parameterSets`/`createParameterSets(args)` for `walk-forward`.
326
391
 
327
392
  ---
328
393
 
@@ -344,6 +409,7 @@ The examples are a good place to start if you want something runnable before wir
344
409
  ```js
345
410
  import { backtest, getHistoricalCandles, ema } from "tradelab";
346
411
  import { fetchHistorical } from "tradelab/data";
412
+ import { LiveEngine, PaperEngine } from "tradelab/live";
347
413
  ```
348
414
 
349
415
  ### CommonJS
@@ -351,24 +417,27 @@ import { fetchHistorical } from "tradelab/data";
351
417
  ```js
352
418
  const { backtest, getHistoricalCandles, ema } = require("tradelab");
353
419
  const { fetchHistorical } = require("tradelab/data");
420
+ const { LiveEngine, PaperEngine } = require("tradelab/live");
354
421
  ```
355
422
 
356
423
  ---
357
424
 
358
425
  ## Documentation
359
426
 
360
- | Guide | What it covers |
361
- |---|---|
362
- | [Backtest engine](docs/backtest-engine.md) | Signal contract, all options, result shape, portfolio mode, walk-forward |
363
- | [Data, reporting, and CLI](docs/data-reporting-cli.md) | Data loading, cache behavior, exports, CLI reference |
364
- | [API reference](docs/api-reference.md) | Compact index of every public export |
427
+ | Guide | What it covers |
428
+ | ------------------------------------------------------ | ------------------------------------------------------------------------------ |
429
+ | [Backtest engine](docs/backtest-engine.md) | Signal contract, all options, result shape, portfolio mode, walk-forward |
430
+ | [Data, reporting, and CLI](docs/data-reporting-cli.md) | Data loading, cache behavior, exports, CLI reference |
431
+ | [Live trading](docs/live-trading.md) | Live engine, broker adapters, paper mode, orchestration, and state persistence |
432
+ | [Strategy examples](docs/examples.md) | Mean reversion, breakout, sentiment, LLM, and portfolio strategy patterns |
433
+ | [API reference](docs/api-reference.md) | Compact index of every public export |
365
434
 
366
435
  ---
367
436
 
368
437
  ## Common mistakes
369
438
 
370
439
  - Using unsorted candles or mixed intervals in a single series
371
- - Reading `trades` as if they were always full positions use `positions` for top-line analysis
440
+ - Reading `trades` as if they were always full positions - use `positions` for top-line analysis
372
441
  - Leaving costs at zero and overestimating edge
373
442
  - Trusting one backtest without out-of-sample validation
374
443
  - Debugging a strategy with `strict: false` when lookahead is possible
@@ -380,4 +449,4 @@ const { fetchHistorical } = require("tradelab/data");
380
449
  - Node `18+` is required
381
450
  - Yahoo downloads are cached under `output/data` by default
382
451
  - CommonJS and ESM are both supported
383
- - The engine is built for historical research not brokerage execution, tick-level simulation, or exchange microstructure modeling
452
+ - Live adapters support broker execution workflows, but this is still not an exchange microstructure simulator