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.
- package/CHANGELOG.md +46 -0
- package/README.md +185 -388
- package/dist/cjs/index.cjs +31 -9
- package/dist/cjs/live.cjs +409 -7
- package/docs/README.md +32 -66
- package/docs/api-reference.md +269 -144
- package/docs/backtest-engine.md +167 -321
- package/docs/data-reporting-cli.md +114 -156
- package/docs/examples.md +6 -6
- package/docs/live-trading.md +254 -134
- package/docs/mcp.md +244 -23
- package/docs/research.md +99 -45
- package/examples/mcpLiveTrading.js +77 -0
- package/package.json +11 -3
- package/src/engine/optimize.js +25 -1
- package/src/engine/portfolio.js +4 -1
- package/src/live/dashboard/server.js +67 -8
- package/src/live/engine/paperEngine.js +5 -0
- package/src/live/index.js +2 -0
- package/src/live/session.js +402 -0
- package/src/mcp/liveTools.js +179 -0
- package/src/mcp/schemas.js +119 -0
- package/src/mcp/server.js +5 -1
- package/src/mcp/tools.js +125 -2
- package/templates/dashboard.html +595 -108
- package/types/index.d.ts +25 -0
- package/types/live.d.ts +99 -0
- package/types/mcp.d.ts +17 -0
- package/docs/superpowers/plans/2026-00-overview.md +0 -101
- package/docs/superpowers/plans/2026-01-metrics-correctness.md +0 -873
- package/docs/superpowers/plans/2026-02-indicator-library.md +0 -677
- package/docs/superpowers/plans/2026-03-overfitting-toolkit.md +0 -882
- package/docs/superpowers/plans/2026-04-async-signals-seeding.md +0 -981
- package/docs/superpowers/plans/2026-05-mcp-server.md +0 -758
- package/docs/superpowers/plans/2026-06-parallel-param-sweep.md +0 -508
- package/docs/superpowers/plans/2026-07-funding-carry-costs.md +0 -535
- package/docs/superpowers/plans/2026-08-live-dashboard.md +0 -547
- 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
|
|
3
|
+
<small>[Back to docs](README.md)</small>
|
|
4
4
|
|
|
5
|
-
`tradelab-mcp` exposes
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
22
|
+
The server runs over stdio. It does not start an HTTP port.
|
|
17
23
|
|
|
18
|
-
##
|
|
24
|
+
## Install
|
|
19
25
|
|
|
20
|
-
|
|
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
|
-
|
|
28
|
+
```bash
|
|
29
|
+
npx -y tradelab tradelab-mcp
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or install globally:
|
|
27
33
|
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
```
|
|
54
211
|
|
|
55
|
-
|
|
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
|
-
|
|
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
|
|
285
|
+
<small>[Back to docs](README.md)</small>
|
package/docs/research.md
CHANGED
|
@@ -1,35 +1,32 @@
|
|
|
1
|
-
# Research
|
|
1
|
+
# Research checks
|
|
2
2
|
|
|
3
|
-
<small>[Back to
|
|
3
|
+
<small>[Back to docs](README.md)</small>
|
|
4
4
|
|
|
5
|
-
The `research` namespace contains
|
|
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
|
|
11
|
+
const tradePnls = result.positions.map((position) => position.exit.pnl);
|
|
12
12
|
|
|
13
|
-
const mc = research.monteCarlo({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
18
|
+
|
|
19
|
+
console.log(mc.finalEquity.p5);
|
|
25
20
|
```
|
|
26
21
|
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
47
|
+
Use `blockSize > 1` when you want to preserve short streaks in the realized trade sequence.
|
|
49
48
|
|
|
50
|
-
##
|
|
49
|
+
## Deflated Sharpe
|
|
51
50
|
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
|
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
|
-
##
|
|
89
|
+
## Probability Of Backtest Overfitting
|
|
78
90
|
|
|
79
|
-
|
|
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
|
-
|
|
95
|
+
|
|
96
|
+
const pbo = research.probabilityOfBacktestOverfitting(matrix, {
|
|
97
|
+
groups: 8,
|
|
98
|
+
});
|
|
84
99
|
```
|
|
85
100
|
|
|
86
|
-
|
|
101
|
+
Input shape:
|
|
87
102
|
|
|
88
|
-
|
|
103
|
+
| Dimension | Meaning |
|
|
104
|
+
| --------- | ----------------------------------- |
|
|
105
|
+
| Rows | Strategy variants or parameter sets |
|
|
106
|
+
| Columns | Comparable per-period returns |
|
|
89
107
|
|
|
90
|
-
|
|
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
|
|
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
|
|
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.
|
|
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",
|
|
@@ -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.
|
|
99
|
+
"esbuild": "^0.28.1",
|
|
92
100
|
"eslint": "^9.25.1",
|
|
93
101
|
"globals": "^15.15.0",
|
|
94
102
|
"prettier": "^3.5.3",
|
package/src/engine/optimize.js
CHANGED
|
@@ -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(
|
|
86
|
+
const worker = new Worker(workerUrl(), {
|
|
63
87
|
workerData: { candles, signalModulePath, interval, backtestOptions },
|
|
64
88
|
});
|
|
65
89
|
workers.push(worker);
|