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
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/ta/index.js
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ atr: () => atr,
24
+ bollinger: () => bollinger,
25
+ detectFVG: () => detectFVG,
26
+ donchian: () => donchian,
27
+ ema: () => ema,
28
+ keltner: () => keltner,
29
+ lastSwing: () => lastSwing,
30
+ macd: () => macd,
31
+ rsi: () => rsi,
32
+ stochastic: () => stochastic,
33
+ structureState: () => structureState,
34
+ supertrend: () => supertrend,
35
+ swingHigh: () => swingHigh,
36
+ swingLow: () => swingLow,
37
+ vwap: () => vwap
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/utils/indicators.js
42
+ function ema(values, period = 14) {
43
+ if (!values?.length) return [];
44
+ const lookback = Math.max(1, period | 0);
45
+ const output = new Array(values.length);
46
+ let warmupSum = 0;
47
+ for (let index = 0; index < values.length; index += 1) {
48
+ const value = values[index];
49
+ if (!Number.isFinite(value)) {
50
+ output[index] = index === 0 ? 0 : output[index - 1];
51
+ continue;
52
+ }
53
+ if (index < lookback) {
54
+ warmupSum += value;
55
+ output[index] = index === lookback - 1 ? warmupSum / lookback : value;
56
+ continue;
57
+ }
58
+ const smoothing = 2 / (lookback + 1);
59
+ output[index] = value * smoothing + output[index - 1] * (1 - smoothing);
60
+ }
61
+ return output;
62
+ }
63
+ function swingHigh(bars, index, left = 2, right = 2) {
64
+ if (index < left || index + right >= bars.length) return false;
65
+ const high = bars[index].high;
66
+ for (let cursor = index - left; cursor <= index + right; cursor += 1) {
67
+ if (cursor !== index && bars[cursor].high >= high) return false;
68
+ }
69
+ return true;
70
+ }
71
+ function swingLow(bars, index, left = 2, right = 2) {
72
+ if (index < left || index + right >= bars.length) return false;
73
+ const low = bars[index].low;
74
+ for (let cursor = index - left; cursor <= index + right; cursor += 1) {
75
+ if (cursor !== index && bars[cursor].low <= low) return false;
76
+ }
77
+ return true;
78
+ }
79
+ function detectFVG(bars, index) {
80
+ if (index < 2) return null;
81
+ const first = bars[index - 2];
82
+ const third = bars[index];
83
+ if (first.high < third.low) {
84
+ return {
85
+ type: "bull",
86
+ top: first.high,
87
+ bottom: third.low,
88
+ mid: (first.high + third.low) / 2
89
+ };
90
+ }
91
+ if (first.low > third.high) {
92
+ return {
93
+ type: "bear",
94
+ top: third.high,
95
+ bottom: first.low,
96
+ mid: (third.high + first.low) / 2
97
+ };
98
+ }
99
+ return null;
100
+ }
101
+ function lastSwing(bars, index, direction) {
102
+ for (let cursor = index - 1; cursor >= 0; cursor -= 1) {
103
+ if (direction === "up" && swingLow(bars, cursor)) {
104
+ return { idx: cursor, price: bars[cursor].low };
105
+ }
106
+ if (direction === "down" && swingHigh(bars, cursor)) {
107
+ return { idx: cursor, price: bars[cursor].high };
108
+ }
109
+ }
110
+ return null;
111
+ }
112
+ function structureState(bars, index) {
113
+ return {
114
+ lastLow: lastSwing(bars, index, "up"),
115
+ lastHigh: lastSwing(bars, index, "down")
116
+ };
117
+ }
118
+ function atr(bars, period = 14) {
119
+ if (!bars?.length || period <= 0) return [];
120
+ const trueRanges = new Array(bars.length);
121
+ for (let index = 0; index < bars.length; index += 1) {
122
+ if (index === 0) {
123
+ trueRanges[index] = bars[index].high - bars[index].low;
124
+ continue;
125
+ }
126
+ const high = bars[index].high;
127
+ const low = bars[index].low;
128
+ const previousClose = bars[index - 1].close;
129
+ trueRanges[index] = Math.max(
130
+ high - low,
131
+ Math.abs(high - previousClose),
132
+ Math.abs(low - previousClose)
133
+ );
134
+ }
135
+ const output = new Array(trueRanges.length);
136
+ let previousAtr;
137
+ for (let index = 0; index < trueRanges.length; index += 1) {
138
+ if (index < period) {
139
+ output[index] = void 0;
140
+ if (index === period - 1) {
141
+ let seed = 0;
142
+ for (let cursor = 0; cursor < period; cursor += 1) {
143
+ seed += trueRanges[cursor];
144
+ }
145
+ previousAtr = seed / period;
146
+ output[index] = previousAtr;
147
+ }
148
+ continue;
149
+ }
150
+ previousAtr = (previousAtr * (period - 1) + trueRanges[index]) / period;
151
+ output[index] = previousAtr;
152
+ }
153
+ return output;
154
+ }
155
+
156
+ // src/ta/oscillators.js
157
+ function rsi(closes, period = 14) {
158
+ const out = new Array(closes.length).fill(void 0);
159
+ if (closes.length <= period) return out;
160
+ let gainSum = 0;
161
+ let lossSum = 0;
162
+ for (let i = 1; i <= period; i += 1) {
163
+ const change = closes[i] - closes[i - 1];
164
+ if (change >= 0) gainSum += change;
165
+ else lossSum -= change;
166
+ }
167
+ let avgGain = gainSum / period;
168
+ let avgLoss = lossSum / period;
169
+ out[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
170
+ for (let i = period + 1; i < closes.length; i += 1) {
171
+ const change = closes[i] - closes[i - 1];
172
+ const gain = change > 0 ? change : 0;
173
+ const loss = change < 0 ? -change : 0;
174
+ avgGain = (avgGain * (period - 1) + gain) / period;
175
+ avgLoss = (avgLoss * (period - 1) + loss) / period;
176
+ out[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
177
+ }
178
+ return out;
179
+ }
180
+ function macd(closes, fast = 12, slow = 26, signalPeriod = 9) {
181
+ const emaFast = ema(closes, fast);
182
+ const emaSlow = ema(closes, slow);
183
+ const macdLine = closes.map((_, i) => emaFast[i] - emaSlow[i]);
184
+ const signalLine = ema(macdLine, signalPeriod);
185
+ const histogram = macdLine.map((v, i) => v - signalLine[i]);
186
+ return { macd: macdLine, signal: signalLine, histogram };
187
+ }
188
+ function stochastic(bars, kPeriod = 14, dPeriod = 3) {
189
+ const k = new Array(bars.length).fill(void 0);
190
+ for (let i = kPeriod - 1; i < bars.length; i += 1) {
191
+ let hh = -Infinity;
192
+ let ll = Infinity;
193
+ for (let j = i - kPeriod + 1; j <= i; j += 1) {
194
+ if (bars[j].high > hh) hh = bars[j].high;
195
+ if (bars[j].low < ll) ll = bars[j].low;
196
+ }
197
+ const range = hh - ll;
198
+ k[i] = range === 0 ? 0 : (bars[i].close - ll) / range * 100;
199
+ }
200
+ const d = new Array(bars.length).fill(void 0);
201
+ for (let i = 0; i < bars.length; i += 1) {
202
+ if (i < kPeriod - 1 + dPeriod - 1) continue;
203
+ let sum = 0;
204
+ for (let j = i - dPeriod + 1; j <= i; j += 1) sum += k[j];
205
+ d[i] = sum / dPeriod;
206
+ }
207
+ return { k, d };
208
+ }
209
+
210
+ // src/ta/channels.js
211
+ function rollingMean(values, period, i) {
212
+ let sum = 0;
213
+ for (let j = i - period + 1; j <= i; j += 1) sum += values[j];
214
+ return sum / period;
215
+ }
216
+ function bollinger(closes, period = 20, mult = 2) {
217
+ const middle = new Array(closes.length).fill(void 0);
218
+ const upper = new Array(closes.length).fill(void 0);
219
+ const lower = new Array(closes.length).fill(void 0);
220
+ for (let i = period - 1; i < closes.length; i += 1) {
221
+ const avg = rollingMean(closes, period, i);
222
+ let variance = 0;
223
+ for (let j = i - period + 1; j <= i; j += 1) variance += (closes[j] - avg) ** 2;
224
+ const sd = Math.sqrt(variance / period);
225
+ middle[i] = avg;
226
+ upper[i] = avg + mult * sd;
227
+ lower[i] = avg - mult * sd;
228
+ }
229
+ return { middle, upper, lower };
230
+ }
231
+ function donchian(bars, period = 20) {
232
+ const upper = new Array(bars.length).fill(void 0);
233
+ const lower = new Array(bars.length).fill(void 0);
234
+ const middle = new Array(bars.length).fill(void 0);
235
+ for (let i = period - 1; i < bars.length; i += 1) {
236
+ let hh = -Infinity;
237
+ let ll = Infinity;
238
+ for (let j = i - period + 1; j <= i; j += 1) {
239
+ if (bars[j].high > hh) hh = bars[j].high;
240
+ if (bars[j].low < ll) ll = bars[j].low;
241
+ }
242
+ upper[i] = hh;
243
+ lower[i] = ll;
244
+ middle[i] = (hh + ll) / 2;
245
+ }
246
+ return { upper, lower, middle };
247
+ }
248
+ function keltner(bars, emaPeriod = 20, atrPeriod = 14, mult = 2) {
249
+ const closes = bars.map((b) => b.close);
250
+ const mid = ema(closes, emaPeriod);
251
+ const range = atr(bars, atrPeriod);
252
+ const upper = new Array(bars.length).fill(void 0);
253
+ const lower = new Array(bars.length).fill(void 0);
254
+ const middle = new Array(bars.length).fill(void 0);
255
+ for (let i = 0; i < bars.length; i += 1) {
256
+ if (range[i] === void 0) continue;
257
+ middle[i] = mid[i];
258
+ upper[i] = mid[i] + mult * range[i];
259
+ lower[i] = mid[i] - mult * range[i];
260
+ }
261
+ return { upper, lower, middle };
262
+ }
263
+
264
+ // src/ta/trend.js
265
+ function supertrend(bars, period = 10, mult = 3) {
266
+ const range = atr(bars, period);
267
+ const line = new Array(bars.length).fill(void 0);
268
+ const direction = new Array(bars.length).fill(void 0);
269
+ let prevUpper = Infinity;
270
+ let prevLower = -Infinity;
271
+ let prevDir = 1;
272
+ for (let i = 0; i < bars.length; i += 1) {
273
+ if (range[i] === void 0) continue;
274
+ const mid = (bars[i].high + bars[i].low) / 2;
275
+ const basicUpper = mid + mult * range[i];
276
+ const basicLower = mid - mult * range[i];
277
+ const close = bars[i].close;
278
+ const prevClose = i > 0 ? bars[i - 1].close : close;
279
+ const upper = basicUpper < prevUpper || prevClose > prevUpper ? basicUpper : prevUpper;
280
+ const lower = basicLower > prevLower || prevClose < prevLower ? basicLower : prevLower;
281
+ let dir = prevDir;
282
+ if (prevDir === 1 && close < lower) dir = -1;
283
+ else if (prevDir === -1 && close > upper) dir = 1;
284
+ line[i] = dir === 1 ? lower : upper;
285
+ direction[i] = dir;
286
+ prevUpper = upper;
287
+ prevLower = lower;
288
+ prevDir = dir;
289
+ }
290
+ return { line, direction };
291
+ }
292
+ function dayKeyUTC(timeMs) {
293
+ const d = new Date(timeMs);
294
+ return d.getUTCFullYear() * 1e4 + (d.getUTCMonth() + 1) * 100 + d.getUTCDate();
295
+ }
296
+ function vwap(bars) {
297
+ const out = new Array(bars.length).fill(void 0);
298
+ let currentDay = null;
299
+ let cumPV = 0;
300
+ let cumV = 0;
301
+ let cumTP = 0;
302
+ let count = 0;
303
+ for (let i = 0; i < bars.length; i += 1) {
304
+ const day = dayKeyUTC(bars[i].time);
305
+ if (day !== currentDay) {
306
+ currentDay = day;
307
+ cumPV = 0;
308
+ cumV = 0;
309
+ cumTP = 0;
310
+ count = 0;
311
+ }
312
+ const tp = (bars[i].high + bars[i].low + bars[i].close) / 3;
313
+ const vol = Number.isFinite(bars[i].volume) ? bars[i].volume : 0;
314
+ cumPV += tp * vol;
315
+ cumV += vol;
316
+ cumTP += tp;
317
+ count += 1;
318
+ out[i] = cumV > 0 ? cumPV / cumV : cumTP / count;
319
+ }
320
+ return out;
321
+ }
322
+ // Annotate the CommonJS export names for ESM import in node:
323
+ 0 && (module.exports = {
324
+ atr,
325
+ bollinger,
326
+ detectFVG,
327
+ donchian,
328
+ ema,
329
+ keltner,
330
+ lastSwing,
331
+ macd,
332
+ rsi,
333
+ stochastic,
334
+ structureState,
335
+ supertrend,
336
+ swingHigh,
337
+ swingLow,
338
+ vwap
339
+ });
package/docs/README.md CHANGED
@@ -1,78 +1,44 @@
1
- # tradelab docs
1
+ # Documentation
2
2
 
3
- ## Guides
3
+ Use this page to choose the right guide. If you are new to tradelab, read the first three links in order.
4
4
 
5
- - [Backtest engine](backtest-engine.md)
6
- - [Data, reporting, and CLI](data-reporting-cli.md)
7
- - [Live trading](live-trading.md)
8
- - [Strategy examples](examples.md)
9
- - [API reference](api-reference.md)
5
+ ## Start Here
10
6
 
11
- ## Choose a path
7
+ 1. [Backtesting](backtest-engine.md) - the `signal()` contract, result shape, costs, portfolio runs, walk-forward validation, and parameter sweeps.
8
+ 2. [Data, reporting, and CLI](data-reporting-cli.md) - Yahoo data, CSV files, cache helpers, exported reports, and terminal commands.
9
+ 3. [Live trading](live-trading.md) - paper mode, broker adapters, persisted state, multi-system orchestration, and the local dashboard.
12
10
 
13
- | Goal | Start here |
14
- | ------------------------------------------ | ------------------------------------------------- |
15
- | Run one strategy on one dataset | [Backtest engine](backtest-engine.md) |
16
- | Load Yahoo or CSV data | [Data, reporting, and CLI](data-reporting-cli.md) |
17
- | Export reports or machine-readable results | [Data, reporting, and CLI](data-reporting-cli.md) |
18
- | Run multiple symbols together | [Backtest engine](backtest-engine.md) |
19
- | Run walk-forward validation | [Backtest engine](backtest-engine.md) |
20
- | Run one strategy live or in paper mode | [Live trading](live-trading.md) |
21
- | Run multiple live systems together | [Live trading](live-trading.md) |
22
- | See complete strategy patterns | [Strategy examples](examples.md) |
23
- | Check the exact public exports | [API reference](api-reference.md) |
11
+ ## Reference
24
12
 
25
- ## Package scope
13
+ - [API reference](api-reference.md) - public exports by module.
14
+ - [Research tools](research.md) - Monte Carlo, deflated Sharpe, PBO, and CPCV.
15
+ - [MCP server](mcp.md) - `tradelab-mcp` setup and tool list.
16
+ - [Strategy examples](examples.md) - complete strategy patterns you can adapt.
26
17
 
27
- tradelab is built for:
18
+ ## Common Paths
28
19
 
29
- - candle-based strategy research
30
- - optional tick or quote replay with event-driven fills
31
- - historical backtests with configurable fills and costs
32
- - live and paper execution using broker adapters
33
- - CSV and Yahoo-based data workflows
34
- - exportable outputs for review or automation
20
+ | If you want to... | Read |
21
+ | --------------------------------- | --------------------------------------------------------- |
22
+ | Run one strategy on OHLCV candles | [Backtesting](backtest-engine.md) |
23
+ | Load Yahoo or CSV data | [Data, reporting, and CLI](data-reporting-cli.md) |
24
+ | Export HTML, CSV, or JSON | [Data, reporting, and CLI](data-reporting-cli.md) |
25
+ | Combine several systems | [Backtesting](backtest-engine.md#portfolio-backtests) |
26
+ | Test parameter stability | [Backtesting](backtest-engine.md#walk-forward-validation) |
27
+ | Run a local paper session | [Live trading](live-trading.md) |
28
+ | Connect an MCP client | [MCP server](mcp.md) |
29
+ | Check exact function names | [API reference](api-reference.md) |
35
30
 
36
- tradelab is not built for:
31
+ ## Package Scope
37
32
 
38
- - exchange microstructure modeling
33
+ tradelab is built for strategy research and operational dry-runs:
39
34
 
40
- ## Common workflows
35
+ - candle and tick backtests
36
+ - shared-capital portfolio simulation
37
+ - realistic cost assumptions
38
+ - walk-forward validation and overfitting checks
39
+ - paper and live execution through broker adapters
40
+ - local reports and machine-readable exports
41
41
 
42
- ### Single strategy workflow
42
+ It is not an exchange simulator. It does not try to model full market depth, queue priority, latency, or venue-specific microstructure.
43
43
 
44
- 1. Load candles with `getHistoricalCandles()` or your own dataset
45
- 2. Run `backtest()`
46
- 3. Inspect `result.metrics` and `result.positions`
47
- 4. Export HTML, CSV, or JSON if needed
48
-
49
- ### Multi-symbol workflow
50
-
51
- 1. Prepare one candle array per symbol
52
- 2. Run `backtestPortfolio()`
53
- 3. Review combined `metrics`, `positions`, and `eqSeries`
54
-
55
- ### Validation workflow
56
-
57
- 1. Build a `signalFactory(params)`
58
- 2. Create parameter sets
59
- 3. Run `walkForwardOptimize()`
60
- 4. Review per-window winners before trusting the aggregate result
61
-
62
- ### Live execution workflow
63
-
64
- 1. Build a `signal()` used in backtest first
65
- 2. Wire it into `LiveEngine` with a broker or `PaperEngine`
66
- 3. Persist state with `JsonFileStorage`
67
- 4. Start with `tradelab paper` or `tradelab live --paper`
68
- 5. Inspect persisted state with `tradelab status`
69
-
70
- ## Documentation map
71
-
72
- - [Backtest engine](backtest-engine.md): strategy inputs, engine options, result shape, portfolio mode, walk-forward mode
73
- - [Data, reporting, and CLI](data-reporting-cli.md): data loading, cache behavior, exports, terminal usage
74
- - [Live trading](live-trading.md): live engine, broker adapters, paper mode, orchestration, lifecycle, and state
75
- - [Strategy examples](examples.md): mean reversion, breakout, sentiment, LLM, and portfolio research patterns
76
- - [API reference](api-reference.md): compact export index
77
-
78
- <small>[Back to README.md](../README.md)</small>
44
+ [Back to README](../README.md)