topstepx-api 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.
package/README.md ADDED
@@ -0,0 +1,736 @@
1
+ # topstepx-api
2
+
3
+ A framework-agnostic TypeScript client for the TopstepX trading API with REST and SignalR data feeds via WebSocket.
4
+
5
+ ## Features
6
+
7
+ - Full REST API coverage (accounts, orders, positions, trades, contracts, history)
8
+ - Real-time WebSocket data via SignalR (quotes, trades, market depth)
9
+ - Automatic token management and refresh
10
+ - TypeScript-first with complete type definitions
11
+ - Works with any Node.js framework (Express, Fastify, NestJS, etc.)
12
+ - Dual ESM/CommonJS support
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install topstepx-api
18
+ ```
19
+
20
+ ## Environment Setup
21
+
22
+ Create a `.env` file in your project root:
23
+
24
+ ```env
25
+ TOPSTEP_USERNAME=your_username
26
+ TOPSTEP_API_KEY=your_api_key
27
+ ```
28
+
29
+ Get your API key from your TopstepX account settings.
30
+
31
+ ## Quick Start
32
+
33
+ ```typescript
34
+ import { TopstepXClient, OrderType, OrderSide } from 'topstepx-api';
35
+
36
+ const client = new TopstepXClient({
37
+ username: process.env.TOPSTEP_USERNAME!,
38
+ apiKey: process.env.TOPSTEP_API_KEY!,
39
+ });
40
+
41
+ await client.connect();
42
+
43
+ // Get accounts
44
+ const accounts = await client.accounts.search({ onlyActiveAccounts: true });
45
+ console.log('Accounts:', accounts);
46
+
47
+ // Place a market order
48
+ const order = await client.orders.place({
49
+ accountId: accounts[0].id,
50
+ contractId: 'CON.F.US.ENQ.M25',
51
+ type: OrderType.Market,
52
+ side: OrderSide.Buy,
53
+ size: 1,
54
+ });
55
+ console.log('Order placed:', order.orderId);
56
+
57
+ // Disconnect when done
58
+ await client.disconnect();
59
+ ```
60
+
61
+ ## API Reference
62
+
63
+ ### TopstepXClient
64
+
65
+ The main client class that provides access to all APIs.
66
+
67
+ #### Constructor
68
+
69
+ ```typescript
70
+ const client = new TopstepXClient({
71
+ username: string; // Required: TopstepX username
72
+ apiKey: string; // Required: TopstepX API key
73
+ baseUrl?: string; // Optional: API base URL (default: https://api.topstepx.com)
74
+ marketHubUrl?: string; // Optional: Market WebSocket URL
75
+ userHubUrl?: string; // Optional: User WebSocket URL
76
+ autoRefresh?: boolean; // Optional: Auto-refresh tokens (default: true)
77
+ tokenValidityHours?: number; // Optional: Token validity period (default: 24)
78
+ });
79
+ ```
80
+
81
+ #### Methods
82
+
83
+ | Method | Description |
84
+ |--------|-------------|
85
+ | `connect()` | Authenticate and establish WebSocket connections |
86
+ | `disconnect()` | Close all connections and cleanup |
87
+ | `getToken()` | Get the current session token |
88
+ | `isConnected` | Check if WebSocket connections are active |
89
+
90
+ #### Events
91
+
92
+ ```typescript
93
+ client.on('connected', () => console.log('Connected'));
94
+ client.on('disconnected', () => console.log('Disconnected'));
95
+ client.on('error', (error) => console.error('Error:', error));
96
+ ```
97
+
98
+ ---
99
+
100
+ ### Accounts API
101
+
102
+ Access via `client.accounts`
103
+
104
+ #### search
105
+
106
+ Search for accounts.
107
+
108
+ ```typescript
109
+ const accounts = await client.accounts.search({
110
+ onlyActiveAccounts: boolean;
111
+ });
112
+
113
+ // Returns: Account[]
114
+ interface Account {
115
+ id: number;
116
+ name: string;
117
+ canTrade: boolean;
118
+ isVisible: boolean;
119
+ }
120
+ ```
121
+
122
+ ---
123
+
124
+ ### Orders API
125
+
126
+ Access via `client.orders`
127
+
128
+ #### place
129
+
130
+ Place a new order.
131
+
132
+ ```typescript
133
+ import { OrderType, OrderSide } from 'topstepx-api';
134
+
135
+ const result = await client.orders.place({
136
+ accountId: number;
137
+ contractId: string;
138
+ type: OrderType; // Market, Limit, Stop, StopLimit
139
+ side: OrderSide; // Buy, Sell
140
+ size: number;
141
+ limitPrice?: number; // Required for Limit/StopLimit
142
+ stopPrice?: number; // Required for Stop/StopLimit
143
+ trailPrice?: number; // Optional trailing stop price
144
+ customTag?: string; // Optional custom identifier
145
+ linkedOrderId?: number; // Optional linked order (OCO)
146
+ });
147
+
148
+ // Returns: { orderId: number, success: boolean, ... }
149
+ ```
150
+
151
+ #### cancel
152
+
153
+ Cancel an existing order.
154
+
155
+ ```typescript
156
+ await client.orders.cancel({
157
+ accountId: number;
158
+ orderId: number;
159
+ });
160
+ ```
161
+
162
+ #### modify
163
+
164
+ Modify an existing order.
165
+
166
+ ```typescript
167
+ await client.orders.modify({
168
+ accountId: number;
169
+ orderId: number;
170
+ size?: number;
171
+ limitPrice?: number;
172
+ stopPrice?: number;
173
+ trailPrice?: number;
174
+ });
175
+ ```
176
+
177
+ #### search
178
+
179
+ Search historical orders.
180
+
181
+ ```typescript
182
+ const orders = await client.orders.search({
183
+ accountId: number;
184
+ startTimestamp?: string; // ISO 8601 format
185
+ endTimestamp?: string;
186
+ });
187
+
188
+ // Returns: Order[]
189
+ interface Order {
190
+ id: number;
191
+ accountId: number;
192
+ contractId: string;
193
+ creationTimestamp: string;
194
+ updateTimestamp: string | null;
195
+ status: OrderStatus;
196
+ type: OrderType;
197
+ side: OrderSide;
198
+ size: number;
199
+ limitPrice: number | null;
200
+ stopPrice: number | null;
201
+ }
202
+ ```
203
+
204
+ #### searchOpen
205
+
206
+ Get currently open orders.
207
+
208
+ ```typescript
209
+ const openOrders = await client.orders.searchOpen({
210
+ accountId: number;
211
+ });
212
+ ```
213
+
214
+ ---
215
+
216
+ ### Positions API
217
+
218
+ Access via `client.positions`
219
+
220
+ #### searchOpen
221
+
222
+ Get open positions.
223
+
224
+ ```typescript
225
+ const positions = await client.positions.searchOpen({
226
+ accountId: number;
227
+ });
228
+
229
+ // Returns: Position[]
230
+ interface Position {
231
+ id: number;
232
+ accountId: number;
233
+ contractId: string;
234
+ creationTimestamp: string;
235
+ type: PositionType; // Long, Short
236
+ size: number;
237
+ averagePrice: number;
238
+ }
239
+ ```
240
+
241
+ #### close
242
+
243
+ Close a position entirely.
244
+
245
+ ```typescript
246
+ await client.positions.close({
247
+ accountId: number;
248
+ contractId: string;
249
+ });
250
+ ```
251
+
252
+ #### partialClose
253
+
254
+ Partially close a position.
255
+
256
+ ```typescript
257
+ await client.positions.partialClose({
258
+ accountId: number;
259
+ contractId: string;
260
+ size: number; // Number of contracts to close
261
+ });
262
+ ```
263
+
264
+ ---
265
+
266
+ ### Trades API
267
+
268
+ Access via `client.trades`
269
+
270
+ #### search
271
+
272
+ Search trade history.
273
+
274
+ ```typescript
275
+ const trades = await client.trades.search({
276
+ accountId: number;
277
+ startTimestamp: string; // ISO 8601 format
278
+ endTimestamp: string;
279
+ });
280
+
281
+ // Returns: Trade[]
282
+ interface Trade {
283
+ id: number;
284
+ accountId: number;
285
+ contractId: string;
286
+ creationTimestamp: string;
287
+ price: number;
288
+ profitAndLoss: number | null;
289
+ fees: number;
290
+ side: OrderSide;
291
+ size: number;
292
+ voided: boolean;
293
+ orderId: number;
294
+ }
295
+ ```
296
+
297
+ ---
298
+
299
+ ### Contracts API
300
+
301
+ Access via `client.contracts`
302
+
303
+ #### search
304
+
305
+ Search for contracts/symbols.
306
+
307
+ ```typescript
308
+ const contracts = await client.contracts.search({
309
+ searchText: string; // e.g., "ES", "NQ", "CL"
310
+ live: boolean; // true for live, false for sim
311
+ });
312
+
313
+ // Returns: Contract[]
314
+ interface Contract {
315
+ id: string;
316
+ name: string;
317
+ description: string;
318
+ tickSize: number;
319
+ tickValue: number;
320
+ activeContract: boolean;
321
+ }
322
+ ```
323
+
324
+ #### searchById
325
+
326
+ Get a specific contract by ID.
327
+
328
+ ```typescript
329
+ const contract = await client.contracts.searchById({
330
+ contractId: string; // e.g., "CON.F.US.ENQ.M25"
331
+ live: boolean;
332
+ });
333
+
334
+ // Returns: Contract | null
335
+ ```
336
+
337
+ ---
338
+
339
+ ### History API
340
+
341
+ Access via `client.history`
342
+
343
+ #### retrieveBars
344
+
345
+ Get historical OHLCV bars.
346
+
347
+ ```typescript
348
+ import { BarUnit } from 'topstepx-api';
349
+
350
+ const bars = await client.history.retrieveBars({
351
+ contractId: string;
352
+ live: boolean;
353
+ startTime: string; // ISO 8601 format
354
+ endTime: string;
355
+ unit: BarUnit; // Second, Minute, Hour, Day, Week, Month
356
+ unitNumber: number; // e.g., 5 for 5-minute bars
357
+ limit: number; // Max bars to return
358
+ includePartialBar: boolean;
359
+ });
360
+
361
+ // Returns: Bar[]
362
+ interface Bar {
363
+ t: string; // timestamp
364
+ o: number; // open
365
+ h: number; // high
366
+ l: number; // low
367
+ c: number; // close
368
+ v: number; // volume
369
+ }
370
+ ```
371
+
372
+ ---
373
+
374
+ ### Market Hub (Real-time Market Data)
375
+
376
+ Access via `client.marketHub`
377
+
378
+ Subscribe to real-time market data via WebSocket.
379
+
380
+ #### Subscribing
381
+
382
+ ```typescript
383
+ // Subscribe to all market data for a contract
384
+ await client.marketHub.subscribe('CON.F.US.ENQ.M25');
385
+
386
+ // Or subscribe selectively
387
+ await client.marketHub.subscribeQuotes('CON.F.US.ENQ.M25');
388
+ await client.marketHub.subscribeTrades('CON.F.US.ENQ.M25');
389
+ await client.marketHub.subscribeDepth('CON.F.US.ENQ.M25');
390
+ ```
391
+
392
+ #### Unsubscribing
393
+
394
+ ```typescript
395
+ await client.marketHub.unsubscribe('CON.F.US.ENQ.M25');
396
+
397
+ // Or unsubscribe selectively
398
+ await client.marketHub.unsubscribeQuotes('CON.F.US.ENQ.M25');
399
+ await client.marketHub.unsubscribeTrades('CON.F.US.ENQ.M25');
400
+ await client.marketHub.unsubscribeDepth('CON.F.US.ENQ.M25');
401
+ ```
402
+
403
+ #### Events
404
+
405
+ ```typescript
406
+ // Quote updates
407
+ client.marketHub.on('quote', ({ contractId, data }) => {
408
+ for (const quote of data) {
409
+ console.log(`${contractId}: Bid ${quote.bestBid} / Ask ${quote.bestAsk}`);
410
+ }
411
+ });
412
+
413
+ // Trade updates
414
+ client.marketHub.on('trade', ({ contractId, data }) => {
415
+ for (const trade of data) {
416
+ console.log(`${contractId}: ${trade.volume} @ ${trade.price}`);
417
+ }
418
+ });
419
+
420
+ // Market depth updates
421
+ client.marketHub.on('depth', ({ contractId, data }) => {
422
+ for (const level of data) {
423
+ console.log(`${contractId}: ${level.volume} @ ${level.price}`);
424
+ }
425
+ });
426
+ ```
427
+
428
+ #### Event Types
429
+
430
+ ```typescript
431
+ interface Quote {
432
+ symbol: string;
433
+ lastPrice: number;
434
+ bestBid: number;
435
+ bestAsk: number;
436
+ change: number;
437
+ changePercent: number;
438
+ volume: number;
439
+ lastUpdated: string;
440
+ timestamp: string;
441
+ }
442
+
443
+ interface MarketTrade {
444
+ symbolId: string;
445
+ price: number;
446
+ timestamp: string;
447
+ type: 0 | 1; // 0 = Bid, 1 = Ask
448
+ volume: number;
449
+ }
450
+
451
+ interface MarketDepth {
452
+ price: number;
453
+ volume: number;
454
+ currentVolume: number;
455
+ type: number;
456
+ timestamp: string;
457
+ }
458
+ ```
459
+
460
+ ---
461
+
462
+ ### User Hub (Real-time Account Data)
463
+
464
+ Access via `client.userHub`
465
+
466
+ Subscribe to real-time account updates via WebSocket.
467
+
468
+ #### Subscribing
469
+
470
+ ```typescript
471
+ // Subscribe to all account updates
472
+ await client.userHub.subscribe(accountId);
473
+
474
+ // Or subscribe selectively
475
+ await client.userHub.subscribeOrders(accountId);
476
+ await client.userHub.subscribePositions(accountId);
477
+ await client.userHub.subscribeTrades(accountId);
478
+ ```
479
+
480
+ #### Unsubscribing
481
+
482
+ ```typescript
483
+ await client.userHub.unsubscribe(accountId);
484
+ ```
485
+
486
+ #### Events
487
+
488
+ ```typescript
489
+ // Order updates
490
+ client.userHub.on('order', (order) => {
491
+ console.log(`Order ${order.id}: ${order.status}`);
492
+ });
493
+
494
+ // Position updates
495
+ client.userHub.on('position', (position) => {
496
+ console.log(`Position: ${position.size} contracts @ ${position.averagePrice}`);
497
+ });
498
+
499
+ // Trade executions
500
+ client.userHub.on('trade', (trade) => {
501
+ console.log(`Trade: ${trade.size} @ ${trade.price}, P&L: ${trade.profitAndLoss}`);
502
+ });
503
+
504
+ // Account updates
505
+ client.userHub.on('account', (account) => {
506
+ console.log(`Account ${account.name}: Can trade = ${account.canTrade}`);
507
+ });
508
+ ```
509
+
510
+ ---
511
+
512
+ ## Enums
513
+
514
+ ```typescript
515
+ import {
516
+ OrderType,
517
+ OrderSide,
518
+ OrderStatus,
519
+ BarUnit,
520
+ PositionType,
521
+ TradeType,
522
+ } from 'topstepx-api';
523
+
524
+ // OrderType
525
+ OrderType.Limit // 1
526
+ OrderType.Market // 2
527
+ OrderType.Stop // 3
528
+ OrderType.StopLimit // 4
529
+
530
+ // OrderSide
531
+ OrderSide.Buy // 0
532
+ OrderSide.Sell // 1
533
+
534
+ // OrderStatus
535
+ OrderStatus.Pending // 0
536
+ OrderStatus.Working // 1
537
+ OrderStatus.Filled // 2
538
+ OrderStatus.Cancelled // 3
539
+ OrderStatus.Rejected // 4
540
+ OrderStatus.PartiallyFilled // 5
541
+
542
+ // BarUnit
543
+ BarUnit.Second // 1
544
+ BarUnit.Minute // 2
545
+ BarUnit.Hour // 3
546
+ BarUnit.Day // 4
547
+ BarUnit.Week // 5
548
+ BarUnit.Month // 6
549
+
550
+ // PositionType
551
+ PositionType.Long // 0
552
+ PositionType.Short // 1
553
+ ```
554
+
555
+ ---
556
+
557
+ ## Error Handling
558
+
559
+ The library provides typed errors for different failure scenarios:
560
+
561
+ ```typescript
562
+ import {
563
+ TopstepXError,
564
+ AuthenticationError,
565
+ ApiError,
566
+ ConnectionError,
567
+ } from 'topstepx-api';
568
+
569
+ try {
570
+ await client.connect();
571
+ await client.orders.place({ ... });
572
+ } catch (error) {
573
+ if (error instanceof AuthenticationError) {
574
+ console.error('Auth failed:', error.message, error.code);
575
+ } else if (error instanceof ApiError) {
576
+ console.error(`API error on ${error.endpoint}:`, error.message);
577
+ } else if (error instanceof ConnectionError) {
578
+ console.error('WebSocket error:', error.message);
579
+ }
580
+ }
581
+ ```
582
+
583
+ All errors extend `TopstepXError` and include:
584
+ - `message` - Error description
585
+ - `code` - Error code (if applicable)
586
+ - `timestamp` - When the error occurred
587
+ - `toJSON()` - Serialize for logging
588
+
589
+ ---
590
+
591
+ ## Complete Example
592
+
593
+ ```typescript
594
+ import {
595
+ TopstepXClient,
596
+ OrderType,
597
+ OrderSide,
598
+ BarUnit,
599
+ ApiError,
600
+ } from 'topstepx-api';
601
+ import 'dotenv/config';
602
+
603
+ async function main() {
604
+ const client = new TopstepXClient({
605
+ username: process.env.TOPSTEP_USERNAME!,
606
+ apiKey: process.env.TOPSTEP_API_KEY!,
607
+ });
608
+
609
+ try {
610
+ // Connect
611
+ await client.connect();
612
+ console.log('Connected to TopstepX');
613
+
614
+ // Get accounts
615
+ const accounts = await client.accounts.search({ onlyActiveAccounts: true });
616
+ const account = accounts[0];
617
+ console.log(`Using account: ${account.name} (${account.id})`);
618
+
619
+ // Search for ES contract
620
+ const contracts = await client.contracts.search({
621
+ searchText: 'ES',
622
+ live: false,
623
+ });
624
+ const esContract = contracts.find(c => c.activeContract);
625
+ console.log(`Found contract: ${esContract?.id}`);
626
+
627
+ // Get historical data
628
+ const endTime = new Date();
629
+ const startTime = new Date(endTime.getTime() - 24 * 60 * 60 * 1000);
630
+
631
+ const bars = await client.history.retrieveBars({
632
+ contractId: esContract!.id,
633
+ live: false,
634
+ startTime: startTime.toISOString(),
635
+ endTime: endTime.toISOString(),
636
+ unit: BarUnit.Hour,
637
+ unitNumber: 1,
638
+ limit: 24,
639
+ includePartialBar: false,
640
+ });
641
+ console.log(`Retrieved ${bars.length} hourly bars`);
642
+
643
+ // Subscribe to real-time quotes
644
+ client.marketHub.on('quote', ({ contractId, data }) => {
645
+ const quote = data[0];
646
+ console.log(`${contractId}: ${quote.bestBid} / ${quote.bestAsk}`);
647
+ });
648
+ await client.marketHub.subscribeQuotes(esContract!.id);
649
+
650
+ // Subscribe to account updates
651
+ client.userHub.on('order', (order) => {
652
+ console.log(`Order update: ${order.id} - ${order.status}`);
653
+ });
654
+ await client.userHub.subscribe(account.id);
655
+
656
+ // Place a limit order
657
+ const order = await client.orders.place({
658
+ accountId: account.id,
659
+ contractId: esContract!.id,
660
+ type: OrderType.Limit,
661
+ side: OrderSide.Buy,
662
+ size: 1,
663
+ limitPrice: bars[bars.length - 1].c - 10, // 10 points below last close
664
+ });
665
+ console.log(`Placed order: ${order.orderId}`);
666
+
667
+ // Check open orders
668
+ const openOrders = await client.orders.searchOpen({ accountId: account.id });
669
+ console.log(`Open orders: ${openOrders.length}`);
670
+
671
+ // Cancel the order
672
+ await client.orders.cancel({
673
+ accountId: account.id,
674
+ orderId: order.orderId,
675
+ });
676
+ console.log('Order cancelled');
677
+
678
+ // Keep running for real-time updates
679
+ await new Promise(resolve => setTimeout(resolve, 30000));
680
+
681
+ } catch (error) {
682
+ if (error instanceof ApiError) {
683
+ console.error(`API Error [${error.endpoint}]: ${error.message}`);
684
+ } else {
685
+ console.error('Error:', error);
686
+ }
687
+ } finally {
688
+ await client.disconnect();
689
+ console.log('Disconnected');
690
+ }
691
+ }
692
+
693
+ main();
694
+ ```
695
+
696
+ ---
697
+
698
+ ## TypeScript Support
699
+
700
+ All types are exported for full TypeScript support:
701
+
702
+ ```typescript
703
+ import type {
704
+ // Config
705
+ TopstepXClientConfig,
706
+ TopstepXClientEvents,
707
+
708
+ // REST types
709
+ Account,
710
+ Order,
711
+ Position,
712
+ Trade,
713
+ Contract,
714
+ Bar,
715
+
716
+ // Request types
717
+ PlaceOrderRequest,
718
+ ModifyOrderRequest,
719
+ SearchOrdersRequest,
720
+ RetrieveBarsRequest,
721
+
722
+ // WebSocket types
723
+ Quote,
724
+ MarketTrade,
725
+ MarketDepth,
726
+ OrderUpdate,
727
+ PositionUpdate,
728
+ TradeUpdate,
729
+ } from 'topstepx-api';
730
+ ```
731
+
732
+ ---
733
+
734
+ ## License
735
+
736
+ MIT