tradelab 1.0.0 → 1.1.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 (67) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +75 -12
  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 +1893 -1003
  7. package/dist/cjs/live.cjs +134 -25
  8. package/dist/cjs/ta.cjs +339 -0
  9. package/docs/api-reference.md +46 -0
  10. package/docs/backtest-engine.md +112 -0
  11. package/docs/live-trading.md +51 -0
  12. package/docs/mcp.md +64 -0
  13. package/docs/research.md +103 -0
  14. package/docs/superpowers/plans/2026-00-overview.md +101 -0
  15. package/docs/superpowers/plans/2026-01-metrics-correctness.md +873 -0
  16. package/docs/superpowers/plans/2026-02-indicator-library.md +677 -0
  17. package/docs/superpowers/plans/2026-03-overfitting-toolkit.md +882 -0
  18. package/docs/superpowers/plans/2026-04-async-signals-seeding.md +981 -0
  19. package/docs/superpowers/plans/2026-05-mcp-server.md +758 -0
  20. package/docs/superpowers/plans/2026-06-parallel-param-sweep.md +508 -0
  21. package/docs/superpowers/plans/2026-07-funding-carry-costs.md +535 -0
  22. package/docs/superpowers/plans/2026-08-live-dashboard.md +547 -0
  23. package/docs/superpowers/plans/HANDOFF.md +88 -0
  24. package/examples/liveDashboard.js +33 -0
  25. package/examples/llmSignal.js +33 -0
  26. package/examples/optimize.js +25 -0
  27. package/package.json +16 -2
  28. package/src/engine/asyncSignal.js +28 -0
  29. package/src/engine/backtest.js +13 -1
  30. package/src/engine/backtestAsync.js +27 -0
  31. package/src/engine/backtestTicks.js +13 -2
  32. package/src/engine/barSystemRunner.js +96 -41
  33. package/src/engine/execution.js +39 -0
  34. package/src/engine/grid.js +15 -0
  35. package/src/engine/llmSignal.js +84 -0
  36. package/src/engine/optimize.js +86 -0
  37. package/src/engine/optimizeWorker.js +67 -0
  38. package/src/engine/walkForward.js +1 -0
  39. package/src/index.js +9 -0
  40. package/src/live/dashboard/server.js +120 -0
  41. package/src/live/engine/liveEngine.js +2 -2
  42. package/src/live/index.js +1 -0
  43. package/src/mcp/schemas.js +48 -0
  44. package/src/mcp/server.js +31 -0
  45. package/src/mcp/tools.js +142 -0
  46. package/src/metrics/annualize.js +32 -0
  47. package/src/metrics/benchmark.js +55 -0
  48. package/src/metrics/buildMetrics.js +34 -13
  49. package/src/metrics/finite.js +17 -0
  50. package/src/research/combinations.js +18 -0
  51. package/src/research/cpcv.js +47 -0
  52. package/src/research/deflatedSharpe.js +35 -0
  53. package/src/research/index.js +6 -0
  54. package/src/research/monteCarlo.js +88 -0
  55. package/src/research/pbo.js +69 -0
  56. package/src/research/stats.js +78 -0
  57. package/src/strategies/builtins.js +96 -0
  58. package/src/strategies/index.js +30 -0
  59. package/src/ta/channels.js +67 -0
  60. package/src/ta/index.js +16 -0
  61. package/src/ta/oscillators.js +70 -0
  62. package/src/ta/trend.js +78 -0
  63. package/src/utils/random.js +33 -0
  64. package/templates/dashboard.html +174 -0
  65. package/types/index.d.ts +154 -0
  66. package/types/live.d.ts +15 -0
  67. package/types/ta.d.ts +45 -0
@@ -0,0 +1,70 @@
1
+ // src/ta/oscillators.js
2
+ import { ema } from "../utils/indicators.js";
3
+
4
+ /**
5
+ * Wilder's RSI. Returns a full-length array; warmup positions are `undefined`.
6
+ */
7
+ export function rsi(closes, period = 14) {
8
+ const out = new Array(closes.length).fill(undefined);
9
+ if (closes.length <= period) return out;
10
+
11
+ let gainSum = 0;
12
+ let lossSum = 0;
13
+ for (let i = 1; i <= period; i += 1) {
14
+ const change = closes[i] - closes[i - 1];
15
+ if (change >= 0) gainSum += change;
16
+ else lossSum -= change;
17
+ }
18
+ let avgGain = gainSum / period;
19
+ let avgLoss = lossSum / period;
20
+ out[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
21
+
22
+ for (let i = period + 1; i < closes.length; i += 1) {
23
+ const change = closes[i] - closes[i - 1];
24
+ const gain = change > 0 ? change : 0;
25
+ const loss = change < 0 ? -change : 0;
26
+ avgGain = (avgGain * (period - 1) + gain) / period;
27
+ avgLoss = (avgLoss * (period - 1) + loss) / period;
28
+ out[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
29
+ }
30
+ return out;
31
+ }
32
+
33
+ /**
34
+ * MACD line, signal line, histogram. All full-length, aligned to input.
35
+ */
36
+ export function macd(closes, fast = 12, slow = 26, signalPeriod = 9) {
37
+ const emaFast = ema(closes, fast);
38
+ const emaSlow = ema(closes, slow);
39
+ const macdLine = closes.map((_, i) => emaFast[i] - emaSlow[i]);
40
+ const signalLine = ema(macdLine, signalPeriod);
41
+ const histogram = macdLine.map((v, i) => v - signalLine[i]);
42
+ return { macd: macdLine, signal: signalLine, histogram };
43
+ }
44
+
45
+ /**
46
+ * Stochastic oscillator %K (smoothed close position in the high-low range) and
47
+ * %D (SMA of %K). `bars` need { high, low, close }.
48
+ */
49
+ export function stochastic(bars, kPeriod = 14, dPeriod = 3) {
50
+ const k = new Array(bars.length).fill(undefined);
51
+ for (let i = kPeriod - 1; i < bars.length; i += 1) {
52
+ let hh = -Infinity;
53
+ let ll = Infinity;
54
+ for (let j = i - kPeriod + 1; j <= i; j += 1) {
55
+ if (bars[j].high > hh) hh = bars[j].high;
56
+ if (bars[j].low < ll) ll = bars[j].low;
57
+ }
58
+ const range = hh - ll;
59
+ k[i] = range === 0 ? 0 : ((bars[i].close - ll) / range) * 100;
60
+ }
61
+
62
+ const d = new Array(bars.length).fill(undefined);
63
+ for (let i = 0; i < bars.length; i += 1) {
64
+ if (i < kPeriod - 1 + dPeriod - 1) continue;
65
+ let sum = 0;
66
+ for (let j = i - dPeriod + 1; j <= i; j += 1) sum += k[j];
67
+ d[i] = sum / dPeriod;
68
+ }
69
+ return { k, d };
70
+ }
@@ -0,0 +1,78 @@
1
+ // src/ta/trend.js
2
+ import { atr } from "../utils/indicators.js";
3
+
4
+ /**
5
+ * Supertrend. Returns { line, direction } full-length.
6
+ * direction: 1 = uptrend (line is support below price), -1 = downtrend.
7
+ */
8
+ export function supertrend(bars, period = 10, mult = 3) {
9
+ const range = atr(bars, period);
10
+ const line = new Array(bars.length).fill(undefined);
11
+ const direction = new Array(bars.length).fill(undefined);
12
+
13
+ let prevUpper = Infinity;
14
+ let prevLower = -Infinity;
15
+ let prevDir = 1;
16
+
17
+ for (let i = 0; i < bars.length; i += 1) {
18
+ if (range[i] === undefined) continue;
19
+ const mid = (bars[i].high + bars[i].low) / 2;
20
+ const basicUpper = mid + mult * range[i];
21
+ const basicLower = mid - mult * range[i];
22
+ const close = bars[i].close;
23
+ const prevClose = i > 0 ? bars[i - 1].close : close;
24
+
25
+ const upper = basicUpper < prevUpper || prevClose > prevUpper ? basicUpper : prevUpper;
26
+ const lower = basicLower > prevLower || prevClose < prevLower ? basicLower : prevLower;
27
+
28
+ let dir = prevDir;
29
+ if (prevDir === 1 && close < lower) dir = -1;
30
+ else if (prevDir === -1 && close > upper) dir = 1;
31
+
32
+ line[i] = dir === 1 ? lower : upper;
33
+ direction[i] = dir;
34
+
35
+ prevUpper = upper;
36
+ prevLower = lower;
37
+ prevDir = dir;
38
+ }
39
+ return { line, direction };
40
+ }
41
+
42
+ function dayKeyUTC(timeMs) {
43
+ const d = new Date(timeMs);
44
+ return d.getUTCFullYear() * 10000 + (d.getUTCMonth() + 1) * 100 + d.getUTCDate();
45
+ }
46
+
47
+ /**
48
+ * Session VWAP, reset on each UTC calendar day. `bars` need
49
+ * { time, high, low, close, volume }. When volume is missing/zero, falls back
50
+ * to an unweighted cumulative typical-price average for that day.
51
+ */
52
+ export function vwap(bars) {
53
+ const out = new Array(bars.length).fill(undefined);
54
+ let currentDay = null;
55
+ let cumPV = 0;
56
+ let cumV = 0;
57
+ let cumTP = 0;
58
+ let count = 0;
59
+
60
+ for (let i = 0; i < bars.length; i += 1) {
61
+ const day = dayKeyUTC(bars[i].time);
62
+ if (day !== currentDay) {
63
+ currentDay = day;
64
+ cumPV = 0;
65
+ cumV = 0;
66
+ cumTP = 0;
67
+ count = 0;
68
+ }
69
+ const tp = (bars[i].high + bars[i].low + bars[i].close) / 3;
70
+ const vol = Number.isFinite(bars[i].volume) ? bars[i].volume : 0;
71
+ cumPV += tp * vol;
72
+ cumV += vol;
73
+ cumTP += tp;
74
+ count += 1;
75
+ out[i] = cumV > 0 ? cumPV / cumV : cumTP / count;
76
+ }
77
+ return out;
78
+ }
@@ -0,0 +1,33 @@
1
+ function xmur3(str) {
2
+ let h = 1779033703 ^ str.length;
3
+ for (let i = 0; i < str.length; i += 1) {
4
+ h = Math.imul(h ^ str.charCodeAt(i), 3432918353);
5
+ h = (h << 13) | (h >>> 19);
6
+ }
7
+ return () => {
8
+ h = Math.imul(h ^ (h >>> 16), 2246822507);
9
+ h = Math.imul(h ^ (h >>> 13), 3266489909);
10
+ return (h ^= h >>> 16) >>> 0;
11
+ };
12
+ }
13
+
14
+ function mulberry32(seed) {
15
+ let state = seed >>> 0;
16
+ return () => {
17
+ state = (state + 0x6d2b79f5) >>> 0;
18
+ let t = Math.imul(state ^ (state >>> 15), state | 1);
19
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
20
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
21
+ };
22
+ }
23
+
24
+ /** Returns a deterministic () => float-in-[0,1) generator seeded by `seed`. */
25
+ export function makeRng(seed = "tradelab") {
26
+ const seedFn = xmur3(String(seed));
27
+ return mulberry32(seedFn());
28
+ }
29
+
30
+ /** Integer in [0, maxExclusive) from an rng produced by makeRng. */
31
+ export function randInt(rng, maxExclusive) {
32
+ return Math.floor(rng() * maxExclusive);
33
+ }
@@ -0,0 +1,174 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>tradelab live</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: dark;
10
+ }
11
+
12
+ body {
13
+ margin: 0;
14
+ font:
15
+ 14px/1.5 ui-monospace,
16
+ SFMono-Regular,
17
+ Menlo,
18
+ monospace;
19
+ background: #0f172a;
20
+ color: #e2e8f0;
21
+ }
22
+
23
+ header {
24
+ padding: 16px 24px;
25
+ border-bottom: 1px solid #1e293b;
26
+ display: flex;
27
+ gap: 24px;
28
+ align-items: baseline;
29
+ }
30
+
31
+ h1 {
32
+ font-size: 16px;
33
+ margin: 0;
34
+ color: #38bdf8;
35
+ }
36
+
37
+ .grid {
38
+ display: grid;
39
+ grid-template-columns: 1fr 1fr;
40
+ gap: 16px;
41
+ padding: 24px;
42
+ }
43
+
44
+ .card {
45
+ background: #111827;
46
+ border: 1px solid #1e293b;
47
+ border-radius: 8px;
48
+ padding: 16px;
49
+ }
50
+
51
+ .card h2 {
52
+ font-size: 12px;
53
+ text-transform: uppercase;
54
+ letter-spacing: 0.08em;
55
+ color: #64748b;
56
+ margin: 0 0 12px;
57
+ }
58
+
59
+ .big {
60
+ font-size: 28px;
61
+ font-weight: 600;
62
+ }
63
+
64
+ .pos {
65
+ color: #4ade80;
66
+ }
67
+
68
+ .neg {
69
+ color: #f87171;
70
+ }
71
+
72
+ ul {
73
+ list-style: none;
74
+ margin: 0;
75
+ padding: 0;
76
+ max-height: 320px;
77
+ overflow: auto;
78
+ }
79
+
80
+ li {
81
+ padding: 6px 0;
82
+ border-bottom: 1px solid #1e293b;
83
+ display: flex;
84
+ gap: 12px;
85
+ }
86
+
87
+ .halt {
88
+ color: #fbbf24;
89
+ }
90
+
91
+ time {
92
+ color: #475569;
93
+ }
94
+ </style>
95
+ </head>
96
+ <body>
97
+ <header>
98
+ <h1>tradelab live</h1>
99
+ <span id="symbol">-</span>
100
+ <span id="conn">connecting...</span>
101
+ </header>
102
+ <div class="grid">
103
+ <div class="card">
104
+ <h2>Equity</h2>
105
+ <div class="big" id="equity">-</div>
106
+ <div>Day PnL: <span id="dayPnl">-</span></div>
107
+ </div>
108
+ <div class="card">
109
+ <h2>Open position</h2>
110
+ <div id="position">flat</div>
111
+ </div>
112
+ <div class="card">
113
+ <h2>Risk</h2>
114
+ <div id="risk">-</div>
115
+ </div>
116
+ <div class="card">
117
+ <h2>Recent events</h2>
118
+ <ul id="events"></ul>
119
+ </div>
120
+ </div>
121
+ <script>
122
+ const fmt = (n) =>
123
+ typeof n === "number" ? n.toLocaleString(undefined, { maximumFractionDigits: 2 }) : "-";
124
+
125
+ function applyState(s) {
126
+ document.getElementById("symbol").textContent = s.symbol ?? s.id ?? "portfolio";
127
+ document.getElementById("equity").textContent = fmt(s.equity);
128
+ const dp = document.getElementById("dayPnl");
129
+ dp.textContent = fmt(s.dayPnl);
130
+ dp.className = (s.dayPnl ?? 0) >= 0 ? "pos" : "neg";
131
+ const p = s.openPosition;
132
+ document.getElementById("position").textContent = p
133
+ ? `${p.side} ${fmt(p.size)} @ ${fmt(p.entryFill ?? p.entry)} | uPnL ${fmt(p.unrealizedPnl)}`
134
+ : "flat";
135
+ document.getElementById("risk").textContent = s.risk ? JSON.stringify(s.risk) : "ok";
136
+ }
137
+
138
+ async function pollState() {
139
+ try {
140
+ applyState(await (await fetch("/state")).json());
141
+ } catch {}
142
+ }
143
+
144
+ pollState();
145
+ setInterval(pollState, 3000);
146
+
147
+ const list = document.getElementById("events");
148
+ const es = new EventSource("/events");
149
+ es.onopen = () => (document.getElementById("conn").textContent = "live");
150
+ es.onerror = () => (document.getElementById("conn").textContent = "disconnected");
151
+ es.onmessage = (e) => {
152
+ const msg = JSON.parse(e.data);
153
+ const li = document.createElement("li");
154
+ const halt = msg.event.startsWith("risk:");
155
+ li.innerHTML =
156
+ "<time>" +
157
+ new Date(msg.t).toLocaleTimeString() +
158
+ "</time>" +
159
+ '<span class="' +
160
+ (halt ? "halt" : "") +
161
+ '">' +
162
+ msg.event +
163
+ "</span>" +
164
+ "<span>" +
165
+ (msg.payload?.symbol ?? "") +
166
+ "</span>";
167
+ list.prepend(li);
168
+ while (list.children.length > 100) list.removeChild(list.lastChild);
169
+ if (["equity:update", "position:opened", "position:closed"].includes(msg.event))
170
+ pollState();
171
+ };
172
+ </script>
173
+ </body>
174
+ </html>
package/types/index.d.ts CHANGED
@@ -82,6 +82,7 @@ export interface TradeExit {
82
82
  time: number;
83
83
  reason: string;
84
84
  pnl: number;
85
+ financing?: number;
85
86
  exitATR?: number;
86
87
  }
87
88
 
@@ -238,6 +239,9 @@ export interface SignalResult {
238
239
  }
239
240
 
240
241
  export type SignalFunction = (context: SignalContext) => SignalResult | null;
242
+ export type AsyncSignalFunction = (
243
+ context: SignalContext
244
+ ) => SignalResult | null | Promise<SignalResult | null>;
241
245
 
242
246
  export interface PendingOrder {
243
247
  side: Side;
@@ -268,6 +272,15 @@ export interface ExecutionCostOptions {
268
272
  commissionPerUnit?: number;
269
273
  commissionPerOrder?: number;
270
274
  minCommission?: number;
275
+ carry?: {
276
+ longAnnualBps?: number;
277
+ shortAnnualBps?: number;
278
+ };
279
+ funding?: {
280
+ rateBps?: number;
281
+ intervalMs?: number;
282
+ anchorMs?: number;
283
+ };
271
284
  }
272
285
 
273
286
  export interface MfeTrailOptions {
@@ -337,6 +350,11 @@ export interface BacktestOptions {
337
350
  strict?: boolean;
338
351
  }
339
352
 
353
+ export interface BacktestAsyncOptions extends Omit<BacktestOptions, "signal"> {
354
+ signal: AsyncSignalFunction;
355
+ signalBudgetMs?: number;
356
+ }
357
+
340
358
  export interface BacktestTickOptions {
341
359
  ticks: Tick[];
342
360
  symbol?: string;
@@ -357,6 +375,7 @@ export interface BacktestTickOptions {
357
375
  collectEqSeries?: boolean;
358
376
  collectReplay?: boolean;
359
377
  queueFillProbability?: number;
378
+ seed?: string;
360
379
  oco?: OCOOptions;
361
380
  }
362
381
 
@@ -542,6 +561,81 @@ export interface ArtifactPaths {
542
561
  metrics: string | null;
543
562
  }
544
563
 
564
+ export interface LlmSignalOptions {
565
+ resolve: AsyncSignalFunction;
566
+ budgetMs?: number;
567
+ onError?: "skip" | "throw";
568
+ }
569
+
570
+ export interface LlmDecisionLogEntry {
571
+ index: number;
572
+ time?: number;
573
+ close?: number;
574
+ latencyMs: number;
575
+ result?: SignalResult | null;
576
+ error?: string;
577
+ }
578
+
579
+ export interface StrategyParamSpec {
580
+ type: string;
581
+ default?: unknown;
582
+ description?: string;
583
+ [key: string]: unknown;
584
+ }
585
+
586
+ export interface StrategyDefinition {
587
+ description: string;
588
+ params: Record<string, StrategyParamSpec>;
589
+ factory: (params?: Record<string, unknown>) => SignalFunction;
590
+ }
591
+
592
+ export interface StrategySummary {
593
+ name: string;
594
+ description: string;
595
+ params: Record<string, StrategyParamSpec>;
596
+ }
597
+
598
+ export interface ResearchPercentileBands {
599
+ p5: number;
600
+ p25?: number;
601
+ p50: number;
602
+ p75?: number;
603
+ p95: number;
604
+ }
605
+
606
+ export interface MonteCarloResult {
607
+ iterations: number;
608
+ blockSize: number;
609
+ finalEquity: Required<ResearchPercentileBands>;
610
+ maxDrawdown: Required<ResearchPercentileBands>;
611
+ pathBands: Array<Pick<ResearchPercentileBands, "p5" | "p50" | "p95">>;
612
+ probProfit: number;
613
+ }
614
+
615
+ export interface PboResult {
616
+ pbo: number;
617
+ combos: number;
618
+ medianLogit: number;
619
+ }
620
+
621
+ export interface CpcvSplit {
622
+ train: number[];
623
+ test: number[];
624
+ testGroups: number[];
625
+ }
626
+
627
+ export interface OptimizeResultEntry {
628
+ params: Record<string, unknown>;
629
+ metrics?: Partial<BacktestMetrics>;
630
+ error?: string;
631
+ }
632
+
633
+ export interface OptimizeResult {
634
+ results: OptimizeResultEntry[];
635
+ leaderboard: OptimizeResultEntry[];
636
+ best: OptimizeResultEntry | null;
637
+ }
638
+
545
639
  /**
546
640
  * Run a candle-based backtest.
547
641
  *
@@ -550,7 +644,18 @@ export interface ArtifactPaths {
550
644
  * chart-friendly replay frames/events in `replay`.
551
645
  */
552
646
  export function backtest(options: BacktestOptions): BacktestResult;
647
+ export function backtestAsync(options: BacktestAsyncOptions): Promise<BacktestResult>;
553
648
  export function backtestTicks(options: BacktestTickOptions): BacktestResult;
649
+ export function grid(spec?: Record<string, unknown | unknown[]>): Array<Record<string, unknown>>;
650
+ export function optimize(options: {
651
+ candles: Candle[];
652
+ signalModulePath: string;
653
+ parameterSets: Array<Record<string, unknown>>;
654
+ interval?: string;
655
+ backtestOptions?: Partial<BacktestOptions>;
656
+ concurrency?: number;
657
+ scoreBy?: keyof BacktestMetrics | string;
658
+ }): Promise<OptimizeResult>;
554
659
  export function backtestPortfolio(options: {
555
660
  systems: PortfolioSystem[];
556
661
  equity?: number;
@@ -581,6 +686,55 @@ export function buildMetrics(input: {
581
686
  eqSeries?: EquityPoint[];
582
687
  }): BacktestMetrics;
583
688
 
689
+ export class LlmSignal {
690
+ constructor(options: LlmSignalOptions);
691
+ resolve: AsyncSignalFunction;
692
+ budgetMs: number;
693
+ onError: "skip" | "throw";
694
+ log: LlmDecisionLogEntry[];
695
+ signal(context: SignalContext): Promise<SignalResult | null>;
696
+ }
697
+
698
+ export function registerStrategy(name: string, def: StrategyDefinition): void;
699
+ export function listStrategies(): StrategySummary[];
700
+ export function getStrategy(name: string): StrategyDefinition["factory"];
701
+
702
+ export namespace research {
703
+ function monteCarlo(options: {
704
+ tradePnls: number[];
705
+ equityStart?: number;
706
+ iterations?: number;
707
+ blockSize?: number;
708
+ seed?: string | number;
709
+ }): MonteCarloResult;
710
+ function deflatedSharpe(options: {
711
+ sharpe: number;
712
+ sampleSize: number;
713
+ numTrials?: number;
714
+ sharpeStd?: number;
715
+ skew?: number;
716
+ kurtosis?: number;
717
+ }): number;
718
+ function sweepHaircut(options: { numTrials: number; sharpeStd: number }): {
719
+ expectedMaxSharpe: number;
720
+ numTrials: number;
721
+ };
722
+ function probabilityOfBacktestOverfitting(
723
+ performanceMatrix: number[][],
724
+ options?: { groups?: number }
725
+ ): PboResult;
726
+ function combinatorialPurgedSplits(options: {
727
+ nObservations: number;
728
+ nGroups?: number;
729
+ nTestGroups?: number;
730
+ embargo?: number;
731
+ }): CpcvSplit[];
732
+ function combinations(n: number, k: number): number[][];
733
+ function normalCdf(x: number): number;
734
+ function normalPpf(p: number): number;
735
+ function moments(values: number[]): { mean: number; std: number; skew: number; kurtosis: number };
736
+ }
737
+
584
738
  export function getHistoricalCandles(options?: HistoricalDataOptions): Promise<Candle[]>;
585
739
  export function backtestHistorical(options: BacktestHistoricalOptions): Promise<BacktestResult>;
586
740
  export function fetchHistorical(
package/types/live.d.ts CHANGED
@@ -330,6 +330,21 @@ export class LiveOrchestrator {
330
330
  getStatus(): Record<string, unknown>;
331
331
  }
332
332
 
333
+ export interface DashboardServer {
334
+ start(): Promise<string>;
335
+ close(): Promise<void>;
336
+ server: import("node:http").Server;
337
+ }
338
+
339
+ export function createDashboardServer(options: {
340
+ source: {
341
+ eventBus: EventBus;
342
+ getStatus?: () => Record<string, unknown>;
343
+ };
344
+ port?: number;
345
+ maxBuffer?: number;
346
+ }): DashboardServer;
347
+
333
348
  export function createEventBus(): EventBus;
334
349
  export function createLogger(options?: {
335
350
  level?: "debug" | "info" | "warn" | "error" | "silent";
package/types/ta.d.ts ADDED
@@ -0,0 +1,45 @@
1
+ // types/ta.d.ts
2
+ export type Candle = {
3
+ time: number;
4
+ open: number;
5
+ high: number;
6
+ low: number;
7
+ close: number;
8
+ volume?: number;
9
+ };
10
+
11
+ export function ema(values: number[], period?: number): number[];
12
+ export function atr(bars: Candle[], period?: number): (number | undefined)[];
13
+ export function rsi(closes: number[], period?: number): (number | undefined)[];
14
+ export function macd(
15
+ closes: number[],
16
+ fast?: number,
17
+ slow?: number,
18
+ signalPeriod?: number
19
+ ): { macd: number[]; signal: number[]; histogram: number[] };
20
+ export function stochastic(
21
+ bars: Candle[],
22
+ kPeriod?: number,
23
+ dPeriod?: number
24
+ ): { k: (number | undefined)[]; d: (number | undefined)[] };
25
+ export function bollinger(
26
+ closes: number[],
27
+ period?: number,
28
+ mult?: number
29
+ ): { middle: (number | undefined)[]; upper: (number | undefined)[]; lower: (number | undefined)[] };
30
+ export function donchian(
31
+ bars: Candle[],
32
+ period?: number
33
+ ): { upper: (number | undefined)[]; lower: (number | undefined)[]; middle: (number | undefined)[] };
34
+ export function keltner(
35
+ bars: Candle[],
36
+ emaPeriod?: number,
37
+ atrPeriod?: number,
38
+ mult?: number
39
+ ): { upper: (number | undefined)[]; lower: (number | undefined)[]; middle: (number | undefined)[] };
40
+ export function supertrend(
41
+ bars: Candle[],
42
+ period?: number,
43
+ mult?: number
44
+ ): { line: (number | undefined)[]; direction: (number | undefined)[] };
45
+ export function vwap(bars: Candle[]): (number | undefined)[];