@finatic/client 0.0.142 → 0.9.1
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/CHANGELOG.md +16 -0
- package/LICENSE +39 -0
- package/README.md +54 -425
- package/dist/index.d.ts +7329 -1579
- package/dist/index.js +8110 -6114
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8047 -6085
- package/dist/index.mjs.map +1 -1
- package/package.json +77 -33
- package/dist/types/core/client/ApiClient.d.ts +0 -270
- package/dist/types/core/client/FinaticConnect.d.ts +0 -332
- package/dist/types/core/portal/PortalUI.d.ts +0 -37
- package/dist/types/index.d.ts +0 -12
- package/dist/types/lib/logger/index.d.ts +0 -2
- package/dist/types/lib/logger/logger.d.ts +0 -4
- package/dist/types/lib/logger/logger.types.d.ts +0 -28
- package/dist/types/mocks/MockApiClient.d.ts +0 -171
- package/dist/types/mocks/MockDataProvider.d.ts +0 -139
- package/dist/types/mocks/MockFactory.d.ts +0 -53
- package/dist/types/mocks/utils.d.ts +0 -24
- package/dist/types/themes/portalPresets.d.ts +0 -9
- package/dist/types/types/api/auth.d.ts +0 -93
- package/dist/types/types/api/broker.d.ts +0 -421
- package/dist/types/types/api/core.d.ts +0 -46
- package/dist/types/types/api/errors.d.ts +0 -31
- package/dist/types/types/api/orders.d.ts +0 -39
- package/dist/types/types/api/portfolio.d.ts +0 -55
- package/dist/types/types/common/pagination.d.ts +0 -33
- package/dist/types/types/connect.d.ts +0 -58
- package/dist/types/types/index.d.ts +0 -13
- package/dist/types/types/portal.d.ts +0 -204
- package/dist/types/types/ui/theme.d.ts +0 -104
- package/dist/types/utils/brokerUtils.d.ts +0 -30
- package/dist/types/utils/errors.d.ts +0 -45
- package/dist/types/utils/events.d.ts +0 -12
- package/dist/types/utils/themeUtils.d.ts +0 -34
- package/src/core/client/ApiClient.ts +0 -2004
- package/src/core/client/FinaticConnect.ts +0 -1606
- package/src/core/portal/PortalUI.ts +0 -335
- package/src/index.d.ts +0 -23
- package/src/index.ts +0 -100
- package/src/lib/logger/index.ts +0 -3
- package/src/lib/logger/logger.ts +0 -332
- package/src/lib/logger/logger.types.ts +0 -34
- package/src/mocks/MockApiClient.ts +0 -1058
- package/src/mocks/MockDataProvider.ts +0 -986
- package/src/mocks/MockFactory.ts +0 -97
- package/src/mocks/utils.ts +0 -133
- package/src/themes/portalPresets.ts +0 -1307
- package/src/types/api/auth.ts +0 -112
- package/src/types/api/broker.ts +0 -461
- package/src/types/api/core.ts +0 -53
- package/src/types/api/errors.ts +0 -35
- package/src/types/api/orders.ts +0 -45
- package/src/types/api/portfolio.ts +0 -59
- package/src/types/common/pagination.ts +0 -164
- package/src/types/connect.ts +0 -56
- package/src/types/index.ts +0 -25
- package/src/types/portal.ts +0 -214
- package/src/types/ui/theme.ts +0 -105
- package/src/utils/brokerUtils.ts +0 -104
- package/src/utils/errors.ts +0 -104
- package/src/utils/events.ts +0 -66
- package/src/utils/themeUtils.ts +0 -165
|
@@ -1,2004 +0,0 @@
|
|
|
1
|
-
import { Order } from '../../types/api/orders';
|
|
2
|
-
import {
|
|
3
|
-
BrokerInfo,
|
|
4
|
-
BrokerAccount,
|
|
5
|
-
BrokerOrder,
|
|
6
|
-
BrokerPosition,
|
|
7
|
-
BrokerBalance,
|
|
8
|
-
BrokerDataOptions,
|
|
9
|
-
DisconnectCompanyResponse,
|
|
10
|
-
} from '../../types/api/broker';
|
|
11
|
-
import { BrokerOrderParams, BrokerExtras } from '../../types/api/broker';
|
|
12
|
-
import { CryptoOrderOptions, OptionsOrderOptions, OrderResponse } from '../../types/api/orders';
|
|
13
|
-
import { BrokerConnection } from '../../types/api/broker';
|
|
14
|
-
import {
|
|
15
|
-
OrdersFilter,
|
|
16
|
-
PositionsFilter,
|
|
17
|
-
AccountsFilter,
|
|
18
|
-
BalancesFilter,
|
|
19
|
-
OrderFill,
|
|
20
|
-
OrderEvent,
|
|
21
|
-
OrderGroup,
|
|
22
|
-
PositionLot,
|
|
23
|
-
PositionLotFill,
|
|
24
|
-
OrderFillsFilter,
|
|
25
|
-
OrderEventsFilter,
|
|
26
|
-
OrderGroupsFilter,
|
|
27
|
-
PositionLotsFilter,
|
|
28
|
-
PositionLotFillsFilter,
|
|
29
|
-
} from '../../types/api/broker';
|
|
30
|
-
import { TradingContext } from '../../types/api/orders';
|
|
31
|
-
import { ApiPaginationInfo, PaginatedResult } from '../../types/common/pagination';
|
|
32
|
-
import { ApiResponse } from '../../types/api/core';
|
|
33
|
-
import { PortalUrlResponse } from '../../types/api/core';
|
|
34
|
-
import { setupLogger, buildLoggerExtra, LoggerExtra } from '../../lib/logger';
|
|
35
|
-
import {
|
|
36
|
-
DeviceInfo,
|
|
37
|
-
SessionState,
|
|
38
|
-
TokenInfo,
|
|
39
|
-
SessionResponse,
|
|
40
|
-
SessionResponseData,
|
|
41
|
-
OtpRequestResponse,
|
|
42
|
-
OtpVerifyResponse,
|
|
43
|
-
SessionValidationResponse,
|
|
44
|
-
SessionAuthenticateResponse,
|
|
45
|
-
UserToken,
|
|
46
|
-
RefreshTokenRequest,
|
|
47
|
-
RefreshTokenResponse,
|
|
48
|
-
} from '../../types/api/auth';
|
|
49
|
-
import {
|
|
50
|
-
ApiError,
|
|
51
|
-
SessionError,
|
|
52
|
-
AuthenticationError,
|
|
53
|
-
AuthorizationError,
|
|
54
|
-
RateLimitError,
|
|
55
|
-
CompanyAccessError,
|
|
56
|
-
OrderError,
|
|
57
|
-
OrderValidationError,
|
|
58
|
-
TradingNotEnabledError,
|
|
59
|
-
} from '../../utils/errors';
|
|
60
|
-
// Supabase import removed - SDK no longer depends on Supabase
|
|
61
|
-
|
|
62
|
-
export class ApiClient {
|
|
63
|
-
private readonly baseUrl: string;
|
|
64
|
-
protected readonly deviceInfo?: DeviceInfo;
|
|
65
|
-
protected currentSessionState: SessionState | null = null;
|
|
66
|
-
protected currentSessionId: string | null = null;
|
|
67
|
-
private tradingContext: TradingContext = {};
|
|
68
|
-
|
|
69
|
-
// Session management (no Supabase needed)
|
|
70
|
-
|
|
71
|
-
// Session and company context
|
|
72
|
-
private companyId: string | null = null;
|
|
73
|
-
private csrfToken: string | null = null;
|
|
74
|
-
private readonly logger = setupLogger('FinaticClientSDK.ApiClient', undefined, {
|
|
75
|
-
codebase: 'FinaticClientSDK',
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
private buildLoggerExtra(functionName: string, metadata?: Record<string, unknown>): LoggerExtra {
|
|
79
|
-
return {
|
|
80
|
-
module: 'ApiClient',
|
|
81
|
-
function: functionName,
|
|
82
|
-
...(metadata ? buildLoggerExtra(metadata) : {}),
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
constructor(baseUrl: string, deviceInfo?: DeviceInfo) {
|
|
87
|
-
this.baseUrl = baseUrl;
|
|
88
|
-
this.deviceInfo = deviceInfo;
|
|
89
|
-
// Ensure baseUrl doesn't end with a slash
|
|
90
|
-
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
91
|
-
// Append /api/v1 if not already present
|
|
92
|
-
if (!this.baseUrl.includes('/api/v1')) {
|
|
93
|
-
this.baseUrl = `${this.baseUrl}/api/v1`;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// No Supabase initialization needed - SDK is clean
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Supabase initialization removed - SDK no longer depends on Supabase
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Set session context (session ID, company ID, CSRF token)
|
|
103
|
-
*/
|
|
104
|
-
setSessionContext(sessionId: string, companyId: string, csrfToken?: string): void {
|
|
105
|
-
this.currentSessionId = sessionId;
|
|
106
|
-
this.companyId = companyId;
|
|
107
|
-
this.csrfToken = csrfToken || null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Get the current session ID
|
|
112
|
-
*/
|
|
113
|
-
getCurrentSessionId(): string | null {
|
|
114
|
-
return this.currentSessionId;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Get the current company ID
|
|
119
|
-
*/
|
|
120
|
-
getCurrentCompanyId(): string | null {
|
|
121
|
-
return this.companyId;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Get the current CSRF token
|
|
126
|
-
*/
|
|
127
|
-
getCurrentCsrfToken(): string | null {
|
|
128
|
-
return this.csrfToken;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Get a valid access token (session-based auth - no tokens needed)
|
|
133
|
-
*/
|
|
134
|
-
async getValidAccessToken(): Promise<string> {
|
|
135
|
-
// Session-based auth - return empty token as we use session headers
|
|
136
|
-
return '';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Token expiration check removed - session-based auth doesn't use expiring tokens
|
|
140
|
-
|
|
141
|
-
// Supabase refresh method removed - SDK no longer uses Supabase tokens
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Perform the actual Supabase session refresh
|
|
145
|
-
*/
|
|
146
|
-
// Supabase refresh method removed - SDK no longer uses Supabase tokens
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Clear session tokens (useful for logout)
|
|
150
|
-
*/
|
|
151
|
-
clearTokens(): void {
|
|
152
|
-
// Session-based auth - no tokens to clear
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Get current session info (for debugging/testing) - session-based auth
|
|
157
|
-
*/
|
|
158
|
-
getTokenInfo(): { accessToken: string; refreshToken: string; expiresAt: number } | null {
|
|
159
|
-
// Session-based auth - no tokens to return
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Make a request to the API.
|
|
165
|
-
*/
|
|
166
|
-
protected async request<T>(
|
|
167
|
-
path: string,
|
|
168
|
-
options: {
|
|
169
|
-
method: string;
|
|
170
|
-
headers?: Record<string, string>;
|
|
171
|
-
body?: any;
|
|
172
|
-
params?: Record<string, string>;
|
|
173
|
-
}
|
|
174
|
-
): Promise<T> {
|
|
175
|
-
// Ensure path starts with a slash
|
|
176
|
-
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
177
|
-
const url = new URL(`${this.baseUrl}${normalizedPath}`);
|
|
178
|
-
|
|
179
|
-
if (options.params) {
|
|
180
|
-
Object.entries(options.params).forEach(([key, value]) => {
|
|
181
|
-
url.searchParams.append(key, value);
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Get Supabase JWT token
|
|
186
|
-
const accessToken = await this.getValidAccessToken();
|
|
187
|
-
|
|
188
|
-
// Build comprehensive headers object with all available session data
|
|
189
|
-
const comprehensiveHeaders: Record<string, string> = {
|
|
190
|
-
'Content-Type': 'application/json',
|
|
191
|
-
Authorization: `Bearer ${accessToken}`,
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
// Add device info if available
|
|
195
|
-
if (this.deviceInfo) {
|
|
196
|
-
comprehensiveHeaders['X-Device-Info'] = JSON.stringify({
|
|
197
|
-
ip_address: this.deviceInfo.ip_address || '',
|
|
198
|
-
user_agent: this.deviceInfo.user_agent || '',
|
|
199
|
-
fingerprint: this.deviceInfo.fingerprint || '',
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Add session headers if available (filter out empty values)
|
|
204
|
-
if (this.currentSessionId && this.currentSessionId.trim() !== '') {
|
|
205
|
-
comprehensiveHeaders['X-Session-ID'] = this.currentSessionId;
|
|
206
|
-
comprehensiveHeaders['Session-ID'] = this.currentSessionId;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (this.companyId && this.companyId.trim() !== '') {
|
|
210
|
-
comprehensiveHeaders['X-Company-ID'] = this.companyId;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (this.csrfToken && this.csrfToken.trim() !== '') {
|
|
214
|
-
comprehensiveHeaders['X-CSRF-Token'] = this.csrfToken;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Add any additional headers from options (these will override defaults)
|
|
218
|
-
if (options.headers) {
|
|
219
|
-
Object.entries(options.headers).forEach(([key, value]) => {
|
|
220
|
-
if (value !== undefined && value !== null && value.trim() !== '') {
|
|
221
|
-
comprehensiveHeaders[key] = value;
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Safari-specific fix: Ensure all headers are explicitly set and not empty
|
|
227
|
-
// Safari can be strict about header formatting and empty values
|
|
228
|
-
const safariSafeHeaders: Record<string, string> = {};
|
|
229
|
-
Object.entries(comprehensiveHeaders).forEach(([key, value]) => {
|
|
230
|
-
if (value && value.trim() !== '') {
|
|
231
|
-
// Ensure header names are properly formatted for Safari
|
|
232
|
-
const normalizedKey = key.trim();
|
|
233
|
-
const normalizedValue = value.trim();
|
|
234
|
-
safariSafeHeaders[normalizedKey] = normalizedValue;
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
this.logger.debug('Dispatching API request', this.buildLoggerExtra('request', {
|
|
239
|
-
url: url.toString(),
|
|
240
|
-
method: options.method,
|
|
241
|
-
has_body: Boolean(options.body),
|
|
242
|
-
}));
|
|
243
|
-
|
|
244
|
-
const response = await fetch(url.toString(), {
|
|
245
|
-
method: options.method,
|
|
246
|
-
headers: safariSafeHeaders,
|
|
247
|
-
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
this.logger.debug('Received API response', this.buildLoggerExtra('request', {
|
|
251
|
-
url: url.toString(),
|
|
252
|
-
status: response.status,
|
|
253
|
-
}));
|
|
254
|
-
|
|
255
|
-
if (!response.ok) {
|
|
256
|
-
const error = await response.json();
|
|
257
|
-
const apiError = this.handleError(response.status, error);
|
|
258
|
-
this.logger.exception('API request failed', apiError, this.buildLoggerExtra('request', {
|
|
259
|
-
url: url.toString(),
|
|
260
|
-
status: response.status,
|
|
261
|
-
}));
|
|
262
|
-
throw apiError;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const data = await response.json();
|
|
266
|
-
|
|
267
|
-
// Check if the response has a success field and it's false
|
|
268
|
-
if (data && typeof data === 'object' && 'success' in data && data.success === false) {
|
|
269
|
-
// For order endpoints, provide more context
|
|
270
|
-
const isOrderEndpoint = path.includes('/brokers/orders');
|
|
271
|
-
if (isOrderEndpoint) {
|
|
272
|
-
// Add context that this is an order-related error
|
|
273
|
-
data._isOrderError = true;
|
|
274
|
-
}
|
|
275
|
-
const apiError = this.handleError(data.status_code || 500, data);
|
|
276
|
-
this.logger.exception('API response indicated failure', apiError, this.buildLoggerExtra('request', {
|
|
277
|
-
url: url.toString(),
|
|
278
|
-
status: data.status_code || 500,
|
|
279
|
-
}));
|
|
280
|
-
throw apiError;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Check if the response has a status_code field indicating an error (4xx or 5xx)
|
|
284
|
-
if (data && typeof data === 'object' && 'status_code' in data && data.status_code >= 400) {
|
|
285
|
-
const apiError = this.handleError(data.status_code, data);
|
|
286
|
-
this.logger.exception('API status code error', apiError, this.buildLoggerExtra('request', {
|
|
287
|
-
url: url.toString(),
|
|
288
|
-
status: data.status_code,
|
|
289
|
-
}));
|
|
290
|
-
throw apiError;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Check if the response has errors field with content
|
|
294
|
-
if (
|
|
295
|
-
data &&
|
|
296
|
-
typeof data === 'object' &&
|
|
297
|
-
'errors' in data &&
|
|
298
|
-
data.errors &&
|
|
299
|
-
Array.isArray(data.errors) &&
|
|
300
|
-
data.errors.length > 0
|
|
301
|
-
) {
|
|
302
|
-
const apiError = this.handleError(data.status_code || 500, data);
|
|
303
|
-
this.logger.exception('API response contained errors', apiError, this.buildLoggerExtra('request', {
|
|
304
|
-
url: url.toString(),
|
|
305
|
-
status: data.status_code || 500,
|
|
306
|
-
}));
|
|
307
|
-
throw apiError;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return data;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Handle API errors. This method can be overridden by language-specific implementations.
|
|
315
|
-
*/
|
|
316
|
-
protected handleError(status: number, error: any): ApiError {
|
|
317
|
-
// Extract message from the error object with multiple fallback options
|
|
318
|
-
let message = 'API request failed';
|
|
319
|
-
|
|
320
|
-
if (error && typeof error === 'object') {
|
|
321
|
-
// Try different possible message fields
|
|
322
|
-
message =
|
|
323
|
-
error.message ||
|
|
324
|
-
error.detail?.message ||
|
|
325
|
-
error.error?.message ||
|
|
326
|
-
error.errors?.[0]?.message ||
|
|
327
|
-
(typeof error.errors === 'string' ? error.errors : null) ||
|
|
328
|
-
'API request failed';
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Check if this is an order-related error (either from order endpoints or order validation)
|
|
332
|
-
const isOrderError =
|
|
333
|
-
error._isOrderError ||
|
|
334
|
-
message.includes('ORDER_FAILED') ||
|
|
335
|
-
message.includes('AUTH_ERROR') ||
|
|
336
|
-
message.includes('not found or not available for trading') ||
|
|
337
|
-
message.includes('Symbol') ||
|
|
338
|
-
(error.errors &&
|
|
339
|
-
Array.isArray(error.errors) &&
|
|
340
|
-
error.errors.some(
|
|
341
|
-
(e: any) =>
|
|
342
|
-
e.category === 'INVALID_ORDER' ||
|
|
343
|
-
e.message?.includes('Symbol') ||
|
|
344
|
-
e.message?.includes('not available for trading')
|
|
345
|
-
));
|
|
346
|
-
|
|
347
|
-
if (isOrderError) {
|
|
348
|
-
// Check if this is a validation error (400 status with specific validation messages)
|
|
349
|
-
const isValidationError =
|
|
350
|
-
status === 400 &&
|
|
351
|
-
(message.includes('not found or not available for trading') ||
|
|
352
|
-
message.includes('Symbol') ||
|
|
353
|
-
(error.errors &&
|
|
354
|
-
Array.isArray(error.errors) &&
|
|
355
|
-
error.errors.some(
|
|
356
|
-
(e: any) =>
|
|
357
|
-
e.category === 'INVALID_ORDER' ||
|
|
358
|
-
e.message?.includes('Symbol') ||
|
|
359
|
-
e.message?.includes('not available for trading')
|
|
360
|
-
)));
|
|
361
|
-
|
|
362
|
-
if (isValidationError) {
|
|
363
|
-
return new OrderValidationError(message, error);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// For order placement errors, provide more specific error messages
|
|
367
|
-
if (message.includes('ORDER_FAILED') || message.includes('AUTH_ERROR')) {
|
|
368
|
-
// Extract the specific error from the nested structure
|
|
369
|
-
const orderErrorMatch = message.match(/\[([^\]]+)\]/g);
|
|
370
|
-
if (orderErrorMatch && orderErrorMatch.length > 0) {
|
|
371
|
-
// Take the last error in the chain (most specific)
|
|
372
|
-
const specificError = orderErrorMatch[orderErrorMatch.length - 1].replace(/[[\]]/g, '');
|
|
373
|
-
message = `Order failed: ${specificError}`;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Use OrderError for order-related failures
|
|
378
|
-
return new OrderError(message, error);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
switch (status) {
|
|
382
|
-
case 400:
|
|
383
|
-
return new SessionError(message, error);
|
|
384
|
-
case 401:
|
|
385
|
-
return new AuthenticationError(
|
|
386
|
-
message || 'Unauthorized: Invalid or missing session token',
|
|
387
|
-
error
|
|
388
|
-
);
|
|
389
|
-
case 403:
|
|
390
|
-
if (error.detail?.code === 'NO_COMPANY_ACCESS') {
|
|
391
|
-
return new CompanyAccessError(
|
|
392
|
-
error.detail.message || 'No broker connections found for this company',
|
|
393
|
-
error.detail
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
if (error.detail?.code === 'TRADING_NOT_ENABLED') {
|
|
397
|
-
return new TradingNotEnabledError(
|
|
398
|
-
error.detail.message || 'Trading is not enabled for your company',
|
|
399
|
-
error.detail
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
return new AuthorizationError(
|
|
403
|
-
message || 'Forbidden: No access to the requested data',
|
|
404
|
-
error
|
|
405
|
-
);
|
|
406
|
-
case 404:
|
|
407
|
-
return new ApiError(
|
|
408
|
-
status,
|
|
409
|
-
message || 'Not found: The requested data does not exist',
|
|
410
|
-
error
|
|
411
|
-
);
|
|
412
|
-
case 429:
|
|
413
|
-
return new RateLimitError(message || 'Rate limit exceeded', error);
|
|
414
|
-
case 500:
|
|
415
|
-
return new ApiError(status, message || 'Internal server error', error);
|
|
416
|
-
default:
|
|
417
|
-
return new ApiError(status, message || 'API request failed', error);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Session Management
|
|
422
|
-
async startSession(token: string, userId?: string): Promise<SessionResponse> {
|
|
423
|
-
const response = await this.request<SessionResponse>('/session/start', {
|
|
424
|
-
method: 'POST',
|
|
425
|
-
headers: {
|
|
426
|
-
'Content-Type': 'application/json',
|
|
427
|
-
'One-Time-Token': token,
|
|
428
|
-
'X-Device-Info': JSON.stringify({
|
|
429
|
-
ip_address: this.deviceInfo?.ip_address || '',
|
|
430
|
-
user_agent: this.deviceInfo?.user_agent || '',
|
|
431
|
-
fingerprint: this.deviceInfo?.fingerprint || '',
|
|
432
|
-
}),
|
|
433
|
-
},
|
|
434
|
-
body: {
|
|
435
|
-
user_id: userId,
|
|
436
|
-
},
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
// Store session ID and set state to ACTIVE
|
|
440
|
-
this.currentSessionId = response.data.session_id;
|
|
441
|
-
this.currentSessionState = SessionState.ACTIVE;
|
|
442
|
-
|
|
443
|
-
return response;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// OTP Flow
|
|
447
|
-
async requestOtp(sessionId: string, email: string): Promise<OtpRequestResponse> {
|
|
448
|
-
return this.request<OtpRequestResponse>('/auth/otp/request', {
|
|
449
|
-
method: 'POST',
|
|
450
|
-
headers: {
|
|
451
|
-
'Content-Type': 'application/json',
|
|
452
|
-
'X-Session-ID': sessionId,
|
|
453
|
-
'X-Device-Info': JSON.stringify({
|
|
454
|
-
ip_address: this.deviceInfo?.ip_address || '',
|
|
455
|
-
user_agent: this.deviceInfo?.user_agent || '',
|
|
456
|
-
fingerprint: this.deviceInfo?.fingerprint || '',
|
|
457
|
-
}),
|
|
458
|
-
},
|
|
459
|
-
body: {
|
|
460
|
-
email,
|
|
461
|
-
},
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
async verifyOtp(sessionId: string, otp: string): Promise<OtpVerifyResponse> {
|
|
466
|
-
const response = await this.request<OtpVerifyResponse>('/auth/otp/verify', {
|
|
467
|
-
method: 'POST',
|
|
468
|
-
headers: {
|
|
469
|
-
'Content-Type': 'application/json',
|
|
470
|
-
'X-Session-ID': sessionId,
|
|
471
|
-
'X-Device-Info': JSON.stringify({
|
|
472
|
-
ip_address: this.deviceInfo?.ip_address || '',
|
|
473
|
-
user_agent: this.deviceInfo?.user_agent || '',
|
|
474
|
-
fingerprint: this.deviceInfo?.fingerprint || '',
|
|
475
|
-
}),
|
|
476
|
-
},
|
|
477
|
-
body: {
|
|
478
|
-
otp,
|
|
479
|
-
},
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
// OTP verification successful - tokens are handled by Supabase client
|
|
483
|
-
if (response.success && response.data) {
|
|
484
|
-
// Note: Supabase JWT will be handled by the Supabase client
|
|
485
|
-
// The backend now creates/retrieves Supabase users and returns session info
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
return response;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Direct Authentication
|
|
492
|
-
async authenticateDirectly(
|
|
493
|
-
sessionId: string,
|
|
494
|
-
userId: string
|
|
495
|
-
): Promise<SessionAuthenticateResponse> {
|
|
496
|
-
// Ensure session is active before authenticating
|
|
497
|
-
if (this.currentSessionState !== SessionState.ACTIVE) {
|
|
498
|
-
throw new SessionError('Session must be in ACTIVE state to authenticate');
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const response = await this.request<SessionAuthenticateResponse>('/session/authenticate', {
|
|
502
|
-
method: 'POST',
|
|
503
|
-
headers: {
|
|
504
|
-
'Content-Type': 'application/json',
|
|
505
|
-
'Session-ID': sessionId,
|
|
506
|
-
'X-Session-ID': sessionId,
|
|
507
|
-
'X-Device-Info': JSON.stringify({
|
|
508
|
-
ip_address: this.deviceInfo?.ip_address || '',
|
|
509
|
-
user_agent: this.deviceInfo?.user_agent || '',
|
|
510
|
-
fingerprint: this.deviceInfo?.fingerprint || '',
|
|
511
|
-
}),
|
|
512
|
-
},
|
|
513
|
-
body: {
|
|
514
|
-
session_id: sessionId,
|
|
515
|
-
user_id: userId,
|
|
516
|
-
},
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
// Store tokens after successful direct authentication
|
|
520
|
-
if (response.success && response.data) {
|
|
521
|
-
// Session-based auth - no token storage needed
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
return response;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
// Portal Management
|
|
528
|
-
/**
|
|
529
|
-
* Get the portal URL for an active session
|
|
530
|
-
* @param sessionId The session identifier
|
|
531
|
-
* @returns Portal URL response
|
|
532
|
-
* @throws SessionError if session is not in ACTIVE state
|
|
533
|
-
*/
|
|
534
|
-
public async getPortalUrl(sessionId: string): Promise<PortalUrlResponse> {
|
|
535
|
-
if (this.currentSessionState !== SessionState.ACTIVE) {
|
|
536
|
-
throw new SessionError('Session must be in ACTIVE state to get portal URL');
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return this.request<PortalUrlResponse>('/session/portal', {
|
|
540
|
-
method: 'GET',
|
|
541
|
-
headers: {
|
|
542
|
-
'Content-Type': 'application/json',
|
|
543
|
-
'Session-ID': sessionId,
|
|
544
|
-
'X-Session-ID': sessionId,
|
|
545
|
-
'X-Device-Info': JSON.stringify({
|
|
546
|
-
ip_address: this.deviceInfo?.ip_address || '',
|
|
547
|
-
user_agent: this.deviceInfo?.user_agent || '',
|
|
548
|
-
fingerprint: this.deviceInfo?.fingerprint || '',
|
|
549
|
-
}),
|
|
550
|
-
},
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
async completePortalSession(sessionId: string): Promise<PortalUrlResponse> {
|
|
555
|
-
return this.request<PortalUrlResponse>(`/portal/${sessionId}/complete`, {
|
|
556
|
-
method: 'POST',
|
|
557
|
-
headers: {
|
|
558
|
-
'Content-Type': 'application/json',
|
|
559
|
-
},
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Portfolio Management
|
|
564
|
-
|
|
565
|
-
async getOrders(): Promise<{ data: Order[] }> {
|
|
566
|
-
const accessToken = await this.getValidAccessToken();
|
|
567
|
-
return this.request<{ data: Order[] }>('/brokers/data/orders', {
|
|
568
|
-
method: 'GET',
|
|
569
|
-
headers: {
|
|
570
|
-
Authorization: `Bearer ${accessToken}`,
|
|
571
|
-
},
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// Enhanced Trading Methods with Session Management
|
|
576
|
-
async placeBrokerOrder(
|
|
577
|
-
params: Partial<BrokerOrderParams> & {
|
|
578
|
-
symbol: string;
|
|
579
|
-
orderQty: number;
|
|
580
|
-
action: 'Buy' | 'Sell';
|
|
581
|
-
orderType: 'Market' | 'Limit' | 'Stop' | 'StopLimit';
|
|
582
|
-
assetType: 'equity' | 'equity_option' | 'crypto' | 'forex' | 'future' | 'future_option';
|
|
583
|
-
},
|
|
584
|
-
extras: BrokerExtras = {},
|
|
585
|
-
connection_id?: string
|
|
586
|
-
): Promise<OrderResponse> {
|
|
587
|
-
const accessToken = await this.getValidAccessToken();
|
|
588
|
-
|
|
589
|
-
// Get broker and account from context or params
|
|
590
|
-
const broker = params.broker || this.tradingContext.broker;
|
|
591
|
-
const accountNumber = params.accountNumber || this.tradingContext.accountNumber;
|
|
592
|
-
|
|
593
|
-
if (!broker) {
|
|
594
|
-
throw new Error('Broker not set. Call setBroker() or pass broker parameter.');
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
if (!accountNumber) {
|
|
598
|
-
throw new Error('Account not set. Call setAccount() or pass accountNumber parameter.');
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// Merge context with provided parameters
|
|
602
|
-
const fullParams: BrokerOrderParams = {
|
|
603
|
-
broker:
|
|
604
|
-
((params.broker || this.tradingContext.broker) as
|
|
605
|
-
| 'robinhood'
|
|
606
|
-
| 'tasty_trade'
|
|
607
|
-
| 'ninja_trader') ||
|
|
608
|
-
(() => {
|
|
609
|
-
throw new Error('Broker not set. Call setBroker() or pass broker parameter.');
|
|
610
|
-
})(),
|
|
611
|
-
accountNumber:
|
|
612
|
-
params.accountNumber ||
|
|
613
|
-
this.tradingContext.accountNumber ||
|
|
614
|
-
(() => {
|
|
615
|
-
throw new Error('Account not set. Call setAccount() or pass accountNumber parameter.');
|
|
616
|
-
})(),
|
|
617
|
-
symbol: params.symbol,
|
|
618
|
-
orderQty: params.orderQty,
|
|
619
|
-
action: params.action,
|
|
620
|
-
orderType: params.orderType,
|
|
621
|
-
assetType: params.assetType,
|
|
622
|
-
timeInForce: params.timeInForce || 'day',
|
|
623
|
-
price: params.price,
|
|
624
|
-
stopPrice: params.stopPrice,
|
|
625
|
-
order_id: params.order_id,
|
|
626
|
-
};
|
|
627
|
-
|
|
628
|
-
// Build request body with camelCase parameter names
|
|
629
|
-
const requestBody = this.buildOrderRequestBody(fullParams, extras);
|
|
630
|
-
|
|
631
|
-
// Add query parameters if connection_id is provided
|
|
632
|
-
const queryParams: Record<string, string> = {};
|
|
633
|
-
if (connection_id) {
|
|
634
|
-
queryParams.connection_id = connection_id;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
return this.request<OrderResponse>('/brokers/orders', {
|
|
638
|
-
method: 'POST',
|
|
639
|
-
headers: {
|
|
640
|
-
'Content-Type': 'application/json',
|
|
641
|
-
Authorization: `Bearer ${accessToken}`,
|
|
642
|
-
'Session-ID': this.currentSessionId || '',
|
|
643
|
-
'X-Session-ID': this.currentSessionId || '',
|
|
644
|
-
'X-Device-Info': JSON.stringify(this.deviceInfo),
|
|
645
|
-
},
|
|
646
|
-
body: requestBody,
|
|
647
|
-
params: queryParams,
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
async cancelBrokerOrder(
|
|
652
|
-
orderId: string,
|
|
653
|
-
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
654
|
-
extras: any = {},
|
|
655
|
-
connection_id?: string
|
|
656
|
-
): Promise<OrderResponse> {
|
|
657
|
-
const accessToken = await this.getValidAccessToken();
|
|
658
|
-
|
|
659
|
-
const selectedBroker = broker || this.tradingContext.broker;
|
|
660
|
-
if (!selectedBroker) {
|
|
661
|
-
throw new Error('Broker not set. Call setBroker() or pass broker parameter.');
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
const accountNumber = this.tradingContext.accountNumber;
|
|
665
|
-
|
|
666
|
-
// Build query parameters as required by API documentation
|
|
667
|
-
const queryParams: Record<string, string> = {};
|
|
668
|
-
|
|
669
|
-
// Add optional parameters if available
|
|
670
|
-
if (accountNumber) {
|
|
671
|
-
queryParams.account_number = accountNumber.toString();
|
|
672
|
-
}
|
|
673
|
-
if (connection_id) {
|
|
674
|
-
queryParams.connection_id = connection_id;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// Build optional request body if extras are provided
|
|
678
|
-
let body: any = undefined;
|
|
679
|
-
if (Object.keys(extras).length > 0) {
|
|
680
|
-
body = {
|
|
681
|
-
broker: selectedBroker,
|
|
682
|
-
order: {
|
|
683
|
-
order_id: orderId,
|
|
684
|
-
account_number: accountNumber,
|
|
685
|
-
...extras,
|
|
686
|
-
},
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
return this.request<OrderResponse>(`/brokers/orders/${orderId}`, {
|
|
691
|
-
method: 'DELETE',
|
|
692
|
-
headers: {
|
|
693
|
-
'Content-Type': 'application/json',
|
|
694
|
-
'Session-ID': this.currentSessionId || '',
|
|
695
|
-
'X-Session-ID': this.currentSessionId || '',
|
|
696
|
-
'X-Device-Info': JSON.stringify(this.deviceInfo),
|
|
697
|
-
},
|
|
698
|
-
body,
|
|
699
|
-
params: queryParams,
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
async modifyBrokerOrder(
|
|
704
|
-
orderId: string,
|
|
705
|
-
params: Partial<BrokerOrderParams>,
|
|
706
|
-
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
707
|
-
extras: any = {},
|
|
708
|
-
connection_id?: string
|
|
709
|
-
): Promise<OrderResponse> {
|
|
710
|
-
const accessToken = await this.getValidAccessToken();
|
|
711
|
-
|
|
712
|
-
const selectedBroker = broker || this.tradingContext.broker;
|
|
713
|
-
if (!selectedBroker) {
|
|
714
|
-
throw new Error('Broker not set. Call setBroker() or pass broker parameter.');
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
// Build request body with camelCase parameter names and include broker
|
|
718
|
-
const requestBody = this.buildModifyRequestBody(params, extras, selectedBroker);
|
|
719
|
-
|
|
720
|
-
// Add query parameters if connection_id is provided
|
|
721
|
-
const queryParams: Record<string, string> = {};
|
|
722
|
-
if (connection_id) {
|
|
723
|
-
queryParams.connection_id = connection_id;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
return this.request<OrderResponse>(`/brokers/orders/${orderId}`, {
|
|
727
|
-
method: 'PATCH',
|
|
728
|
-
headers: {
|
|
729
|
-
'Content-Type': 'application/json',
|
|
730
|
-
'Session-ID': this.currentSessionId || '',
|
|
731
|
-
'X-Session-ID': this.currentSessionId || '',
|
|
732
|
-
'X-Device-Info': JSON.stringify(this.deviceInfo),
|
|
733
|
-
},
|
|
734
|
-
body: requestBody,
|
|
735
|
-
params: queryParams,
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// Context management methods
|
|
740
|
-
setBroker(broker: 'robinhood' | 'tasty_trade' | 'ninja_trader'): void {
|
|
741
|
-
this.tradingContext.broker = broker;
|
|
742
|
-
// Clear account when broker changes
|
|
743
|
-
this.tradingContext.accountNumber = undefined;
|
|
744
|
-
this.tradingContext.accountId = undefined;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
setAccount(accountNumber: string, accountId?: string): void {
|
|
748
|
-
this.tradingContext.accountNumber = accountNumber;
|
|
749
|
-
this.tradingContext.accountId = accountId;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
// Stock convenience methods
|
|
754
|
-
async placeStockMarketOrder(
|
|
755
|
-
symbol: string,
|
|
756
|
-
orderQty: number,
|
|
757
|
-
action: 'Buy' | 'Sell',
|
|
758
|
-
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
759
|
-
accountNumber?: string,
|
|
760
|
-
extras: BrokerExtras = {},
|
|
761
|
-
connection_id?: string
|
|
762
|
-
): Promise<OrderResponse> {
|
|
763
|
-
return this.placeBrokerOrder(
|
|
764
|
-
{
|
|
765
|
-
symbol,
|
|
766
|
-
orderQty,
|
|
767
|
-
action,
|
|
768
|
-
orderType: 'Market',
|
|
769
|
-
assetType: 'equity',
|
|
770
|
-
timeInForce: 'day',
|
|
771
|
-
broker,
|
|
772
|
-
accountNumber,
|
|
773
|
-
},
|
|
774
|
-
extras,
|
|
775
|
-
connection_id
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
async placeStockLimitOrder(
|
|
780
|
-
symbol: string,
|
|
781
|
-
orderQty: number,
|
|
782
|
-
action: 'Buy' | 'Sell',
|
|
783
|
-
price: number,
|
|
784
|
-
timeInForce: 'day' | 'gtc' = 'gtc',
|
|
785
|
-
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
786
|
-
accountNumber?: string,
|
|
787
|
-
extras: BrokerExtras = {},
|
|
788
|
-
connection_id?: string
|
|
789
|
-
): Promise<OrderResponse> {
|
|
790
|
-
return this.placeBrokerOrder(
|
|
791
|
-
{
|
|
792
|
-
symbol,
|
|
793
|
-
orderQty,
|
|
794
|
-
action,
|
|
795
|
-
orderType: 'Limit',
|
|
796
|
-
assetType: 'equity',
|
|
797
|
-
price,
|
|
798
|
-
timeInForce,
|
|
799
|
-
broker,
|
|
800
|
-
accountNumber,
|
|
801
|
-
},
|
|
802
|
-
extras,
|
|
803
|
-
connection_id
|
|
804
|
-
);
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
async placeStockStopOrder(
|
|
808
|
-
symbol: string,
|
|
809
|
-
orderQty: number,
|
|
810
|
-
action: 'Buy' | 'Sell',
|
|
811
|
-
stopPrice: number,
|
|
812
|
-
timeInForce: 'day' | 'gtc' = 'day',
|
|
813
|
-
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
814
|
-
accountNumber?: string,
|
|
815
|
-
extras: BrokerExtras = {},
|
|
816
|
-
connection_id?: string
|
|
817
|
-
): Promise<OrderResponse> {
|
|
818
|
-
return this.placeBrokerOrder(
|
|
819
|
-
{
|
|
820
|
-
symbol,
|
|
821
|
-
orderQty,
|
|
822
|
-
action,
|
|
823
|
-
orderType: 'Stop',
|
|
824
|
-
assetType: 'equity',
|
|
825
|
-
stopPrice,
|
|
826
|
-
timeInForce,
|
|
827
|
-
broker,
|
|
828
|
-
accountNumber,
|
|
829
|
-
},
|
|
830
|
-
extras,
|
|
831
|
-
connection_id
|
|
832
|
-
);
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// Crypto convenience methods
|
|
836
|
-
async placeCryptoMarketOrder(
|
|
837
|
-
symbol: string,
|
|
838
|
-
orderQty: number,
|
|
839
|
-
action: 'Buy' | 'Sell',
|
|
840
|
-
options: CryptoOrderOptions = {},
|
|
841
|
-
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
842
|
-
accountNumber?: string,
|
|
843
|
-
extras: BrokerExtras = {}
|
|
844
|
-
): Promise<OrderResponse> {
|
|
845
|
-
return this.placeBrokerOrder(
|
|
846
|
-
{
|
|
847
|
-
symbol,
|
|
848
|
-
orderQty,
|
|
849
|
-
action,
|
|
850
|
-
orderType: 'Market',
|
|
851
|
-
assetType: 'crypto',
|
|
852
|
-
timeInForce: 'day',
|
|
853
|
-
broker,
|
|
854
|
-
accountNumber,
|
|
855
|
-
...options,
|
|
856
|
-
},
|
|
857
|
-
extras
|
|
858
|
-
);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
async placeCryptoLimitOrder(
|
|
862
|
-
symbol: string,
|
|
863
|
-
orderQty: number,
|
|
864
|
-
action: 'Buy' | 'Sell',
|
|
865
|
-
price: number,
|
|
866
|
-
timeInForce: 'day' | 'gtc' = 'gtc',
|
|
867
|
-
options: CryptoOrderOptions = {},
|
|
868
|
-
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
869
|
-
accountNumber?: string,
|
|
870
|
-
extras: BrokerExtras = {}
|
|
871
|
-
): Promise<OrderResponse> {
|
|
872
|
-
return this.placeBrokerOrder(
|
|
873
|
-
{
|
|
874
|
-
symbol,
|
|
875
|
-
orderQty,
|
|
876
|
-
action,
|
|
877
|
-
orderType: 'Limit',
|
|
878
|
-
assetType: 'crypto',
|
|
879
|
-
price,
|
|
880
|
-
timeInForce,
|
|
881
|
-
broker,
|
|
882
|
-
accountNumber,
|
|
883
|
-
...options,
|
|
884
|
-
},
|
|
885
|
-
extras
|
|
886
|
-
);
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
// Options convenience methods
|
|
890
|
-
async placeOptionsMarketOrder(
|
|
891
|
-
symbol: string,
|
|
892
|
-
orderQty: number,
|
|
893
|
-
action: 'Buy' | 'Sell',
|
|
894
|
-
options: OptionsOrderOptions,
|
|
895
|
-
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
896
|
-
accountNumber?: string,
|
|
897
|
-
extras: BrokerExtras = {}
|
|
898
|
-
): Promise<OrderResponse> {
|
|
899
|
-
return this.placeBrokerOrder(
|
|
900
|
-
{
|
|
901
|
-
symbol,
|
|
902
|
-
orderQty,
|
|
903
|
-
action,
|
|
904
|
-
orderType: 'Market',
|
|
905
|
-
assetType: 'equity_option',
|
|
906
|
-
timeInForce: 'day',
|
|
907
|
-
broker,
|
|
908
|
-
accountNumber,
|
|
909
|
-
...options,
|
|
910
|
-
},
|
|
911
|
-
extras
|
|
912
|
-
);
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
async placeOptionsLimitOrder(
|
|
916
|
-
symbol: string,
|
|
917
|
-
orderQty: number,
|
|
918
|
-
action: 'Buy' | 'Sell',
|
|
919
|
-
price: number,
|
|
920
|
-
options: OptionsOrderOptions,
|
|
921
|
-
timeInForce: 'day' | 'gtc' = 'gtc',
|
|
922
|
-
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
923
|
-
accountNumber?: string,
|
|
924
|
-
extras: BrokerExtras = {}
|
|
925
|
-
): Promise<OrderResponse> {
|
|
926
|
-
return this.placeBrokerOrder(
|
|
927
|
-
{
|
|
928
|
-
symbol,
|
|
929
|
-
orderQty,
|
|
930
|
-
action,
|
|
931
|
-
orderType: 'Limit',
|
|
932
|
-
assetType: 'equity_option',
|
|
933
|
-
price,
|
|
934
|
-
timeInForce,
|
|
935
|
-
broker,
|
|
936
|
-
accountNumber,
|
|
937
|
-
...options,
|
|
938
|
-
},
|
|
939
|
-
extras
|
|
940
|
-
);
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// Futures convenience methods
|
|
944
|
-
async placeFuturesMarketOrder(
|
|
945
|
-
symbol: string,
|
|
946
|
-
orderQty: number,
|
|
947
|
-
action: 'Buy' | 'Sell',
|
|
948
|
-
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
949
|
-
accountNumber?: string,
|
|
950
|
-
extras: BrokerExtras = {}
|
|
951
|
-
): Promise<OrderResponse> {
|
|
952
|
-
return this.placeBrokerOrder(
|
|
953
|
-
{
|
|
954
|
-
symbol,
|
|
955
|
-
orderQty,
|
|
956
|
-
action,
|
|
957
|
-
orderType: 'Market',
|
|
958
|
-
assetType: 'future',
|
|
959
|
-
timeInForce: 'day',
|
|
960
|
-
broker,
|
|
961
|
-
accountNumber,
|
|
962
|
-
},
|
|
963
|
-
extras
|
|
964
|
-
);
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
async placeFuturesLimitOrder(
|
|
968
|
-
symbol: string,
|
|
969
|
-
orderQty: number,
|
|
970
|
-
action: 'Buy' | 'Sell',
|
|
971
|
-
price: number,
|
|
972
|
-
timeInForce: 'day' | 'gtc' = 'gtc',
|
|
973
|
-
broker?: 'robinhood' | 'tasty_trade' | 'ninja_trader',
|
|
974
|
-
accountNumber?: string,
|
|
975
|
-
extras: BrokerExtras = {}
|
|
976
|
-
): Promise<OrderResponse> {
|
|
977
|
-
return this.placeBrokerOrder(
|
|
978
|
-
{
|
|
979
|
-
symbol,
|
|
980
|
-
orderQty,
|
|
981
|
-
action,
|
|
982
|
-
orderType: 'Limit',
|
|
983
|
-
assetType: 'future',
|
|
984
|
-
price,
|
|
985
|
-
timeInForce,
|
|
986
|
-
broker,
|
|
987
|
-
accountNumber,
|
|
988
|
-
},
|
|
989
|
-
extras
|
|
990
|
-
);
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
private buildOrderRequestBody(params: BrokerOrderParams, extras: BrokerExtras = {}) {
|
|
994
|
-
const baseOrder: any = {
|
|
995
|
-
order_id: params.order_id,
|
|
996
|
-
orderType: params.orderType,
|
|
997
|
-
assetType: params.assetType,
|
|
998
|
-
action: params.action,
|
|
999
|
-
timeInForce: params.timeInForce,
|
|
1000
|
-
accountNumber: params.accountNumber,
|
|
1001
|
-
symbol: params.symbol,
|
|
1002
|
-
orderQty: params.orderQty,
|
|
1003
|
-
};
|
|
1004
|
-
|
|
1005
|
-
if (params.price !== undefined) baseOrder.price = params.price;
|
|
1006
|
-
if (params.stopPrice !== undefined) baseOrder.stopPrice = params.stopPrice;
|
|
1007
|
-
|
|
1008
|
-
// Apply broker-specific defaults – map camelCase extras property keys to snake_case before merging
|
|
1009
|
-
const brokerExtras = this.applyBrokerDefaults(params.broker, extras);
|
|
1010
|
-
|
|
1011
|
-
return {
|
|
1012
|
-
broker: params.broker,
|
|
1013
|
-
order: {
|
|
1014
|
-
...baseOrder,
|
|
1015
|
-
...brokerExtras,
|
|
1016
|
-
},
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
private buildModifyRequestBody(params: Partial<BrokerOrderParams>, extras: any, broker: string) {
|
|
1021
|
-
const order: any = {};
|
|
1022
|
-
|
|
1023
|
-
if (params.order_id !== undefined) order.order_id = params.order_id;
|
|
1024
|
-
if (params.orderType !== undefined) order.orderType = params.orderType;
|
|
1025
|
-
if (params.assetType !== undefined) order.assetType = params.assetType;
|
|
1026
|
-
if (params.action !== undefined) order.action = params.action;
|
|
1027
|
-
if (params.timeInForce !== undefined) order.timeInForce = params.timeInForce;
|
|
1028
|
-
if (params.accountNumber !== undefined) order.accountNumber = params.accountNumber;
|
|
1029
|
-
if (params.symbol !== undefined) order.symbol = params.symbol;
|
|
1030
|
-
if (params.orderQty !== undefined) order.orderQty = params.orderQty;
|
|
1031
|
-
if (params.price !== undefined) order.price = params.price;
|
|
1032
|
-
if (params.stopPrice !== undefined) order.stopPrice = params.stopPrice;
|
|
1033
|
-
|
|
1034
|
-
// Apply broker-specific defaults (handles snake_case conversion)
|
|
1035
|
-
const brokerExtras = this.applyBrokerDefaults(broker, extras);
|
|
1036
|
-
|
|
1037
|
-
return {
|
|
1038
|
-
broker,
|
|
1039
|
-
order: {
|
|
1040
|
-
...order,
|
|
1041
|
-
...brokerExtras,
|
|
1042
|
-
},
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
private applyBrokerDefaults(broker: string, extras: any): any {
|
|
1047
|
-
// If the caller provided a broker-scoped extras object (e.g. { ninjaTrader: { ... } })
|
|
1048
|
-
// pull the nested object for easier processing.
|
|
1049
|
-
if (extras && typeof extras === 'object') {
|
|
1050
|
-
const scoped =
|
|
1051
|
-
broker === 'robinhood'
|
|
1052
|
-
? extras.robinhood
|
|
1053
|
-
: broker === 'ninja_trader'
|
|
1054
|
-
? extras.ninjaTrader
|
|
1055
|
-
: broker === 'tasty_trade'
|
|
1056
|
-
? extras.tastyTrade
|
|
1057
|
-
: undefined;
|
|
1058
|
-
if (scoped) {
|
|
1059
|
-
extras = scoped;
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
switch (broker) {
|
|
1064
|
-
case 'robinhood':
|
|
1065
|
-
return {
|
|
1066
|
-
...extras,
|
|
1067
|
-
extendedHours: extras?.extendedHours ?? extras?.extended_hours ?? true,
|
|
1068
|
-
marketHours: extras?.marketHours ?? extras?.market_hours ?? 'regular_hours',
|
|
1069
|
-
trailType: extras?.trailType ?? extras?.trail_type ?? 'percentage',
|
|
1070
|
-
};
|
|
1071
|
-
case 'ninja_trader':
|
|
1072
|
-
return {
|
|
1073
|
-
...extras,
|
|
1074
|
-
accountSpec: extras?.accountSpec ?? extras?.account_spec ?? '',
|
|
1075
|
-
isAutomated: extras?.isAutomated ?? extras?.is_automated ?? true,
|
|
1076
|
-
};
|
|
1077
|
-
case 'tasty_trade':
|
|
1078
|
-
return {
|
|
1079
|
-
...extras,
|
|
1080
|
-
automatedSource: extras?.automatedSource ?? extras?.automated_source ?? true,
|
|
1081
|
-
};
|
|
1082
|
-
default:
|
|
1083
|
-
return extras;
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
async getUserToken(sessionId: string): Promise<UserToken> {
|
|
1088
|
-
const accessToken = await this.getValidAccessToken();
|
|
1089
|
-
return this.request<UserToken>(`/session/${sessionId}/user`, {
|
|
1090
|
-
method: 'GET',
|
|
1091
|
-
headers: {
|
|
1092
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1093
|
-
},
|
|
1094
|
-
});
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
/**
|
|
1098
|
-
* Get the current session state
|
|
1099
|
-
*/
|
|
1100
|
-
getCurrentSessionState(): SessionState | null {
|
|
1101
|
-
return this.currentSessionState;
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
/**
|
|
1105
|
-
* Refresh the current session to extend its lifetime
|
|
1106
|
-
* Note: This now uses Supabase session refresh instead of custom endpoint
|
|
1107
|
-
*/
|
|
1108
|
-
async refreshSession(): Promise<{
|
|
1109
|
-
success: boolean;
|
|
1110
|
-
response_data: {
|
|
1111
|
-
session_id: string;
|
|
1112
|
-
company_id: string;
|
|
1113
|
-
status: string;
|
|
1114
|
-
expires_at: string;
|
|
1115
|
-
user_id: string;
|
|
1116
|
-
auto_login: boolean;
|
|
1117
|
-
};
|
|
1118
|
-
message: string;
|
|
1119
|
-
status_code: number;
|
|
1120
|
-
}> {
|
|
1121
|
-
if (!this.currentSessionId || !this.companyId) {
|
|
1122
|
-
throw new SessionError('No active session to refresh');
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
// Session-based auth - no token refresh needed
|
|
1126
|
-
|
|
1127
|
-
// Return session info in expected format
|
|
1128
|
-
return {
|
|
1129
|
-
success: true,
|
|
1130
|
-
response_data: {
|
|
1131
|
-
session_id: this.currentSessionId,
|
|
1132
|
-
company_id: this.companyId,
|
|
1133
|
-
status: 'active',
|
|
1134
|
-
expires_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(), // 1 hour from now
|
|
1135
|
-
user_id: '', // Session-based auth - user_id comes from session
|
|
1136
|
-
auto_login: false,
|
|
1137
|
-
},
|
|
1138
|
-
message: 'Session refreshed successfully',
|
|
1139
|
-
status_code: 200,
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
// Broker Data Management
|
|
1144
|
-
async getBrokerList(): Promise<{
|
|
1145
|
-
_id: string;
|
|
1146
|
-
response_data: BrokerInfo[];
|
|
1147
|
-
message: string;
|
|
1148
|
-
status_code: number;
|
|
1149
|
-
warnings: null;
|
|
1150
|
-
errors: null;
|
|
1151
|
-
}> {
|
|
1152
|
-
// Public endpoint - no auth required
|
|
1153
|
-
return this.request<{
|
|
1154
|
-
_id: string;
|
|
1155
|
-
response_data: BrokerInfo[];
|
|
1156
|
-
message: string;
|
|
1157
|
-
status_code: number;
|
|
1158
|
-
warnings: null;
|
|
1159
|
-
errors: null;
|
|
1160
|
-
}>('/brokers/', {
|
|
1161
|
-
method: 'GET',
|
|
1162
|
-
});
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
async getBrokerAccounts(options?: BrokerDataOptions): Promise<{
|
|
1166
|
-
_id: string;
|
|
1167
|
-
response_data: BrokerAccount[];
|
|
1168
|
-
message: string;
|
|
1169
|
-
status_code: number;
|
|
1170
|
-
warnings: null;
|
|
1171
|
-
errors: null;
|
|
1172
|
-
}> {
|
|
1173
|
-
const accessToken = await this.getValidAccessToken();
|
|
1174
|
-
const params: Record<string, string> = {};
|
|
1175
|
-
|
|
1176
|
-
if (options?.broker_name) {
|
|
1177
|
-
params.broker_id = options.broker_name;
|
|
1178
|
-
}
|
|
1179
|
-
if (options?.account_id) {
|
|
1180
|
-
params.account_id = options.account_id;
|
|
1181
|
-
}
|
|
1182
|
-
if (options?.symbol) {
|
|
1183
|
-
params.symbol = options.symbol;
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
return this.request<{
|
|
1187
|
-
_id: string;
|
|
1188
|
-
response_data: BrokerAccount[];
|
|
1189
|
-
message: string;
|
|
1190
|
-
status_code: number;
|
|
1191
|
-
warnings: null;
|
|
1192
|
-
errors: null;
|
|
1193
|
-
}>('/brokers/data/accounts', {
|
|
1194
|
-
method: 'GET',
|
|
1195
|
-
headers: {
|
|
1196
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1197
|
-
},
|
|
1198
|
-
params,
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
async getBrokerOrders(options?: BrokerDataOptions): Promise<{
|
|
1203
|
-
_id: string;
|
|
1204
|
-
response_data: BrokerOrder[];
|
|
1205
|
-
message: string;
|
|
1206
|
-
status_code: number;
|
|
1207
|
-
warnings: null;
|
|
1208
|
-
errors: null;
|
|
1209
|
-
}> {
|
|
1210
|
-
const accessToken = await this.getValidAccessToken();
|
|
1211
|
-
const params: Record<string, string> = {};
|
|
1212
|
-
|
|
1213
|
-
if (options?.broker_name) {
|
|
1214
|
-
params.broker_id = options.broker_name;
|
|
1215
|
-
}
|
|
1216
|
-
if (options?.account_id) {
|
|
1217
|
-
params.account_id = options.account_id;
|
|
1218
|
-
}
|
|
1219
|
-
if (options?.symbol) {
|
|
1220
|
-
params.symbol = options.symbol;
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
return this.request<{
|
|
1224
|
-
_id: string;
|
|
1225
|
-
response_data: BrokerOrder[];
|
|
1226
|
-
message: string;
|
|
1227
|
-
status_code: number;
|
|
1228
|
-
warnings: null;
|
|
1229
|
-
errors: null;
|
|
1230
|
-
}>('/brokers/data/orders', {
|
|
1231
|
-
method: 'GET',
|
|
1232
|
-
headers: {
|
|
1233
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1234
|
-
},
|
|
1235
|
-
params,
|
|
1236
|
-
});
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
async getBrokerPositions(options?: BrokerDataOptions): Promise<{
|
|
1240
|
-
_id: string;
|
|
1241
|
-
response_data: BrokerPosition[];
|
|
1242
|
-
message: string;
|
|
1243
|
-
status_code: number;
|
|
1244
|
-
warnings: null;
|
|
1245
|
-
errors: null;
|
|
1246
|
-
}> {
|
|
1247
|
-
const accessToken = await this.getValidAccessToken();
|
|
1248
|
-
const params: Record<string, string> = {};
|
|
1249
|
-
|
|
1250
|
-
if (options?.broker_name) {
|
|
1251
|
-
params.broker_id = options.broker_name;
|
|
1252
|
-
}
|
|
1253
|
-
if (options?.account_id) {
|
|
1254
|
-
params.account_id = options.account_id;
|
|
1255
|
-
}
|
|
1256
|
-
if (options?.symbol) {
|
|
1257
|
-
params.symbol = options.symbol;
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
return this.request<{
|
|
1261
|
-
_id: string;
|
|
1262
|
-
response_data: BrokerPosition[];
|
|
1263
|
-
message: string;
|
|
1264
|
-
status_code: number;
|
|
1265
|
-
warnings: null;
|
|
1266
|
-
errors: null;
|
|
1267
|
-
}>('/brokers/data/positions', {
|
|
1268
|
-
method: 'GET',
|
|
1269
|
-
headers: {
|
|
1270
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1271
|
-
},
|
|
1272
|
-
params,
|
|
1273
|
-
});
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
async getBrokerBalances(options?: BrokerDataOptions): Promise<{
|
|
1277
|
-
_id: string;
|
|
1278
|
-
response_data: BrokerBalance[];
|
|
1279
|
-
message: string;
|
|
1280
|
-
status_code: number;
|
|
1281
|
-
warnings: null;
|
|
1282
|
-
errors: null;
|
|
1283
|
-
}> {
|
|
1284
|
-
const accessToken = await this.getValidAccessToken();
|
|
1285
|
-
const params: Record<string, string> = {};
|
|
1286
|
-
|
|
1287
|
-
if (options?.broker_name) {
|
|
1288
|
-
params.broker_id = options.broker_name;
|
|
1289
|
-
}
|
|
1290
|
-
if (options?.account_id) {
|
|
1291
|
-
params.account_id = options.account_id;
|
|
1292
|
-
}
|
|
1293
|
-
if (options?.symbol) {
|
|
1294
|
-
params.symbol = options.symbol;
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
return this.request<{
|
|
1298
|
-
_id: string;
|
|
1299
|
-
response_data: BrokerBalance[];
|
|
1300
|
-
message: string;
|
|
1301
|
-
status_code: number;
|
|
1302
|
-
warnings: null;
|
|
1303
|
-
errors: null;
|
|
1304
|
-
}>('/brokers/data/balances', {
|
|
1305
|
-
method: 'GET',
|
|
1306
|
-
headers: {
|
|
1307
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1308
|
-
},
|
|
1309
|
-
params,
|
|
1310
|
-
});
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
async getBrokerConnections(): Promise<{
|
|
1314
|
-
_id: string;
|
|
1315
|
-
response_data: BrokerConnection[];
|
|
1316
|
-
message: string;
|
|
1317
|
-
status_code: number;
|
|
1318
|
-
warnings: null;
|
|
1319
|
-
errors: null;
|
|
1320
|
-
}> {
|
|
1321
|
-
const accessToken = await this.getValidAccessToken();
|
|
1322
|
-
return this.request<{
|
|
1323
|
-
_id: string;
|
|
1324
|
-
response_data: BrokerConnection[];
|
|
1325
|
-
message: string;
|
|
1326
|
-
status_code: number;
|
|
1327
|
-
warnings: null;
|
|
1328
|
-
errors: null;
|
|
1329
|
-
}>('/brokers/connections', {
|
|
1330
|
-
method: 'GET',
|
|
1331
|
-
headers: {
|
|
1332
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1333
|
-
},
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
async getBalances(filters?: any): Promise<{
|
|
1338
|
-
_id: string;
|
|
1339
|
-
response_data: any[];
|
|
1340
|
-
message: string;
|
|
1341
|
-
status_code: number;
|
|
1342
|
-
warnings: null;
|
|
1343
|
-
errors: null;
|
|
1344
|
-
}> {
|
|
1345
|
-
const accessToken = await this.getValidAccessToken();
|
|
1346
|
-
const params = new URLSearchParams();
|
|
1347
|
-
if (filters) {
|
|
1348
|
-
Object.entries(filters).forEach(([key, value]) => {
|
|
1349
|
-
if (value !== undefined && value !== null) {
|
|
1350
|
-
params.append(key, String(value));
|
|
1351
|
-
}
|
|
1352
|
-
});
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
const queryString = params.toString();
|
|
1356
|
-
const url = queryString ? `/brokers/data/balances?${queryString}` : '/brokers/data/balances';
|
|
1357
|
-
|
|
1358
|
-
return this.request<{
|
|
1359
|
-
_id: string;
|
|
1360
|
-
response_data: any[];
|
|
1361
|
-
message: string;
|
|
1362
|
-
status_code: number;
|
|
1363
|
-
warnings: null;
|
|
1364
|
-
errors: null;
|
|
1365
|
-
}>(url, {
|
|
1366
|
-
method: 'GET',
|
|
1367
|
-
headers: {
|
|
1368
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1369
|
-
},
|
|
1370
|
-
});
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
// Page-based pagination methods
|
|
1374
|
-
async getBrokerOrdersPage(
|
|
1375
|
-
page: number = 1,
|
|
1376
|
-
perPage: number = 100,
|
|
1377
|
-
filters?: OrdersFilter
|
|
1378
|
-
): Promise<PaginatedResult<BrokerOrder[]>> {
|
|
1379
|
-
const accessToken = await this.getValidAccessToken();
|
|
1380
|
-
const offset = (page - 1) * perPage;
|
|
1381
|
-
const params: Record<string, string> = {
|
|
1382
|
-
limit: perPage.toString(),
|
|
1383
|
-
offset: offset.toString(),
|
|
1384
|
-
};
|
|
1385
|
-
|
|
1386
|
-
// Add filter parameters
|
|
1387
|
-
if (filters) {
|
|
1388
|
-
if (filters.broker_id) params.broker_id = filters.broker_id;
|
|
1389
|
-
if (filters.connection_id) params.connection_id = filters.connection_id;
|
|
1390
|
-
if (filters.account_id) params.account_id = filters.account_id;
|
|
1391
|
-
if (filters.symbol) params.symbol = filters.symbol;
|
|
1392
|
-
if (filters.status) params.status = filters.status;
|
|
1393
|
-
if (filters.side) params.side = filters.side;
|
|
1394
|
-
if (filters.asset_type) params.asset_type = filters.asset_type;
|
|
1395
|
-
if (filters.created_after) params.created_after = filters.created_after;
|
|
1396
|
-
if (filters.created_before) params.created_before = filters.created_before;
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
const response = await this.request<ApiResponse<BrokerOrder[]>>('/brokers/data/orders', {
|
|
1400
|
-
method: 'GET',
|
|
1401
|
-
headers: {
|
|
1402
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1403
|
-
},
|
|
1404
|
-
params,
|
|
1405
|
-
});
|
|
1406
|
-
|
|
1407
|
-
// Create navigation callback for pagination
|
|
1408
|
-
const navigationCallback = async (
|
|
1409
|
-
newOffset: number,
|
|
1410
|
-
newLimit: number
|
|
1411
|
-
): Promise<PaginatedResult<BrokerOrder[]>> => {
|
|
1412
|
-
const newParams: Record<string, string> = {
|
|
1413
|
-
limit: newLimit.toString(),
|
|
1414
|
-
offset: newOffset.toString(),
|
|
1415
|
-
};
|
|
1416
|
-
|
|
1417
|
-
// Add filter parameters
|
|
1418
|
-
if (filters) {
|
|
1419
|
-
if (filters.broker_id) newParams.broker_id = filters.broker_id;
|
|
1420
|
-
if (filters.connection_id) newParams.connection_id = filters.connection_id;
|
|
1421
|
-
if (filters.account_id) newParams.account_id = filters.account_id;
|
|
1422
|
-
if (filters.symbol) newParams.symbol = filters.symbol;
|
|
1423
|
-
if (filters.status) newParams.status = filters.status;
|
|
1424
|
-
if (filters.side) newParams.side = filters.side;
|
|
1425
|
-
if (filters.asset_type) newParams.asset_type = filters.asset_type;
|
|
1426
|
-
if (filters.created_after) newParams.created_after = filters.created_after;
|
|
1427
|
-
if (filters.created_before) newParams.created_before = filters.created_before;
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
const newResponse = await this.request<ApiResponse<BrokerOrder[]>>('/brokers/data/orders', {
|
|
1431
|
-
method: 'GET',
|
|
1432
|
-
headers: {
|
|
1433
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1434
|
-
},
|
|
1435
|
-
params: newParams,
|
|
1436
|
-
});
|
|
1437
|
-
|
|
1438
|
-
return new PaginatedResult(
|
|
1439
|
-
newResponse.response_data,
|
|
1440
|
-
newResponse.pagination || {
|
|
1441
|
-
has_more: false,
|
|
1442
|
-
next_offset: newOffset,
|
|
1443
|
-
current_offset: newOffset,
|
|
1444
|
-
limit: newLimit,
|
|
1445
|
-
},
|
|
1446
|
-
navigationCallback
|
|
1447
|
-
);
|
|
1448
|
-
};
|
|
1449
|
-
|
|
1450
|
-
return new PaginatedResult(
|
|
1451
|
-
response.response_data,
|
|
1452
|
-
response.pagination || {
|
|
1453
|
-
has_more: false,
|
|
1454
|
-
next_offset: offset,
|
|
1455
|
-
current_offset: offset,
|
|
1456
|
-
limit: perPage,
|
|
1457
|
-
},
|
|
1458
|
-
navigationCallback
|
|
1459
|
-
);
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
async getBrokerAccountsPage(
|
|
1463
|
-
page: number = 1,
|
|
1464
|
-
perPage: number = 100,
|
|
1465
|
-
filters?: AccountsFilter
|
|
1466
|
-
): Promise<PaginatedResult<BrokerAccount[]>> {
|
|
1467
|
-
const accessToken = await this.getValidAccessToken();
|
|
1468
|
-
const offset = (page - 1) * perPage;
|
|
1469
|
-
const params: Record<string, string> = {
|
|
1470
|
-
limit: perPage.toString(),
|
|
1471
|
-
offset: offset.toString(),
|
|
1472
|
-
};
|
|
1473
|
-
|
|
1474
|
-
// Add filter parameters
|
|
1475
|
-
if (filters) {
|
|
1476
|
-
if (filters.broker_id) params.broker_id = filters.broker_id;
|
|
1477
|
-
if (filters.connection_id) params.connection_id = filters.connection_id;
|
|
1478
|
-
if (filters.account_type) params.account_type = filters.account_type;
|
|
1479
|
-
if (filters.status) params.status = filters.status;
|
|
1480
|
-
if (filters.currency) params.currency = filters.currency;
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
const response = await this.request<ApiResponse<BrokerAccount[]>>('/brokers/data/accounts', {
|
|
1484
|
-
method: 'GET',
|
|
1485
|
-
headers: {
|
|
1486
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1487
|
-
},
|
|
1488
|
-
params,
|
|
1489
|
-
});
|
|
1490
|
-
|
|
1491
|
-
// Create navigation callback for pagination
|
|
1492
|
-
const navigationCallback = async (
|
|
1493
|
-
newOffset: number,
|
|
1494
|
-
newLimit: number
|
|
1495
|
-
): Promise<PaginatedResult<BrokerAccount[]>> => {
|
|
1496
|
-
const newParams: Record<string, string> = {
|
|
1497
|
-
limit: newLimit.toString(),
|
|
1498
|
-
offset: newOffset.toString(),
|
|
1499
|
-
};
|
|
1500
|
-
|
|
1501
|
-
// Add filter parameters
|
|
1502
|
-
if (filters) {
|
|
1503
|
-
if (filters.broker_id) newParams.broker_id = filters.broker_id;
|
|
1504
|
-
if (filters.connection_id) newParams.connection_id = filters.connection_id;
|
|
1505
|
-
if (filters.account_type) newParams.account_type = filters.account_type;
|
|
1506
|
-
if (filters.status) newParams.status = filters.status;
|
|
1507
|
-
if (filters.currency) newParams.currency = filters.currency;
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
const newResponse = await this.request<ApiResponse<BrokerAccount[]>>(
|
|
1511
|
-
'/brokers/data/accounts',
|
|
1512
|
-
{
|
|
1513
|
-
method: 'GET',
|
|
1514
|
-
headers: {
|
|
1515
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1516
|
-
},
|
|
1517
|
-
params: newParams,
|
|
1518
|
-
}
|
|
1519
|
-
);
|
|
1520
|
-
|
|
1521
|
-
return new PaginatedResult(
|
|
1522
|
-
newResponse.response_data,
|
|
1523
|
-
newResponse.pagination || {
|
|
1524
|
-
has_more: false,
|
|
1525
|
-
next_offset: newOffset,
|
|
1526
|
-
current_offset: newOffset,
|
|
1527
|
-
limit: newLimit,
|
|
1528
|
-
},
|
|
1529
|
-
navigationCallback
|
|
1530
|
-
);
|
|
1531
|
-
};
|
|
1532
|
-
|
|
1533
|
-
return new PaginatedResult(
|
|
1534
|
-
response.response_data,
|
|
1535
|
-
response.pagination || {
|
|
1536
|
-
has_more: false,
|
|
1537
|
-
next_offset: offset,
|
|
1538
|
-
current_offset: offset,
|
|
1539
|
-
limit: perPage,
|
|
1540
|
-
},
|
|
1541
|
-
navigationCallback
|
|
1542
|
-
);
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
async getBrokerPositionsPage(
|
|
1546
|
-
page: number = 1,
|
|
1547
|
-
perPage: number = 100,
|
|
1548
|
-
filters?: PositionsFilter
|
|
1549
|
-
): Promise<PaginatedResult<BrokerPosition[]>> {
|
|
1550
|
-
const accessToken = await this.getValidAccessToken();
|
|
1551
|
-
const offset = (page - 1) * perPage;
|
|
1552
|
-
const params: Record<string, string> = {
|
|
1553
|
-
limit: perPage.toString(),
|
|
1554
|
-
offset: offset.toString(),
|
|
1555
|
-
};
|
|
1556
|
-
|
|
1557
|
-
// Add filter parameters
|
|
1558
|
-
if (filters) {
|
|
1559
|
-
if (filters.broker_id) params.broker_id = filters.broker_id;
|
|
1560
|
-
if (filters.account_id) params.account_id = filters.account_id;
|
|
1561
|
-
if (filters.symbol) params.symbol = filters.symbol;
|
|
1562
|
-
if (filters.position_status) params.position_status = filters.position_status;
|
|
1563
|
-
if (filters.side) params.side = filters.side;
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
const response = await this.request<ApiResponse<BrokerPosition[]>>('/brokers/data/positions', {
|
|
1567
|
-
method: 'GET',
|
|
1568
|
-
headers: {
|
|
1569
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1570
|
-
},
|
|
1571
|
-
params,
|
|
1572
|
-
});
|
|
1573
|
-
|
|
1574
|
-
// Create navigation callback for pagination
|
|
1575
|
-
const navigationCallback = async (
|
|
1576
|
-
newOffset: number,
|
|
1577
|
-
newLimit: number
|
|
1578
|
-
): Promise<PaginatedResult<BrokerPosition[]>> => {
|
|
1579
|
-
const newParams: Record<string, string> = {
|
|
1580
|
-
limit: newLimit.toString(),
|
|
1581
|
-
offset: newOffset.toString(),
|
|
1582
|
-
};
|
|
1583
|
-
|
|
1584
|
-
// Add filter parameters
|
|
1585
|
-
if (filters) {
|
|
1586
|
-
if (filters.broker_id) newParams.broker_id = filters.broker_id;
|
|
1587
|
-
if (filters.account_id) newParams.account_id = filters.account_id;
|
|
1588
|
-
if (filters.symbol) newParams.symbol = filters.symbol;
|
|
1589
|
-
if (filters.position_status) newParams.position_status = filters.position_status;
|
|
1590
|
-
if (filters.side) newParams.side = filters.side;
|
|
1591
|
-
}
|
|
1592
|
-
|
|
1593
|
-
const newResponse = await this.request<ApiResponse<BrokerPosition[]>>(
|
|
1594
|
-
'/brokers/data/positions',
|
|
1595
|
-
{
|
|
1596
|
-
method: 'GET',
|
|
1597
|
-
headers: {
|
|
1598
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1599
|
-
},
|
|
1600
|
-
params: newParams,
|
|
1601
|
-
}
|
|
1602
|
-
);
|
|
1603
|
-
|
|
1604
|
-
return new PaginatedResult(
|
|
1605
|
-
newResponse.response_data,
|
|
1606
|
-
newResponse.pagination || {
|
|
1607
|
-
has_more: false,
|
|
1608
|
-
next_offset: newOffset,
|
|
1609
|
-
current_offset: newOffset,
|
|
1610
|
-
limit: newLimit,
|
|
1611
|
-
},
|
|
1612
|
-
navigationCallback
|
|
1613
|
-
);
|
|
1614
|
-
};
|
|
1615
|
-
|
|
1616
|
-
return new PaginatedResult(
|
|
1617
|
-
response.response_data,
|
|
1618
|
-
response.pagination || {
|
|
1619
|
-
has_more: false,
|
|
1620
|
-
next_offset: offset,
|
|
1621
|
-
current_offset: offset,
|
|
1622
|
-
limit: perPage,
|
|
1623
|
-
},
|
|
1624
|
-
navigationCallback
|
|
1625
|
-
);
|
|
1626
|
-
}
|
|
1627
|
-
|
|
1628
|
-
async getBrokerBalancesPage(
|
|
1629
|
-
page: number = 1,
|
|
1630
|
-
perPage: number = 100,
|
|
1631
|
-
filters?: BalancesFilter
|
|
1632
|
-
): Promise<PaginatedResult<BrokerBalance[]>> {
|
|
1633
|
-
const accessToken = await this.getValidAccessToken();
|
|
1634
|
-
const offset = (page - 1) * perPage;
|
|
1635
|
-
const params: Record<string, string> = {
|
|
1636
|
-
limit: perPage.toString(),
|
|
1637
|
-
offset: offset.toString(),
|
|
1638
|
-
};
|
|
1639
|
-
|
|
1640
|
-
// Add filter parameters
|
|
1641
|
-
if (filters) {
|
|
1642
|
-
if (filters.broker_id) params.broker_id = filters.broker_id;
|
|
1643
|
-
if (filters.connection_id) params.connection_id = filters.connection_id;
|
|
1644
|
-
if (filters.account_id) params.account_id = filters.account_id;
|
|
1645
|
-
if (filters.is_end_of_day_snapshot !== undefined)
|
|
1646
|
-
params.is_end_of_day_snapshot = filters.is_end_of_day_snapshot.toString();
|
|
1647
|
-
if (filters.balance_created_after)
|
|
1648
|
-
params.balance_created_after = filters.balance_created_after;
|
|
1649
|
-
if (filters.balance_created_before)
|
|
1650
|
-
params.balance_created_before = filters.balance_created_before;
|
|
1651
|
-
if (filters.with_metadata !== undefined)
|
|
1652
|
-
params.with_metadata = filters.with_metadata.toString();
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
const response = await this.request<ApiResponse<BrokerBalance[]>>('/brokers/data/balances', {
|
|
1656
|
-
method: 'GET',
|
|
1657
|
-
headers: {
|
|
1658
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1659
|
-
},
|
|
1660
|
-
params,
|
|
1661
|
-
});
|
|
1662
|
-
|
|
1663
|
-
// Create navigation callback for pagination
|
|
1664
|
-
const navigationCallback = async (
|
|
1665
|
-
newOffset: number,
|
|
1666
|
-
newLimit: number
|
|
1667
|
-
): Promise<PaginatedResult<BrokerBalance[]>> => {
|
|
1668
|
-
const newParams: Record<string, string> = {
|
|
1669
|
-
limit: newLimit.toString(),
|
|
1670
|
-
offset: newOffset.toString(),
|
|
1671
|
-
};
|
|
1672
|
-
|
|
1673
|
-
// Add filter parameters
|
|
1674
|
-
if (filters) {
|
|
1675
|
-
if (filters.broker_id) newParams.broker_id = filters.broker_id;
|
|
1676
|
-
if (filters.connection_id) newParams.connection_id = filters.connection_id;
|
|
1677
|
-
if (filters.account_id) newParams.account_id = filters.account_id;
|
|
1678
|
-
if (filters.is_end_of_day_snapshot !== undefined)
|
|
1679
|
-
newParams.is_end_of_day_snapshot = filters.is_end_of_day_snapshot.toString();
|
|
1680
|
-
if (filters.balance_created_after)
|
|
1681
|
-
newParams.balance_created_after = filters.balance_created_after;
|
|
1682
|
-
if (filters.balance_created_before)
|
|
1683
|
-
newParams.balance_created_before = filters.balance_created_before;
|
|
1684
|
-
if (filters.with_metadata !== undefined)
|
|
1685
|
-
newParams.with_metadata = filters.with_metadata.toString();
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
const newResponse = await this.request<ApiResponse<BrokerBalance[]>>(
|
|
1689
|
-
'/brokers/data/balances',
|
|
1690
|
-
{
|
|
1691
|
-
method: 'GET',
|
|
1692
|
-
headers: {
|
|
1693
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1694
|
-
},
|
|
1695
|
-
params: newParams,
|
|
1696
|
-
}
|
|
1697
|
-
);
|
|
1698
|
-
|
|
1699
|
-
return new PaginatedResult(
|
|
1700
|
-
newResponse.response_data,
|
|
1701
|
-
newResponse.pagination || {
|
|
1702
|
-
has_more: false,
|
|
1703
|
-
next_offset: newOffset,
|
|
1704
|
-
current_offset: newOffset,
|
|
1705
|
-
limit: newLimit,
|
|
1706
|
-
},
|
|
1707
|
-
navigationCallback
|
|
1708
|
-
);
|
|
1709
|
-
};
|
|
1710
|
-
|
|
1711
|
-
return new PaginatedResult(
|
|
1712
|
-
response.response_data,
|
|
1713
|
-
response.pagination || {
|
|
1714
|
-
has_more: false,
|
|
1715
|
-
next_offset: offset,
|
|
1716
|
-
current_offset: offset,
|
|
1717
|
-
limit: perPage,
|
|
1718
|
-
},
|
|
1719
|
-
navigationCallback
|
|
1720
|
-
);
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
// Navigation methods
|
|
1724
|
-
async getNextPage<T>(
|
|
1725
|
-
previousResult: PaginatedResult<T>,
|
|
1726
|
-
fetchFunction: (offset: number, limit: number) => Promise<PaginatedResult<T>>
|
|
1727
|
-
): Promise<PaginatedResult<T> | null> {
|
|
1728
|
-
if (!previousResult.hasNext) {
|
|
1729
|
-
return null;
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
return fetchFunction(previousResult.metadata.nextOffset, previousResult.metadata.limit);
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
/**
|
|
1736
|
-
* Check if this is a mock client
|
|
1737
|
-
* @returns false for real API client
|
|
1738
|
-
*/
|
|
1739
|
-
isMockClient(): boolean {
|
|
1740
|
-
return false;
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
/**
|
|
1744
|
-
* Disconnect a company from a broker connection
|
|
1745
|
-
* @param connectionId - The connection ID to disconnect
|
|
1746
|
-
* @returns Promise with disconnect response
|
|
1747
|
-
*/
|
|
1748
|
-
async disconnectCompany(connectionId: string): Promise<DisconnectCompanyResponse> {
|
|
1749
|
-
const accessToken = await this.getValidAccessToken();
|
|
1750
|
-
return this.request<DisconnectCompanyResponse>(`/brokers/disconnect/${connectionId}`, {
|
|
1751
|
-
method: 'DELETE',
|
|
1752
|
-
headers: {
|
|
1753
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1754
|
-
},
|
|
1755
|
-
});
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
/**
|
|
1759
|
-
* Get order fills for a specific order
|
|
1760
|
-
* @param orderId - The order ID
|
|
1761
|
-
* @param filter - Optional filter parameters
|
|
1762
|
-
* @returns Promise with order fills response
|
|
1763
|
-
*/
|
|
1764
|
-
async getOrderFills(
|
|
1765
|
-
orderId: string,
|
|
1766
|
-
filter?: OrderFillsFilter
|
|
1767
|
-
): Promise<{
|
|
1768
|
-
_id: string;
|
|
1769
|
-
response_data: OrderFill[];
|
|
1770
|
-
message: string;
|
|
1771
|
-
status_code: number;
|
|
1772
|
-
warnings: null;
|
|
1773
|
-
errors: null;
|
|
1774
|
-
}> {
|
|
1775
|
-
const accessToken = await this.getValidAccessToken();
|
|
1776
|
-
const params: Record<string, string> = {};
|
|
1777
|
-
|
|
1778
|
-
if (filter?.connection_id) {
|
|
1779
|
-
params.connection_id = filter.connection_id;
|
|
1780
|
-
}
|
|
1781
|
-
if (filter?.limit) {
|
|
1782
|
-
params.limit = filter.limit.toString();
|
|
1783
|
-
}
|
|
1784
|
-
if (filter?.offset) {
|
|
1785
|
-
params.offset = filter.offset.toString();
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
|
-
return this.request<{
|
|
1789
|
-
_id: string;
|
|
1790
|
-
response_data: OrderFill[];
|
|
1791
|
-
message: string;
|
|
1792
|
-
status_code: number;
|
|
1793
|
-
warnings: null;
|
|
1794
|
-
errors: null;
|
|
1795
|
-
}>(`/brokers/data/orders/${orderId}/fills`, {
|
|
1796
|
-
method: 'GET',
|
|
1797
|
-
headers: {
|
|
1798
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1799
|
-
},
|
|
1800
|
-
params,
|
|
1801
|
-
});
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
/**
|
|
1805
|
-
* Get order events for a specific order
|
|
1806
|
-
* @param orderId - The order ID
|
|
1807
|
-
* @param filter - Optional filter parameters
|
|
1808
|
-
* @returns Promise with order events response
|
|
1809
|
-
*/
|
|
1810
|
-
async getOrderEvents(
|
|
1811
|
-
orderId: string,
|
|
1812
|
-
filter?: OrderEventsFilter
|
|
1813
|
-
): Promise<{
|
|
1814
|
-
_id: string;
|
|
1815
|
-
response_data: OrderEvent[];
|
|
1816
|
-
message: string;
|
|
1817
|
-
status_code: number;
|
|
1818
|
-
warnings: null;
|
|
1819
|
-
errors: null;
|
|
1820
|
-
}> {
|
|
1821
|
-
const accessToken = await this.getValidAccessToken();
|
|
1822
|
-
const params: Record<string, string> = {};
|
|
1823
|
-
|
|
1824
|
-
if (filter?.connection_id) {
|
|
1825
|
-
params.connection_id = filter.connection_id;
|
|
1826
|
-
}
|
|
1827
|
-
if (filter?.limit) {
|
|
1828
|
-
params.limit = filter.limit.toString();
|
|
1829
|
-
}
|
|
1830
|
-
if (filter?.offset) {
|
|
1831
|
-
params.offset = filter.offset.toString();
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
|
-
return this.request<{
|
|
1835
|
-
_id: string;
|
|
1836
|
-
response_data: OrderEvent[];
|
|
1837
|
-
message: string;
|
|
1838
|
-
status_code: number;
|
|
1839
|
-
warnings: null;
|
|
1840
|
-
errors: null;
|
|
1841
|
-
}>(`/brokers/data/orders/${orderId}/events`, {
|
|
1842
|
-
method: 'GET',
|
|
1843
|
-
headers: {
|
|
1844
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1845
|
-
},
|
|
1846
|
-
params,
|
|
1847
|
-
});
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
/**
|
|
1851
|
-
* Get order groups
|
|
1852
|
-
* @param filter - Optional filter parameters
|
|
1853
|
-
* @returns Promise with order groups response
|
|
1854
|
-
*/
|
|
1855
|
-
async getOrderGroups(
|
|
1856
|
-
filter?: OrderGroupsFilter
|
|
1857
|
-
): Promise<{
|
|
1858
|
-
_id: string;
|
|
1859
|
-
response_data: OrderGroup[];
|
|
1860
|
-
message: string;
|
|
1861
|
-
status_code: number;
|
|
1862
|
-
warnings: null;
|
|
1863
|
-
errors: null;
|
|
1864
|
-
}> {
|
|
1865
|
-
const accessToken = await this.getValidAccessToken();
|
|
1866
|
-
const params: Record<string, string> = {};
|
|
1867
|
-
|
|
1868
|
-
if (filter?.broker_id) {
|
|
1869
|
-
params.broker_id = filter.broker_id;
|
|
1870
|
-
}
|
|
1871
|
-
if (filter?.connection_id) {
|
|
1872
|
-
params.connection_id = filter.connection_id;
|
|
1873
|
-
}
|
|
1874
|
-
if (filter?.limit) {
|
|
1875
|
-
params.limit = filter.limit.toString();
|
|
1876
|
-
}
|
|
1877
|
-
if (filter?.offset) {
|
|
1878
|
-
params.offset = filter.offset.toString();
|
|
1879
|
-
}
|
|
1880
|
-
if (filter?.created_after) {
|
|
1881
|
-
params.created_after = filter.created_after;
|
|
1882
|
-
}
|
|
1883
|
-
if (filter?.created_before) {
|
|
1884
|
-
params.created_before = filter.created_before;
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
return this.request<{
|
|
1888
|
-
_id: string;
|
|
1889
|
-
response_data: OrderGroup[];
|
|
1890
|
-
message: string;
|
|
1891
|
-
status_code: number;
|
|
1892
|
-
warnings: null;
|
|
1893
|
-
errors: null;
|
|
1894
|
-
}>('/brokers/data/orders/groups', {
|
|
1895
|
-
method: 'GET',
|
|
1896
|
-
headers: {
|
|
1897
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1898
|
-
},
|
|
1899
|
-
params,
|
|
1900
|
-
});
|
|
1901
|
-
}
|
|
1902
|
-
|
|
1903
|
-
/**
|
|
1904
|
-
* Get position lots (tax lots for positions)
|
|
1905
|
-
* @param filter - Optional filter parameters
|
|
1906
|
-
* @returns Promise with position lots response
|
|
1907
|
-
*/
|
|
1908
|
-
async getPositionLots(
|
|
1909
|
-
filter?: PositionLotsFilter
|
|
1910
|
-
): Promise<{
|
|
1911
|
-
_id: string;
|
|
1912
|
-
response_data: PositionLot[];
|
|
1913
|
-
message: string;
|
|
1914
|
-
status_code: number;
|
|
1915
|
-
warnings: null;
|
|
1916
|
-
errors: null;
|
|
1917
|
-
}> {
|
|
1918
|
-
const accessToken = await this.getValidAccessToken();
|
|
1919
|
-
const params: Record<string, string> = {};
|
|
1920
|
-
|
|
1921
|
-
if (filter?.broker_id) {
|
|
1922
|
-
params.broker_id = filter.broker_id;
|
|
1923
|
-
}
|
|
1924
|
-
if (filter?.connection_id) {
|
|
1925
|
-
params.connection_id = filter.connection_id;
|
|
1926
|
-
}
|
|
1927
|
-
if (filter?.account_id) {
|
|
1928
|
-
params.account_id = filter.account_id;
|
|
1929
|
-
}
|
|
1930
|
-
if (filter?.symbol) {
|
|
1931
|
-
params.symbol = filter.symbol;
|
|
1932
|
-
}
|
|
1933
|
-
if (filter?.position_id) {
|
|
1934
|
-
params.position_id = filter.position_id;
|
|
1935
|
-
}
|
|
1936
|
-
if (filter?.limit) {
|
|
1937
|
-
params.limit = filter.limit.toString();
|
|
1938
|
-
}
|
|
1939
|
-
if (filter?.offset) {
|
|
1940
|
-
params.offset = filter.offset.toString();
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
|
-
return this.request<{
|
|
1944
|
-
_id: string;
|
|
1945
|
-
response_data: PositionLot[];
|
|
1946
|
-
message: string;
|
|
1947
|
-
status_code: number;
|
|
1948
|
-
warnings: null;
|
|
1949
|
-
errors: null;
|
|
1950
|
-
}>('/brokers/data/positions/lots', {
|
|
1951
|
-
method: 'GET',
|
|
1952
|
-
headers: {
|
|
1953
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1954
|
-
},
|
|
1955
|
-
params,
|
|
1956
|
-
});
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
/**
|
|
1960
|
-
* Get position lot fills for a specific lot
|
|
1961
|
-
* @param lotId - The position lot ID
|
|
1962
|
-
* @param filter - Optional filter parameters
|
|
1963
|
-
* @returns Promise with position lot fills response
|
|
1964
|
-
*/
|
|
1965
|
-
async getPositionLotFills(
|
|
1966
|
-
lotId: string,
|
|
1967
|
-
filter?: PositionLotFillsFilter
|
|
1968
|
-
): Promise<{
|
|
1969
|
-
_id: string;
|
|
1970
|
-
response_data: PositionLotFill[];
|
|
1971
|
-
message: string;
|
|
1972
|
-
status_code: number;
|
|
1973
|
-
warnings: null;
|
|
1974
|
-
errors: null;
|
|
1975
|
-
}> {
|
|
1976
|
-
const accessToken = await this.getValidAccessToken();
|
|
1977
|
-
const params: Record<string, string> = {};
|
|
1978
|
-
|
|
1979
|
-
if (filter?.connection_id) {
|
|
1980
|
-
params.connection_id = filter.connection_id;
|
|
1981
|
-
}
|
|
1982
|
-
if (filter?.limit) {
|
|
1983
|
-
params.limit = filter.limit.toString();
|
|
1984
|
-
}
|
|
1985
|
-
if (filter?.offset) {
|
|
1986
|
-
params.offset = filter.offset.toString();
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
return this.request<{
|
|
1990
|
-
_id: string;
|
|
1991
|
-
response_data: PositionLotFill[];
|
|
1992
|
-
message: string;
|
|
1993
|
-
status_code: number;
|
|
1994
|
-
warnings: null;
|
|
1995
|
-
errors: null;
|
|
1996
|
-
}>(`/brokers/data/positions/lots/${lotId}/fills`, {
|
|
1997
|
-
method: 'GET',
|
|
1998
|
-
headers: {
|
|
1999
|
-
Authorization: `Bearer ${accessToken}`,
|
|
2000
|
-
},
|
|
2001
|
-
params,
|
|
2002
|
-
});
|
|
2003
|
-
}
|
|
2004
|
-
}
|