tradelab 0.4.0 → 1.0.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 (54) hide show
  1. package/README.md +121 -52
  2. package/bin/tradelab.js +340 -49
  3. package/dist/cjs/data.cjs +210 -155
  4. package/dist/cjs/index.cjs +1782 -274
  5. package/dist/cjs/live.cjs +3350 -0
  6. package/docs/README.md +26 -9
  7. package/docs/api-reference.md +89 -26
  8. package/docs/backtest-engine.md +74 -60
  9. package/docs/data-reporting-cli.md +66 -36
  10. package/docs/examples.md +275 -0
  11. package/docs/live-trading.md +186 -0
  12. package/examples/yahooEmaCross.js +1 -6
  13. package/package.json +18 -3
  14. package/src/data/csv.js +24 -14
  15. package/src/data/index.js +1 -5
  16. package/src/data/yahoo.js +6 -19
  17. package/src/engine/backtest.js +137 -144
  18. package/src/engine/backtestTicks.js +481 -0
  19. package/src/engine/barSystemRunner.js +1027 -0
  20. package/src/engine/execution.js +11 -39
  21. package/src/engine/portfolio.js +237 -66
  22. package/src/engine/walkForward.js +132 -13
  23. package/src/index.js +3 -11
  24. package/src/live/broker/alpaca.js +254 -0
  25. package/src/live/broker/binance.js +351 -0
  26. package/src/live/broker/coinbase.js +339 -0
  27. package/src/live/broker/interactiveBrokers.js +123 -0
  28. package/src/live/broker/interface.js +74 -0
  29. package/src/live/clock.js +56 -0
  30. package/src/live/engine/candleAggregator.js +154 -0
  31. package/src/live/engine/liveEngine.js +694 -0
  32. package/src/live/engine/paperEngine.js +453 -0
  33. package/src/live/engine/riskManager.js +185 -0
  34. package/src/live/engine/stateManager.js +112 -0
  35. package/src/live/events.js +48 -0
  36. package/src/live/feed/brokerFeed.js +35 -0
  37. package/src/live/feed/interface.js +28 -0
  38. package/src/live/feed/pollingFeed.js +105 -0
  39. package/src/live/index.js +27 -0
  40. package/src/live/logger.js +82 -0
  41. package/src/live/orchestrator.js +133 -0
  42. package/src/live/storage/interface.js +36 -0
  43. package/src/live/storage/jsonFileStorage.js +112 -0
  44. package/src/metrics/buildMetrics.js +103 -100
  45. package/src/reporting/exportBacktestArtifacts.js +1 -4
  46. package/src/reporting/exportTradesCsv.js +2 -7
  47. package/src/reporting/renderHtmlReport.js +8 -13
  48. package/src/utils/indicators.js +1 -2
  49. package/src/utils/positionSizing.js +16 -2
  50. package/src/utils/time.js +4 -12
  51. package/templates/report.html +23 -9
  52. package/templates/report.js +83 -69
  53. package/types/index.d.ts +98 -4
  54. package/types/live.d.ts +382 -0
@@ -0,0 +1,382 @@
1
+ import type {
2
+ BacktestTrade,
3
+ Candle,
4
+ EquityPoint,
5
+ ExecutionCostOptions,
6
+ OpenPosition,
7
+ PendingOrder,
8
+ SignalFunction,
9
+ SignalResult,
10
+ } from "./index.d.ts";
11
+
12
+ export interface BrokerConfig {
13
+ apiKey?: string;
14
+ apiSecret?: string;
15
+ passphrase?: string;
16
+ paper?: boolean;
17
+ baseUrl?: string;
18
+ wsUrl?: string;
19
+ [key: string]: unknown;
20
+ }
21
+
22
+ export interface AccountInfo {
23
+ equity: number;
24
+ buyingPower: number;
25
+ cash: number;
26
+ currency: string;
27
+ marginUsed?: number;
28
+ }
29
+
30
+ export interface LiveOrder {
31
+ symbol: string;
32
+ side: "buy" | "sell";
33
+ type: "market" | "limit" | "stop" | "stop_limit";
34
+ qty: number;
35
+ limitPrice?: number;
36
+ stopPrice?: number;
37
+ timeInForce?: "day" | "gtc" | "ioc" | "fok";
38
+ clientOrderId?: string;
39
+ }
40
+
41
+ export interface OrderModification {
42
+ qty?: number;
43
+ limitPrice?: number;
44
+ stopPrice?: number;
45
+ }
46
+
47
+ export interface OrderReceipt {
48
+ orderId: string;
49
+ clientOrderId?: string;
50
+ status: "new" | "partially_filled" | "filled" | "canceled" | "rejected" | "expired";
51
+ filledQty: number;
52
+ avgFillPrice?: number;
53
+ filledAt?: number;
54
+ symbol: string;
55
+ side: "buy" | "sell";
56
+ type: string;
57
+ qty: number;
58
+ rejectReason?: string;
59
+ }
60
+
61
+ export interface BrokerPosition {
62
+ symbol: string;
63
+ side: "long" | "short";
64
+ qty: number;
65
+ avgEntry: number;
66
+ marketValue: number;
67
+ unrealizedPnl: number;
68
+ }
69
+
70
+ export interface Subscription {
71
+ unsubscribe(): void;
72
+ }
73
+
74
+ export interface StoredState {
75
+ openPosition: OpenPosition | null;
76
+ pendingOrder: PendingOrder | null;
77
+ equity: number;
78
+ candleBuffer: Candle[];
79
+ strategyState: Record<string, unknown>;
80
+ lastBarTime: number | null;
81
+ dayPnl: number;
82
+ dayTrades: number;
83
+ tradeIdCounter: number;
84
+ savedAt: number;
85
+ }
86
+
87
+ export class EventBus extends import("node:events").EventEmitter {
88
+ emitEvent(event: string, payload?: Record<string, unknown>): true;
89
+ onAny(handler: (input: { event: string; payload: Record<string, unknown> }) => void): () => void;
90
+ }
91
+
92
+ export class LiveLogger {
93
+ constructor(options?: {
94
+ level?: "debug" | "info" | "warn" | "error" | "silent";
95
+ stream?: NodeJS.WritableStream;
96
+ });
97
+ attach(eventBus: EventBus): () => void;
98
+ detach(): void;
99
+ }
100
+
101
+ export class BrokerClock {
102
+ constructor(options?: { warnThresholdMs?: number });
103
+ syncWithBroker(broker: BrokerAdapter): Promise<{
104
+ serverTime: number | null;
105
+ localTime: number;
106
+ offsetMs: number;
107
+ warning: string | null;
108
+ }>;
109
+ now(): number;
110
+ }
111
+
112
+ export class BrokerAdapter extends import("node:events").EventEmitter {
113
+ connect(config?: BrokerConfig): Promise<void>;
114
+ disconnect(): Promise<void>;
115
+ isConnected(): boolean;
116
+ getAccount(): Promise<AccountInfo>;
117
+ getPositions(): Promise<BrokerPosition[]>;
118
+ getServerTime(): Promise<number>;
119
+ submitOrder(order: LiveOrder): Promise<OrderReceipt>;
120
+ cancelOrder(orderId: string): Promise<void>;
121
+ modifyOrder(orderId: string, changes: OrderModification): Promise<OrderReceipt>;
122
+ getOpenOrders(): Promise<OrderReceipt[]>;
123
+ getOrderStatus(orderId: string): Promise<OrderReceipt>;
124
+ subscribeQuotes(symbol: string, handler: (quote: unknown) => void): Promise<Subscription>;
125
+ subscribeTrades(symbol: string, handler: (trade: unknown) => void): Promise<Subscription>;
126
+ subscribeBars(
127
+ symbol: string,
128
+ interval: string,
129
+ handler: (bar: Candle) => void
130
+ ): Promise<Subscription>;
131
+ getHistoricalBars(symbol: string, interval: string, limit: number): Promise<Candle[]>;
132
+ supportsPaperNative(): boolean;
133
+ }
134
+
135
+ export class AlpacaBroker extends BrokerAdapter {}
136
+ export class BinanceBroker extends BrokerAdapter {}
137
+ export class CoinbaseBroker extends BrokerAdapter {}
138
+ export class InteractiveBrokersBroker extends BrokerAdapter {}
139
+
140
+ export class FeedProvider {
141
+ connect(): Promise<void>;
142
+ disconnect(): Promise<void>;
143
+ subscribeBars(
144
+ symbol: string,
145
+ interval: string,
146
+ handler: (bar: Candle) => void
147
+ ): Subscription | Promise<Subscription>;
148
+ subscribeTicks(
149
+ symbol: string,
150
+ handler: (tick: unknown) => void
151
+ ): Subscription | Promise<Subscription>;
152
+ getHistoricalBars(symbol: string, interval: string, count: number): Promise<Candle[]>;
153
+ }
154
+
155
+ export class BrokerFeed extends FeedProvider {
156
+ constructor(options: { broker: BrokerAdapter });
157
+ }
158
+
159
+ export class PollingFeed extends FeedProvider {
160
+ constructor(options: {
161
+ broker: BrokerAdapter;
162
+ pollIntervalMs?: number;
163
+ defaultBarsPerPoll?: number;
164
+ });
165
+ pollOnce(): Promise<void>;
166
+ startPolling(): void;
167
+ stopPolling(): void;
168
+ }
169
+
170
+ export class StorageProvider {
171
+ load(namespace: string): Promise<StoredState | null>;
172
+ save(namespace: string, state: StoredState): Promise<void>;
173
+ appendTrade(namespace: string, trade: BacktestTrade): Promise<void>;
174
+ appendEquityPoint(namespace: string, point: EquityPoint): Promise<void>;
175
+ loadTrades(namespace: string): Promise<BacktestTrade[]>;
176
+ loadEquityCurve(namespace: string): Promise<EquityPoint[]>;
177
+ clear(namespace: string): Promise<void>;
178
+ }
179
+
180
+ export class JsonFileStorage extends StorageProvider {
181
+ constructor(options?: { baseDir?: string });
182
+ }
183
+
184
+ export interface RiskManagerOptions {
185
+ maxDailyLossPct?: number;
186
+ maxDailyLossDollars?: number;
187
+ maxDrawdownPct?: number;
188
+ maxPositions?: number;
189
+ maxPositionPct?: number;
190
+ maxDailyTrades?: number;
191
+ cooldownAfterLossMs?: number;
192
+ allowedSessions?: string;
193
+ allowedWindows?: string;
194
+ }
195
+
196
+ export class RiskManager {
197
+ constructor(options?: RiskManagerOptions);
198
+ initialize(equity: number, timeMs?: number): void;
199
+ update(input: { timeMs: number; equity: number }): void;
200
+ canTrade(input?: { timeMs?: number }): { ok: boolean; reason: string | null };
201
+ canOpenPosition(input?: {
202
+ timeMs?: number;
203
+ positionCount?: number;
204
+ positionValue?: number;
205
+ equity?: number | null;
206
+ }): { ok: boolean; reason: string | null };
207
+ recordTrade(input?: { pnl?: number; timeMs?: number; equity?: number | null }): void;
208
+ halt(reason?: string): void;
209
+ clearHalt(): void;
210
+ getState(): Record<string, unknown>;
211
+ }
212
+
213
+ export class StateManager {
214
+ constructor(options: { storage: StorageProvider });
215
+ load(namespace: string): Promise<StoredState | null>;
216
+ save(namespace: string, state: StoredState): Promise<void>;
217
+ appendTrade(namespace: string, trade: BacktestTrade): Promise<void>;
218
+ appendEquityPoint(namespace: string, point: EquityPoint): Promise<void>;
219
+ loadTrades(namespace: string): Promise<BacktestTrade[]>;
220
+ loadEquityCurve(namespace: string): Promise<EquityPoint[]>;
221
+ clear(namespace: string): Promise<void>;
222
+ reconcile(input: {
223
+ persistedState: StoredState | null;
224
+ brokerPositions?: BrokerPosition[];
225
+ symbol: string;
226
+ }): {
227
+ status: "ok" | "warn" | "error";
228
+ action: "none" | "adopt-broker" | "closed-externally" | "external-position" | "mismatch";
229
+ message: string;
230
+ adoptedPosition: OpenPosition | null;
231
+ mismatch: { persisted: OpenPosition; broker: BrokerPosition } | null;
232
+ };
233
+ }
234
+
235
+ export class CandleAggregator extends import("node:events").EventEmitter {
236
+ constructor(options?: {
237
+ mode?: "stream" | "tick" | "poll";
238
+ interval?: string;
239
+ graceMs?: number;
240
+ session?: string;
241
+ });
242
+ processBar(bar: Candle, options?: { isFinal?: boolean }): void;
243
+ processTick(tick: unknown): void;
244
+ processPolledBars(bars: Candle[]): void;
245
+ forceClose(timeMs?: number): void;
246
+ }
247
+
248
+ export class PaperEngine extends BrokerAdapter {
249
+ constructor(options?: {
250
+ equity?: number;
251
+ currency?: string;
252
+ slippageBps?: number;
253
+ feeBps?: number;
254
+ costs?: ExecutionCostOptions | null;
255
+ qtyStep?: number;
256
+ });
257
+ setHistoricalBars(symbol: string, interval: string, bars: Candle[]): void;
258
+ simulateBar(symbol: string, interval: string, bar: Candle): Promise<void>;
259
+ }
260
+
261
+ export interface LiveEngineOptions {
262
+ id?: string;
263
+ signal: SignalFunction;
264
+ symbol: string;
265
+ interval: string;
266
+ broker: BrokerAdapter;
267
+ brokerConfig?: BrokerConfig;
268
+ feed?: FeedProvider;
269
+ storage?: StorageProvider;
270
+ eventBus?: EventBus;
271
+ equity?: number;
272
+ useBrokerAccountEquity?: boolean;
273
+ mode?: "streaming" | "polling";
274
+ pollIntervalMs?: number;
275
+ paper?: boolean;
276
+ warmupBars?: number;
277
+ riskPct?: number;
278
+ costs?: ExecutionCostOptions | null;
279
+ finalTP_R?: number;
280
+ maxDailyLossPct?: number;
281
+ flattenAtClose?: boolean;
282
+ qtyStep?: number;
283
+ minQty?: number;
284
+ maxLeverage?: number;
285
+ dailyMaxTrades?: number;
286
+ entryChase?: {
287
+ enabled?: boolean;
288
+ afterBars?: number;
289
+ maxSlipR?: number;
290
+ convertOnExpiry?: boolean;
291
+ };
292
+ risk?: RiskManagerOptions;
293
+ logLevel?: "debug" | "info" | "warn" | "error" | "silent";
294
+ }
295
+
296
+ export class LiveEngine {
297
+ constructor(options: LiveEngineOptions);
298
+ readonly eventBus: EventBus;
299
+ start(): Promise<void>;
300
+ stop(options?: { flattenOnShutdown?: boolean }): Promise<void>;
301
+ handleBar(bar: Candle): Promise<void>;
302
+ pollOnce(): Promise<void>;
303
+ getStatus(): Record<string, unknown>;
304
+ }
305
+
306
+ export interface LiveSystemConfig extends Omit<
307
+ LiveEngineOptions,
308
+ "broker" | "feed" | "storage" | "eventBus"
309
+ > {
310
+ id?: string;
311
+ weight?: number;
312
+ }
313
+
314
+ export class LiveOrchestrator {
315
+ constructor(options: {
316
+ systems: LiveSystemConfig[];
317
+ broker: BrokerAdapter;
318
+ brokerConfig?: BrokerConfig;
319
+ feed?: FeedProvider;
320
+ storage?: StorageProvider;
321
+ eventBus?: EventBus;
322
+ equity?: number;
323
+ allocation?: "equal" | "weight";
324
+ maxDailyLossPct?: number;
325
+ risk?: RiskManagerOptions;
326
+ });
327
+ readonly eventBus: EventBus;
328
+ start(): Promise<void>;
329
+ stop(): Promise<void>;
330
+ getStatus(): Record<string, unknown>;
331
+ }
332
+
333
+ export function createEventBus(): EventBus;
334
+ export function createLogger(options?: {
335
+ level?: "debug" | "info" | "warn" | "error" | "silent";
336
+ }): LiveLogger;
337
+ export function createClock(options?: { warnThresholdMs?: number }): BrokerClock;
338
+
339
+ export function createAlpacaBroker(options?: { fetchImpl?: typeof fetch }): AlpacaBroker;
340
+ export function createBinanceBroker(options?: { fetchImpl?: typeof fetch }): BinanceBroker;
341
+ export function createCoinbaseBroker(options?: { fetchImpl?: typeof fetch }): CoinbaseBroker;
342
+ export function createInteractiveBrokersBroker(
343
+ options?: Record<string, unknown>
344
+ ): InteractiveBrokersBroker;
345
+ export function createBrokerFeed(options: { broker: BrokerAdapter }): BrokerFeed;
346
+ export function createPollingFeed(options: {
347
+ broker: BrokerAdapter;
348
+ pollIntervalMs?: number;
349
+ defaultBarsPerPoll?: number;
350
+ }): PollingFeed;
351
+ export function createJsonFileStorage(options?: { baseDir?: string }): JsonFileStorage;
352
+ export function createRiskManager(options?: RiskManagerOptions): RiskManager;
353
+ export function createStateManager(options: { storage: StorageProvider }): StateManager;
354
+ export function createCandleAggregator(options?: {
355
+ mode?: "stream" | "tick" | "poll";
356
+ interval?: string;
357
+ graceMs?: number;
358
+ session?: string;
359
+ }): CandleAggregator;
360
+ export function createPaperEngine(options?: {
361
+ equity?: number;
362
+ currency?: string;
363
+ slippageBps?: number;
364
+ feeBps?: number;
365
+ costs?: ExecutionCostOptions | null;
366
+ qtyStep?: number;
367
+ }): PaperEngine;
368
+ export function createLiveEngine(options: LiveEngineOptions): LiveEngine;
369
+ export function createLiveOrchestrator(options: {
370
+ systems: LiveSystemConfig[];
371
+ broker: BrokerAdapter;
372
+ brokerConfig?: BrokerConfig;
373
+ feed?: FeedProvider;
374
+ storage?: StorageProvider;
375
+ eventBus?: EventBus;
376
+ equity?: number;
377
+ allocation?: "equal" | "weight";
378
+ maxDailyLossPct?: number;
379
+ risk?: RiskManagerOptions;
380
+ }): LiveOrchestrator;
381
+
382
+ export type { SignalResult };