tradelab 1.1.0 → 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 (38) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +185 -388
  3. package/dist/cjs/index.cjs +31 -9
  4. package/dist/cjs/live.cjs +409 -7
  5. package/docs/README.md +32 -66
  6. package/docs/api-reference.md +269 -144
  7. package/docs/backtest-engine.md +167 -321
  8. package/docs/data-reporting-cli.md +114 -156
  9. package/docs/examples.md +6 -6
  10. package/docs/live-trading.md +254 -134
  11. package/docs/mcp.md +244 -23
  12. package/docs/research.md +99 -45
  13. package/examples/mcpLiveTrading.js +77 -0
  14. package/package.json +11 -3
  15. package/src/engine/optimize.js +25 -1
  16. package/src/engine/portfolio.js +4 -1
  17. package/src/live/dashboard/server.js +67 -8
  18. package/src/live/engine/paperEngine.js +5 -0
  19. package/src/live/index.js +2 -0
  20. package/src/live/session.js +402 -0
  21. package/src/mcp/liveTools.js +179 -0
  22. package/src/mcp/schemas.js +119 -0
  23. package/src/mcp/server.js +5 -1
  24. package/src/mcp/tools.js +125 -2
  25. package/templates/dashboard.html +595 -108
  26. package/types/index.d.ts +25 -0
  27. package/types/live.d.ts +99 -0
  28. package/types/mcp.d.ts +17 -0
  29. package/docs/superpowers/plans/2026-00-overview.md +0 -101
  30. package/docs/superpowers/plans/2026-01-metrics-correctness.md +0 -873
  31. package/docs/superpowers/plans/2026-02-indicator-library.md +0 -677
  32. package/docs/superpowers/plans/2026-03-overfitting-toolkit.md +0 -882
  33. package/docs/superpowers/plans/2026-04-async-signals-seeding.md +0 -981
  34. package/docs/superpowers/plans/2026-05-mcp-server.md +0 -758
  35. package/docs/superpowers/plans/2026-06-parallel-param-sweep.md +0 -508
  36. package/docs/superpowers/plans/2026-07-funding-carry-costs.md +0 -535
  37. package/docs/superpowers/plans/2026-08-live-dashboard.md +0 -547
  38. package/docs/superpowers/plans/HANDOFF.md +0 -88
@@ -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,43 +44,116 @@ 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:
57
+
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` |
66
+
67
+ Return `null` to do nothing. Return an order intent to open a trade:
68
+
69
+ ```js
70
+ return {
71
+ side: "long",
72
+ entry: bar.close,
73
+ stop: bar.close * 0.98,
74
+ rr: 2,
75
+ riskPct: 0.5,
76
+ };
77
+ ```
78
+
79
+ Useful fields:
80
+
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
101
+ ```
64
102
 
65
- - `signal()` is called with the same context shape as backtesting
66
- - `signal()` may be async; `LiveEngine` awaits the decision before normalizing it
67
- - market and limit/stop order lifecycles are tracked through broker events
68
- - state is persisted after fills, order updates, and equity updates
69
- - `getStatus()` returns runtime and risk state for health checks
103
+ Common options:
70
104
 
71
- Async/model-backed signals can use `LlmSignal` from the main package:
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` |
115
+
116
+ Strategy modules can export `default`, `createSignal(args)`, or `signal`:
72
117
 
73
118
  ```js
74
- import { LlmSignal } from "tradelab";
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
+ }
75
139
 
76
- const llm = new LlmSignal({
77
- budgetMs: 2000,
78
- onError: "skip",
79
- async resolve(context) {
80
- // Call a model or agent here.
81
140
  return null;
82
- },
83
- });
141
+ };
142
+ }
143
+ ```
84
144
 
85
- const engine = new LiveEngine({
86
- symbol: "AAPL",
87
- interval: "1m",
88
- broker,
89
- signal: llm.signal,
90
- });
145
+ ```bash
146
+ tradelab paper \
147
+ --strategy ./strategies/ema-signal.js \
148
+ --symbol AAPL \
149
+ --interval 1m \
150
+ --mode polling \
151
+ --once true
91
152
  ```
92
153
 
93
- `LlmSignal` caches one decision per bar, passes a no-lookahead candle view to `resolve()`, and records decisions in `llm.log`. Use `backtestAsync()` to test the same signal before running it live.
154
+ ## Run Multiple Systems
94
155
 
95
- ## `LiveOrchestrator` quick start
156
+ Use `LiveOrchestrator` when several systems share one account and broker.
96
157
 
97
158
  ```js
98
159
  import { LiveOrchestrator, PaperEngine, JsonFileStorage } from "tradelab/live";
@@ -102,136 +163,195 @@ const orchestrator = new LiveOrchestrator({
102
163
  storage: new JsonFileStorage({ baseDir: "./output/live-state" }),
103
164
  allocation: "weight",
104
165
  systems: [
105
- { id: "spy", symbol: "SPY", interval: "1m", weight: 2, signal: signalA },
106
- { id: "qqq", symbol: "QQQ", interval: "1m", weight: 1, signal: signalB },
166
+ { id: "spy", symbol: "SPY", interval: "1m", weight: 2, signal: spySignal },
167
+ { id: "qqq", symbol: "QQQ", interval: "1m", weight: 1, signal: qqqSignal },
107
168
  ],
108
169
  });
109
170
 
110
171
  await orchestrator.start();
111
- const status = orchestrator.getStatus();
172
+ console.log(orchestrator.getStatus());
112
173
  await orchestrator.stop();
113
174
  ```
114
175
 
115
- Use orchestrator when multiple systems should share one broker/account context.
176
+ CLI config:
116
177
 
117
- ## CLI live commands
178
+ ```json
179
+ {
180
+ "allocation": "weight",
181
+ "equity": 50000,
182
+ "systems": [
183
+ {
184
+ "id": "spy-system",
185
+ "symbol": "SPY",
186
+ "interval": "1m",
187
+ "strategy": "./strategies/spy.js",
188
+ "weight": 2
189
+ },
190
+ {
191
+ "id": "qqq-system",
192
+ "symbol": "QQQ",
193
+ "interval": "1m",
194
+ "strategy": "./strategies/qqq.js",
195
+ "weight": 1
196
+ }
197
+ ]
198
+ }
199
+ ```
118
200
 
119
- | Command | Purpose |
120
- | ----------------- | -------------------------------------------- |
121
- | `tradelab live` | Run live engine or orchestrator (`--config`) |
122
- | `tradelab paper` | Shortcut for `live` with paper broker mode |
123
- | `tradelab status` | Inspect persisted live state |
201
+ ```bash
202
+ tradelab live --config ./live-portfolio.json --paper --mode polling --once true
203
+ ```
124
204
 
125
- ## Live dashboard
205
+ ## Dashboard
126
206
 
127
- Use `createDashboardServer()` to watch a running `LiveEngine` or `LiveOrchestrator` locally. The dashboard serves a static page over `node:http`, streams live events with Server-Sent Events at `/events`, and reads current state from `/state`.
207
+ Start a local dashboard for an engine or orchestrator:
128
208
 
129
209
  ```js
130
210
  import { createDashboardServer } from "tradelab/live";
131
211
 
132
212
  const dashboard = createDashboardServer({ source: engine, port: 4317 });
133
213
  const url = await dashboard.start();
134
- console.log(`dashboard: ${url}`);
135
214
 
136
- // Later, during shutdown:
215
+ console.log(url);
216
+
217
+ // On shutdown:
137
218
  await dashboard.close();
138
219
  ```
139
220
 
140
- The page shows equity, day PnL, open position, risk state, and a recent event tail for signals, fills, position changes, equity updates, and risk halts. New browser clients receive a bounded replay of recent events so the page is useful immediately after opening.
221
+ The dashboard is a zero-dependency dark trading cockpit served from a single HTML file. It includes:
141
222
 
142
- The CLI can start the same dashboard for both single-engine and config/orchestrator runs:
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
143
230
 
144
- ```bash
145
- tradelab paper --symbol AAPL --interval 1m --mode polling --dashboard --dashboardPort 4317
146
- tradelab live --config ./live-portfolio.json --paper --dashboard --dashboardPort 4317
147
- ```
231
+ The dashboard exposes:
148
232
 
149
- The dashboard implementation is ESM-first. The CommonJS live bundle can be imported, but packaged dashboard usage should prefer `import { createDashboardServer } from "tradelab/live"`.
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) |
150
239
 
151
- ### Single-system paper run
240
+ ### `/command` endpoint
152
241
 
153
- ```bash
154
- tradelab paper \
155
- --id aapl-1m \
156
- --symbol AAPL \
157
- --interval 1m \
158
- --mode polling \
159
- --once true \
160
- --stateDir ./output/live-state
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
+ });
161
258
  ```
162
259
 
163
- ### Orchestrator run from config
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:
164
265
 
165
266
  ```bash
166
- tradelab live \
167
- --config ./live-portfolio.json \
168
- --paper \
169
- --mode polling \
170
- --once true \
171
- --stateDir ./output/live-state
267
+ tradelab paper --symbol AAPL --interval 1m --mode polling --dashboard --dashboardPort 4317
268
+ tradelab live --config ./live-portfolio.json --paper --dashboard --dashboardPort 4317
172
269
  ```
173
270
 
174
- Example config:
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.
175
272
 
176
- ```json
177
- {
178
- "allocation": "weight",
179
- "equity": 50000,
180
- "systems": [
181
- {
182
- "id": "spy-system",
183
- "symbol": "SPY",
184
- "interval": "1m",
185
- "strategy": "./strategies/spySignal.js",
186
- "weight": 2
187
- },
188
- {
189
- "id": "qqq-system",
190
- "symbol": "QQQ",
191
- "interval": "1m",
192
- "strategy": "./strategies/qqqSignal.js",
193
- "weight": 1
194
- }
195
- ]
196
- }
197
- ```
273
+ ## State And Recovery
198
274
 
199
- ### State inspection
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:
200
284
 
201
285
  ```bash
202
286
  tradelab status --dir ./output/live-state
203
287
  tradelab status --dir ./output/live-state --namespace spy-system
204
288
  ```
205
289
 
206
- ## 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.
207
291
 
208
- Live state is namespaced and persisted as:
292
+ ## Risk Controls
209
293
 
210
- - `state.json` (latest engine state)
211
- - `trades.jsonl` (append-only)
212
- - `equity.jsonl` (append-only)
294
+ Pass top-level risk options to `LiveEngine`, or group them under `risk`.
213
295
 
214
- 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
+ ```
215
312
 
216
- ## Broker notes
313
+ The risk manager can block new positions and emit warning or halt events. It does not silently change your signal logic.
217
314
 
218
- - Alpaca and Binance adapters support native paper modes.
219
- - Coinbase adapter is live API only; use `PaperEngine` for simulated Coinbase workflows.
220
- - Interactive Brokers adapter requires `@stoqey/ib` to be installed.
315
+ ## Broker Notes
221
316
 
222
- For runtime compatibility and options, see [types/live.d.ts](../types/live.d.ts).
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 |
223
324
 
224
- ## Eventing and logs
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.
225
326
 
226
- `EventBus` emits lifecycle and execution events such as:
327
+ ## Events
227
328
 
228
- - `connected`, `shutdown`
329
+ `EventBus` emits lifecycle, order, position, equity, and risk events:
330
+
331
+ - `connected`
332
+ - `shutdown`
229
333
  - `signal`
230
- - `order:submitted`, `order:filled`, `order:rejected`, `order:canceled`
231
- - `position:opened`, `position:closed`
334
+ - `order:submitted`
335
+ - `order:filled`
336
+ - `order:rejected`
337
+ - `order:canceled`
338
+ - `position:opened`
339
+ - `position:closed`
232
340
  - `equity:update`
233
- - `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
+ ```
234
354
 
235
- Attach `LiveLogger` for structured JSON logs.
355
+ See [api-reference.md](api-reference.md#live-module-tradelablive) for the full live export list.
236
356
 
237
- <small>[Back to main page](README.md)</small>
357
+ <small>[Back to docs](README.md)</small>