tradelab 1.1.0 → 1.2.1

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 (39) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +183 -373
  3. package/dist/cjs/index.cjs +39 -12
  4. package/dist/cjs/live.cjs +457 -18
  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 +6 -2
  17. package/src/live/dashboard/server.js +67 -8
  18. package/src/live/engine/paperEngine.js +21 -11
  19. package/src/live/index.js +2 -0
  20. package/src/live/session.js +439 -0
  21. package/src/mcp/liveTools.js +202 -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/src/research/monteCarlo.js +6 -2
  26. package/templates/dashboard.html +595 -108
  27. package/types/index.d.ts +25 -0
  28. package/types/live.d.ts +102 -1
  29. package/types/mcp.d.ts +17 -0
  30. package/docs/superpowers/plans/2026-00-overview.md +0 -101
  31. package/docs/superpowers/plans/2026-01-metrics-correctness.md +0 -873
  32. package/docs/superpowers/plans/2026-02-indicator-library.md +0 -677
  33. package/docs/superpowers/plans/2026-03-overfitting-toolkit.md +0 -882
  34. package/docs/superpowers/plans/2026-04-async-signals-seeding.md +0 -981
  35. package/docs/superpowers/plans/2026-05-mcp-server.md +0 -758
  36. package/docs/superpowers/plans/2026-06-parallel-param-sweep.md +0 -508
  37. package/docs/superpowers/plans/2026-07-funding-carry-costs.md +0 -535
  38. package/docs/superpowers/plans/2026-08-live-dashboard.md +0 -547
  39. package/docs/superpowers/plans/HANDOFF.md +0 -88
package/docs/mcp.md CHANGED
@@ -1,31 +1,52 @@
1
1
  # MCP server
2
2
 
3
- <small>[Back to main page](README.md)</small>
3
+ <small>[Back to docs](README.md)</small>
4
4
 
5
- `tradelab-mcp` exposes the research loop to MCP-capable agents such as Claude Desktop, Cursor, and Claude Code.
5
+ `tradelab-mcp` exposes both a **research API** (backtest/strategy tools) and a **live trading API** (paper and live sessions) over the Model Context Protocol. Use it from MCP clients to research strategies, run paper trading loops, and optionally place real orders through a gated live mode.
6
6
 
7
- ## Tools
7
+ ## Safety
8
+
9
+ **Paper is the default and always safe.** Every session is paper unless you explicitly request live mode. Live mode requires all three gates simultaneously — if any is missing the call throws and nothing is created:
10
+
11
+ 1. Environment variable `TRADELAB_ALLOW_LIVE=true` must be set in the server process.
12
+ 2. The `create_session` call must include `confirmLive: true`.
13
+ 3. A broker with valid credentials must be resolvable (passed via `brokerFactory` in `SessionManager`).
14
+
15
+ Every session also enforces:
16
+
17
+ - `maxDailyLossPct` — if realized day PnL drops below this percentage of starting equity, all new `place_order` calls are rejected for the remainder of the day.
18
+ - `halt_all` — an emergency kill-switch tool that flattens all positions and stops all sessions in the server process.
8
19
 
9
- | Tool | Purpose |
10
- | ----------------- | ----------------------------------------------------------------------- |
11
- | `list_strategies` | List built-in strategies and their tunable parameters |
12
- | `fetch_candles` | Fetch Yahoo or CSV candles and return a compact first/last bar summary |
13
- | `run_backtest` | Run a named strategy with JSON params and return compact metrics |
14
- | `walk_forward` | Run a named strategy over a parameter grid and return stability metrics |
20
+ Brackets (stop + target) are true OCO: when one leg fills, the sibling is canceled automatically.
15
21
 
16
- Tool outputs are summaries for agent context, not full report payloads. `run_backtest` returns metrics and a small trade preview, but not replay frames.
22
+ The server runs over stdio. It does not start an HTTP port.
17
23
 
18
- ## Agent research loop
24
+ ## Install
19
25
 
20
- 1. Call `list_strategies` to inspect available strategy names and parameters.
21
- 2. Call `fetch_candles` or provide inline `candles`.
22
- 3. Call `run_backtest` with a strategy name and params.
23
- 4. Read `metrics`, especially trade count, profit factor, drawdown, and annualized Sharpe.
24
- 5. Call `walk_forward` with a parameter grid to check out-of-sample stability.
26
+ Use the published package:
25
27
 
26
- ## Claude Desktop config
28
+ ```bash
29
+ npx -y tradelab tradelab-mcp
30
+ ```
31
+
32
+ Or install globally:
27
33
 
28
- Use this with the published package:
34
+ ```bash
35
+ npm install -g tradelab
36
+ tradelab-mcp
37
+ ```
38
+
39
+ From a local checkout:
40
+
41
+ ```bash
42
+ npm install
43
+ npm run build
44
+ node bin/tradelab-mcp.js
45
+ ```
46
+
47
+ ## MCP Client Config
48
+
49
+ Claude Desktop example:
29
50
 
30
51
  ```json
31
52
  {
@@ -38,7 +59,7 @@ Use this with the published package:
38
59
  }
39
60
  ```
40
61
 
41
- After installing globally with `npm install -g tradelab`, you can use:
62
+ Global install example:
42
63
 
43
64
  ```json
44
65
  {
@@ -50,15 +71,215 @@ After installing globally with `npm install -g tradelab`, you can use:
50
71
  }
51
72
  ```
52
73
 
53
- ## Strategies
74
+ Local checkout example:
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "tradelab": {
80
+ "command": "node",
81
+ "args": ["/absolute/path/to/tradelab/bin/tradelab-mcp.js"]
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## Tools
88
+
89
+ ### Research tools
90
+
91
+ | Tool | Use it to |
92
+ | -------------------- | --------------------------------------------------------------------- |
93
+ | `list_strategies` | See built-in strategy names and tunable parameters |
94
+ | `fetch_candles` | Load Yahoo or CSV candles and return first/last bars |
95
+ | `run_backtest` | Run one named strategy and return compact metrics |
96
+ | `walk_forward` | Run a parameter grid through walk-forward validation |
97
+ | `analyze_robustness` | Backtest + Monte Carlo + Deflated Sharpe — validate before you trade |
98
+ | `optimize_strategy` | In-process grid sweep; returns a leaderboard sorted by chosen metric |
99
+ | `compare_strategies` | Run several named strategies on the same dataset, ranked head-to-head |
100
+ | `candle_stats` | Sanity-check candle data: count, date range, price range, interval |
101
+
102
+ Tool responses are intentionally compact. They are meant for planning and comparison, not for replacing full HTML/CSV/JSON reports from the CLI.
103
+
104
+ ### Live trading tools
105
+
106
+ | Tool | Args (required) | Returns |
107
+ | ----------------- | -------------------------------------------------------- | ------------------------------------ |
108
+ | `create_session` | `sessionId`, `symbol` | session status snapshot |
109
+ | `list_sessions` | — | array of session statuses |
110
+ | `session_status` | `sessionId` | full refresh (positions/orders/risk) |
111
+ | `feed_price` | `sessionId`, `bar` OR `price` | status after fills |
112
+ | `place_order` | `sessionId`, `side`, `type?`, `qty?` OR `riskPct`+`stop` | order receipt |
113
+ | `close_position` | `sessionId`, `symbol?` | order receipt |
114
+ | `flatten` | `sessionId` | `{ ok: true }` |
115
+ | `cancel_order` | `sessionId`, `orderId` | `{ ok: true }` |
116
+ | `account` | `sessionId` | broker account info |
117
+ | `positions` | `sessionId` | open positions |
118
+ | `recent_events` | `sessionId`, `limit?` | event log |
119
+ | `attach_strategy` | `sessionId`, `strategy`, `params?` | `{ ok: true }` |
120
+ | `halt_all` | — | `{ ok: true, sessionsHalted: N }` |
121
+
122
+ ## Agent trading loop
123
+
124
+ A typical autonomous paper-trading loop:
125
+
126
+ 1. Call `create_session` with `sessionId`, `symbol`, and `equity` (paper by default).
127
+ 2. Call `feed_price` with each new bar as it arrives — fills resting bracket orders automatically.
128
+ 3. Call `place_order` with `riskPct` + `stop` to size automatically; add `target` or `rr` for a bracket.
129
+ 4. Call `session_status` any time for a snapshot of positions, orders, equity, and risk state.
130
+ 5. Call `flatten` or `halt_all` to emergency-close everything.
131
+
132
+ If you attach a strategy with `attach_strategy`, `feed_price` will auto-evaluate it each bar and place orders when the session is flat. Attached strategies receive the same `{ candles, index, bar, equity, openPosition, pendingOrder }` context as `backtest()`, and returned order intents default to a market order unless `type` is set.
133
+
134
+ ## Typical Research Flow
135
+
136
+ 1. Call `list_strategies`.
137
+ 2. Choose a built-in strategy such as `ema-cross`, `rsi-reversion`, `donchian-breakout`, or `buy-hold`.
138
+ 3. Call `fetch_candles` for a quick data sanity check, or pass a `data` object directly to `run_backtest`.
139
+ 4. Call `run_backtest` with `strategy`, `params`, and either `candles` or `data`.
140
+ 5. Inspect trade count, profit factor, drawdown, return, and Sharpe fields.
141
+ 6. Call `walk_forward` with a grid to see whether parameters hold up out of sample.
142
+
143
+ ## Example Calls
144
+
145
+ Fetch candles:
146
+
147
+ ```json
148
+ {
149
+ "source": "yahoo",
150
+ "symbol": "SPY",
151
+ "interval": "1d",
152
+ "period": "1y",
153
+ "cache": true
154
+ }
155
+ ```
156
+
157
+ Run a backtest:
158
+
159
+ ```json
160
+ {
161
+ "data": {
162
+ "source": "yahoo",
163
+ "symbol": "SPY",
164
+ "interval": "1d",
165
+ "period": "2y",
166
+ "cache": true
167
+ },
168
+ "symbol": "SPY",
169
+ "interval": "1d",
170
+ "strategy": "ema-cross",
171
+ "params": {
172
+ "fast": 10,
173
+ "slow": 30,
174
+ "rr": 2
175
+ },
176
+ "backtestOptions": {
177
+ "warmupBars": 40,
178
+ "riskPct": 1,
179
+ "collectReplay": false
180
+ }
181
+ }
182
+ ```
183
+
184
+ Run walk-forward validation:
185
+
186
+ ```json
187
+ {
188
+ "data": {
189
+ "source": "yahoo",
190
+ "symbol": "QQQ",
191
+ "interval": "1d",
192
+ "period": "3y"
193
+ },
194
+ "interval": "1d",
195
+ "strategy": "ema-cross",
196
+ "trainBars": 180,
197
+ "testBars": 60,
198
+ "mode": "anchored",
199
+ "scoreBy": "profitFactor",
200
+ "grid": {
201
+ "fast": [8, 10, 12],
202
+ "slow": [30, 40, 50],
203
+ "rr": [1.5, 2, 3]
204
+ },
205
+ "backtestOptions": {
206
+ "warmupBars": 60,
207
+ "riskPct": 1
208
+ }
209
+ }
210
+ ```
54
211
 
55
- Agents cannot pass JavaScript closures over MCP, so strategies are name-addressable. Built-ins currently include:
212
+ ## Strategy Names
213
+
214
+ MCP calls cannot pass JavaScript functions, so strategies are selected by name.
215
+
216
+ Built-ins:
56
217
 
57
218
  - `ema-cross`
58
219
  - `rsi-reversion`
59
220
  - `donchian-breakout`
60
221
  - `buy-hold`
61
222
 
62
- Register custom strategies in application code with `registerStrategy(name, def)` from the main package. A strategy definition includes `description`, `params`, and a `factory(params)` function that returns a normal tradelab `signal(context)`.
223
+ In application code, register custom strategies with `registerStrategy(name, definition)`:
224
+
225
+ ```js
226
+ import { registerStrategy } from "tradelab";
227
+
228
+ registerStrategy("my-breakout", {
229
+ description: "Simple close-over-high breakout",
230
+ params: {
231
+ lookback: { type: "number", default: 20 },
232
+ rr: { type: "number", default: 2 },
233
+ },
234
+ factory(params) {
235
+ return ({ candles, bar }) => {
236
+ if (candles.length < params.lookback + 1) return null;
237
+
238
+ const recent = candles.slice(-params.lookback - 1, -1);
239
+ const high = Math.max(...recent.map((candle) => candle.high));
240
+
241
+ if (bar.close <= high) return null;
242
+
243
+ return {
244
+ side: "long",
245
+ entry: bar.close,
246
+ stop: Math.min(...recent.map((candle) => candle.low)),
247
+ rr: params.rr,
248
+ };
249
+ };
250
+ },
251
+ });
252
+ ```
253
+
254
+ The packaged `tradelab-mcp` server only knows strategies registered in the package process. For project-specific strategies, create a small wrapper server that imports your registrations before calling `createServer()` from `tradelab/mcp`.
255
+
256
+ ```js
257
+ // mcp-server.js
258
+ import "./strategies/register.js";
259
+ import { startStdioServer } from "tradelab/mcp";
260
+
261
+ await startStdioServer();
262
+ ```
263
+
264
+ ## Public Server API
265
+
266
+ ```js
267
+ import { createServer, startStdioServer } from "tradelab/mcp";
268
+ ```
269
+
270
+ | Export | Purpose |
271
+ | -------------------- | --------------------------------------------- |
272
+ | `createServer()` | Build an `McpServer` with tradelab tools |
273
+ | `startStdioServer()` | Create the server and connect stdio transport |
274
+
275
+ ## Troubleshooting
276
+
277
+ | Symptom | Check |
278
+ | ------------------------------- | ----------------------------------------------------------------------- |
279
+ | Client says server disconnected | The command must stay running and write protocol messages only to stdio |
280
+ | `npx` starts slowly | Install globally or point the client at a local checkout |
281
+ | Yahoo fetch fails | Try a shorter `period`, set `cache: false`, or use CSV data |
282
+ | No trades | Verify candle count, `warmupBars`, params, and stop placement |
283
+ | Custom strategy not found | Register it in the same Node process that starts the MCP server |
63
284
 
64
- <small>[Back to main page](README.md)</small>
285
+ <small>[Back to docs](README.md)</small>
package/docs/research.md CHANGED
@@ -1,35 +1,32 @@
1
- # Research & overfitting
1
+ # Research checks
2
2
 
3
- <small>[Back to main page](README.md)</small>
3
+ <small>[Back to docs](README.md)</small>
4
4
 
5
- The `research` namespace contains pure statistical helpers for checking whether a backtest is robust enough to take seriously.
5
+ The `research` namespace contains statistical checks for the part of trading research that usually fails quietly: too many trials, too few trades, unstable winners, and lucky trade order.
6
6
 
7
7
  ```js
8
8
  import { backtest, research } from "tradelab";
9
9
 
10
10
  const result = backtest({ candles, interval: "1d", signal });
11
- const pnls = result.positions.map((p) => p.exit.pnl);
11
+ const tradePnls = result.positions.map((position) => position.exit.pnl);
12
12
 
13
- const mc = research.monteCarlo({ tradePnls: pnls, equityStart: 10_000, seed: 1 });
14
- console.log("5% worst final equity:", mc.finalEquity.p5);
15
-
16
- const dsr = research.deflatedSharpe({
17
- sharpe: result.metrics.sharpeDaily,
18
- sampleSize: result.metrics.trades,
19
- numTrials: 20,
20
- sharpeStd: 0.5,
21
- skew: 0,
22
- kurtosis: 3,
13
+ const mc = research.monteCarlo({
14
+ tradePnls,
15
+ equityStart: 10_000,
16
+ seed: "spy-ema-v1",
23
17
  });
24
- console.log("Deflated Sharpe prob:", dsr);
18
+
19
+ console.log(mc.finalEquity.p5);
25
20
  ```
26
21
 
27
- ## `research.monteCarlo(options)`
22
+ Use these helpers after a backtest or parameter sweep. They do not fetch data or run the strategy for you.
23
+
24
+ ## Monte Carlo
28
25
 
29
- Seeded block-bootstrap of trade PnLs.
26
+ `research.monteCarlo(options)` bootstraps completed trade PnLs into many alternate equity paths.
30
27
 
31
28
  ```js
32
- research.monteCarlo({
29
+ const simulation = research.monteCarlo({
33
30
  tradePnls,
34
31
  equityStart: 10_000,
35
32
  iterations: 1000,
@@ -40,54 +37,79 @@ research.monteCarlo({
40
37
 
41
38
  Returns:
42
39
 
43
- - `finalEquity`: `{ p5, p25, p50, p75, p95 }`
44
- - `maxDrawdown`: `{ p5, p25, p50, p75, p95 }`
45
- - `pathBands`: per-trade-step `{ p5, p50, p95 }` equity bands
46
- - `probProfit`: fraction of simulations ending above starting equity
40
+ | Field | Meaning |
41
+ | ------------- | ------------------------------------------------------------- |
42
+ | `finalEquity` | Percentiles of final equity: `p5`, `p25`, `p50`, `p75`, `p95` |
43
+ | `maxDrawdown` | Percentiles of maximum drawdown across simulations |
44
+ | `pathBands` | Per-trade-step equity bands for charting |
45
+ | `probProfit` | Fraction of simulations ending above starting equity |
47
46
 
48
- Use `blockSize > 1` when you want to preserve short streaks in the resampled trade sequence.
47
+ Use `blockSize > 1` when you want to preserve short streaks in the realized trade sequence.
49
48
 
50
- ## `research.deflatedSharpe(options)`
49
+ ## Deflated Sharpe
51
50
 
52
- Returns a probability in `[0, 1]` that the observed Sharpe is real after accounting for finite sample size, non-normality, and multiple trials.
51
+ `research.deflatedSharpe(options)` estimates how convincing an observed Sharpe is after accounting for finite sample size, non-normal returns, and multiple trials.
53
52
 
54
53
  ```js
55
- research.deflatedSharpe({
56
- sharpe,
57
- sampleSize,
58
- numTrials,
59
- sharpeStd,
60
- skew,
61
- kurtosis,
54
+ const probability = research.deflatedSharpe({
55
+ sharpe: result.metrics.sharpeDaily,
56
+ sampleSize: result.metrics.trades,
57
+ numTrials: 20,
58
+ sharpeStd: 0.5,
59
+ skew: 0,
60
+ kurtosis: 3,
62
61
  });
63
62
  ```
64
63
 
65
- Below roughly `0.95`, treat the Sharpe as not convincingly significant.
64
+ Interpretation:
65
+
66
+ | Value | Practical read |
67
+ | --------------- | ------------------------------------------------ |
68
+ | `< 0.8` | Weak evidence. Treat the result as exploratory |
69
+ | `0.8` to `0.95` | Interesting, but not enough on its own |
70
+ | `> 0.95` | Stronger evidence, assuming inputs are realistic |
66
71
 
67
- ## `research.sweepHaircut(options)`
72
+ This is not a guarantee that a strategy will work live. It is a way to penalize easy-to-overfit research.
68
73
 
69
- Estimates the expected maximum Sharpe under the null when trying many strategy variants.
74
+ ## Sweep Haircut
75
+
76
+ `research.sweepHaircut(options)` estimates the Sharpe hurdle created by trying many variants.
70
77
 
71
78
  ```js
72
- research.sweepHaircut({ numTrials: 50, sharpeStd: 0.4 });
79
+ const haircut = research.sweepHaircut({
80
+ numTrials: 50,
81
+ sharpeStd: 0.4,
82
+ });
83
+
84
+ console.log(haircut.expectedMaxSharpe);
73
85
  ```
74
86
 
75
- Use `expectedMaxSharpe` as the multiple-testing hurdle your selected strategy should clear.
87
+ Use `expectedMaxSharpe` as a rough threshold: if your selected strategy barely clears what random searching could have produced, keep testing before trusting it.
76
88
 
77
- ## `research.probabilityOfBacktestOverfitting(matrix, options)`
89
+ ## Probability Of Backtest Overfitting
78
90
 
79
- CSCV estimate of Probability of Backtest Overfitting.
91
+ `research.probabilityOfBacktestOverfitting(matrix, options)` estimates PBO with combinatorially symmetric cross-validation.
80
92
 
81
93
  ```js
82
94
  const matrix = parameterSets.map((params) => returnsForParams(params));
83
- const pbo = research.probabilityOfBacktestOverfitting(matrix, { groups: 8 });
95
+
96
+ const pbo = research.probabilityOfBacktestOverfitting(matrix, {
97
+ groups: 8,
98
+ });
84
99
  ```
85
100
 
86
- Rows are strategy variants or parameter sets. Columns are per-period returns. `pbo > 0.5` means the selection process is likely overfit; lower is better.
101
+ Input shape:
87
102
 
88
- ## `research.combinatorialPurgedSplits(options)`
103
+ | Dimension | Meaning |
104
+ | --------- | ----------------------------------- |
105
+ | Rows | Strategy variants or parameter sets |
106
+ | Columns | Comparable per-period returns |
89
107
 
90
- Creates CPCV train/test index splits with optional embargo.
108
+ `pbo > 0.5` means the selection process is likely overfit. Lower is better.
109
+
110
+ ## Purged Splits
111
+
112
+ `research.combinatorialPurgedSplits(options)` creates train/test index splits with optional embargo.
91
113
 
92
114
  ```js
93
115
  const splits = research.combinatorialPurgedSplits({
@@ -98,6 +120,38 @@ const splits = research.combinatorialPurgedSplits({
98
120
  });
99
121
  ```
100
122
 
101
- Each split is `{ train, test, testGroups }`. Training observations near test blocks are purged by `embargo` observations to reduce leakage from overlapping or serially correlated samples.
123
+ Each split is:
124
+
125
+ ```js
126
+ {
127
+ train: [0, 1, 2],
128
+ test: [30, 31, 32],
129
+ testGroups: [2, 3]
130
+ }
131
+ ```
132
+
133
+ Use an embargo when labels, indicators, or trade outcomes overlap nearby observations. It keeps training rows too close to the test block out of the training set.
134
+
135
+ ## Low-Level Stats
136
+
137
+ These are exported for advanced research code:
138
+
139
+ | Export | Purpose |
140
+ | ---------------------------------- | ------------------------------ |
141
+ | `research.combinations(values, k)` | Generate k-combinations |
142
+ | `research.normalCdf(x)` | Standard normal CDF |
143
+ | `research.normalPpf(p)` | Standard normal inverse CDF |
144
+ | `research.moments(values)` | Mean, variance, skew, kurtosis |
145
+
146
+ ## A Practical Gate
147
+
148
+ For a strategy you might run live, combine several checks:
149
+
150
+ 1. Backtest on realistic costs and slippage.
151
+ 2. Run walk-forward validation with `walkForwardOptimize()`.
152
+ 3. Check parameter stability in `bestParamsSummary`.
153
+ 4. Run Monte Carlo on completed trade PnLs.
154
+ 5. Penalize multiple trials with deflated Sharpe or sweep haircut.
155
+ 6. Re-run on a later untouched data period before using live credentials.
102
156
 
103
- <small>[Back to main page](README.md)</small>
157
+ <small>[Back to docs](README.md)</small>
@@ -0,0 +1,77 @@
1
+ /**
2
+ * mcpLiveTrading.js — Programmatic demo of TradingSession (no network, paper only).
3
+ *
4
+ * Shows: create session, feed bars, place a bracket order (entry → stop + target OCO),
5
+ * simulate the target hitting, and print final status.
6
+ *
7
+ * node examples/mcpLiveTrading.js
8
+ */
9
+
10
+ import { PaperEngine } from "../src/live/engine/paperEngine.js";
11
+ import { TradingSession } from "../src/live/session.js";
12
+
13
+ function bar(time, price, { high = price, low = price } = {}) {
14
+ return { time, open: price, high, low, close: price, volume: 1000 };
15
+ }
16
+
17
+ async function main() {
18
+ // 1. Create a paper session backed by an in-process PaperEngine.
19
+ const broker = new PaperEngine({ equity: 10_000 });
20
+ const session = new TradingSession({
21
+ id: "demo",
22
+ symbol: "AAPL",
23
+ interval: "1m",
24
+ broker,
25
+ equity: 10_000,
26
+ maxDailyLossPct: 2, // halt trading if day loss exceeds 2%
27
+ });
28
+ await session.start();
29
+
30
+ console.log("Session started:", session.getStatus().id);
31
+
32
+ // 2. Feed the first price bar so PaperEngine knows the mark.
33
+ await session.pushBar(bar(1, 100));
34
+
35
+ // 3. Place a risk-sized bracket order: long with stop at 98 and target at 104.
36
+ // Risk: 1% of $10k = $100 risk, $2 per share risk → size = 50 shares.
37
+ const receipt = await session.placeOrder({
38
+ side: "long",
39
+ type: "market",
40
+ riskPct: 1,
41
+ stop: 98,
42
+ target: 104,
43
+ });
44
+ console.log(
45
+ "Entry filled:",
46
+ receipt.status,
47
+ "qty:",
48
+ receipt.filledQty,
49
+ "@",
50
+ receipt.avgFillPrice
51
+ );
52
+
53
+ let status = session.getStatus();
54
+ console.log("Open positions:", status.positions.length);
55
+ console.log("Resting bracket orders (stop + limit target):", status.openOrders.length);
56
+
57
+ // 4. Feed a bar that hits the target price (high = 104).
58
+ await session.pushBar(bar(2, 104, { high: 104 }));
59
+
60
+ // 5. Target filled, stop canceled (OCO). Position is closed.
61
+ status = session.getStatus();
62
+ console.log("\nAfter target bar:");
63
+ console.log(" positions:", status.positions.length, "(should be 0 — flat)");
64
+ console.log(" openOrders:", status.openOrders.length, "(should be 0 — stop canceled)");
65
+ console.log(" equity:", status.equity.toFixed(2), "(should be > $10,000)");
66
+ console.log(" dayPnl:", status.dayPnl.toFixed(2));
67
+ console.log(" risk.halted:", status.risk.halted, "(should be false)");
68
+
69
+ // 6. Shut down the session.
70
+ await session.stop();
71
+ console.log("\nSession stopped. Done.");
72
+ }
73
+
74
+ main().catch((err) => {
75
+ console.error(err);
76
+ process.exit(1);
77
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tradelab",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Backtesting toolkit for Node.js with strategy simulation, historical data loading, and report generation",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.cjs",
@@ -45,13 +45,21 @@
45
45
  "require": "./dist/cjs/ta.cjs"
46
46
  },
47
47
  "./mcp": {
48
+ "types": "./types/mcp.d.ts",
48
49
  "import": "./src/mcp/server.js"
49
50
  },
50
51
  "./package.json": "./package.json"
51
52
  },
52
53
  "files": [
53
54
  "bin",
54
- "docs",
55
+ "docs/README.md",
56
+ "docs/api-reference.md",
57
+ "docs/backtest-engine.md",
58
+ "docs/data-reporting-cli.md",
59
+ "docs/examples.md",
60
+ "docs/live-trading.md",
61
+ "docs/mcp.md",
62
+ "docs/research.md",
55
63
  "dist",
56
64
  "src",
57
65
  "types",
@@ -88,7 +96,7 @@
88
96
  "devDependencies": {
89
97
  "@eslint/js": "^9.25.1",
90
98
  "@types/node": "^22.15.2",
91
- "esbuild": "^0.27.3",
99
+ "esbuild": "^0.28.1",
92
100
  "eslint": "^9.25.1",
93
101
  "globals": "^15.15.0",
94
102
  "prettier": "^3.5.3",
@@ -1,5 +1,8 @@
1
1
  import { Worker } from "node:worker_threads";
2
2
  import os from "node:os";
3
+ import { existsSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
3
6
 
4
7
  function defaultConcurrency() {
5
8
  return Math.max(1, (os.cpus()?.length ?? 2) - 1);
@@ -10,6 +13,27 @@ function scoreValue(metrics, scoreBy) {
10
13
  return Number.isFinite(v) ? v : -Infinity;
11
14
  }
12
15
 
16
+ function callerModuleDir() {
17
+ const stack = new Error().stack || "";
18
+ const lines = stack.split("\n").slice(1);
19
+ const match = lines
20
+ .map((line) => line.match(/(?:\()?(file:\/\/\/[^\s)]+|\/[^\s)]+):\d+:\d+/))
21
+ .find(Boolean);
22
+ if (!match) return process.cwd();
23
+ const filePath = match[1].startsWith("file://") ? fileURLToPath(match[1]) : match[1];
24
+ return path.dirname(filePath);
25
+ }
26
+
27
+ function workerUrl() {
28
+ const here = callerModuleDir();
29
+ const candidates = [
30
+ path.join(here, "optimizeWorker.js"),
31
+ path.join(here, "..", "..", "src", "engine", "optimizeWorker.js"),
32
+ path.join(process.cwd(), "src", "engine", "optimizeWorker.js"),
33
+ ];
34
+ return pathToFileURL(candidates.find((candidate) => existsSync(candidate)) || candidates[0]);
35
+ }
36
+
13
37
  export function optimize({
14
38
  candles,
15
39
  signalModulePath,
@@ -59,7 +83,7 @@ export function optimize({
59
83
  };
60
84
 
61
85
  for (let i = 0; i < poolSize; i += 1) {
62
- const worker = new Worker(new URL("./optimizeWorker.js", import.meta.url), {
86
+ const worker = new Worker(workerUrl(), {
63
87
  workerData: { candles, signalModulePath, interval, backtestOptions },
64
88
  });
65
89
  workers.push(worker);