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
package/docs/mcp.md ADDED
@@ -0,0 +1,285 @@
1
+ # MCP server
2
+
3
+ <small>[Back to docs](README.md)</small>
4
+
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
+
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.
19
+
20
+ Brackets (stop + target) are true OCO: when one leg fills, the sibling is canceled automatically.
21
+
22
+ The server runs over stdio. It does not start an HTTP port.
23
+
24
+ ## Install
25
+
26
+ Use the published package:
27
+
28
+ ```bash
29
+ npx -y tradelab tradelab-mcp
30
+ ```
31
+
32
+ Or install globally:
33
+
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:
50
+
51
+ ```json
52
+ {
53
+ "mcpServers": {
54
+ "tradelab": {
55
+ "command": "npx",
56
+ "args": ["-y", "tradelab", "tradelab-mcp"]
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ Global install example:
63
+
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "tradelab": {
68
+ "command": "tradelab-mcp"
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
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.
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
+ ```
211
+
212
+ ## Strategy Names
213
+
214
+ MCP calls cannot pass JavaScript functions, so strategies are selected by name.
215
+
216
+ Built-ins:
217
+
218
+ - `ema-cross`
219
+ - `rsi-reversion`
220
+ - `donchian-breakout`
221
+ - `buy-hold`
222
+
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 |
284
+
285
+ <small>[Back to docs](README.md)</small>
@@ -0,0 +1,157 @@
1
+ # Research checks
2
+
3
+ <small>[Back to docs](README.md)</small>
4
+
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
+
7
+ ```js
8
+ import { backtest, research } from "tradelab";
9
+
10
+ const result = backtest({ candles, interval: "1d", signal });
11
+ const tradePnls = result.positions.map((position) => position.exit.pnl);
12
+
13
+ const mc = research.monteCarlo({
14
+ tradePnls,
15
+ equityStart: 10_000,
16
+ seed: "spy-ema-v1",
17
+ });
18
+
19
+ console.log(mc.finalEquity.p5);
20
+ ```
21
+
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
25
+
26
+ `research.monteCarlo(options)` bootstraps completed trade PnLs into many alternate equity paths.
27
+
28
+ ```js
29
+ const simulation = research.monteCarlo({
30
+ tradePnls,
31
+ equityStart: 10_000,
32
+ iterations: 1000,
33
+ blockSize: 1,
34
+ seed: "run-1",
35
+ });
36
+ ```
37
+
38
+ Returns:
39
+
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 |
46
+
47
+ Use `blockSize > 1` when you want to preserve short streaks in the realized trade sequence.
48
+
49
+ ## Deflated Sharpe
50
+
51
+ `research.deflatedSharpe(options)` estimates how convincing an observed Sharpe is after accounting for finite sample size, non-normal returns, and multiple trials.
52
+
53
+ ```js
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,
61
+ });
62
+ ```
63
+
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 |
71
+
72
+ This is not a guarantee that a strategy will work live. It is a way to penalize easy-to-overfit research.
73
+
74
+ ## Sweep Haircut
75
+
76
+ `research.sweepHaircut(options)` estimates the Sharpe hurdle created by trying many variants.
77
+
78
+ ```js
79
+ const haircut = research.sweepHaircut({
80
+ numTrials: 50,
81
+ sharpeStd: 0.4,
82
+ });
83
+
84
+ console.log(haircut.expectedMaxSharpe);
85
+ ```
86
+
87
+ Use `expectedMaxSharpe` as a rough threshold: if your selected strategy barely clears what random searching could have produced, keep testing before trusting it.
88
+
89
+ ## Probability Of Backtest Overfitting
90
+
91
+ `research.probabilityOfBacktestOverfitting(matrix, options)` estimates PBO with combinatorially symmetric cross-validation.
92
+
93
+ ```js
94
+ const matrix = parameterSets.map((params) => returnsForParams(params));
95
+
96
+ const pbo = research.probabilityOfBacktestOverfitting(matrix, {
97
+ groups: 8,
98
+ });
99
+ ```
100
+
101
+ Input shape:
102
+
103
+ | Dimension | Meaning |
104
+ | --------- | ----------------------------------- |
105
+ | Rows | Strategy variants or parameter sets |
106
+ | Columns | Comparable per-period returns |
107
+
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.
113
+
114
+ ```js
115
+ const splits = research.combinatorialPurgedSplits({
116
+ nObservations: candles.length,
117
+ nGroups: 6,
118
+ nTestGroups: 2,
119
+ embargo: 3,
120
+ });
121
+ ```
122
+
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.
156
+
157
+ <small>[Back to docs](README.md)</small>
@@ -0,0 +1,33 @@
1
+ // Run: node examples/liveDashboard.js
2
+ // Then open the printed URL.
3
+ import {
4
+ LiveEngine,
5
+ PaperEngine,
6
+ JsonFileStorage,
7
+ createDashboardServer,
8
+ } from "../src/live/index.js";
9
+
10
+ const engine = new LiveEngine({
11
+ id: "aapl-1m",
12
+ symbol: "AAPL",
13
+ interval: "1m",
14
+ mode: "polling",
15
+ broker: new PaperEngine({ equity: 25_000 }),
16
+ storage: new JsonFileStorage({ baseDir: "./output/live-state" }),
17
+ signal({ bar, openPosition }) {
18
+ if (openPosition) return null;
19
+ return { side: "long", stop: bar.close - 1, rr: 2 };
20
+ },
21
+ });
22
+
23
+ const dashboard = createDashboardServer({ source: engine });
24
+ const url = await dashboard.start();
25
+ console.log(`Dashboard running at ${url} - Ctrl+C to stop`);
26
+
27
+ await engine.start();
28
+
29
+ process.on("SIGINT", async () => {
30
+ await engine.stop();
31
+ await dashboard.close();
32
+ process.exit(0);
33
+ });
@@ -0,0 +1,33 @@
1
+ // Run: node examples/llmSignal.js
2
+ import { backtestAsync, LlmSignal, getHistoricalCandles } from "../src/index.js";
3
+
4
+ const candles = await getHistoricalCandles({
5
+ source: "yahoo",
6
+ symbol: "SPY",
7
+ interval: "1d",
8
+ period: "1y",
9
+ cache: true,
10
+ });
11
+
12
+ // Stand-in for a real model call. Replace `resolve` with an LLM/agent request.
13
+ const llm = new LlmSignal({
14
+ budgetMs: 2000,
15
+ onError: "skip",
16
+ async resolve({ candles: history, bar }) {
17
+ const closes = history.map((c) => c.close);
18
+ const recent = closes.slice(-5);
19
+ const rising = recent.every((c, i) => i === 0 || c >= recent[i - 1]);
20
+ return rising ? { side: "long", stop: bar.close * 0.98, rr: 2 } : null;
21
+ },
22
+ });
23
+
24
+ const result = await backtestAsync({
25
+ candles,
26
+ symbol: "SPY",
27
+ interval: "1d",
28
+ signal: llm.signal,
29
+ signalBudgetMs: 3000,
30
+ });
31
+
32
+ console.log("trades:", result.metrics.trades, "PnL:", result.metrics.totalPnL.toFixed(2));
33
+ console.log("decisions logged:", llm.log.length);
@@ -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
+ });
@@ -0,0 +1,25 @@
1
+ // Run: node examples/optimize.js
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { optimize, grid, getHistoricalCandles } from "../src/index.js";
5
+
6
+ const here = path.dirname(fileURLToPath(import.meta.url));
7
+
8
+ const candles = await getHistoricalCandles({
9
+ source: "yahoo",
10
+ symbol: "SPY",
11
+ interval: "1d",
12
+ period: "2y",
13
+ cache: true,
14
+ });
15
+
16
+ const { leaderboard, best } = await optimize({
17
+ candles,
18
+ interval: "1d",
19
+ signalModulePath: path.join(here, "..", "test", "fixtures", "emaSignal.js"),
20
+ parameterSets: grid({ fast: [5, 8, 10, 12], slow: [20, 30, 50] }),
21
+ scoreBy: "sharpeAnnualized",
22
+ });
23
+
24
+ console.log("best params:", best?.params, "sharpe:", best?.metrics.sharpeAnnualized);
25
+ console.table(leaderboard.slice(0, 5).map((r) => ({ ...r.params, ...r.metrics })));
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "tradelab",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
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",
7
7
  "module": "./src/index.js",
8
8
  "types": "./types/index.d.ts",
9
9
  "bin": {
10
- "tradelab": "bin/tradelab.js"
10
+ "tradelab": "bin/tradelab.js",
11
+ "tradelab-mcp": "bin/tradelab-mcp.js"
11
12
  },
12
13
  "license": "MIT",
13
14
  "repository": {
@@ -38,17 +39,34 @@
38
39
  "import": "./src/live/index.js",
39
40
  "require": "./dist/cjs/live.cjs"
40
41
  },
42
+ "./ta": {
43
+ "types": "./types/ta.d.ts",
44
+ "import": "./src/ta/index.js",
45
+ "require": "./dist/cjs/ta.cjs"
46
+ },
47
+ "./mcp": {
48
+ "types": "./types/mcp.d.ts",
49
+ "import": "./src/mcp/server.js"
50
+ },
41
51
  "./package.json": "./package.json"
42
52
  },
43
53
  "files": [
44
54
  "bin",
45
- "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",
46
63
  "dist",
47
64
  "src",
48
65
  "types",
49
66
  "templates",
50
67
  "examples/*.js",
51
68
  "README.md",
69
+ "CHANGELOG.md",
52
70
  "LICENSE"
53
71
  ],
54
72
  "scripts": {
@@ -78,10 +96,14 @@
78
96
  "devDependencies": {
79
97
  "@eslint/js": "^9.25.1",
80
98
  "@types/node": "^22.15.2",
81
- "esbuild": "^0.27.3",
99
+ "esbuild": "^0.28.1",
82
100
  "eslint": "^9.25.1",
83
101
  "globals": "^15.15.0",
84
102
  "prettier": "^3.5.3",
85
103
  "typescript": "^5.8.3"
104
+ },
105
+ "dependencies": {
106
+ "@modelcontextprotocol/sdk": "^1.29.0",
107
+ "zod": "^4.4.3"
86
108
  }
87
109
  }