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,535 +0,0 @@
1
- # Funding, Borrow & Overnight Carry Costs Implementation Plan
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** Model the time-based costs the current engine ignores — overnight financing/borrow (annualized carry) and perpetual-futures funding — so leveraged longs and shorts don't show phantom edge.
6
-
7
- **Architecture:** Per-fill costs (slippage/spread/commission) already live in `applyFill`. Carry is _time_-based, not fill-based, so add a pure `financingCost({ side, notional, fromMs, toMs, costs })` to `execution.js` and deduct it inside each engine's leg-close accounting (`backtest.js`, `barSystemRunner.js`, `backtestTicks.js`). Each closed leg pays carry on its own quantity for its own hold duration. Config lives under `costs.carry` (annualized bps) and `costs.funding` (per-interval bps).
8
-
9
- **Tech Stack:** Node ESM, `node:test`. No new dependencies.
10
-
11
- ---
12
-
13
- ### Task 1: `financingCost` + funding-event counting
14
-
15
- **Files:**
16
-
17
- - Modify: `src/engine/execution.js`
18
- - Test: `test/engine/financing.test.js`
19
-
20
- - [ ] **Step 1: Write the failing test**
21
-
22
- ```js
23
- // test/engine/financing.test.js
24
- import test from "node:test";
25
- import assert from "node:assert/strict";
26
- import { financingCost, fundingEvents } from "../../src/engine/execution.js";
27
-
28
- const YEAR = 365 * 24 * 60 * 60 * 1000;
29
-
30
- test("carry: a long held one year at 5% annual on 10k notional costs 500", () => {
31
- const cost = financingCost({
32
- side: "long",
33
- notional: 10_000,
34
- fromMs: 0,
35
- toMs: YEAR,
36
- costs: { carry: { longAnnualBps: 500, shortAnnualBps: 800 } },
37
- });
38
- assert.ok(Math.abs(cost - 500) < 1e-6);
39
- });
40
-
41
- test("carry: a short uses shortAnnualBps", () => {
42
- const cost = financingCost({
43
- side: "short",
44
- notional: 10_000,
45
- fromMs: 0,
46
- toMs: YEAR / 2,
47
- costs: { carry: { longAnnualBps: 500, shortAnnualBps: 800 } },
48
- });
49
- assert.ok(Math.abs(cost - 400) < 1e-6); // 8% * 0.5yr * 10k
50
- });
51
-
52
- test("fundingEvents counts boundaries strictly after from and up to to", () => {
53
- const h8 = 8 * 60 * 60 * 1000;
54
- // anchor 0, interval 8h: boundaries at 8h,16h,24h within (0, 24h] => 3
55
- assert.equal(fundingEvents(0, 24 * 60 * 60 * 1000, h8, 0), 3);
56
- assert.equal(fundingEvents(0, h8 - 1, h8, 0), 0);
57
- assert.equal(fundingEvents(h8, 24 * 60 * 60 * 1000, h8, 0), 2);
58
- });
59
-
60
- test("funding: a long pays when rate is positive, a short receives", () => {
61
- const h8 = 8 * 60 * 60 * 1000;
62
- const base = { funding: { rateBps: 10, intervalMs: h8, anchorMs: 0 } };
63
- const longCost = financingCost({
64
- side: "long",
65
- notional: 10_000,
66
- fromMs: 0,
67
- toMs: 24 * 60 * 60 * 1000,
68
- costs: base,
69
- });
70
- const shortCost = financingCost({
71
- side: "short",
72
- notional: 10_000,
73
- fromMs: 0,
74
- toMs: 24 * 60 * 60 * 1000,
75
- costs: base,
76
- });
77
- assert.ok(Math.abs(longCost - 30) < 1e-6); // 3 events * 10bps * 10k = 30
78
- assert.ok(Math.abs(shortCost + 30) < 1e-6); // short receives funding
79
- });
80
-
81
- test("no carry/funding config => zero cost", () => {
82
- assert.equal(
83
- financingCost({ side: "long", notional: 10_000, fromMs: 0, toMs: YEAR, costs: {} }),
84
- 0
85
- );
86
- assert.equal(
87
- financingCost({ side: "long", notional: 10_000, fromMs: 0, toMs: YEAR, costs: null }),
88
- 0
89
- );
90
- });
91
- ```
92
-
93
- - [ ] **Step 2: Run to verify it fails**
94
-
95
- Run: `node --test test/engine/financing.test.js`
96
- Expected: FAIL — `financingCost`/`fundingEvents` not exported.
97
-
98
- - [ ] **Step 3: Add to src/engine/execution.js**
99
-
100
- Append to `src/engine/execution.js`:
101
-
102
- ```js
103
- const MS_PER_YEAR = 365 * 24 * 60 * 60 * 1000;
104
-
105
- /**
106
- * Count funding boundaries in the half-open interval (fromMs, toMs], given a
107
- * funding `intervalMs` cadence anchored at `anchorMs`.
108
- */
109
- export function fundingEvents(fromMs, toMs, intervalMs, anchorMs = 0) {
110
- if (!(intervalMs > 0) || toMs <= fromMs) return 0;
111
- const firstK = Math.floor((fromMs - anchorMs) / intervalMs) + 1;
112
- const lastK = Math.floor((toMs - anchorMs) / intervalMs);
113
- return Math.max(0, lastK - firstK + 1);
114
- }
115
-
116
- /**
117
- * Time-based financing cost for holding `notional` from `fromMs` to `toMs`.
118
- * Positive return = cost to the position (subtract from PnL).
119
- *
120
- * costs.carry = { longAnnualBps, shortAnnualBps } annualized borrow/margin
121
- * costs.funding = { rateBps, intervalMs, anchorMs } perp funding per interval;
122
- * longs pay when rateBps > 0, shorts receive (and vice versa).
123
- */
124
- export function financingCost({ side, notional, fromMs, toMs, costs }) {
125
- const model = costs || {};
126
- const absNotional = Math.abs(notional);
127
- let cost = 0;
128
-
129
- if (model.carry) {
130
- const annualBps =
131
- side === "long" ? (model.carry.longAnnualBps ?? 0) : (model.carry.shortAnnualBps ?? 0);
132
- const years = Math.max(0, toMs - fromMs) / MS_PER_YEAR;
133
- cost += absNotional * (annualBps / 10000) * years;
134
- }
135
-
136
- const funding = model.funding;
137
- if (funding && funding.intervalMs > 0 && Number.isFinite(funding.rateBps)) {
138
- const count = fundingEvents(fromMs, toMs, funding.intervalMs, funding.anchorMs ?? 0);
139
- const perEvent = absNotional * (funding.rateBps / 10000);
140
- cost += (side === "long" ? 1 : -1) * perEvent * count;
141
- }
142
-
143
- return cost;
144
- }
145
- ```
146
-
147
- - [ ] **Step 4: Run to verify it passes**
148
-
149
- Run: `node --test test/engine/financing.test.js`
150
- Expected: PASS (5 tests).
151
-
152
- - [ ] **Step 5: Commit**
153
-
154
- ```bash
155
- git add src/engine/execution.js test/engine/financing.test.js
156
- git commit -m "feat: add financingCost (carry + funding) helper
157
-
158
- Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
159
- ```
160
-
161
- ---
162
-
163
- ### Task 2: Deduct financing in the candle engine
164
-
165
- **Files:**
166
-
167
- - Modify: `src/engine/backtest.js`
168
- - Test: `test/financingIntegration.test.js`
169
-
170
- - [ ] **Step 1: Write the failing test**
171
-
172
- ```js
173
- // test/financingIntegration.test.js
174
- import test from "node:test";
175
- import assert from "node:assert/strict";
176
- import { backtest } from "../src/index.js";
177
-
178
- // Daily candles, flat price so gross PnL is ~0; carry should make the long lose.
179
- function flatCandles(n = 30) {
180
- const start = Date.UTC(2025, 0, 2, 14, 30, 0);
181
- return Array.from({ length: n }, (_, i) => ({
182
- time: start + i * 86_400_000,
183
- open: 100,
184
- high: 100.5,
185
- low: 99.5,
186
- close: 100,
187
- volume: 1000,
188
- }));
189
- }
190
-
191
- test("overnight carry reduces a long's realized PnL vs no-carry", () => {
192
- const candles = flatCandles();
193
- const opts = {
194
- candles,
195
- interval: "1d",
196
- warmupBars: 1,
197
- flattenAtClose: false,
198
- scaleOutAtR: 0,
199
- signal({ index, bar, openPosition }) {
200
- if (openPosition || index !== 1) return null;
201
- return { side: "long", entry: bar.close, stop: bar.close - 2, rr: 50, _maxBarsInTrade: 20 };
202
- },
203
- };
204
- const noCarry = backtest(opts);
205
- const withCarry = backtest({
206
- ...opts,
207
- costs: { carry: { longAnnualBps: 1000, shortAnnualBps: 1000 } },
208
- });
209
- assert.ok(withCarry.metrics.totalPnL < noCarry.metrics.totalPnL);
210
- // the closed leg records its financing
211
- const leg = withCarry.positions[0];
212
- assert.ok(leg.exit.financing > 0);
213
- });
214
- ```
215
-
216
- - [ ] **Step 2: Run to verify it fails**
217
-
218
- Run: `node --test test/financingIntegration.test.js`
219
- Expected: FAIL — PnL unchanged (financing not deducted) and `leg.exit.financing`
220
- is undefined.
221
-
222
- - [ ] **Step 3: Import financingCost in backtest.js**
223
-
224
- In `src/engine/backtest.js`, the existing import from `./execution.js` lists
225
- `applyFill, clampStop, ...`. Add `financingCost` to that import list:
226
-
227
- ```js
228
- import {
229
- applyFill,
230
- clampStop,
231
- touchedLimit,
232
- ocoExitCheck,
233
- isEODBar,
234
- roundStep,
235
- estimateBarMs,
236
- dayKeyUTC,
237
- dayKeyET,
238
- financingCost,
239
- } from "./execution.js";
240
- ```
241
-
242
- - [ ] **Step 4: Deduct financing inside closeLeg**
243
-
244
- In `backtest.js`'s `closeLeg`, find:
245
-
246
- ```js
247
- const grossPnl = (exitPx - entryFill) * direction * qty;
248
- const entryFeePortion = (openPos.entryFeeTotal || 0) * (qty / openPos.initSize);
249
- const pnl = grossPnl - entryFeePortion - exitFeeTotal;
250
- ```
251
-
252
- Replace with:
253
-
254
- ```js
255
- const grossPnl = (exitPx - entryFill) * direction * qty;
256
- const entryFeePortion = (openPos.entryFeeTotal || 0) * (qty / openPos.initSize);
257
- const financing = financingCost({
258
- side: openPos.side,
259
- notional: entryFill * qty,
260
- fromMs: openPos.openTime,
261
- toMs: time,
262
- costs,
263
- });
264
- const pnl = grossPnl - entryFeePortion - exitFeeTotal - financing;
265
- ```
266
-
267
- - [ ] **Step 5: Record financing on the leg**
268
-
269
- In the same `closeLeg`, find the `record` object's `exit` block:
270
-
271
- ```js
272
- exit: {
273
- price: exitPx,
274
- time,
275
- reason,
276
- pnl,
277
- exitATR: openPos._lastATR ?? undefined,
278
- },
279
- ```
280
-
281
- Replace with:
282
-
283
- ```js
284
- exit: {
285
- price: exitPx,
286
- time,
287
- reason,
288
- pnl,
289
- financing,
290
- exitATR: openPos._lastATR ?? undefined,
291
- },
292
- ```
293
-
294
- - [ ] **Step 6: Run test + suite**
295
-
296
- Run: `node --test test/financingIntegration.test.js`
297
- Expected: PASS.
298
-
299
- Run: `node --test`
300
- Expected: PASS — default `costs` (no carry/funding) yields `financing === 0`, so
301
- all existing tests are unaffected.
302
-
303
- - [ ] **Step 7: Commit**
304
-
305
- ```bash
306
- git add src/engine/backtest.js test/financingIntegration.test.js
307
- git commit -m "feat: deduct overnight/funding carry in candle backtest
308
-
309
- Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
310
- ```
311
-
312
- ---
313
-
314
- ### Task 3: Same deduction in portfolio runner + tick engine
315
-
316
- **Files:**
317
-
318
- - Modify: `src/engine/barSystemRunner.js`
319
- - Modify: `src/engine/backtestTicks.js`
320
- - Test: `test/financingIntegration.test.js` (append portfolio + tick cases)
321
-
322
- - [ ] **Step 1: Append the failing tests**
323
-
324
- ```js
325
- // append to test/financingIntegration.test.js
326
- import { backtestPortfolio, backtestTicks } from "../src/index.js";
327
-
328
- test("portfolio runner deducts carry", () => {
329
- const start = Date.UTC(2025, 0, 2, 14, 30, 0);
330
- const candles = Array.from({ length: 30 }, (_, i) => ({
331
- time: start + i * 86_400_000,
332
- open: 100,
333
- high: 100.5,
334
- low: 99.5,
335
- close: 100,
336
- volume: 1000,
337
- }));
338
- const signal = ({ index, bar, openPosition }) =>
339
- !openPosition && index === 1
340
- ? { side: "long", entry: bar.close, stop: bar.close - 2, rr: 50, _maxBarsInTrade: 20 }
341
- : null;
342
- const base = { equity: 10_000, systems: [{ symbol: "X", candles, signal }] };
343
- const noCarry = backtestPortfolio({ ...base });
344
- const withCarry = backtestPortfolio({
345
- equity: 10_000,
346
- costs: { carry: { longAnnualBps: 1000, shortAnnualBps: 1000 } },
347
- systems: [
348
- {
349
- symbol: "X",
350
- candles,
351
- signal,
352
- costs: { carry: { longAnnualBps: 1000, shortAnnualBps: 1000 } },
353
- },
354
- ],
355
- });
356
- assert.ok(withCarry.metrics.totalPnL <= noCarry.metrics.totalPnL);
357
- });
358
-
359
- test("tick engine deducts carry", () => {
360
- const start = Date.UTC(2025, 0, 2, 14, 30, 0);
361
- const ticks = Array.from({ length: 500 }, (_, i) => ({
362
- time: start + i * 60_000,
363
- bid: 100,
364
- ask: 100.02,
365
- }));
366
- const signal = ({ index, bar, openPosition }) =>
367
- !openPosition && index === 1
368
- ? { side: "long", entry: bar.close, stop: bar.close - 0.5, rr: 100 }
369
- : null;
370
- const withCarry = backtestTicks({
371
- ticks,
372
- signal,
373
- costs: { carry: { longAnnualBps: 5000, shortAnnualBps: 5000 } },
374
- });
375
- assert.equal(typeof withCarry.metrics.totalPnL, "number");
376
- if (withCarry.positions.length) assert.ok(withCarry.positions[0].exit.financing >= 0);
377
- });
378
- ```
379
-
380
- Note: `backtestPortfolio` must thread `system.costs` into each runner's options.
381
- Verify it already does (the runner reads `this.options.costs`). If portfolio does
382
- not forward per-system `costs`, add that mapping where it constructs each
383
- `BarSystemRunner`.
384
-
385
- - [ ] **Step 2: Run to verify it fails**
386
-
387
- Run: `node --test test/financingIntegration.test.js`
388
- Expected: FAIL — `financing` not deducted in runner/tick engines.
389
-
390
- - [ ] **Step 3: Edit barSystemRunner.js closeLeg**
391
-
392
- Add `financingCost` to the `./execution.js` import list in
393
- `src/engine/barSystemRunner.js`, then in its `closeLeg` find:
394
-
395
- ```js
396
- const grossPnl = (exitPx - entryFill) * direction * qty;
397
- const entryFeePortion = (openPos.entryFeeTotal || 0) * (qty / openPos.initSize);
398
- const pnl = grossPnl - entryFeePortion - exitFeeTotal;
399
- ```
400
-
401
- Replace with:
402
-
403
- ```js
404
- const grossPnl = (exitPx - entryFill) * direction * qty;
405
- const entryFeePortion = (openPos.entryFeeTotal || 0) * (qty / openPos.initSize);
406
- const financing = financingCost({
407
- side: openPos.side,
408
- notional: entryFill * qty,
409
- fromMs: openPos.openTime,
410
- toMs: time,
411
- costs: this.options.costs,
412
- });
413
- const pnl = grossPnl - entryFeePortion - exitFeeTotal - financing;
414
- ```
415
-
416
- And add `financing,` to that method's `record.exit` object (same edit shape as
417
- Task 2 Step 5).
418
-
419
- - [ ] **Step 4: Edit backtestTicks.js closePosition**
420
-
421
- Add `financingCost` to the `./execution.js` import in `src/engine/backtestTicks.js`,
422
- then in `closePosition` find:
423
-
424
- ```js
425
- const grossPnl = (price - open.entryFill) * direction * open.size;
426
- const pnl = grossPnl - (open.entryFeeTotal || 0) - feeTotal;
427
- ```
428
-
429
- Replace with:
430
-
431
- ```js
432
- const grossPnl = (price - open.entryFill) * direction * open.size;
433
- const financing = financingCost({
434
- side: open.side,
435
- notional: open.entryFill * open.size,
436
- fromMs: open.openTime,
437
- toMs: tick.time,
438
- costs,
439
- });
440
- const pnl = grossPnl - (open.entryFeeTotal || 0) - feeTotal - financing;
441
- ```
442
-
443
- Then in the `trade` object's `exit` block in the same function, add `financing,`:
444
-
445
- ```js
446
- exit: {
447
- price,
448
- time: tick.time,
449
- reason,
450
- pnl,
451
- financing,
452
- },
453
- ```
454
-
455
- - [ ] **Step 5: Run test + full suite**
456
-
457
- Run: `node --test test/financingIntegration.test.js`
458
- Expected: PASS.
459
-
460
- Run: `node --test`
461
- Expected: PASS.
462
-
463
- - [ ] **Step 6: Lint + commit**
464
-
465
- ```bash
466
- npm run lint
467
- git add src/engine/barSystemRunner.js src/engine/backtestTicks.js test/financingIntegration.test.js
468
- git commit -m "feat: deduct carry in portfolio and tick engines
469
-
470
- Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
471
- ```
472
-
473
- ---
474
-
475
- ### Task 4: Docs
476
-
477
- **Files:**
478
-
479
- - Modify: `docs/backtest-engine.md` and `README.md`
480
-
481
- - [ ] **Step 1: Document the cost-model additions**
482
-
483
- In the execution/cost-modeling section, add:
484
-
485
- ````markdown
486
- ### Financing & funding (time-based costs)
487
-
488
- In addition to per-fill slippage/spread/commission, the cost model can charge
489
- the cost of _holding_ a position:
490
-
491
- ```js
492
- costs: {
493
- carry: {
494
- longAnnualBps: 600, // margin/borrow on a long, annualized bps
495
- shortAnnualBps: 300, // borrow on a short, annualized bps
496
- },
497
- funding: {
498
- rateBps: 1, // per-interval perp funding (bps of notional)
499
- intervalMs: 8 * 60 * 60e3, // 8h funding cadence
500
- anchorMs: 0, // funding timestamp alignment
501
- },
502
- }
503
- ```
504
-
505
- - **Carry** accrues continuously: `notional * (annualBps/10000) * yearsHeld`.
506
- - **Funding** charges at each interval boundary crossed while the position is
507
- open. Longs pay when `rateBps > 0`; shorts receive (and vice versa).
508
- - Each closed leg pays carry on its own quantity for its own hold time, and the
509
- amount is reported on `position.exit.financing`.
510
- ````
511
-
512
- - [ ] **Step 2: Update the README costs note**
513
-
514
- Under "Execution and cost modeling", add a one-line mention of `costs.carry` and
515
- `costs.funding` for perps/shorts, pointing to the docs section above.
516
-
517
- - [ ] **Step 3: Format, test, commit**
518
-
519
- ```bash
520
- npm run format:check && npm test
521
- git add docs/backtest-engine.md README.md
522
- git commit -m "docs: document carry and funding cost model
523
-
524
- Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"
525
- ```
526
-
527
- ---
528
-
529
- ## Self-review checklist
530
-
531
- - [ ] Default behavior unchanged: with no `costs.carry`/`costs.funding`, `financingCost` returns 0 and every existing test stays green. ✔ (Task 1 Step 1 last case)
532
- - [ ] Carry charged per leg on the leg's own qty and hold window (correct under scale-outs). ✔ (Tasks 2, 3)
533
- - [ ] Funding sign is correct: long pays positive rate, short receives. ✔ (Task 1 Step 1)
534
- - [ ] `position.exit.financing` is surfaced in all three engines for transparency. ✔
535
- - [ ] Portfolio forwards per-system `costs` to each runner (verified in Task 3 Step 1 note). ✔