tradelab 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +188 -328
  3. package/bin/tradelab-mcp.js +7 -0
  4. package/bin/tradelab.js +29 -0
  5. package/dist/cjs/data.cjs +149 -26
  6. package/dist/cjs/index.cjs +1917 -1005
  7. package/dist/cjs/live.cjs +536 -25
  8. package/dist/cjs/ta.cjs +339 -0
  9. package/docs/README.md +32 -66
  10. package/docs/api-reference.md +283 -112
  11. package/docs/backtest-engine.md +210 -252
  12. package/docs/data-reporting-cli.md +114 -156
  13. package/docs/examples.md +6 -6
  14. package/docs/live-trading.md +263 -92
  15. package/docs/mcp.md +285 -0
  16. package/docs/research.md +157 -0
  17. package/examples/liveDashboard.js +33 -0
  18. package/examples/llmSignal.js +33 -0
  19. package/examples/mcpLiveTrading.js +77 -0
  20. package/examples/optimize.js +25 -0
  21. package/package.json +26 -4
  22. package/src/engine/asyncSignal.js +28 -0
  23. package/src/engine/backtest.js +13 -1
  24. package/src/engine/backtestAsync.js +27 -0
  25. package/src/engine/backtestTicks.js +13 -2
  26. package/src/engine/barSystemRunner.js +96 -41
  27. package/src/engine/execution.js +39 -0
  28. package/src/engine/grid.js +15 -0
  29. package/src/engine/llmSignal.js +84 -0
  30. package/src/engine/optimize.js +110 -0
  31. package/src/engine/optimizeWorker.js +67 -0
  32. package/src/engine/portfolio.js +4 -1
  33. package/src/engine/walkForward.js +1 -0
  34. package/src/index.js +9 -0
  35. package/src/live/dashboard/server.js +179 -0
  36. package/src/live/engine/liveEngine.js +2 -2
  37. package/src/live/engine/paperEngine.js +5 -0
  38. package/src/live/index.js +3 -0
  39. package/src/live/session.js +402 -0
  40. package/src/mcp/liveTools.js +179 -0
  41. package/src/mcp/schemas.js +167 -0
  42. package/src/mcp/server.js +35 -0
  43. package/src/mcp/tools.js +265 -0
  44. package/src/metrics/annualize.js +32 -0
  45. package/src/metrics/benchmark.js +55 -0
  46. package/src/metrics/buildMetrics.js +34 -13
  47. package/src/metrics/finite.js +17 -0
  48. package/src/research/combinations.js +18 -0
  49. package/src/research/cpcv.js +47 -0
  50. package/src/research/deflatedSharpe.js +35 -0
  51. package/src/research/index.js +6 -0
  52. package/src/research/monteCarlo.js +88 -0
  53. package/src/research/pbo.js +69 -0
  54. package/src/research/stats.js +78 -0
  55. package/src/strategies/builtins.js +96 -0
  56. package/src/strategies/index.js +30 -0
  57. package/src/ta/channels.js +67 -0
  58. package/src/ta/index.js +16 -0
  59. package/src/ta/oscillators.js +70 -0
  60. package/src/ta/trend.js +78 -0
  61. package/src/utils/random.js +33 -0
  62. package/templates/dashboard.html +661 -0
  63. package/types/index.d.ts +179 -0
  64. package/types/live.d.ts +114 -0
  65. package/types/mcp.d.ts +17 -0
  66. package/types/ta.d.ts +45 -0
package/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
 
@@ -131,6 +132,14 @@ export interface SideBreakdownEntry {
131
132
  avgR: number;
132
133
  }
133
134
 
135
+ export interface BenchmarkStats {
136
+ alpha: number | null;
137
+ beta: number | null;
138
+ correlation: number | null;
139
+ informationRatio: number | null;
140
+ trackingError: number | null;
141
+ }
142
+
134
143
  /** Aggregate performance metrics returned by `backtest()`. */
135
144
  export interface BacktestMetrics {
136
145
  /** Count of completed positions included in the aggregate metrics. */
@@ -145,6 +154,12 @@ export interface BacktestMetrics {
145
154
  avgR: number;
146
155
  /** Daily Sharpe ratio alias for quick access. */
147
156
  sharpe: number;
157
+ /** Annualized Sharpe ratio derived from the configured interval or bar spacing. */
158
+ sharpeAnnualized: number;
159
+ /** Annualized Sortino ratio derived from the configured interval or bar spacing. */
160
+ sortinoAnnualized: number;
161
+ /** Number of periods per year used for annualized metrics. */
162
+ annualizationPeriods: number;
148
163
  sharpePerTrade: number;
149
164
  sortinoPerTrade: number;
150
165
  /** Maximum drawdown percent alias. */
@@ -168,6 +183,7 @@ export interface BacktestMetrics {
168
183
  /** Daily Sharpe ratio computed from realized equity changes. */
169
184
  sharpeDaily: number;
170
185
  sortinoDaily: number;
186
+ benchmark: BenchmarkStats;
171
187
  /** Long/short breakdown grouped by completed position side. */
172
188
  sideBreakdown: {
173
189
  long: SideBreakdownEntry;
@@ -238,6 +254,9 @@ export interface SignalResult {
238
254
  }
239
255
 
240
256
  export type SignalFunction = (context: SignalContext) => SignalResult | null;
257
+ export type AsyncSignalFunction = (
258
+ context: SignalContext
259
+ ) => SignalResult | null | Promise<SignalResult | null>;
241
260
 
242
261
  export interface PendingOrder {
243
262
  side: Side;
@@ -268,6 +287,15 @@ export interface ExecutionCostOptions {
268
287
  commissionPerUnit?: number;
269
288
  commissionPerOrder?: number;
270
289
  minCommission?: number;
290
+ carry?: {
291
+ longAnnualBps?: number;
292
+ shortAnnualBps?: number;
293
+ };
294
+ funding?: {
295
+ rateBps?: number;
296
+ intervalMs?: number;
297
+ anchorMs?: number;
298
+ };
271
299
  }
272
300
 
273
301
  export interface MfeTrailOptions {
@@ -337,6 +365,11 @@ export interface BacktestOptions {
337
365
  strict?: boolean;
338
366
  }
339
367
 
368
+ export interface BacktestAsyncOptions extends Omit<BacktestOptions, "signal"> {
369
+ signal: AsyncSignalFunction;
370
+ signalBudgetMs?: number;
371
+ }
372
+
340
373
  export interface BacktestTickOptions {
341
374
  ticks: Tick[];
342
375
  symbol?: string;
@@ -357,6 +390,7 @@ export interface BacktestTickOptions {
357
390
  collectEqSeries?: boolean;
358
391
  collectReplay?: boolean;
359
392
  queueFillProbability?: number;
393
+ seed?: string;
360
394
  oco?: OCOOptions;
361
395
  }
362
396
 
@@ -542,6 +576,81 @@ export interface ArtifactPaths {
542
576
  metrics: string | null;
543
577
  }
544
578
 
579
+ export interface LlmSignalOptions {
580
+ resolve: AsyncSignalFunction;
581
+ budgetMs?: number;
582
+ onError?: "skip" | "throw";
583
+ }
584
+
585
+ export interface LlmDecisionLogEntry {
586
+ index: number;
587
+ time?: number;
588
+ close?: number;
589
+ latencyMs: number;
590
+ result?: SignalResult | null;
591
+ error?: string;
592
+ }
593
+
594
+ export interface StrategyParamSpec {
595
+ type: string;
596
+ default?: unknown;
597
+ description?: string;
598
+ [key: string]: unknown;
599
+ }
600
+
601
+ export interface StrategyDefinition {
602
+ description: string;
603
+ params: Record<string, StrategyParamSpec>;
604
+ factory: (params?: Record<string, unknown>) => SignalFunction;
605
+ }
606
+
607
+ export interface StrategySummary {
608
+ name: string;
609
+ description: string;
610
+ params: Record<string, StrategyParamSpec>;
611
+ }
612
+
613
+ export interface ResearchPercentileBands {
614
+ p5: number;
615
+ p25?: number;
616
+ p50: number;
617
+ p75?: number;
618
+ p95: number;
619
+ }
620
+
621
+ export interface MonteCarloResult {
622
+ iterations: number;
623
+ blockSize: number;
624
+ finalEquity: Required<ResearchPercentileBands>;
625
+ maxDrawdown: Required<ResearchPercentileBands>;
626
+ pathBands: Array<Pick<ResearchPercentileBands, "p5" | "p50" | "p95">>;
627
+ probProfit: number;
628
+ }
629
+
630
+ export interface PboResult {
631
+ pbo: number;
632
+ combos: number;
633
+ medianLogit: number;
634
+ }
635
+
636
+ export interface CpcvSplit {
637
+ train: number[];
638
+ test: number[];
639
+ testGroups: number[];
640
+ }
641
+
642
+ export interface OptimizeResultEntry {
643
+ params: Record<string, unknown>;
644
+ metrics?: Partial<BacktestMetrics>;
645
+ error?: string;
646
+ }
647
+
648
+ export interface OptimizeResult {
649
+ results: OptimizeResultEntry[];
650
+ leaderboard: OptimizeResultEntry[];
651
+ best: OptimizeResultEntry | null;
652
+ }
653
+
545
654
  /**
546
655
  * Run a candle-based backtest.
547
656
  *
@@ -550,10 +659,22 @@ export interface ArtifactPaths {
550
659
  * chart-friendly replay frames/events in `replay`.
551
660
  */
552
661
  export function backtest(options: BacktestOptions): BacktestResult;
662
+ export function backtestAsync(options: BacktestAsyncOptions): Promise<BacktestResult>;
553
663
  export function backtestTicks(options: BacktestTickOptions): BacktestResult;
664
+ export function grid(spec?: Record<string, unknown | unknown[]>): Array<Record<string, unknown>>;
665
+ export function optimize(options: {
666
+ candles: Candle[];
667
+ signalModulePath: string;
668
+ parameterSets: Array<Record<string, unknown>>;
669
+ interval?: string;
670
+ backtestOptions?: Partial<BacktestOptions>;
671
+ concurrency?: number;
672
+ scoreBy?: keyof BacktestMetrics | string;
673
+ }): Promise<OptimizeResult>;
554
674
  export function backtestPortfolio(options: {
555
675
  systems: PortfolioSystem[];
556
676
  equity?: number;
677
+ interval?: string;
557
678
  allocation?: "equal" | "weight";
558
679
  collectEqSeries?: boolean;
559
680
  collectReplay?: boolean;
@@ -579,7 +700,65 @@ export function buildMetrics(input: {
579
700
  candles: Candle[];
580
701
  estBarMs: number;
581
702
  eqSeries?: EquityPoint[];
703
+ interval?: string;
704
+ benchmarkReturns?: number[];
582
705
  }): BacktestMetrics;
706
+ export function benchmarkStats(
707
+ strategyReturns: number[],
708
+ benchmarkReturns: number[]
709
+ ): BenchmarkStats;
710
+ export function clampFinite(value: unknown, fallback?: number): number;
711
+ export const BIG_NUMBER: number;
712
+ export function periodsPerYear(interval?: string, estBarMs?: number): number;
713
+
714
+ export class LlmSignal {
715
+ constructor(options: LlmSignalOptions);
716
+ resolve: AsyncSignalFunction;
717
+ budgetMs: number;
718
+ onError: "skip" | "throw";
719
+ log: LlmDecisionLogEntry[];
720
+ signal(context: SignalContext): Promise<SignalResult | null>;
721
+ }
722
+
723
+ export function registerStrategy(name: string, def: StrategyDefinition): void;
724
+ export function listStrategies(): StrategySummary[];
725
+ export function getStrategy(name: string): StrategyDefinition["factory"];
726
+
727
+ export namespace research {
728
+ function monteCarlo(options: {
729
+ tradePnls: number[];
730
+ equityStart?: number;
731
+ iterations?: number;
732
+ blockSize?: number;
733
+ seed?: string | number;
734
+ }): MonteCarloResult;
735
+ function deflatedSharpe(options: {
736
+ sharpe: number;
737
+ sampleSize: number;
738
+ numTrials?: number;
739
+ sharpeStd?: number;
740
+ skew?: number;
741
+ kurtosis?: number;
742
+ }): number;
743
+ function sweepHaircut(options: { numTrials: number; sharpeStd: number }): {
744
+ expectedMaxSharpe: number;
745
+ numTrials: number;
746
+ };
747
+ function probabilityOfBacktestOverfitting(
748
+ performanceMatrix: number[][],
749
+ options?: { groups?: number }
750
+ ): PboResult;
751
+ function combinatorialPurgedSplits(options: {
752
+ nObservations: number;
753
+ nGroups?: number;
754
+ nTestGroups?: number;
755
+ embargo?: number;
756
+ }): CpcvSplit[];
757
+ function combinations(n: number, k: number): number[][];
758
+ function normalCdf(x: number): number;
759
+ function normalPpf(p: number): number;
760
+ function moments(values: number[]): { mean: number; std: number; skew: number; kurtosis: number };
761
+ }
583
762
 
584
763
  export function getHistoricalCandles(options?: HistoricalDataOptions): Promise<Candle[]>;
585
764
  export function backtestHistorical(options: BacktestHistoricalOptions): Promise<BacktestResult>;
package/types/live.d.ts CHANGED
@@ -84,6 +84,8 @@ export interface StoredState {
84
84
  savedAt: number;
85
85
  }
86
86
 
87
+ export const LIVE_EVENTS: string[];
88
+
87
89
  export class EventBus extends import("node:events").EventEmitter {
88
90
  emitEvent(event: string, payload?: Record<string, unknown>): true;
89
91
  onAny(handler: (input: { event: string; payload: Record<string, unknown> }) => void): () => void;
@@ -330,6 +332,21 @@ export class LiveOrchestrator {
330
332
  getStatus(): Record<string, unknown>;
331
333
  }
332
334
 
335
+ export interface DashboardServer {
336
+ start(): Promise<string>;
337
+ close(): Promise<void>;
338
+ server: import("node:http").Server;
339
+ }
340
+
341
+ export function createDashboardServer(options: {
342
+ source: {
343
+ eventBus: EventBus;
344
+ getStatus?: () => Record<string, unknown>;
345
+ };
346
+ port?: number;
347
+ maxBuffer?: number;
348
+ }): DashboardServer;
349
+
333
350
  export function createEventBus(): EventBus;
334
351
  export function createLogger(options?: {
335
352
  level?: "debug" | "info" | "warn" | "error" | "silent";
@@ -380,3 +397,100 @@ export function createLiveOrchestrator(options: {
380
397
  }): LiveOrchestrator;
381
398
 
382
399
  export type { SignalResult };
400
+
401
+ // ── TradingSession / SessionManager ──────────────────────────────────────────
402
+
403
+ export interface TradingSessionOptions {
404
+ id?: string;
405
+ symbol: string;
406
+ interval?: string;
407
+ broker: BrokerAdapter;
408
+ mode?: "paper" | "live";
409
+ equity?: number;
410
+ riskPct?: number;
411
+ maxDailyLossPct?: number;
412
+ maxPositionPct?: number;
413
+ qtyStep?: number;
414
+ minQty?: number;
415
+ maxLeverage?: number;
416
+ eventBus?: EventBus;
417
+ }
418
+
419
+ export interface SessionPlaceOrderOptions {
420
+ side: "long" | "short" | "buy" | "sell";
421
+ type?: "market" | "limit" | "stop" | "stop_limit";
422
+ qty?: number;
423
+ riskPct?: number;
424
+ stop?: number;
425
+ target?: number;
426
+ rr?: number;
427
+ limitPrice?: number;
428
+ }
429
+
430
+ export interface SessionStatus {
431
+ id: string;
432
+ symbol: string;
433
+ interval: string;
434
+ mode: "paper" | "live";
435
+ running: boolean;
436
+ equity: number;
437
+ dayPnl: number;
438
+ lastPrice: number | null;
439
+ positions: BrokerPosition[];
440
+ openOrders: OrderReceipt[];
441
+ risk: {
442
+ halted: boolean;
443
+ haltReason: string | null;
444
+ dayPnl: number;
445
+ dayTrades: number;
446
+ [key: string]: unknown;
447
+ };
448
+ }
449
+
450
+ export class TradingSession {
451
+ constructor(options: TradingSessionOptions);
452
+ readonly id: string;
453
+ readonly symbol: string;
454
+ readonly interval: string;
455
+ readonly mode: "paper" | "live";
456
+ readonly eventBus: EventBus;
457
+ equity: number;
458
+ lastPrice: number | null;
459
+ running: boolean;
460
+ candleBuffer: import("./index.d.ts").Candle[];
461
+ static liveAllowed(): boolean;
462
+ start(): Promise<void>;
463
+ stop(options?: { flatten?: boolean }): Promise<void>;
464
+ pushBar(bar: import("./index.d.ts").Candle): Promise<void>;
465
+ placeOrder(options: SessionPlaceOrderOptions): Promise<OrderReceipt>;
466
+ closePosition(symbol?: string): Promise<OrderReceipt | null>;
467
+ flatten(): Promise<void>;
468
+ cancelOrder(orderId: string): Promise<void>;
469
+ getAccount(): Promise<AccountInfo>;
470
+ getPositions(): Promise<BrokerPosition[]>;
471
+ recentEvents(limit?: number): Array<{ event: string; payload: unknown; t: number }>;
472
+ getStatus(): SessionStatus;
473
+ refresh(): Promise<SessionStatus>;
474
+ }
475
+
476
+ export interface SessionManagerOptions {
477
+ brokerFactory?: (options: Record<string, unknown>) => BrokerAdapter;
478
+ }
479
+
480
+ export interface CreateSessionOptions extends Partial<TradingSessionOptions> {
481
+ id: string;
482
+ symbol: string;
483
+ confirmLive?: boolean;
484
+ broker?: BrokerAdapter;
485
+ }
486
+
487
+ export class SessionManager {
488
+ constructor(options?: SessionManagerOptions);
489
+ create(options: CreateSessionOptions): Promise<TradingSession>;
490
+ get(id: string): TradingSession | null;
491
+ list(): TradingSession[];
492
+ remove(id: string, options?: { flatten?: boolean }): Promise<void>;
493
+ haltAll(): Promise<void>;
494
+ }
495
+
496
+ export function createSessionManager(options?: SessionManagerOptions): SessionManager;
package/types/mcp.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SessionManager } from "./live.d.ts";
3
+
4
+ export function createServer(): McpServer;
5
+ export function startStdioServer(): Promise<McpServer>;
6
+
7
+ export interface McpToolHandler {
8
+ description: string;
9
+ handler(args: Record<string, unknown>): Promise<unknown>;
10
+ }
11
+
12
+ export const mcpTools: Record<string, McpToolHandler>;
13
+ export const researchTools: Record<string, McpToolHandler>;
14
+ export const liveTools: Record<string, McpToolHandler>;
15
+
16
+ /** The shared SessionManager instance used by the MCP server process. */
17
+ export const sessionManager: SessionManager;
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)[];