tradelab 1.0.1 → 1.2.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 (66) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +188 -328
  3. package/bin/tradelab-mcp.js +7 -0
  4. package/bin/tradelab.js +29 -0
  5. package/dist/cjs/data.cjs +149 -26
  6. package/dist/cjs/index.cjs +1917 -1005
  7. package/dist/cjs/live.cjs +536 -25
  8. package/dist/cjs/ta.cjs +339 -0
  9. package/docs/README.md +32 -66
  10. package/docs/api-reference.md +283 -112
  11. package/docs/backtest-engine.md +210 -252
  12. package/docs/data-reporting-cli.md +114 -156
  13. package/docs/examples.md +6 -6
  14. package/docs/live-trading.md +263 -92
  15. package/docs/mcp.md +285 -0
  16. package/docs/research.md +157 -0
  17. package/examples/liveDashboard.js +33 -0
  18. package/examples/llmSignal.js +33 -0
  19. package/examples/mcpLiveTrading.js +77 -0
  20. package/examples/optimize.js +25 -0
  21. package/package.json +26 -4
  22. package/src/engine/asyncSignal.js +28 -0
  23. package/src/engine/backtest.js +13 -1
  24. package/src/engine/backtestAsync.js +27 -0
  25. package/src/engine/backtestTicks.js +13 -2
  26. package/src/engine/barSystemRunner.js +96 -41
  27. package/src/engine/execution.js +39 -0
  28. package/src/engine/grid.js +15 -0
  29. package/src/engine/llmSignal.js +84 -0
  30. package/src/engine/optimize.js +110 -0
  31. package/src/engine/optimizeWorker.js +67 -0
  32. package/src/engine/portfolio.js +4 -1
  33. package/src/engine/walkForward.js +1 -0
  34. package/src/index.js +9 -0
  35. package/src/live/dashboard/server.js +179 -0
  36. package/src/live/engine/liveEngine.js +2 -2
  37. package/src/live/engine/paperEngine.js +5 -0
  38. package/src/live/index.js +3 -0
  39. package/src/live/session.js +402 -0
  40. package/src/mcp/liveTools.js +179 -0
  41. package/src/mcp/schemas.js +167 -0
  42. package/src/mcp/server.js +35 -0
  43. package/src/mcp/tools.js +265 -0
  44. package/src/metrics/annualize.js +32 -0
  45. package/src/metrics/benchmark.js +55 -0
  46. package/src/metrics/buildMetrics.js +34 -13
  47. package/src/metrics/finite.js +17 -0
  48. package/src/research/combinations.js +18 -0
  49. package/src/research/cpcv.js +47 -0
  50. package/src/research/deflatedSharpe.js +35 -0
  51. package/src/research/index.js +6 -0
  52. package/src/research/monteCarlo.js +88 -0
  53. package/src/research/pbo.js +69 -0
  54. package/src/research/stats.js +78 -0
  55. package/src/strategies/builtins.js +96 -0
  56. package/src/strategies/index.js +30 -0
  57. package/src/ta/channels.js +67 -0
  58. package/src/ta/index.js +16 -0
  59. package/src/ta/oscillators.js +70 -0
  60. package/src/ta/trend.js +78 -0
  61. package/src/utils/random.js +33 -0
  62. package/templates/dashboard.html +661 -0
  63. package/types/index.d.ts +179 -0
  64. package/types/live.d.ts +114 -0
  65. package/types/mcp.d.ts +17 -0
  66. package/types/ta.d.ts +45 -0
@@ -1,54 +1,42 @@
1
- # Live trading
1
+ # Live and paper trading
2
2
 
3
- <small>[Back to main page](README.md)</small>
3
+ <small>[Back to docs](README.md)</small>
4
4
 
5
- This guide covers the `tradelab/live` module and the live CLI commands.
5
+ Use `tradelab/live` when you want the same strategy contract from `backtest()` to run against a paper broker or a broker adapter.
6
6
 
7
- ## Overview
7
+ The live module is intentionally small:
8
8
 
9
- The live stack is built to reuse the same signal contract as backtesting:
10
-
11
- - write and validate `signal()` with `backtest()`
12
- - run the same signal in `LiveEngine` or `LiveOrchestrator`
13
- - choose a real broker adapter or `PaperEngine`
14
- - persist state with `JsonFileStorage` for restart safety
15
-
16
- Import path:
9
+ - a signal receives finalized candles and returns the same order intent used in backtests
10
+ - a broker adapter handles account, order, fill, and position operations
11
+ - a feed provides bars or ticks
12
+ - storage persists state so a process restart can recover cleanly
13
+ - risk controls can block new orders or halt a system
17
14
 
18
15
  ```js
19
- import { LiveEngine, LiveOrchestrator, PaperEngine } from "tradelab/live";
16
+ import { LiveEngine, PaperEngine, JsonFileStorage } from "tradelab/live";
20
17
  ```
21
18
 
22
- ## Module components
23
-
24
- | Component | Purpose |
25
- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
26
- | `LiveEngine` | Single-system live or paper execution loop |
27
- | `LiveOrchestrator` | Multi-system live execution with shared broker and aggregated status |
28
- | `PaperEngine` | In-process broker simulator implementing the broker adapter contract |
29
- | `AlpacaBroker` / `BinanceBroker` / `CoinbaseBroker` / `InteractiveBrokersBroker` | Real broker adapters |
30
- | `BrokerFeed` / `PollingFeed` | Feed adapters for streaming or polling operation |
31
- | `RiskManager` | Session windows, daily loss gates, drawdown halts, position checks |
32
- | `StateManager` / `JsonFileStorage` | Persisted state, trades, and equity curve |
33
- | `EventBus` / `LiveLogger` | Event fanout and structured logging |
34
-
35
- ## `LiveEngine` quick start
19
+ ## Start With Paper Mode
36
20
 
37
21
  ```js
38
22
  import { LiveEngine, PaperEngine, JsonFileStorage } from "tradelab/live";
39
23
 
24
+ const broker = new PaperEngine({ equity: 25_000 });
25
+
40
26
  const engine = new LiveEngine({
41
- id: "aapl-1m",
27
+ id: "aapl-paper",
42
28
  symbol: "AAPL",
43
29
  interval: "1m",
44
- broker: new PaperEngine({ equity: 25_000 }),
30
+ broker,
45
31
  storage: new JsonFileStorage({ baseDir: "./output/live-state" }),
32
+ mode: "polling",
46
33
  riskPct: 1,
47
- mode: "streaming",
48
34
  signal({ bar, openPosition }) {
49
35
  if (openPosition) return null;
36
+
50
37
  return {
51
38
  side: "long",
39
+ entry: bar.close,
52
40
  stop: bar.close - 1,
53
41
  rr: 2,
54
42
  };
@@ -56,71 +44,136 @@ const engine = new LiveEngine({
56
44
  });
57
45
 
58
46
  await engine.start();
59
- // ... run until shutdown condition
47
+ await engine.pollOnce();
48
+ console.log(engine.getStatus());
60
49
  await engine.stop();
61
50
  ```
62
51
 
63
- Important behavior:
52
+ `PaperEngine` implements the broker interface in memory. Use it first for CLI runs, dashboard checks, and strategy wiring.
53
+
54
+ ## Signal Contract
55
+
56
+ The signal function receives:
64
57
 
65
- - `signal()` is called with the same context shape as backtesting
66
- - market and limit/stop order lifecycles are tracked through broker events
67
- - state is persisted after fills, order updates, and equity updates
68
- - `getStatus()` returns runtime and risk state for health checks
58
+ | Field | Meaning |
59
+ | -------------- | ---------------------------------------------- |
60
+ | `candles` | Finalized candle history available at this bar |
61
+ | `index` | Current candle index |
62
+ | `bar` | Current candle |
63
+ | `equity` | Current engine equity |
64
+ | `openPosition` | Current open position, or `null` |
65
+ | `pendingOrder` | Current pending entry order, or `null` |
69
66
 
70
- ## `LiveOrchestrator` quick start
67
+ Return `null` to do nothing. Return an order intent to open a trade:
71
68
 
72
69
  ```js
73
- import { LiveOrchestrator, PaperEngine, JsonFileStorage } from "tradelab/live";
70
+ return {
71
+ side: "long",
72
+ entry: bar.close,
73
+ stop: bar.close * 0.98,
74
+ rr: 2,
75
+ riskPct: 0.5,
76
+ };
77
+ ```
74
78
 
75
- const orchestrator = new LiveOrchestrator({
76
- broker: new PaperEngine({ equity: 100_000 }),
77
- storage: new JsonFileStorage({ baseDir: "./output/live-state" }),
78
- allocation: "weight",
79
- systems: [
80
- { id: "spy", symbol: "SPY", interval: "1m", weight: 2, signal: signalA },
81
- { id: "qqq", symbol: "QQQ", interval: "1m", weight: 1, signal: signalB },
82
- ],
83
- });
79
+ Useful fields:
84
80
 
85
- await orchestrator.start();
86
- const status = orchestrator.getStatus();
87
- await orchestrator.stop();
81
+ | Field | Meaning |
82
+ | -------------------- | -------------------------------------------- |
83
+ | `side` | `"long"`, `"short"`, `"buy"`, or `"sell"` |
84
+ | `entry` | Planned entry price |
85
+ | `stop` | Stop loss price |
86
+ | `takeProfit` or `rr` | Explicit target, or reward/risk multiple |
87
+ | `qty` | Fixed quantity. If omitted, sizing uses risk |
88
+ | `riskPct` | Percent of equity to risk on this trade |
89
+ | `_maxBarsInTrade` | Force an exit after this many completed bars |
90
+ | `_maxHoldMin` | Force an exit after this many minutes |
91
+
92
+ Use `backtest()` or `backtestAsync()` with the same signal before connecting a real broker.
93
+
94
+ ## Run From The CLI
95
+
96
+ The CLI has two entry points:
97
+
98
+ ```bash
99
+ tradelab paper --symbol AAPL --interval 1m --mode polling --once true
100
+ tradelab live --symbol AAPL --interval 1m --broker alpaca --paper
88
101
  ```
89
102
 
90
- Use orchestrator when multiple systems should share one broker/account context.
103
+ Common options:
104
+
105
+ | Option | Meaning |
106
+ | ----------------- | --------------------------------------------- |
107
+ | `--strategy` | Built-in strategy name or local strategy file |
108
+ | `--symbol` | Symbol passed to broker and feed |
109
+ | `--interval` | Candle interval, such as `1m`, `5m`, or `1d` |
110
+ | `--mode` | `streaming` or `polling` |
111
+ | `--once true` | Run one polling cycle and exit |
112
+ | `--stateDir` | Directory for persisted live state |
113
+ | `--dashboard` | Start the local dashboard |
114
+ | `--dashboardPort` | Dashboard port. Defaults to `4317` |
91
115
 
92
- ## CLI live commands
116
+ Strategy modules can export `default`, `createSignal(args)`, or `signal`:
93
117
 
94
- | Command | Purpose |
95
- | ----------------- | -------------------------------------------- |
96
- | `tradelab live` | Run live engine or orchestrator (`--config`) |
97
- | `tradelab paper` | Shortcut for `live` with paper broker mode |
98
- | `tradelab status` | Inspect persisted live state |
118
+ ```js
119
+ // ./strategies/ema-signal.js
120
+ import { ema } from "tradelab";
121
+
122
+ export function createSignal({ fast = 10, slow = 30, rr = 2 } = {}) {
123
+ return ({ candles, bar }) => {
124
+ if (candles.length < slow + 2) return null;
125
+
126
+ const closes = candles.map((candle) => candle.close);
127
+ const fastLine = ema(closes, Number(fast));
128
+ const slowLine = ema(closes, Number(slow));
129
+ const last = closes.length - 1;
130
+
131
+ if (fastLine[last - 1] <= slowLine[last - 1] && fastLine[last] > slowLine[last]) {
132
+ return {
133
+ side: "long",
134
+ entry: bar.close,
135
+ stop: Math.min(...candles.slice(-15).map((candle) => candle.low)),
136
+ rr: Number(rr),
137
+ };
138
+ }
99
139
 
100
- ### Single-system paper run
140
+ return null;
141
+ };
142
+ }
143
+ ```
101
144
 
102
145
  ```bash
103
146
  tradelab paper \
104
- --id aapl-1m \
147
+ --strategy ./strategies/ema-signal.js \
105
148
  --symbol AAPL \
106
149
  --interval 1m \
107
150
  --mode polling \
108
- --once true \
109
- --stateDir ./output/live-state
151
+ --once true
110
152
  ```
111
153
 
112
- ### Orchestrator run from config
154
+ ## Run Multiple Systems
113
155
 
114
- ```bash
115
- tradelab live \
116
- --config ./live-portfolio.json \
117
- --paper \
118
- --mode polling \
119
- --once true \
120
- --stateDir ./output/live-state
156
+ Use `LiveOrchestrator` when several systems share one account and broker.
157
+
158
+ ```js
159
+ import { LiveOrchestrator, PaperEngine, JsonFileStorage } from "tradelab/live";
160
+
161
+ const orchestrator = new LiveOrchestrator({
162
+ broker: new PaperEngine({ equity: 100_000 }),
163
+ storage: new JsonFileStorage({ baseDir: "./output/live-state" }),
164
+ allocation: "weight",
165
+ systems: [
166
+ { id: "spy", symbol: "SPY", interval: "1m", weight: 2, signal: spySignal },
167
+ { id: "qqq", symbol: "QQQ", interval: "1m", weight: 1, signal: qqqSignal },
168
+ ],
169
+ });
170
+
171
+ await orchestrator.start();
172
+ console.log(orchestrator.getStatus());
173
+ await orchestrator.stop();
121
174
  ```
122
175
 
123
- Example config:
176
+ CLI config:
124
177
 
125
178
  ```json
126
179
  {
@@ -131,56 +184,174 @@ Example config:
131
184
  "id": "spy-system",
132
185
  "symbol": "SPY",
133
186
  "interval": "1m",
134
- "strategy": "./strategies/spySignal.js",
187
+ "strategy": "./strategies/spy.js",
135
188
  "weight": 2
136
189
  },
137
190
  {
138
191
  "id": "qqq-system",
139
192
  "symbol": "QQQ",
140
193
  "interval": "1m",
141
- "strategy": "./strategies/qqqSignal.js",
194
+ "strategy": "./strategies/qqq.js",
142
195
  "weight": 1
143
196
  }
144
197
  ]
145
198
  }
146
199
  ```
147
200
 
148
- ### State inspection
201
+ ```bash
202
+ tradelab live --config ./live-portfolio.json --paper --mode polling --once true
203
+ ```
204
+
205
+ ## Dashboard
206
+
207
+ Start a local dashboard for an engine or orchestrator:
208
+
209
+ ```js
210
+ import { createDashboardServer } from "tradelab/live";
211
+
212
+ const dashboard = createDashboardServer({ source: engine, port: 4317 });
213
+ const url = await dashboard.start();
214
+
215
+ console.log(url);
216
+
217
+ // On shutdown:
218
+ await dashboard.close();
219
+ ```
220
+
221
+ The dashboard is a zero-dependency dark trading cockpit served from a single HTML file. It includes:
222
+
223
+ - **KPI strip** - equity, day P&L (with percent), open position, last price, all in monospace tabular numerals
224
+ - **Equity curve** - canvas chart that grows in real time; green when above session start, red when below
225
+ - **Positions table** - symbol, side badge, qty, entry, mark, unrealized P&L
226
+ - **Open orders table** - type, side, qty, price, with inline cancel
227
+ - **Event feed** - color-coded by severity (fill, exit/warning, reject) with animated entry; capped at 120 rows
228
+ - **Risk-halt banner** - shown when `source.getStatus().risk.halted` is true
229
+ - **Controls** - Stop and Flatten All buttons in the header; cancel links in the orders table
230
+
231
+ The dashboard exposes:
232
+
233
+ | Route | Method | Purpose |
234
+ | ---------- | ------ | ------------------------------------------------------------- |
235
+ | `/` | GET | Static dashboard page |
236
+ | `/state` | GET | Calls optional `source.refresh()`, then returns `getStatus()` |
237
+ | `/events` | GET | Server-Sent Events stream from `eventBus` |
238
+ | `/command` | POST | Dispatch a command to the source (whitelist enforced) |
239
+
240
+ ### `/command` endpoint
241
+
242
+ Send a JSON body with a `type` field. Only the following types are accepted; anything else returns `400`:
243
+
244
+ | type | Source method called |
245
+ | --------------- | ---------------------------------- |
246
+ | `flatten` | `source.flatten()` |
247
+ | `stop` | `source.stop()` |
248
+ | `closePosition` | `source.closePosition(cmd.symbol)` |
249
+ | `cancelOrder` | `source.cancelOrder(cmd.orderId)` |
250
+
251
+ ```js
252
+ // Example: flatten all positions from a browser
253
+ await fetch("/command", {
254
+ method: "POST",
255
+ headers: { "Content-Type": "application/json" },
256
+ body: JSON.stringify({ type: "flatten" }),
257
+ });
258
+ ```
259
+
260
+ ### `source.refresh()`
261
+
262
+ If the source object exposes a `refresh()` method, the dashboard awaits it before each `/state` response. Use this to pull fresh data from a broker before painting the UI - for example, a `TradingSession` from the MCP live tools can expose `refresh()` to sync account state before every poll.
263
+
264
+ CLI:
265
+
266
+ ```bash
267
+ tradelab paper --symbol AAPL --interval 1m --mode polling --dashboard --dashboardPort 4317
268
+ tradelab live --config ./live-portfolio.json --paper --dashboard --dashboardPort 4317
269
+ ```
270
+
271
+ New browser clients receive a bounded replay of recent events. The equity curve grows from the first data point seen in the session; the chart updates on every `equity:update` SSE event and on each `/state` poll.
272
+
273
+ ## State And Recovery
274
+
275
+ `JsonFileStorage` stores one namespace per engine id:
276
+
277
+ | File | Contents |
278
+ | -------------- | ----------------------------------------- |
279
+ | `state.json` | Latest open position, pending order, risk |
280
+ | `trades.jsonl` | Append-only completed trade records |
281
+ | `equity.jsonl` | Append-only equity snapshots |
282
+
283
+ Inspect persisted state:
149
284
 
150
285
  ```bash
151
286
  tradelab status --dir ./output/live-state
152
287
  tradelab status --dir ./output/live-state --namespace spy-system
153
288
  ```
154
289
 
155
- ## State and recovery
290
+ On restart, `StateManager` compares persisted state with broker positions and reports whether the state is clean, externally closed, adopted from broker state, or mismatched.
156
291
 
157
- Live state is namespaced and persisted as:
292
+ ## Risk Controls
158
293
 
159
- - `state.json` (latest engine state)
160
- - `trades.jsonl` (append-only)
161
- - `equity.jsonl` (append-only)
294
+ Pass top-level risk options to `LiveEngine`, or group them under `risk`.
162
295
 
163
- On restart, the engine loads persisted state and reconciles with broker positions.
296
+ ```js
297
+ const engine = new LiveEngine({
298
+ symbol: "AAPL",
299
+ interval: "1m",
300
+ broker,
301
+ signal,
302
+ riskPct: 0.5,
303
+ risk: {
304
+ maxDailyLossPct: 2,
305
+ maxDrawdownPct: 10,
306
+ maxPositions: 1,
307
+ maxDailyTrades: 4,
308
+ allowedWindows: "09:30-11:30,13:00-15:45",
309
+ },
310
+ });
311
+ ```
312
+
313
+ The risk manager can block new positions and emit warning or halt events. It does not silently change your signal logic.
164
314
 
165
- ## Broker notes
315
+ ## Broker Notes
166
316
 
167
- - Alpaca and Binance adapters support native paper modes.
168
- - Coinbase adapter is live API only; use `PaperEngine` for simulated Coinbase workflows.
169
- - Interactive Brokers adapter requires `@stoqey/ib` to be installed.
317
+ | Broker | Notes |
318
+ | -------------------------- | -------------------------------------------------------- |
319
+ | `PaperEngine` | Local simulation. Best first step for any new strategy |
320
+ | `AlpacaBroker` | Supports native paper mode through broker config |
321
+ | `BinanceBroker` | Supports exchange-style crypto workflows |
322
+ | `CoinbaseBroker` | Live API adapter. Use `PaperEngine` for local simulation |
323
+ | `InteractiveBrokersBroker` | Requires `@stoqey/ib` in the consuming application |
170
324
 
171
- For runtime compatibility and options, see [types/live.d.ts](../types/live.d.ts).
325
+ Real broker adapters require credentials and broker-specific account permissions. Start with paper mode, then use the smallest possible order sizes when switching to live credentials.
172
326
 
173
- ## Eventing and logs
327
+ ## Events
174
328
 
175
- `EventBus` emits lifecycle and execution events such as:
329
+ `EventBus` emits lifecycle, order, position, equity, and risk events:
176
330
 
177
- - `connected`, `shutdown`
331
+ - `connected`
332
+ - `shutdown`
178
333
  - `signal`
179
- - `order:submitted`, `order:filled`, `order:rejected`, `order:canceled`
180
- - `position:opened`, `position:closed`
334
+ - `order:submitted`
335
+ - `order:filled`
336
+ - `order:rejected`
337
+ - `order:canceled`
338
+ - `position:opened`
339
+ - `position:closed`
181
340
  - `equity:update`
182
- - `risk:warning`, `risk:halt`
341
+ - `risk:warning`
342
+ - `risk:halt`
343
+
344
+ Attach `LiveLogger` to write structured JSON event logs.
345
+
346
+ ```js
347
+ import { createEventBus, createLogger } from "tradelab/live";
348
+
349
+ const eventBus = createEventBus();
350
+ const logger = createLogger({ level: "info" });
351
+
352
+ logger.attach(eventBus);
353
+ ```
183
354
 
184
- Attach `LiveLogger` for structured JSON logs.
355
+ See [api-reference.md](api-reference.md#live-module-tradelablive) for the full live export list.
185
356
 
186
- <small>[Back to main page](README.md)</small>
357
+ <small>[Back to docs](README.md)</small>