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 +736 -0
- package/dist/index.d.mts +731 -0
- package/dist/index.d.ts +731 -0
- package/dist/index.js +800 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +776 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +72 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var signalr = require('@microsoft/signalr');
|
|
4
|
+
|
|
5
|
+
// src/errors/base-error.ts
|
|
6
|
+
var TopstepXError = class extends Error {
|
|
7
|
+
constructor(message, code) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.name = this.constructor.name;
|
|
11
|
+
this.timestamp = /* @__PURE__ */ new Date();
|
|
12
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
13
|
+
}
|
|
14
|
+
timestamp;
|
|
15
|
+
toJSON() {
|
|
16
|
+
return {
|
|
17
|
+
name: this.name,
|
|
18
|
+
message: this.message,
|
|
19
|
+
code: this.code,
|
|
20
|
+
timestamp: this.timestamp.toISOString()
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/errors/auth-error.ts
|
|
26
|
+
var AuthenticationError = class extends TopstepXError {
|
|
27
|
+
constructor(message, code) {
|
|
28
|
+
super(message, code);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// src/errors/api-error.ts
|
|
33
|
+
var ApiError = class extends TopstepXError {
|
|
34
|
+
constructor(message, code, endpoint) {
|
|
35
|
+
super(message, code);
|
|
36
|
+
this.endpoint = endpoint;
|
|
37
|
+
}
|
|
38
|
+
toJSON() {
|
|
39
|
+
return {
|
|
40
|
+
...super.toJSON(),
|
|
41
|
+
endpoint: this.endpoint
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/errors/connection-error.ts
|
|
47
|
+
var ConnectionError = class extends TopstepXError {
|
|
48
|
+
constructor(message) {
|
|
49
|
+
super(message);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/auth/auth.service.ts
|
|
54
|
+
var AuthService = class {
|
|
55
|
+
sessionToken = null;
|
|
56
|
+
tokenExpiration = null;
|
|
57
|
+
refreshTimer;
|
|
58
|
+
config;
|
|
59
|
+
constructor(config) {
|
|
60
|
+
this.config = {
|
|
61
|
+
username: config.username,
|
|
62
|
+
apiKey: config.apiKey,
|
|
63
|
+
baseUrl: config.baseUrl ?? "https://api.topstepx.com",
|
|
64
|
+
autoRefresh: config.autoRefresh ?? true,
|
|
65
|
+
tokenValidityHours: config.tokenValidityHours ?? 24
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async login() {
|
|
69
|
+
const request = {
|
|
70
|
+
userName: this.config.username,
|
|
71
|
+
apiKey: this.config.apiKey
|
|
72
|
+
};
|
|
73
|
+
const response = await fetch(`${this.config.baseUrl}/api/Auth/loginKey`, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: {
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
Accept: "text/plain"
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify(request)
|
|
80
|
+
});
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new AuthenticationError(
|
|
83
|
+
`HTTP error during login: ${response.status}`,
|
|
84
|
+
response.status
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const data = await response.json();
|
|
88
|
+
if (!data.success || data.errorCode !== 0) {
|
|
89
|
+
throw new AuthenticationError(
|
|
90
|
+
data.errorMessage ?? "Login failed",
|
|
91
|
+
data.errorCode
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
this.sessionToken = data.token;
|
|
95
|
+
this.setTokenExpiration();
|
|
96
|
+
if (this.config.autoRefresh) {
|
|
97
|
+
this.scheduleTokenRefresh();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async validate() {
|
|
101
|
+
if (!this.sessionToken) return false;
|
|
102
|
+
try {
|
|
103
|
+
const response = await fetch(`${this.config.baseUrl}/api/Auth/validate`, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: {
|
|
106
|
+
"Content-Type": "application/json",
|
|
107
|
+
Accept: "text/plain",
|
|
108
|
+
Authorization: `Bearer ${this.sessionToken}`
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
if (response.ok) {
|
|
112
|
+
this.setTokenExpiration();
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async getSessionToken() {
|
|
121
|
+
if (!this.sessionToken || this.isTokenExpired()) {
|
|
122
|
+
const isValid = await this.validate();
|
|
123
|
+
if (!isValid) {
|
|
124
|
+
await this.login();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!this.sessionToken) {
|
|
128
|
+
throw new AuthenticationError("No active session token available");
|
|
129
|
+
}
|
|
130
|
+
return this.sessionToken;
|
|
131
|
+
}
|
|
132
|
+
get baseUrl() {
|
|
133
|
+
return this.config.baseUrl;
|
|
134
|
+
}
|
|
135
|
+
setTokenExpiration() {
|
|
136
|
+
const expiration = /* @__PURE__ */ new Date();
|
|
137
|
+
expiration.setHours(
|
|
138
|
+
expiration.getHours() + this.config.tokenValidityHours
|
|
139
|
+
);
|
|
140
|
+
this.tokenExpiration = expiration;
|
|
141
|
+
}
|
|
142
|
+
isTokenExpired() {
|
|
143
|
+
if (!this.tokenExpiration) return true;
|
|
144
|
+
const buffer = 5 * 60 * 1e3;
|
|
145
|
+
return Date.now() >= this.tokenExpiration.getTime() - buffer;
|
|
146
|
+
}
|
|
147
|
+
scheduleTokenRefresh() {
|
|
148
|
+
if (this.refreshTimer) {
|
|
149
|
+
clearTimeout(this.refreshTimer);
|
|
150
|
+
}
|
|
151
|
+
if (!this.tokenExpiration) return;
|
|
152
|
+
const refreshTime = this.tokenExpiration.getTime() - Date.now() - 10 * 60 * 1e3;
|
|
153
|
+
if (refreshTime > 0) {
|
|
154
|
+
this.refreshTimer = setTimeout(async () => {
|
|
155
|
+
try {
|
|
156
|
+
const isValid = await this.validate();
|
|
157
|
+
if (!isValid) {
|
|
158
|
+
await this.login();
|
|
159
|
+
}
|
|
160
|
+
this.scheduleTokenRefresh();
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error("Token refresh failed:", error);
|
|
163
|
+
}
|
|
164
|
+
}, refreshTime);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
destroy() {
|
|
168
|
+
if (this.refreshTimer) {
|
|
169
|
+
clearTimeout(this.refreshTimer);
|
|
170
|
+
}
|
|
171
|
+
this.sessionToken = null;
|
|
172
|
+
this.tokenExpiration = null;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// src/rest/http-client.ts
|
|
177
|
+
var HttpClient = class {
|
|
178
|
+
config;
|
|
179
|
+
constructor(config) {
|
|
180
|
+
this.config = {
|
|
181
|
+
baseUrl: config.baseUrl,
|
|
182
|
+
getToken: config.getToken,
|
|
183
|
+
timeout: config.timeout ?? 3e4
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async post(endpoint, data) {
|
|
187
|
+
const token = await this.config.getToken();
|
|
188
|
+
const controller = new AbortController();
|
|
189
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
190
|
+
try {
|
|
191
|
+
const response = await fetch(`${this.config.baseUrl}${endpoint}`, {
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: {
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
Accept: "application/json",
|
|
196
|
+
Authorization: `Bearer ${token}`
|
|
197
|
+
},
|
|
198
|
+
body: JSON.stringify(data),
|
|
199
|
+
signal: controller.signal
|
|
200
|
+
});
|
|
201
|
+
clearTimeout(timeoutId);
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
throw new ApiError(
|
|
204
|
+
`HTTP error: ${response.status} ${response.statusText}`,
|
|
205
|
+
response.status,
|
|
206
|
+
endpoint
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const result = await response.json();
|
|
210
|
+
if (!result.success || result.errorCode !== 0) {
|
|
211
|
+
throw new ApiError(
|
|
212
|
+
result.errorMessage ?? "API request failed",
|
|
213
|
+
result.errorCode,
|
|
214
|
+
endpoint
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
} catch (error) {
|
|
219
|
+
clearTimeout(timeoutId);
|
|
220
|
+
if (error instanceof ApiError) throw error;
|
|
221
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
222
|
+
throw new ApiError("Request timeout", -1, endpoint);
|
|
223
|
+
}
|
|
224
|
+
throw new ApiError(
|
|
225
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
226
|
+
-1,
|
|
227
|
+
endpoint
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// src/rest/account/account.api.ts
|
|
234
|
+
var AccountApi = class {
|
|
235
|
+
constructor(http) {
|
|
236
|
+
this.http = http;
|
|
237
|
+
}
|
|
238
|
+
async search(request) {
|
|
239
|
+
const response = await this.http.post("/api/Account/search", request);
|
|
240
|
+
return response.accounts;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// src/rest/order/order.api.ts
|
|
245
|
+
var OrderApi = class {
|
|
246
|
+
/** @internal */
|
|
247
|
+
constructor(http) {
|
|
248
|
+
this.http = http;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Search historical orders within a date range.
|
|
252
|
+
* @param request - Search parameters including accountId and optional date range
|
|
253
|
+
* @returns Array of orders matching the search criteria
|
|
254
|
+
*/
|
|
255
|
+
async search(request) {
|
|
256
|
+
const response = await this.http.post("/api/Order/search", request);
|
|
257
|
+
return response.orders;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get all currently open (working) orders for an account.
|
|
261
|
+
* @param request - Request containing the accountId
|
|
262
|
+
* @returns Array of open orders
|
|
263
|
+
*/
|
|
264
|
+
async searchOpen(request) {
|
|
265
|
+
const response = await this.http.post("/api/Order/searchOpen", request);
|
|
266
|
+
return response.orders;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Place a new order.
|
|
270
|
+
* @param request - Order details including type, side, size, and prices
|
|
271
|
+
* @returns Response containing the new orderId
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```typescript
|
|
275
|
+
* // Market order
|
|
276
|
+
* await client.orders.place({
|
|
277
|
+
* accountId: 123,
|
|
278
|
+
* contractId: 'CON.F.US.ENQ.M25',
|
|
279
|
+
* type: OrderType.Market,
|
|
280
|
+
* side: OrderSide.Buy,
|
|
281
|
+
* size: 1,
|
|
282
|
+
* });
|
|
283
|
+
*
|
|
284
|
+
* // Limit order
|
|
285
|
+
* await client.orders.place({
|
|
286
|
+
* accountId: 123,
|
|
287
|
+
* contractId: 'CON.F.US.ENQ.M25',
|
|
288
|
+
* type: OrderType.Limit,
|
|
289
|
+
* side: OrderSide.Buy,
|
|
290
|
+
* size: 1,
|
|
291
|
+
* limitPrice: 5000.00,
|
|
292
|
+
* });
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
async place(request) {
|
|
296
|
+
return this.http.post(
|
|
297
|
+
"/api/Order/place",
|
|
298
|
+
request
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Cancel an existing order.
|
|
303
|
+
* @param request - Request containing accountId and orderId to cancel
|
|
304
|
+
* @returns Response indicating success or failure
|
|
305
|
+
*/
|
|
306
|
+
async cancel(request) {
|
|
307
|
+
return this.http.post(
|
|
308
|
+
"/api/Order/cancel",
|
|
309
|
+
request
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Modify an existing order's size or price.
|
|
314
|
+
* @param request - Request containing orderId and fields to modify
|
|
315
|
+
* @returns Response indicating success or failure
|
|
316
|
+
*/
|
|
317
|
+
async modify(request) {
|
|
318
|
+
return this.http.post(
|
|
319
|
+
"/api/Order/modify",
|
|
320
|
+
request
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// src/rest/position/position.api.ts
|
|
326
|
+
var PositionApi = class {
|
|
327
|
+
constructor(http) {
|
|
328
|
+
this.http = http;
|
|
329
|
+
}
|
|
330
|
+
async searchOpen(request) {
|
|
331
|
+
const response = await this.http.post("/api/Position/searchOpen", request);
|
|
332
|
+
return response.positions;
|
|
333
|
+
}
|
|
334
|
+
async close(request) {
|
|
335
|
+
return this.http.post(
|
|
336
|
+
"/api/Position/closeContract",
|
|
337
|
+
request
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
async partialClose(request) {
|
|
341
|
+
return this.http.post("/api/Position/partialCloseContract", request);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// src/rest/trade/trade.api.ts
|
|
346
|
+
var TradeApi = class {
|
|
347
|
+
constructor(http) {
|
|
348
|
+
this.http = http;
|
|
349
|
+
}
|
|
350
|
+
async search(request) {
|
|
351
|
+
const response = await this.http.post("/api/Trade/search", request);
|
|
352
|
+
return response.trades;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// src/rest/contract/contract.api.ts
|
|
357
|
+
var ContractApi = class {
|
|
358
|
+
constructor(http) {
|
|
359
|
+
this.http = http;
|
|
360
|
+
}
|
|
361
|
+
async search(request) {
|
|
362
|
+
const response = await this.http.post("/api/Contract/search", request);
|
|
363
|
+
return response.contracts;
|
|
364
|
+
}
|
|
365
|
+
async searchById(request) {
|
|
366
|
+
const response = await this.http.post("/api/Contract/searchById", request);
|
|
367
|
+
return response.contract;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// src/rest/history/history.api.ts
|
|
372
|
+
var HistoryApi = class {
|
|
373
|
+
constructor(http) {
|
|
374
|
+
this.http = http;
|
|
375
|
+
}
|
|
376
|
+
async retrieveBars(request) {
|
|
377
|
+
const response = await this.http.post("/api/History/retrieveBars", request);
|
|
378
|
+
return response.bars;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
var ConnectionManager = class {
|
|
382
|
+
marketConn = null;
|
|
383
|
+
userConn = null;
|
|
384
|
+
config;
|
|
385
|
+
marketConnectionCallbacks = [];
|
|
386
|
+
userConnectionCallbacks = [];
|
|
387
|
+
constructor(config) {
|
|
388
|
+
this.config = {
|
|
389
|
+
marketHubUrl: config.marketHubUrl,
|
|
390
|
+
userHubUrl: config.userHubUrl,
|
|
391
|
+
auth: config.auth,
|
|
392
|
+
reconnectDelays: config.reconnectDelays ?? [0, 2e3, 5e3, 1e4, 3e4]
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
async connect() {
|
|
396
|
+
const token = await this.config.auth.getSessionToken();
|
|
397
|
+
this.marketConn = new signalr.HubConnectionBuilder().withUrl(`${this.config.marketHubUrl}?access_token=${token}`, {
|
|
398
|
+
skipNegotiation: true,
|
|
399
|
+
transport: signalr.HttpTransportType.WebSockets,
|
|
400
|
+
accessTokenFactory: () => this.config.auth.getSessionToken()
|
|
401
|
+
}).withAutomaticReconnect(this.config.reconnectDelays).build();
|
|
402
|
+
this.userConn = new signalr.HubConnectionBuilder().withUrl(`${this.config.userHubUrl}?access_token=${token}`, {
|
|
403
|
+
skipNegotiation: true,
|
|
404
|
+
transport: signalr.HttpTransportType.WebSockets,
|
|
405
|
+
accessTokenFactory: () => this.config.auth.getSessionToken()
|
|
406
|
+
}).withAutomaticReconnect(this.config.reconnectDelays).build();
|
|
407
|
+
this.marketConnectionCallbacks.forEach((cb) => cb(this.marketConn));
|
|
408
|
+
this.userConnectionCallbacks.forEach((cb) => cb(this.userConn));
|
|
409
|
+
try {
|
|
410
|
+
await this.marketConn.start();
|
|
411
|
+
await this.userConn.start();
|
|
412
|
+
} catch (error) {
|
|
413
|
+
throw new ConnectionError(
|
|
414
|
+
`Failed to establish WebSocket connections: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
async disconnect() {
|
|
419
|
+
await Promise.all([this.marketConn?.stop(), this.userConn?.stop()]);
|
|
420
|
+
this.marketConn = null;
|
|
421
|
+
this.userConn = null;
|
|
422
|
+
}
|
|
423
|
+
get isConnected() {
|
|
424
|
+
return this.marketConn?.state === signalr.HubConnectionState.Connected && this.userConn?.state === signalr.HubConnectionState.Connected;
|
|
425
|
+
}
|
|
426
|
+
get marketConnection() {
|
|
427
|
+
if (!this.marketConn) {
|
|
428
|
+
throw new ConnectionError("Market connection not initialized");
|
|
429
|
+
}
|
|
430
|
+
return this.marketConn;
|
|
431
|
+
}
|
|
432
|
+
get userConnection() {
|
|
433
|
+
if (!this.userConn) {
|
|
434
|
+
throw new ConnectionError("User connection not initialized");
|
|
435
|
+
}
|
|
436
|
+
return this.userConn;
|
|
437
|
+
}
|
|
438
|
+
onMarketConnection(callback) {
|
|
439
|
+
this.marketConnectionCallbacks.push(callback);
|
|
440
|
+
if (this.marketConn) callback(this.marketConn);
|
|
441
|
+
}
|
|
442
|
+
onUserConnection(callback) {
|
|
443
|
+
this.userConnectionCallbacks.push(callback);
|
|
444
|
+
if (this.userConn) callback(this.userConn);
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// src/utils/event-emitter.ts
|
|
449
|
+
var TypedEventEmitter = class {
|
|
450
|
+
listeners = /* @__PURE__ */ new Map();
|
|
451
|
+
on(event, callback) {
|
|
452
|
+
if (!this.listeners.has(event)) {
|
|
453
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
454
|
+
}
|
|
455
|
+
this.listeners.get(event).add(callback);
|
|
456
|
+
return this;
|
|
457
|
+
}
|
|
458
|
+
off(event, callback) {
|
|
459
|
+
this.listeners.get(event)?.delete(callback);
|
|
460
|
+
return this;
|
|
461
|
+
}
|
|
462
|
+
once(event, callback) {
|
|
463
|
+
const onceCallback = ((data) => {
|
|
464
|
+
this.off(event, onceCallback);
|
|
465
|
+
callback(data);
|
|
466
|
+
});
|
|
467
|
+
return this.on(event, onceCallback);
|
|
468
|
+
}
|
|
469
|
+
emit(event, ...args) {
|
|
470
|
+
const callbacks = this.listeners.get(event);
|
|
471
|
+
if (!callbacks || callbacks.size === 0) return false;
|
|
472
|
+
callbacks.forEach((callback) => {
|
|
473
|
+
try {
|
|
474
|
+
if (args.length > 0) {
|
|
475
|
+
callback(args[0]);
|
|
476
|
+
} else {
|
|
477
|
+
callback();
|
|
478
|
+
}
|
|
479
|
+
} catch (error) {
|
|
480
|
+
console.error(`Error in event listener for ${String(event)}:`, error);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
removeAllListeners(event) {
|
|
486
|
+
if (event) {
|
|
487
|
+
this.listeners.delete(event);
|
|
488
|
+
} else {
|
|
489
|
+
this.listeners.clear();
|
|
490
|
+
}
|
|
491
|
+
return this;
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// src/realtime/market/market-hub.ts
|
|
496
|
+
var MarketHub = class extends TypedEventEmitter {
|
|
497
|
+
constructor(connectionManager) {
|
|
498
|
+
super();
|
|
499
|
+
this.connectionManager = connectionManager;
|
|
500
|
+
this.setupEventHandlers();
|
|
501
|
+
}
|
|
502
|
+
subscribedQuotes = /* @__PURE__ */ new Set();
|
|
503
|
+
subscribedTrades = /* @__PURE__ */ new Set();
|
|
504
|
+
subscribedDepth = /* @__PURE__ */ new Set();
|
|
505
|
+
setupEventHandlers() {
|
|
506
|
+
this.connectionManager.onMarketConnection((connection) => {
|
|
507
|
+
connection.on("GatewayQuote", (contractId, data) => {
|
|
508
|
+
this.emit("quote", { contractId, data });
|
|
509
|
+
});
|
|
510
|
+
connection.on(
|
|
511
|
+
"GatewayTrade",
|
|
512
|
+
(contractId, data) => {
|
|
513
|
+
this.emit("trade", { contractId, data });
|
|
514
|
+
}
|
|
515
|
+
);
|
|
516
|
+
connection.on(
|
|
517
|
+
"GatewayDepth",
|
|
518
|
+
(contractId, data) => {
|
|
519
|
+
this.emit("depth", { contractId, data });
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
async subscribe(contractId) {
|
|
525
|
+
await Promise.all([
|
|
526
|
+
this.subscribeQuotes(contractId),
|
|
527
|
+
this.subscribeTrades(contractId),
|
|
528
|
+
this.subscribeDepth(contractId)
|
|
529
|
+
]);
|
|
530
|
+
}
|
|
531
|
+
async unsubscribe(contractId) {
|
|
532
|
+
await Promise.all([
|
|
533
|
+
this.unsubscribeQuotes(contractId),
|
|
534
|
+
this.unsubscribeTrades(contractId),
|
|
535
|
+
this.unsubscribeDepth(contractId)
|
|
536
|
+
]);
|
|
537
|
+
}
|
|
538
|
+
async subscribeQuotes(contractId) {
|
|
539
|
+
if (this.subscribedQuotes.has(contractId)) return;
|
|
540
|
+
const connection = this.connectionManager.marketConnection;
|
|
541
|
+
await connection.invoke("SubscribeContractQuotes", contractId);
|
|
542
|
+
this.subscribedQuotes.add(contractId);
|
|
543
|
+
}
|
|
544
|
+
async unsubscribeQuotes(contractId) {
|
|
545
|
+
if (!this.subscribedQuotes.has(contractId)) return;
|
|
546
|
+
const connection = this.connectionManager.marketConnection;
|
|
547
|
+
await connection.invoke("UnsubscribeContractQuotes", contractId);
|
|
548
|
+
this.subscribedQuotes.delete(contractId);
|
|
549
|
+
}
|
|
550
|
+
async subscribeTrades(contractId) {
|
|
551
|
+
if (this.subscribedTrades.has(contractId)) return;
|
|
552
|
+
const connection = this.connectionManager.marketConnection;
|
|
553
|
+
await connection.invoke("SubscribeContractTrades", contractId);
|
|
554
|
+
this.subscribedTrades.add(contractId);
|
|
555
|
+
}
|
|
556
|
+
async unsubscribeTrades(contractId) {
|
|
557
|
+
if (!this.subscribedTrades.has(contractId)) return;
|
|
558
|
+
const connection = this.connectionManager.marketConnection;
|
|
559
|
+
await connection.invoke("UnsubscribeContractTrades", contractId);
|
|
560
|
+
this.subscribedTrades.delete(contractId);
|
|
561
|
+
}
|
|
562
|
+
async subscribeDepth(contractId) {
|
|
563
|
+
if (this.subscribedDepth.has(contractId)) return;
|
|
564
|
+
const connection = this.connectionManager.marketConnection;
|
|
565
|
+
await connection.invoke("SubscribeContractMarketDepth", contractId);
|
|
566
|
+
this.subscribedDepth.add(contractId);
|
|
567
|
+
}
|
|
568
|
+
async unsubscribeDepth(contractId) {
|
|
569
|
+
if (!this.subscribedDepth.has(contractId)) return;
|
|
570
|
+
const connection = this.connectionManager.marketConnection;
|
|
571
|
+
await connection.invoke("UnsubscribeContractMarketDepth", contractId);
|
|
572
|
+
this.subscribedDepth.delete(contractId);
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// src/realtime/user/user-hub.ts
|
|
577
|
+
var UserHub = class extends TypedEventEmitter {
|
|
578
|
+
constructor(connectionManager) {
|
|
579
|
+
super();
|
|
580
|
+
this.connectionManager = connectionManager;
|
|
581
|
+
this.setupEventHandlers();
|
|
582
|
+
}
|
|
583
|
+
subscribedOrders = /* @__PURE__ */ new Set();
|
|
584
|
+
subscribedPositions = /* @__PURE__ */ new Set();
|
|
585
|
+
subscribedTrades = /* @__PURE__ */ new Set();
|
|
586
|
+
setupEventHandlers() {
|
|
587
|
+
this.connectionManager.onUserConnection((connection) => {
|
|
588
|
+
connection.on("GatewayUserAccount", (data) => {
|
|
589
|
+
this.emit("account", data);
|
|
590
|
+
});
|
|
591
|
+
connection.on("GatewayUserOrder", (data) => {
|
|
592
|
+
this.emit("order", data);
|
|
593
|
+
});
|
|
594
|
+
connection.on("GatewayUserPosition", (data) => {
|
|
595
|
+
this.emit("position", data);
|
|
596
|
+
});
|
|
597
|
+
connection.on("GatewayUserTrade", (data) => {
|
|
598
|
+
this.emit("trade", data);
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
async subscribe(accountId) {
|
|
603
|
+
await Promise.all([
|
|
604
|
+
this.subscribeOrders(accountId),
|
|
605
|
+
this.subscribePositions(accountId),
|
|
606
|
+
this.subscribeTrades(accountId)
|
|
607
|
+
]);
|
|
608
|
+
}
|
|
609
|
+
async unsubscribe(accountId) {
|
|
610
|
+
await Promise.all([
|
|
611
|
+
this.unsubscribeOrders(accountId),
|
|
612
|
+
this.unsubscribePositions(accountId),
|
|
613
|
+
this.unsubscribeTrades(accountId)
|
|
614
|
+
]);
|
|
615
|
+
}
|
|
616
|
+
async subscribeOrders(accountId) {
|
|
617
|
+
if (this.subscribedOrders.has(accountId)) return;
|
|
618
|
+
const connection = this.connectionManager.userConnection;
|
|
619
|
+
await connection.invoke("SubscribeOrders", accountId);
|
|
620
|
+
this.subscribedOrders.add(accountId);
|
|
621
|
+
}
|
|
622
|
+
async unsubscribeOrders(accountId) {
|
|
623
|
+
if (!this.subscribedOrders.has(accountId)) return;
|
|
624
|
+
const connection = this.connectionManager.userConnection;
|
|
625
|
+
await connection.invoke("UnsubscribeOrders", accountId);
|
|
626
|
+
this.subscribedOrders.delete(accountId);
|
|
627
|
+
}
|
|
628
|
+
async subscribePositions(accountId) {
|
|
629
|
+
if (this.subscribedPositions.has(accountId)) return;
|
|
630
|
+
const connection = this.connectionManager.userConnection;
|
|
631
|
+
await connection.invoke("SubscribePositions", accountId);
|
|
632
|
+
this.subscribedPositions.add(accountId);
|
|
633
|
+
}
|
|
634
|
+
async unsubscribePositions(accountId) {
|
|
635
|
+
if (!this.subscribedPositions.has(accountId)) return;
|
|
636
|
+
const connection = this.connectionManager.userConnection;
|
|
637
|
+
await connection.invoke("UnsubscribePositions", accountId);
|
|
638
|
+
this.subscribedPositions.delete(accountId);
|
|
639
|
+
}
|
|
640
|
+
async subscribeTrades(accountId) {
|
|
641
|
+
if (this.subscribedTrades.has(accountId)) return;
|
|
642
|
+
const connection = this.connectionManager.userConnection;
|
|
643
|
+
await connection.invoke("SubscribeTrades", accountId);
|
|
644
|
+
this.subscribedTrades.add(accountId);
|
|
645
|
+
}
|
|
646
|
+
async unsubscribeTrades(accountId) {
|
|
647
|
+
if (!this.subscribedTrades.has(accountId)) return;
|
|
648
|
+
const connection = this.connectionManager.userConnection;
|
|
649
|
+
await connection.invoke("UnsubscribeTrades", accountId);
|
|
650
|
+
this.subscribedTrades.delete(accountId);
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
// src/client.ts
|
|
655
|
+
var TopstepXClient = class extends TypedEventEmitter {
|
|
656
|
+
auth;
|
|
657
|
+
connectionManager;
|
|
658
|
+
httpClient;
|
|
659
|
+
/** Account management API */
|
|
660
|
+
accounts;
|
|
661
|
+
/** Order management API (place, cancel, modify, search) */
|
|
662
|
+
orders;
|
|
663
|
+
/** Position management API (search, close) */
|
|
664
|
+
positions;
|
|
665
|
+
/** Trade history API */
|
|
666
|
+
trades;
|
|
667
|
+
/** Contract/symbol search API */
|
|
668
|
+
contracts;
|
|
669
|
+
/** Historical bars/candles API */
|
|
670
|
+
history;
|
|
671
|
+
/** Real-time market data hub (quotes, trades, depth) */
|
|
672
|
+
marketHub;
|
|
673
|
+
/** Real-time account data hub (orders, positions, trades) */
|
|
674
|
+
userHub;
|
|
675
|
+
constructor(config) {
|
|
676
|
+
super();
|
|
677
|
+
const baseUrl = config.baseUrl ?? "https://api.topstepx.com";
|
|
678
|
+
this.auth = new AuthService({
|
|
679
|
+
username: config.username,
|
|
680
|
+
apiKey: config.apiKey,
|
|
681
|
+
baseUrl,
|
|
682
|
+
autoRefresh: config.autoRefresh ?? true,
|
|
683
|
+
tokenValidityHours: config.tokenValidityHours ?? 24
|
|
684
|
+
});
|
|
685
|
+
this.httpClient = new HttpClient({
|
|
686
|
+
baseUrl,
|
|
687
|
+
getToken: () => this.auth.getSessionToken()
|
|
688
|
+
});
|
|
689
|
+
this.accounts = new AccountApi(this.httpClient);
|
|
690
|
+
this.orders = new OrderApi(this.httpClient);
|
|
691
|
+
this.positions = new PositionApi(this.httpClient);
|
|
692
|
+
this.trades = new TradeApi(this.httpClient);
|
|
693
|
+
this.contracts = new ContractApi(this.httpClient);
|
|
694
|
+
this.history = new HistoryApi(this.httpClient);
|
|
695
|
+
this.connectionManager = new ConnectionManager({
|
|
696
|
+
marketHubUrl: config.marketHubUrl ?? "https://rtc.topstepx.com/hubs/market",
|
|
697
|
+
userHubUrl: config.userHubUrl ?? "https://rtc.topstepx.com/hubs/user",
|
|
698
|
+
auth: this.auth
|
|
699
|
+
});
|
|
700
|
+
this.marketHub = new MarketHub(this.connectionManager);
|
|
701
|
+
this.userHub = new UserHub(this.connectionManager);
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Connect to the TopstepX API.
|
|
705
|
+
* Authenticates and establishes WebSocket connections.
|
|
706
|
+
*/
|
|
707
|
+
async connect() {
|
|
708
|
+
await this.auth.login();
|
|
709
|
+
await this.connectionManager.connect();
|
|
710
|
+
this.emit("connected");
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Disconnect from all services.
|
|
714
|
+
*/
|
|
715
|
+
async disconnect() {
|
|
716
|
+
await this.connectionManager.disconnect();
|
|
717
|
+
this.auth.destroy();
|
|
718
|
+
this.emit("disconnected");
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Check if client is connected.
|
|
722
|
+
*/
|
|
723
|
+
get isConnected() {
|
|
724
|
+
return this.connectionManager.isConnected;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Get the current auth token (for advanced use cases).
|
|
728
|
+
*/
|
|
729
|
+
async getToken() {
|
|
730
|
+
return this.auth.getSessionToken();
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
// src/types/enums.ts
|
|
735
|
+
var OrderType = /* @__PURE__ */ ((OrderType2) => {
|
|
736
|
+
OrderType2[OrderType2["Limit"] = 1] = "Limit";
|
|
737
|
+
OrderType2[OrderType2["Market"] = 2] = "Market";
|
|
738
|
+
OrderType2[OrderType2["Stop"] = 3] = "Stop";
|
|
739
|
+
OrderType2[OrderType2["StopLimit"] = 4] = "StopLimit";
|
|
740
|
+
return OrderType2;
|
|
741
|
+
})(OrderType || {});
|
|
742
|
+
var OrderSide = /* @__PURE__ */ ((OrderSide2) => {
|
|
743
|
+
OrderSide2[OrderSide2["Buy"] = 0] = "Buy";
|
|
744
|
+
OrderSide2[OrderSide2["Sell"] = 1] = "Sell";
|
|
745
|
+
return OrderSide2;
|
|
746
|
+
})(OrderSide || {});
|
|
747
|
+
var OrderStatus = /* @__PURE__ */ ((OrderStatus2) => {
|
|
748
|
+
OrderStatus2[OrderStatus2["Pending"] = 0] = "Pending";
|
|
749
|
+
OrderStatus2[OrderStatus2["Working"] = 1] = "Working";
|
|
750
|
+
OrderStatus2[OrderStatus2["Filled"] = 2] = "Filled";
|
|
751
|
+
OrderStatus2[OrderStatus2["Cancelled"] = 3] = "Cancelled";
|
|
752
|
+
OrderStatus2[OrderStatus2["Rejected"] = 4] = "Rejected";
|
|
753
|
+
OrderStatus2[OrderStatus2["PartiallyFilled"] = 5] = "PartiallyFilled";
|
|
754
|
+
return OrderStatus2;
|
|
755
|
+
})(OrderStatus || {});
|
|
756
|
+
var BarUnit = /* @__PURE__ */ ((BarUnit2) => {
|
|
757
|
+
BarUnit2[BarUnit2["Second"] = 1] = "Second";
|
|
758
|
+
BarUnit2[BarUnit2["Minute"] = 2] = "Minute";
|
|
759
|
+
BarUnit2[BarUnit2["Hour"] = 3] = "Hour";
|
|
760
|
+
BarUnit2[BarUnit2["Day"] = 4] = "Day";
|
|
761
|
+
BarUnit2[BarUnit2["Week"] = 5] = "Week";
|
|
762
|
+
BarUnit2[BarUnit2["Month"] = 6] = "Month";
|
|
763
|
+
return BarUnit2;
|
|
764
|
+
})(BarUnit || {});
|
|
765
|
+
var PositionType = /* @__PURE__ */ ((PositionType2) => {
|
|
766
|
+
PositionType2[PositionType2["Long"] = 0] = "Long";
|
|
767
|
+
PositionType2[PositionType2["Short"] = 1] = "Short";
|
|
768
|
+
return PositionType2;
|
|
769
|
+
})(PositionType || {});
|
|
770
|
+
var TradeType = /* @__PURE__ */ ((TradeType2) => {
|
|
771
|
+
TradeType2[TradeType2["Bid"] = 0] = "Bid";
|
|
772
|
+
TradeType2[TradeType2["Ask"] = 1] = "Ask";
|
|
773
|
+
return TradeType2;
|
|
774
|
+
})(TradeType || {});
|
|
775
|
+
|
|
776
|
+
exports.AccountApi = AccountApi;
|
|
777
|
+
exports.ApiError = ApiError;
|
|
778
|
+
exports.AuthService = AuthService;
|
|
779
|
+
exports.AuthenticationError = AuthenticationError;
|
|
780
|
+
exports.BarUnit = BarUnit;
|
|
781
|
+
exports.ConnectionError = ConnectionError;
|
|
782
|
+
exports.ConnectionManager = ConnectionManager;
|
|
783
|
+
exports.ContractApi = ContractApi;
|
|
784
|
+
exports.HistoryApi = HistoryApi;
|
|
785
|
+
exports.HttpClient = HttpClient;
|
|
786
|
+
exports.MarketHub = MarketHub;
|
|
787
|
+
exports.OrderApi = OrderApi;
|
|
788
|
+
exports.OrderSide = OrderSide;
|
|
789
|
+
exports.OrderStatus = OrderStatus;
|
|
790
|
+
exports.OrderType = OrderType;
|
|
791
|
+
exports.PositionApi = PositionApi;
|
|
792
|
+
exports.PositionType = PositionType;
|
|
793
|
+
exports.TopstepXClient = TopstepXClient;
|
|
794
|
+
exports.TopstepXError = TopstepXError;
|
|
795
|
+
exports.TradeApi = TradeApi;
|
|
796
|
+
exports.TradeType = TradeType;
|
|
797
|
+
exports.TypedEventEmitter = TypedEventEmitter;
|
|
798
|
+
exports.UserHub = UserHub;
|
|
799
|
+
//# sourceMappingURL=index.js.map
|
|
800
|
+
//# sourceMappingURL=index.js.map
|