@finatic/client 0.0.133 → 0.0.135
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 +87 -0
- package/dist/index.d.ts +471 -730
- package/dist/index.js +847 -734
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +848 -732
- package/dist/index.mjs.map +1 -1
- package/dist/types/core/client/ApiClient.d.ts +206 -0
- package/dist/types/{client → core/client}/FinaticConnect.d.ts +54 -13
- package/dist/types/{portal → core/portal}/PortalUI.d.ts +1 -1
- package/dist/types/index.d.ts +6 -11
- package/dist/types/mocks/MockApiClient.d.ts +36 -90
- package/dist/types/mocks/MockDataProvider.d.ts +13 -3
- package/dist/types/mocks/MockFactory.d.ts +2 -2
- package/dist/types/{shared/themes → themes}/portalPresets.d.ts +1 -1
- package/dist/types/types/api/auth.d.ts +111 -0
- package/dist/types/types/{api.d.ts → api/broker.d.ts} +56 -284
- package/dist/types/types/api/core.d.ts +46 -0
- package/dist/types/types/api/errors.d.ts +23 -0
- package/dist/types/types/api/orders.d.ts +39 -0
- package/dist/types/types/{shared.d.ts → api/portfolio.d.ts} +26 -21
- package/dist/types/types/common/pagination.d.ts +33 -0
- package/dist/types/types/connect.d.ts +4 -2
- package/dist/types/types/index.d.ts +13 -0
- package/dist/types/types/{theme.d.ts → ui/theme.d.ts} +3 -0
- package/dist/types/utils/brokerUtils.d.ts +30 -0
- package/package.json +4 -3
- package/dist/types/client/ApiClient.d.ts +0 -234
- package/dist/types/mocks/index.d.ts +0 -5
- package/dist/types/security/ApiSecurity.d.ts +0 -24
- package/dist/types/security/RuntimeSecurity.d.ts +0 -28
- package/dist/types/security/SecurityUtils.d.ts +0 -21
- package/dist/types/security/index.d.ts +0 -2
- package/dist/types/services/AnalyticsService.d.ts +0 -18
- package/dist/types/services/ApiClient.d.ts +0 -121
- package/dist/types/services/PortalService.d.ts +0 -24
- package/dist/types/services/TradingService.d.ts +0 -55
- package/dist/types/services/api.d.ts +0 -23
- package/dist/types/services/auth.d.ts +0 -9
- package/dist/types/services/index.d.ts +0 -4
- package/dist/types/services/portfolio.d.ts +0 -10
- package/dist/types/services/trading.d.ts +0 -10
- package/dist/types/shared/index.d.ts +0 -2
- package/dist/types/shared/themes/index.d.ts +0 -2
- package/dist/types/shared/themes/presets.d.ts +0 -3
- package/dist/types/shared/themes/system.d.ts +0 -2
- package/dist/types/shared/types/index.d.ts +0 -110
- package/dist/types/types/config.d.ts +0 -12
- package/dist/types/types/errors.d.ts +0 -47
- package/dist/types/types/security.d.ts +0 -35
- package/dist/types/types.d.ts +0 -157
package/dist/index.js
CHANGED
|
@@ -3,23 +3,12 @@
|
|
|
3
3
|
var uuid = require('uuid');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*/
|
|
8
|
-
var SessionState;
|
|
9
|
-
(function (SessionState) {
|
|
10
|
-
SessionState["PENDING"] = "PENDING";
|
|
11
|
-
SessionState["AUTHENTICATING"] = "AUTHENTICATING";
|
|
12
|
-
SessionState["ACTIVE"] = "ACTIVE";
|
|
13
|
-
SessionState["COMPLETED"] = "COMPLETED";
|
|
14
|
-
SessionState["EXPIRED"] = "EXPIRED";
|
|
15
|
-
})(SessionState || (SessionState = {}));
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Core types for Finatic Connect SDK
|
|
6
|
+
* Pagination-related types and classes
|
|
19
7
|
*/
|
|
20
8
|
class PaginatedResult {
|
|
21
9
|
constructor(data, paginationInfo, navigationCallback) {
|
|
22
10
|
this.data = data;
|
|
11
|
+
this.navigationCallback = navigationCallback;
|
|
23
12
|
this.metadata = {
|
|
24
13
|
hasMore: paginationInfo.has_more,
|
|
25
14
|
nextOffset: paginationInfo.next_offset,
|
|
@@ -29,7 +18,6 @@ class PaginatedResult {
|
|
|
29
18
|
hasNext: paginationInfo.has_more,
|
|
30
19
|
hasPrevious: paginationInfo.current_offset > 0,
|
|
31
20
|
};
|
|
32
|
-
this.navigationCallback = navigationCallback;
|
|
33
21
|
}
|
|
34
22
|
get hasNext() {
|
|
35
23
|
return this.metadata.hasNext;
|
|
@@ -40,10 +28,6 @@ class PaginatedResult {
|
|
|
40
28
|
get currentPage() {
|
|
41
29
|
return this.metadata.currentPage;
|
|
42
30
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Navigate to the next page
|
|
45
|
-
* @returns Promise<PaginatedResult<T> | null> - Next page result or null if no next page
|
|
46
|
-
*/
|
|
47
31
|
async nextPage() {
|
|
48
32
|
if (!this.hasNext || !this.navigationCallback) {
|
|
49
33
|
return null;
|
|
@@ -52,14 +36,10 @@ class PaginatedResult {
|
|
|
52
36
|
return await this.navigationCallback(this.metadata.nextOffset, this.metadata.limit);
|
|
53
37
|
}
|
|
54
38
|
catch (error) {
|
|
55
|
-
console.error('Error
|
|
39
|
+
console.error('Error fetching next page:', error);
|
|
56
40
|
return null;
|
|
57
41
|
}
|
|
58
42
|
}
|
|
59
|
-
/**
|
|
60
|
-
* Navigate to the previous page
|
|
61
|
-
* @returns Promise<PaginatedResult<T> | null> - Previous page result or null if no previous page
|
|
62
|
-
*/
|
|
63
43
|
async previousPage() {
|
|
64
44
|
if (!this.hasPrevious || !this.navigationCallback) {
|
|
65
45
|
return null;
|
|
@@ -69,61 +49,74 @@ class PaginatedResult {
|
|
|
69
49
|
return await this.navigationCallback(previousOffset, this.metadata.limit);
|
|
70
50
|
}
|
|
71
51
|
catch (error) {
|
|
72
|
-
console.error('Error
|
|
52
|
+
console.error('Error fetching previous page:', error);
|
|
73
53
|
return null;
|
|
74
54
|
}
|
|
75
55
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Navigate to a specific page
|
|
78
|
-
* @param pageNumber - The page number to navigate to (1-based)
|
|
79
|
-
* @returns Promise<PaginatedResult<T> | null> - Page result or null if page doesn't exist
|
|
80
|
-
*/
|
|
81
56
|
async goToPage(pageNumber) {
|
|
82
|
-
if (pageNumber < 1
|
|
57
|
+
if (!this.navigationCallback || pageNumber < 1) {
|
|
83
58
|
return null;
|
|
84
59
|
}
|
|
85
|
-
const
|
|
60
|
+
const offset = (pageNumber - 1) * this.metadata.limit;
|
|
86
61
|
try {
|
|
87
|
-
return await this.navigationCallback(
|
|
62
|
+
return await this.navigationCallback(offset, this.metadata.limit);
|
|
88
63
|
}
|
|
89
64
|
catch (error) {
|
|
90
|
-
console.error('Error
|
|
65
|
+
console.error('Error fetching page:', pageNumber, error);
|
|
91
66
|
return null;
|
|
92
67
|
}
|
|
93
68
|
}
|
|
94
|
-
/**
|
|
95
|
-
* Get the first page
|
|
96
|
-
* @returns Promise<PaginatedResult<T> | null> - First page result or null if error
|
|
97
|
-
*/
|
|
98
69
|
async firstPage() {
|
|
99
|
-
|
|
70
|
+
if (!this.navigationCallback) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
return await this.navigationCallback(0, this.metadata.limit);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error('Error fetching first page:', error);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
100
80
|
}
|
|
101
|
-
/**
|
|
102
|
-
* Get the last page (this is a best effort - we don't know the total count)
|
|
103
|
-
* @returns Promise<PaginatedResult<T> | null> - Last page result or null if error
|
|
104
|
-
*/
|
|
105
81
|
async lastPage() {
|
|
106
82
|
if (!this.navigationCallback) {
|
|
107
83
|
return null;
|
|
108
84
|
}
|
|
109
85
|
const findLast = async (page) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return findLast(next);
|
|
86
|
+
if (!page.hasNext) {
|
|
87
|
+
return page;
|
|
113
88
|
}
|
|
114
|
-
|
|
89
|
+
const nextPage = await page.nextPage();
|
|
90
|
+
if (!nextPage) {
|
|
91
|
+
return page;
|
|
92
|
+
}
|
|
93
|
+
return findLast(nextPage);
|
|
115
94
|
};
|
|
116
|
-
|
|
95
|
+
try {
|
|
96
|
+
return await findLast(this);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error('Error fetching last page:', error);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
117
102
|
}
|
|
118
|
-
/**
|
|
119
|
-
* Get pagination info as a string
|
|
120
|
-
* @returns string - Human readable pagination info
|
|
121
|
-
*/
|
|
122
103
|
getPaginationInfo() {
|
|
123
|
-
return `Page ${this.currentPage} (${this.metadata.currentOffset + 1}-${this.metadata.currentOffset + this.metadata.limit})
|
|
104
|
+
return `Page ${this.currentPage} (${this.metadata.currentOffset + 1}-${this.metadata.currentOffset + this.metadata.limit})`;
|
|
124
105
|
}
|
|
125
106
|
}
|
|
126
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Authentication-related types
|
|
110
|
+
*/
|
|
111
|
+
var SessionState;
|
|
112
|
+
(function (SessionState) {
|
|
113
|
+
SessionState["PENDING"] = "PENDING";
|
|
114
|
+
SessionState["AUTHENTICATING"] = "AUTHENTICATING";
|
|
115
|
+
SessionState["ACTIVE"] = "ACTIVE";
|
|
116
|
+
SessionState["COMPLETED"] = "COMPLETED";
|
|
117
|
+
SessionState["EXPIRED"] = "EXPIRED";
|
|
118
|
+
})(SessionState || (SessionState = {}));
|
|
119
|
+
|
|
127
120
|
class BaseError extends Error {
|
|
128
121
|
constructor(message, code = 'UNKNOWN_ERROR') {
|
|
129
122
|
super(message);
|
|
@@ -366,11 +359,68 @@ class ApiClient {
|
|
|
366
359
|
url.searchParams.append(key, value);
|
|
367
360
|
});
|
|
368
361
|
}
|
|
362
|
+
// Build comprehensive headers object with all available session data
|
|
363
|
+
const comprehensiveHeaders = {
|
|
364
|
+
'Content-Type': 'application/json',
|
|
365
|
+
};
|
|
366
|
+
// Add device info if available
|
|
367
|
+
if (this.deviceInfo) {
|
|
368
|
+
comprehensiveHeaders['X-Device-Info'] = JSON.stringify({
|
|
369
|
+
ip_address: this.deviceInfo.ip_address || '',
|
|
370
|
+
user_agent: this.deviceInfo.user_agent || '',
|
|
371
|
+
fingerprint: this.deviceInfo.fingerprint || '',
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
// Add session headers if available (filter out empty values)
|
|
375
|
+
if (this.currentSessionId && this.currentSessionId.trim() !== '') {
|
|
376
|
+
comprehensiveHeaders['X-Session-ID'] = this.currentSessionId;
|
|
377
|
+
comprehensiveHeaders['Session-ID'] = this.currentSessionId;
|
|
378
|
+
}
|
|
379
|
+
if (this.companyId && this.companyId.trim() !== '') {
|
|
380
|
+
comprehensiveHeaders['X-Company-ID'] = this.companyId;
|
|
381
|
+
}
|
|
382
|
+
if (this.csrfToken && this.csrfToken.trim() !== '') {
|
|
383
|
+
comprehensiveHeaders['X-CSRF-Token'] = this.csrfToken;
|
|
384
|
+
}
|
|
385
|
+
// Add any additional headers from options (these will override defaults)
|
|
386
|
+
if (options.headers) {
|
|
387
|
+
Object.entries(options.headers).forEach(([key, value]) => {
|
|
388
|
+
if (value !== undefined && value !== null && value.trim() !== '') {
|
|
389
|
+
comprehensiveHeaders[key] = value;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
// Safari-specific fix: Ensure all headers are explicitly set and not empty
|
|
394
|
+
// Safari can be strict about header formatting and empty values
|
|
395
|
+
const safariSafeHeaders = {};
|
|
396
|
+
Object.entries(comprehensiveHeaders).forEach(([key, value]) => {
|
|
397
|
+
if (value && value.trim() !== '') {
|
|
398
|
+
// Ensure header names are properly formatted for Safari
|
|
399
|
+
const normalizedKey = key.trim();
|
|
400
|
+
const normalizedValue = value.trim();
|
|
401
|
+
safariSafeHeaders[normalizedKey] = normalizedValue;
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
// Debug logging for development
|
|
405
|
+
if (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
|
|
406
|
+
console.log('Request to:', url.toString());
|
|
407
|
+
console.log('Safari-safe headers:', safariSafeHeaders);
|
|
408
|
+
console.log('Browser:', navigator.userAgent);
|
|
409
|
+
console.log('Session ID:', this.currentSessionId);
|
|
410
|
+
console.log('Company ID:', this.companyId);
|
|
411
|
+
console.log('CSRF Token:', this.csrfToken);
|
|
412
|
+
}
|
|
369
413
|
const response = await fetch(url.toString(), {
|
|
370
414
|
method: options.method,
|
|
371
|
-
headers:
|
|
415
|
+
headers: safariSafeHeaders,
|
|
372
416
|
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
373
417
|
});
|
|
418
|
+
// Debug logging for response
|
|
419
|
+
if (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
|
|
420
|
+
console.log('Response status:', response.status);
|
|
421
|
+
console.log('Response headers:', Object.fromEntries(response.headers.entries()));
|
|
422
|
+
console.log('Request was made with headers:', safariSafeHeaders);
|
|
423
|
+
}
|
|
374
424
|
if (!response.ok) {
|
|
375
425
|
const error = await response.json();
|
|
376
426
|
throw this.handleError(response.status, error);
|
|
@@ -619,63 +669,47 @@ class ApiClient {
|
|
|
619
669
|
});
|
|
620
670
|
}
|
|
621
671
|
// Portfolio Management
|
|
622
|
-
async getHoldings(
|
|
672
|
+
async getHoldings() {
|
|
673
|
+
const accessToken = await this.getValidAccessToken();
|
|
623
674
|
return this.request('/portfolio/holdings', {
|
|
624
675
|
method: 'GET',
|
|
625
676
|
headers: {
|
|
626
|
-
'
|
|
627
|
-
Authorization: `Bearer ${accessToken}`,
|
|
677
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
628
678
|
},
|
|
629
679
|
});
|
|
630
680
|
}
|
|
631
|
-
async getOrders(
|
|
632
|
-
|
|
681
|
+
async getOrders() {
|
|
682
|
+
const accessToken = await this.getValidAccessToken();
|
|
683
|
+
return this.request('/data/orders', {
|
|
633
684
|
method: 'GET',
|
|
634
685
|
headers: {
|
|
635
|
-
'
|
|
636
|
-
Authorization: `Bearer ${accessToken}`,
|
|
686
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
637
687
|
},
|
|
638
688
|
});
|
|
639
689
|
}
|
|
640
|
-
async getPortfolio(
|
|
690
|
+
async getPortfolio() {
|
|
691
|
+
const accessToken = await this.getValidAccessToken();
|
|
641
692
|
const response = await this.request('/portfolio/', {
|
|
642
693
|
method: 'GET',
|
|
643
694
|
headers: {
|
|
644
|
-
Authorization: `Bearer ${accessToken}`,
|
|
695
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
645
696
|
'Content-Type': 'application/json',
|
|
646
697
|
},
|
|
647
698
|
});
|
|
648
699
|
return response;
|
|
649
700
|
}
|
|
650
|
-
async placeOrder(accessToken, order) {
|
|
651
|
-
await this.request('/orders/', {
|
|
652
|
-
method: 'POST',
|
|
653
|
-
headers: {
|
|
654
|
-
Authorization: `Bearer ${accessToken}`,
|
|
655
|
-
'Content-Type': 'application/json',
|
|
656
|
-
},
|
|
657
|
-
body: order,
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
// New methods with automatic token management
|
|
661
|
-
async getHoldingsAuto() {
|
|
662
|
-
const accessToken = await this.getValidAccessToken();
|
|
663
|
-
return this.getHoldings(accessToken);
|
|
664
|
-
}
|
|
665
|
-
async getOrdersAuto() {
|
|
666
|
-
const accessToken = await this.getValidAccessToken();
|
|
667
|
-
return this.getOrders(accessToken);
|
|
668
|
-
}
|
|
669
|
-
async getPortfolioAuto() {
|
|
670
|
-
const accessToken = await this.getValidAccessToken();
|
|
671
|
-
return this.getPortfolio(accessToken);
|
|
672
|
-
}
|
|
673
|
-
async placeOrderAuto(order) {
|
|
674
|
-
const accessToken = await this.getValidAccessToken();
|
|
675
|
-
return this.placeOrder(accessToken, order);
|
|
676
|
-
}
|
|
677
701
|
// Enhanced Trading Methods with Session Management
|
|
678
|
-
async placeBrokerOrder(
|
|
702
|
+
async placeBrokerOrder(params, extras = {}, connection_id) {
|
|
703
|
+
const accessToken = await this.getValidAccessToken();
|
|
704
|
+
// Get broker and account from context or params
|
|
705
|
+
const broker = params.broker || this.tradingContext.broker;
|
|
706
|
+
const accountNumber = params.accountNumber || this.tradingContext.accountNumber;
|
|
707
|
+
if (!broker) {
|
|
708
|
+
throw new Error('Broker not set. Call setBroker() or pass broker parameter.');
|
|
709
|
+
}
|
|
710
|
+
if (!accountNumber) {
|
|
711
|
+
throw new Error('Account not set. Call setAccount() or pass accountNumber parameter.');
|
|
712
|
+
}
|
|
679
713
|
// Merge context with provided parameters
|
|
680
714
|
const fullParams = {
|
|
681
715
|
broker: (params.broker || this.tradingContext.broker) ||
|
|
@@ -695,9 +729,15 @@ class ApiClient {
|
|
|
695
729
|
timeInForce: params.timeInForce || 'day',
|
|
696
730
|
price: params.price,
|
|
697
731
|
stopPrice: params.stopPrice,
|
|
732
|
+
order_id: params.order_id,
|
|
698
733
|
};
|
|
699
|
-
// Build request body with
|
|
734
|
+
// Build request body with camelCase parameter names
|
|
700
735
|
const requestBody = this.buildOrderRequestBody(fullParams, extras);
|
|
736
|
+
// Add query parameters if connection_id is provided
|
|
737
|
+
const queryParams = {};
|
|
738
|
+
if (connection_id) {
|
|
739
|
+
queryParams.connection_id = connection_id;
|
|
740
|
+
}
|
|
701
741
|
return this.request('/brokers/orders', {
|
|
702
742
|
method: 'POST',
|
|
703
743
|
headers: {
|
|
@@ -708,17 +748,41 @@ class ApiClient {
|
|
|
708
748
|
'X-Device-Info': JSON.stringify(this.deviceInfo),
|
|
709
749
|
},
|
|
710
750
|
body: requestBody,
|
|
751
|
+
params: queryParams,
|
|
711
752
|
});
|
|
712
753
|
}
|
|
713
|
-
async cancelBrokerOrder(orderId, broker, extras = {}) {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
754
|
+
async cancelBrokerOrder(orderId, broker, extras = {}, connection_id) {
|
|
755
|
+
await this.getValidAccessToken();
|
|
756
|
+
const selectedBroker = broker || this.tradingContext.broker;
|
|
757
|
+
if (!selectedBroker) {
|
|
758
|
+
throw new Error('Broker not set. Call setBroker() or pass broker parameter.');
|
|
759
|
+
}
|
|
760
|
+
const accountNumber = this.tradingContext.accountNumber;
|
|
761
|
+
// Build query parameters as required by API documentation
|
|
762
|
+
const queryParams = {
|
|
763
|
+
broker: selectedBroker,
|
|
764
|
+
order_id: orderId,
|
|
765
|
+
};
|
|
766
|
+
// Add optional parameters if available
|
|
767
|
+
if (accountNumber) {
|
|
768
|
+
queryParams.account_number = accountNumber.toString();
|
|
769
|
+
}
|
|
770
|
+
if (connection_id) {
|
|
771
|
+
queryParams.connection_id = connection_id;
|
|
772
|
+
}
|
|
773
|
+
// Build optional request body if extras are provided
|
|
774
|
+
let body = undefined;
|
|
775
|
+
if (Object.keys(extras).length > 0) {
|
|
776
|
+
body = {
|
|
777
|
+
broker: selectedBroker,
|
|
778
|
+
order: {
|
|
779
|
+
order_id: orderId,
|
|
780
|
+
account_number: accountNumber,
|
|
781
|
+
...extras
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
return this.request('/brokers/orders', {
|
|
722
786
|
method: 'DELETE',
|
|
723
787
|
headers: {
|
|
724
788
|
'Content-Type': 'application/json',
|
|
@@ -727,16 +791,22 @@ class ApiClient {
|
|
|
727
791
|
'X-Device-Info': JSON.stringify(this.deviceInfo),
|
|
728
792
|
},
|
|
729
793
|
body,
|
|
794
|
+
params: queryParams,
|
|
730
795
|
});
|
|
731
796
|
}
|
|
732
|
-
async modifyBrokerOrder(orderId, params, broker, extras = {}) {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
// Build request body with
|
|
739
|
-
const requestBody = this.buildModifyRequestBody(params, extras,
|
|
797
|
+
async modifyBrokerOrder(orderId, params, broker, extras = {}, connection_id) {
|
|
798
|
+
await this.getValidAccessToken();
|
|
799
|
+
const selectedBroker = broker || this.tradingContext.broker;
|
|
800
|
+
if (!selectedBroker) {
|
|
801
|
+
throw new Error('Broker not set. Call setBroker() or pass broker parameter.');
|
|
802
|
+
}
|
|
803
|
+
// Build request body with camelCase parameter names and include broker
|
|
804
|
+
const requestBody = this.buildModifyRequestBody(params, extras, selectedBroker);
|
|
805
|
+
// Add query parameters if connection_id is provided
|
|
806
|
+
const queryParams = {};
|
|
807
|
+
if (connection_id) {
|
|
808
|
+
queryParams.connection_id = connection_id;
|
|
809
|
+
}
|
|
740
810
|
return this.request(`/brokers/orders/${orderId}`, {
|
|
741
811
|
method: 'PATCH',
|
|
742
812
|
headers: {
|
|
@@ -746,6 +816,7 @@ class ApiClient {
|
|
|
746
816
|
'X-Device-Info': JSON.stringify(this.deviceInfo),
|
|
747
817
|
},
|
|
748
818
|
body: requestBody,
|
|
819
|
+
params: queryParams,
|
|
749
820
|
});
|
|
750
821
|
}
|
|
751
822
|
// Context management methods
|
|
@@ -766,392 +837,340 @@ class ApiClient {
|
|
|
766
837
|
this.tradingContext = {};
|
|
767
838
|
}
|
|
768
839
|
// Stock convenience methods
|
|
769
|
-
async placeStockMarketOrder(
|
|
770
|
-
return this.placeBrokerOrder(
|
|
771
|
-
broker,
|
|
772
|
-
accountNumber,
|
|
840
|
+
async placeStockMarketOrder(symbol, orderQty, action, broker, accountNumber, extras = {}, connection_id) {
|
|
841
|
+
return this.placeBrokerOrder({
|
|
773
842
|
symbol,
|
|
774
843
|
orderQty,
|
|
775
844
|
action,
|
|
776
845
|
orderType: 'Market',
|
|
777
846
|
assetType: 'Stock',
|
|
778
847
|
timeInForce: 'day',
|
|
779
|
-
}, extras);
|
|
780
|
-
}
|
|
781
|
-
async placeStockLimitOrder(accessToken, symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
|
|
782
|
-
return this.placeBrokerOrder(accessToken, {
|
|
783
848
|
broker,
|
|
784
849
|
accountNumber,
|
|
850
|
+
}, extras, connection_id);
|
|
851
|
+
}
|
|
852
|
+
async placeStockLimitOrder(symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}, connection_id) {
|
|
853
|
+
return this.placeBrokerOrder({
|
|
785
854
|
symbol,
|
|
786
855
|
orderQty,
|
|
787
856
|
action,
|
|
788
857
|
orderType: 'Limit',
|
|
789
858
|
assetType: 'Stock',
|
|
790
|
-
timeInForce,
|
|
791
859
|
price,
|
|
792
|
-
|
|
793
|
-
}
|
|
794
|
-
async placeStockStopOrder(accessToken, symbol, orderQty, action, stopPrice, timeInForce = 'day', broker, accountNumber, extras = {}) {
|
|
795
|
-
return this.placeBrokerOrder(accessToken, {
|
|
860
|
+
timeInForce,
|
|
796
861
|
broker,
|
|
797
862
|
accountNumber,
|
|
863
|
+
}, extras, connection_id);
|
|
864
|
+
}
|
|
865
|
+
async placeStockStopOrder(symbol, orderQty, action, stopPrice, timeInForce = 'day', broker, accountNumber, extras = {}, connection_id) {
|
|
866
|
+
return this.placeBrokerOrder({
|
|
798
867
|
symbol,
|
|
799
868
|
orderQty,
|
|
800
869
|
action,
|
|
801
870
|
orderType: 'Stop',
|
|
802
871
|
assetType: 'Stock',
|
|
803
|
-
timeInForce,
|
|
804
872
|
stopPrice,
|
|
805
|
-
|
|
806
|
-
}
|
|
807
|
-
// Crypto convenience methods
|
|
808
|
-
async placeCryptoMarketOrder(accessToken, symbol, orderQty, action, options = {}, broker, accountNumber, extras = {}) {
|
|
809
|
-
const orderParams = {
|
|
873
|
+
timeInForce,
|
|
810
874
|
broker,
|
|
811
875
|
accountNumber,
|
|
876
|
+
}, extras, connection_id);
|
|
877
|
+
}
|
|
878
|
+
// Crypto convenience methods
|
|
879
|
+
async placeCryptoMarketOrder(symbol, orderQty, action, options = {}, broker, accountNumber, extras = {}) {
|
|
880
|
+
return this.placeBrokerOrder({
|
|
812
881
|
symbol,
|
|
813
|
-
orderQty
|
|
882
|
+
orderQty,
|
|
814
883
|
action,
|
|
815
884
|
orderType: 'Market',
|
|
816
885
|
assetType: 'Crypto',
|
|
817
|
-
timeInForce: '
|
|
818
|
-
};
|
|
819
|
-
// Add notional if provided
|
|
820
|
-
if (options.notional) {
|
|
821
|
-
orderParams.notional = options.notional;
|
|
822
|
-
}
|
|
823
|
-
return this.placeBrokerOrder(accessToken, orderParams, extras);
|
|
824
|
-
}
|
|
825
|
-
async placeCryptoLimitOrder(accessToken, symbol, orderQty, action, price, timeInForce = 'gtc', options = {}, broker, accountNumber, extras = {}) {
|
|
826
|
-
const orderParams = {
|
|
886
|
+
timeInForce: 'day',
|
|
827
887
|
broker,
|
|
828
888
|
accountNumber,
|
|
889
|
+
...options,
|
|
890
|
+
}, extras);
|
|
891
|
+
}
|
|
892
|
+
async placeCryptoLimitOrder(symbol, orderQty, action, price, timeInForce = 'gtc', options = {}, broker, accountNumber, extras = {}) {
|
|
893
|
+
return this.placeBrokerOrder({
|
|
829
894
|
symbol,
|
|
830
|
-
orderQty
|
|
895
|
+
orderQty,
|
|
831
896
|
action,
|
|
832
897
|
orderType: 'Limit',
|
|
833
898
|
assetType: 'Crypto',
|
|
834
|
-
timeInForce,
|
|
835
899
|
price,
|
|
836
|
-
|
|
837
|
-
// Add notional if provided
|
|
838
|
-
if (options.notional) {
|
|
839
|
-
orderParams.notional = options.notional;
|
|
840
|
-
}
|
|
841
|
-
return this.placeBrokerOrder(accessToken, orderParams, extras);
|
|
842
|
-
}
|
|
843
|
-
// Options convenience methods
|
|
844
|
-
async placeOptionsMarketOrder(accessToken, symbol, orderQty, action, options, broker, accountNumber, extras = {}) {
|
|
845
|
-
const orderParams = {
|
|
900
|
+
timeInForce,
|
|
846
901
|
broker,
|
|
847
902
|
accountNumber,
|
|
903
|
+
...options,
|
|
904
|
+
}, extras);
|
|
905
|
+
}
|
|
906
|
+
// Options convenience methods
|
|
907
|
+
async placeOptionsMarketOrder(symbol, orderQty, action, options, broker, accountNumber, extras = {}) {
|
|
908
|
+
return this.placeBrokerOrder({
|
|
848
909
|
symbol,
|
|
849
910
|
orderQty,
|
|
850
911
|
action,
|
|
851
912
|
orderType: 'Market',
|
|
852
913
|
assetType: 'Option',
|
|
853
914
|
timeInForce: 'day',
|
|
854
|
-
};
|
|
855
|
-
// Add options-specific parameters to extras
|
|
856
|
-
const targetBroker = broker || this.tradingContext.broker;
|
|
857
|
-
const optionsExtras = {
|
|
858
|
-
...extras,
|
|
859
|
-
...(targetBroker && {
|
|
860
|
-
[targetBroker]: {
|
|
861
|
-
...extras[targetBroker],
|
|
862
|
-
strikePrice: options.strikePrice,
|
|
863
|
-
expirationDate: options.expirationDate,
|
|
864
|
-
optionType: options.optionType,
|
|
865
|
-
contractSize: options.contractSize || 100,
|
|
866
|
-
},
|
|
867
|
-
}),
|
|
868
|
-
};
|
|
869
|
-
return this.placeBrokerOrder(accessToken, orderParams, optionsExtras);
|
|
870
|
-
}
|
|
871
|
-
async placeOptionsLimitOrder(accessToken, symbol, orderQty, action, price, options, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
|
|
872
|
-
const orderParams = {
|
|
873
915
|
broker,
|
|
874
916
|
accountNumber,
|
|
917
|
+
...options,
|
|
918
|
+
}, extras);
|
|
919
|
+
}
|
|
920
|
+
async placeOptionsLimitOrder(symbol, orderQty, action, price, options, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
|
|
921
|
+
return this.placeBrokerOrder({
|
|
875
922
|
symbol,
|
|
876
923
|
orderQty,
|
|
877
924
|
action,
|
|
878
925
|
orderType: 'Limit',
|
|
879
926
|
assetType: 'Option',
|
|
880
|
-
timeInForce,
|
|
881
927
|
price,
|
|
882
|
-
|
|
883
|
-
// Add options-specific parameters to extras
|
|
884
|
-
const targetBroker = broker || this.tradingContext.broker;
|
|
885
|
-
const optionsExtras = {
|
|
886
|
-
...extras,
|
|
887
|
-
...(targetBroker && {
|
|
888
|
-
[targetBroker]: {
|
|
889
|
-
...extras[targetBroker],
|
|
890
|
-
strikePrice: options.strikePrice,
|
|
891
|
-
expirationDate: options.expirationDate,
|
|
892
|
-
optionType: options.optionType,
|
|
893
|
-
contractSize: options.contractSize || 100,
|
|
894
|
-
},
|
|
895
|
-
}),
|
|
896
|
-
};
|
|
897
|
-
return this.placeBrokerOrder(accessToken, orderParams, optionsExtras);
|
|
898
|
-
}
|
|
899
|
-
// Futures convenience methods
|
|
900
|
-
async placeFuturesMarketOrder(accessToken, symbol, orderQty, action, broker, accountNumber, extras = {}) {
|
|
901
|
-
return this.placeBrokerOrder(accessToken, {
|
|
928
|
+
timeInForce,
|
|
902
929
|
broker,
|
|
903
930
|
accountNumber,
|
|
931
|
+
...options,
|
|
932
|
+
}, extras);
|
|
933
|
+
}
|
|
934
|
+
// Futures convenience methods
|
|
935
|
+
async placeFuturesMarketOrder(symbol, orderQty, action, broker, accountNumber, extras = {}) {
|
|
936
|
+
return this.placeBrokerOrder({
|
|
904
937
|
symbol,
|
|
905
938
|
orderQty,
|
|
906
939
|
action,
|
|
907
940
|
orderType: 'Market',
|
|
908
|
-
assetType: '
|
|
941
|
+
assetType: 'Future',
|
|
909
942
|
timeInForce: 'day',
|
|
910
|
-
}, extras);
|
|
911
|
-
}
|
|
912
|
-
async placeFuturesLimitOrder(accessToken, symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
|
|
913
|
-
return this.placeBrokerOrder(accessToken, {
|
|
914
943
|
broker,
|
|
915
944
|
accountNumber,
|
|
945
|
+
}, extras);
|
|
946
|
+
}
|
|
947
|
+
async placeFuturesLimitOrder(symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
|
|
948
|
+
return this.placeBrokerOrder({
|
|
916
949
|
symbol,
|
|
917
950
|
orderQty,
|
|
918
951
|
action,
|
|
919
952
|
orderType: 'Limit',
|
|
920
|
-
assetType: '
|
|
921
|
-
timeInForce,
|
|
953
|
+
assetType: 'Future',
|
|
922
954
|
price,
|
|
955
|
+
timeInForce,
|
|
956
|
+
broker,
|
|
957
|
+
accountNumber,
|
|
923
958
|
}, extras);
|
|
924
959
|
}
|
|
925
960
|
buildOrderRequestBody(params, extras = {}) {
|
|
926
961
|
const baseOrder = {
|
|
927
|
-
|
|
928
|
-
|
|
962
|
+
order_id: params.order_id,
|
|
963
|
+
orderType: params.orderType,
|
|
964
|
+
assetType: params.assetType,
|
|
929
965
|
action: params.action,
|
|
930
|
-
|
|
931
|
-
|
|
966
|
+
timeInForce: params.timeInForce,
|
|
967
|
+
accountNumber: params.accountNumber,
|
|
932
968
|
symbol: params.symbol,
|
|
933
|
-
|
|
969
|
+
orderQty: params.orderQty,
|
|
934
970
|
};
|
|
935
971
|
if (params.price !== undefined)
|
|
936
972
|
baseOrder.price = params.price;
|
|
937
973
|
if (params.stopPrice !== undefined)
|
|
938
|
-
baseOrder.
|
|
939
|
-
// Apply broker-specific defaults
|
|
940
|
-
const brokerExtras = this.applyBrokerDefaults(params.broker, extras
|
|
974
|
+
baseOrder.stopPrice = params.stopPrice;
|
|
975
|
+
// Apply broker-specific defaults – map camelCase extras property keys to snake_case before merging
|
|
976
|
+
const brokerExtras = this.applyBrokerDefaults(params.broker, extras);
|
|
941
977
|
return {
|
|
942
978
|
broker: params.broker,
|
|
943
|
-
|
|
944
|
-
|
|
979
|
+
order: {
|
|
980
|
+
...baseOrder,
|
|
981
|
+
...brokerExtras,
|
|
982
|
+
},
|
|
945
983
|
};
|
|
946
984
|
}
|
|
947
985
|
buildModifyRequestBody(params, extras, broker) {
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
986
|
+
const order = {};
|
|
987
|
+
if (params.order_id !== undefined)
|
|
988
|
+
order.order_id = params.order_id;
|
|
951
989
|
if (params.orderType !== undefined)
|
|
952
|
-
|
|
990
|
+
order.orderType = params.orderType;
|
|
953
991
|
if (params.assetType !== undefined)
|
|
954
|
-
|
|
992
|
+
order.assetType = params.assetType;
|
|
955
993
|
if (params.action !== undefined)
|
|
956
|
-
|
|
994
|
+
order.action = params.action;
|
|
957
995
|
if (params.timeInForce !== undefined)
|
|
958
|
-
|
|
996
|
+
order.timeInForce = params.timeInForce;
|
|
959
997
|
if (params.accountNumber !== undefined)
|
|
960
|
-
|
|
998
|
+
order.accountNumber = params.accountNumber;
|
|
961
999
|
if (params.symbol !== undefined)
|
|
962
|
-
|
|
1000
|
+
order.symbol = params.symbol;
|
|
963
1001
|
if (params.orderQty !== undefined)
|
|
964
|
-
|
|
1002
|
+
order.orderQty = params.orderQty;
|
|
965
1003
|
if (params.price !== undefined)
|
|
966
|
-
|
|
1004
|
+
order.price = params.price;
|
|
967
1005
|
if (params.stopPrice !== undefined)
|
|
968
|
-
|
|
969
|
-
// Apply broker-specific defaults
|
|
970
|
-
const brokerExtras = this.applyBrokerDefaults(broker, extras
|
|
1006
|
+
order.stopPrice = params.stopPrice;
|
|
1007
|
+
// Apply broker-specific defaults (handles snake_case conversion)
|
|
1008
|
+
const brokerExtras = this.applyBrokerDefaults(broker, extras);
|
|
971
1009
|
return {
|
|
972
|
-
|
|
973
|
-
|
|
1010
|
+
broker,
|
|
1011
|
+
order: {
|
|
1012
|
+
...order,
|
|
1013
|
+
...brokerExtras,
|
|
1014
|
+
},
|
|
974
1015
|
};
|
|
975
1016
|
}
|
|
976
1017
|
applyBrokerDefaults(broker, extras) {
|
|
1018
|
+
// If the caller provided a broker-scoped extras object (e.g. { ninjaTrader: { ... } })
|
|
1019
|
+
// pull the nested object for easier processing.
|
|
1020
|
+
if (extras && typeof extras === 'object') {
|
|
1021
|
+
const scoped = broker === 'robinhood'
|
|
1022
|
+
? extras.robinhood
|
|
1023
|
+
: broker === 'ninja_trader'
|
|
1024
|
+
? extras.ninjaTrader
|
|
1025
|
+
: broker === 'tasty_trade'
|
|
1026
|
+
? extras.tastyTrade
|
|
1027
|
+
: undefined;
|
|
1028
|
+
if (scoped) {
|
|
1029
|
+
extras = scoped;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
977
1032
|
switch (broker) {
|
|
978
1033
|
case 'robinhood':
|
|
979
1034
|
return {
|
|
980
|
-
extendedHours: extras.extendedHours ?? true,
|
|
981
|
-
marketHours: extras.marketHours ?? 'regular_hours',
|
|
982
|
-
trailType: extras.trailType ?? 'percentage',
|
|
983
1035
|
...extras,
|
|
1036
|
+
extendedHours: extras?.extendedHours ?? extras?.extended_hours ?? true,
|
|
1037
|
+
marketHours: extras?.marketHours ?? extras?.market_hours ?? 'regular_hours',
|
|
1038
|
+
trailType: extras?.trailType ?? extras?.trail_type ?? 'percentage',
|
|
984
1039
|
};
|
|
985
1040
|
case 'ninja_trader':
|
|
986
1041
|
return {
|
|
987
|
-
accountSpec: extras.accountSpec ?? '',
|
|
988
|
-
isAutomated: extras.isAutomated ?? true,
|
|
989
1042
|
...extras,
|
|
1043
|
+
accountSpec: extras?.accountSpec ?? extras?.account_spec ?? '',
|
|
1044
|
+
isAutomated: extras?.isAutomated ?? extras?.is_automated ?? true,
|
|
990
1045
|
};
|
|
991
1046
|
case 'tasty_trade':
|
|
992
1047
|
return {
|
|
993
|
-
automatedSource: extras.automatedSource ?? true,
|
|
994
1048
|
...extras,
|
|
1049
|
+
automatedSource: extras?.automatedSource ?? extras?.automated_source ?? true,
|
|
995
1050
|
};
|
|
996
1051
|
default:
|
|
997
1052
|
return extras;
|
|
998
1053
|
}
|
|
999
1054
|
}
|
|
1000
|
-
async revokeToken(
|
|
1001
|
-
|
|
1055
|
+
async revokeToken() {
|
|
1056
|
+
const accessToken = await this.getValidAccessToken();
|
|
1057
|
+
await this.request('/auth/token/revoke', {
|
|
1002
1058
|
method: 'POST',
|
|
1003
1059
|
headers: {
|
|
1004
|
-
'
|
|
1005
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1060
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
1006
1061
|
},
|
|
1007
1062
|
});
|
|
1063
|
+
this.clearTokens();
|
|
1008
1064
|
}
|
|
1009
1065
|
async getUserToken(userId) {
|
|
1010
|
-
|
|
1011
|
-
|
|
1066
|
+
const accessToken = await this.getValidAccessToken();
|
|
1067
|
+
return this.request(`/auth/token/user/${userId}`, {
|
|
1068
|
+
method: 'GET',
|
|
1012
1069
|
headers: {
|
|
1013
|
-
'
|
|
1070
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
1014
1071
|
},
|
|
1015
|
-
body: { userId },
|
|
1016
1072
|
});
|
|
1017
1073
|
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Get the current session state
|
|
1076
|
+
*/
|
|
1018
1077
|
getCurrentSessionState() {
|
|
1019
1078
|
return this.currentSessionState;
|
|
1020
1079
|
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Refresh the current session to extend its lifetime
|
|
1082
|
+
*/
|
|
1083
|
+
async refreshSession() {
|
|
1084
|
+
if (!this.currentSessionId || !this.companyId) {
|
|
1085
|
+
throw new SessionError('No active session to refresh');
|
|
1086
|
+
}
|
|
1087
|
+
return this.request('/auth/session/refresh', {
|
|
1088
|
+
method: 'POST',
|
|
1089
|
+
headers: {
|
|
1090
|
+
'Session-ID': this.currentSessionId,
|
|
1091
|
+
'X-Company-ID': this.companyId,
|
|
1092
|
+
},
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1021
1095
|
// Broker Data Management
|
|
1022
|
-
async getBrokerList(
|
|
1023
|
-
|
|
1096
|
+
async getBrokerList() {
|
|
1097
|
+
const accessToken = await this.getValidAccessToken();
|
|
1098
|
+
return this.request('/brokers/list', {
|
|
1024
1099
|
method: 'GET',
|
|
1025
1100
|
headers: {
|
|
1026
|
-
'
|
|
1027
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1028
|
-
'Session-ID': this.currentSessionId || '',
|
|
1029
|
-
'X-Session-ID': this.currentSessionId || '',
|
|
1030
|
-
'X-Device-Info': JSON.stringify({
|
|
1031
|
-
ip_address: this.deviceInfo?.ip_address || '',
|
|
1032
|
-
user_agent: this.deviceInfo?.user_agent || '',
|
|
1033
|
-
fingerprint: this.deviceInfo?.fingerprint || '',
|
|
1034
|
-
}),
|
|
1101
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
1035
1102
|
},
|
|
1036
1103
|
});
|
|
1037
1104
|
}
|
|
1038
|
-
async getBrokerAccounts(
|
|
1105
|
+
async getBrokerAccounts(options) {
|
|
1106
|
+
const accessToken = await this.getValidAccessToken();
|
|
1039
1107
|
const params = {};
|
|
1040
|
-
if (options?.broker_name)
|
|
1041
|
-
params.
|
|
1042
|
-
|
|
1108
|
+
if (options?.broker_name) {
|
|
1109
|
+
params.broker_id = options.broker_name;
|
|
1110
|
+
}
|
|
1111
|
+
if (options?.account_id) {
|
|
1043
1112
|
params.account_id = options.account_id;
|
|
1113
|
+
}
|
|
1114
|
+
if (options?.symbol) {
|
|
1115
|
+
params.symbol = options.symbol;
|
|
1116
|
+
}
|
|
1044
1117
|
return this.request('/brokers/data/accounts', {
|
|
1045
1118
|
method: 'GET',
|
|
1046
1119
|
headers: {
|
|
1047
|
-
'
|
|
1048
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1049
|
-
'Session-ID': this.currentSessionId || '',
|
|
1050
|
-
'X-Session-ID': this.currentSessionId || '',
|
|
1051
|
-
'X-Device-Info': JSON.stringify({
|
|
1052
|
-
ip_address: this.deviceInfo?.ip_address || '',
|
|
1053
|
-
user_agent: this.deviceInfo?.user_agent || '',
|
|
1054
|
-
fingerprint: this.deviceInfo?.fingerprint || '',
|
|
1055
|
-
}),
|
|
1120
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
1056
1121
|
},
|
|
1057
1122
|
params,
|
|
1058
1123
|
});
|
|
1059
1124
|
}
|
|
1060
|
-
async getBrokerOrders(
|
|
1125
|
+
async getBrokerOrders(options) {
|
|
1126
|
+
const accessToken = await this.getValidAccessToken();
|
|
1061
1127
|
const params = {};
|
|
1062
|
-
if (options?.broker_name)
|
|
1063
|
-
params.
|
|
1064
|
-
|
|
1128
|
+
if (options?.broker_name) {
|
|
1129
|
+
params.broker_id = options.broker_name;
|
|
1130
|
+
}
|
|
1131
|
+
if (options?.account_id) {
|
|
1065
1132
|
params.account_id = options.account_id;
|
|
1066
|
-
|
|
1133
|
+
}
|
|
1134
|
+
if (options?.symbol) {
|
|
1067
1135
|
params.symbol = options.symbol;
|
|
1136
|
+
}
|
|
1068
1137
|
return this.request('/brokers/data/orders', {
|
|
1069
1138
|
method: 'GET',
|
|
1070
1139
|
headers: {
|
|
1071
|
-
'
|
|
1072
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1073
|
-
'Session-ID': this.currentSessionId || '',
|
|
1074
|
-
'X-Session-ID': this.currentSessionId || '',
|
|
1075
|
-
'X-Device-Info': JSON.stringify({
|
|
1076
|
-
ip_address: this.deviceInfo?.ip_address || '',
|
|
1077
|
-
user_agent: this.deviceInfo?.user_agent || '',
|
|
1078
|
-
fingerprint: this.deviceInfo?.fingerprint || '',
|
|
1079
|
-
}),
|
|
1140
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
1080
1141
|
},
|
|
1081
1142
|
params,
|
|
1082
1143
|
});
|
|
1083
1144
|
}
|
|
1084
|
-
async getBrokerPositions(
|
|
1085
|
-
const
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
if (options?.
|
|
1091
|
-
|
|
1145
|
+
async getBrokerPositions(options) {
|
|
1146
|
+
const accessToken = await this.getValidAccessToken();
|
|
1147
|
+
const params = {};
|
|
1148
|
+
if (options?.broker_name) {
|
|
1149
|
+
params.broker_id = options.broker_name;
|
|
1150
|
+
}
|
|
1151
|
+
if (options?.account_id) {
|
|
1152
|
+
params.account_id = options.account_id;
|
|
1153
|
+
}
|
|
1154
|
+
if (options?.symbol) {
|
|
1155
|
+
params.symbol = options.symbol;
|
|
1156
|
+
}
|
|
1092
1157
|
return this.request('/brokers/data/positions', {
|
|
1093
1158
|
method: 'GET',
|
|
1094
1159
|
headers: {
|
|
1095
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1096
|
-
'Content-Type': 'application/json',
|
|
1097
|
-
'Session-ID': this.currentSessionId || '',
|
|
1098
|
-
'X-Session-ID': this.currentSessionId || '',
|
|
1099
|
-
'X-Device-Info': JSON.stringify({
|
|
1100
|
-
ip_address: this.deviceInfo?.ip_address || '',
|
|
1101
|
-
user_agent: this.deviceInfo?.user_agent || '',
|
|
1102
|
-
fingerprint: this.deviceInfo?.fingerprint || '',
|
|
1103
|
-
}),
|
|
1160
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
1104
1161
|
},
|
|
1105
|
-
params
|
|
1162
|
+
params,
|
|
1106
1163
|
});
|
|
1107
1164
|
}
|
|
1108
|
-
async getBrokerConnections(
|
|
1165
|
+
async getBrokerConnections() {
|
|
1166
|
+
const accessToken = await this.getValidAccessToken();
|
|
1109
1167
|
return this.request('/brokers/connections', {
|
|
1110
1168
|
method: 'GET',
|
|
1111
1169
|
headers: {
|
|
1112
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1113
|
-
'Content-Type': 'application/json',
|
|
1170
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
1114
1171
|
},
|
|
1115
1172
|
});
|
|
1116
1173
|
}
|
|
1117
|
-
// Automatic token management versions of broker methods
|
|
1118
|
-
async getBrokerListAuto() {
|
|
1119
|
-
const accessToken = await this.getValidAccessToken();
|
|
1120
|
-
return this.getBrokerList(accessToken);
|
|
1121
|
-
}
|
|
1122
|
-
async getBrokerAccountsAuto(options) {
|
|
1123
|
-
const accessToken = await this.getValidAccessToken();
|
|
1124
|
-
return this.getBrokerAccounts(accessToken, options);
|
|
1125
|
-
}
|
|
1126
|
-
async getBrokerOrdersAuto(options) {
|
|
1127
|
-
const accessToken = await this.getValidAccessToken();
|
|
1128
|
-
return this.getBrokerOrders(accessToken, options);
|
|
1129
|
-
}
|
|
1130
|
-
async getBrokerPositionsAuto(options) {
|
|
1131
|
-
const accessToken = await this.getValidAccessToken();
|
|
1132
|
-
return this.getBrokerPositions(accessToken, options);
|
|
1133
|
-
}
|
|
1134
|
-
async getBrokerConnectionsAuto() {
|
|
1135
|
-
const accessToken = await this.getValidAccessToken();
|
|
1136
|
-
return this.getBrokerConnections(accessToken);
|
|
1137
|
-
}
|
|
1138
|
-
// Automatic token management versions of trading methods
|
|
1139
|
-
async placeBrokerOrderAuto(params, extras = {}) {
|
|
1140
|
-
const accessToken = await this.getValidAccessToken();
|
|
1141
|
-
return this.placeBrokerOrder(accessToken, params, extras);
|
|
1142
|
-
}
|
|
1143
|
-
async placeStockMarketOrderAuto(symbol, orderQty, action, broker, accountNumber, extras = {}) {
|
|
1144
|
-
const accessToken = await this.getValidAccessToken();
|
|
1145
|
-
return this.placeStockMarketOrder(accessToken, symbol, orderQty, action, broker, accountNumber, extras);
|
|
1146
|
-
}
|
|
1147
|
-
async placeStockLimitOrderAuto(symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
|
|
1148
|
-
const accessToken = await this.getValidAccessToken();
|
|
1149
|
-
return this.placeStockLimitOrder(accessToken, symbol, orderQty, action, price, timeInForce, broker, accountNumber, extras);
|
|
1150
|
-
}
|
|
1151
|
-
async placeStockStopOrderAuto(symbol, orderQty, action, stopPrice, timeInForce = 'day', broker, accountNumber, extras = {}) {
|
|
1152
|
-
const accessToken = await this.getValidAccessToken();
|
|
1153
|
-
return this.placeStockStopOrder(accessToken, symbol, orderQty, action, stopPrice, timeInForce, broker, accountNumber, extras);
|
|
1154
|
-
}
|
|
1155
1174
|
// Page-based pagination methods
|
|
1156
1175
|
async getBrokerOrdersPage(page = 1, perPage = 100, filters) {
|
|
1157
1176
|
const accessToken = await this.getValidAccessToken();
|
|
@@ -1184,25 +1203,57 @@ class ApiClient {
|
|
|
1184
1203
|
const response = await this.request('/brokers/data/orders', {
|
|
1185
1204
|
method: 'GET',
|
|
1186
1205
|
headers: {
|
|
1187
|
-
'Content-Type': 'application/json',
|
|
1188
1206
|
Authorization: `Bearer ${accessToken}`,
|
|
1189
|
-
'X-Company-ID': this.companyId || '',
|
|
1190
|
-
'X-Session-ID': this.currentSessionId || '',
|
|
1191
|
-
'X-CSRF-Token': this.csrfToken || '',
|
|
1192
|
-
'X-Device-Info': JSON.stringify({
|
|
1193
|
-
ip_address: this.deviceInfo?.ip_address || '',
|
|
1194
|
-
user_agent: this.deviceInfo?.user_agent || '',
|
|
1195
|
-
fingerprint: this.deviceInfo?.fingerprint || '',
|
|
1196
|
-
}),
|
|
1197
1207
|
},
|
|
1198
1208
|
params,
|
|
1199
1209
|
});
|
|
1210
|
+
// Create navigation callback for pagination
|
|
1211
|
+
const navigationCallback = async (newOffset, newLimit) => {
|
|
1212
|
+
const newParams = {
|
|
1213
|
+
limit: newLimit.toString(),
|
|
1214
|
+
offset: newOffset.toString(),
|
|
1215
|
+
};
|
|
1216
|
+
// Add filter parameters
|
|
1217
|
+
if (filters) {
|
|
1218
|
+
if (filters.broker_id)
|
|
1219
|
+
newParams.broker_id = filters.broker_id;
|
|
1220
|
+
if (filters.connection_id)
|
|
1221
|
+
newParams.connection_id = filters.connection_id;
|
|
1222
|
+
if (filters.account_id)
|
|
1223
|
+
newParams.account_id = filters.account_id;
|
|
1224
|
+
if (filters.symbol)
|
|
1225
|
+
newParams.symbol = filters.symbol;
|
|
1226
|
+
if (filters.status)
|
|
1227
|
+
newParams.status = filters.status;
|
|
1228
|
+
if (filters.side)
|
|
1229
|
+
newParams.side = filters.side;
|
|
1230
|
+
if (filters.asset_type)
|
|
1231
|
+
newParams.asset_type = filters.asset_type;
|
|
1232
|
+
if (filters.created_after)
|
|
1233
|
+
newParams.created_after = filters.created_after;
|
|
1234
|
+
if (filters.created_before)
|
|
1235
|
+
newParams.created_before = filters.created_before;
|
|
1236
|
+
}
|
|
1237
|
+
const newResponse = await this.request('/brokers/data/orders', {
|
|
1238
|
+
method: 'GET',
|
|
1239
|
+
headers: {
|
|
1240
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1241
|
+
},
|
|
1242
|
+
params: newParams,
|
|
1243
|
+
});
|
|
1244
|
+
return new PaginatedResult(newResponse.response_data, newResponse.pagination || {
|
|
1245
|
+
has_more: false,
|
|
1246
|
+
next_offset: newOffset,
|
|
1247
|
+
current_offset: newOffset,
|
|
1248
|
+
limit: newLimit,
|
|
1249
|
+
}, navigationCallback);
|
|
1250
|
+
};
|
|
1200
1251
|
return new PaginatedResult(response.response_data, response.pagination || {
|
|
1201
1252
|
has_more: false,
|
|
1202
1253
|
next_offset: offset,
|
|
1203
1254
|
current_offset: offset,
|
|
1204
1255
|
limit: perPage,
|
|
1205
|
-
});
|
|
1256
|
+
}, navigationCallback);
|
|
1206
1257
|
}
|
|
1207
1258
|
async getBrokerAccountsPage(page = 1, perPage = 100, filters) {
|
|
1208
1259
|
const accessToken = await this.getValidAccessToken();
|
|
@@ -1227,25 +1278,49 @@ class ApiClient {
|
|
|
1227
1278
|
const response = await this.request('/brokers/data/accounts', {
|
|
1228
1279
|
method: 'GET',
|
|
1229
1280
|
headers: {
|
|
1230
|
-
'Content-Type': 'application/json',
|
|
1231
1281
|
Authorization: `Bearer ${accessToken}`,
|
|
1232
|
-
'X-Company-ID': this.companyId || '',
|
|
1233
|
-
'X-Session-ID': this.currentSessionId || '',
|
|
1234
|
-
'X-CSRF-Token': this.csrfToken || '',
|
|
1235
|
-
'X-Device-Info': JSON.stringify({
|
|
1236
|
-
ip_address: this.deviceInfo?.ip_address || '',
|
|
1237
|
-
user_agent: this.deviceInfo?.user_agent || '',
|
|
1238
|
-
fingerprint: this.deviceInfo?.fingerprint || '',
|
|
1239
|
-
}),
|
|
1240
1282
|
},
|
|
1241
1283
|
params,
|
|
1242
1284
|
});
|
|
1285
|
+
// Create navigation callback for pagination
|
|
1286
|
+
const navigationCallback = async (newOffset, newLimit) => {
|
|
1287
|
+
const newParams = {
|
|
1288
|
+
limit: newLimit.toString(),
|
|
1289
|
+
offset: newOffset.toString(),
|
|
1290
|
+
};
|
|
1291
|
+
// Add filter parameters
|
|
1292
|
+
if (filters) {
|
|
1293
|
+
if (filters.broker_id)
|
|
1294
|
+
newParams.broker_id = filters.broker_id;
|
|
1295
|
+
if (filters.connection_id)
|
|
1296
|
+
newParams.connection_id = filters.connection_id;
|
|
1297
|
+
if (filters.account_type)
|
|
1298
|
+
newParams.account_type = filters.account_type;
|
|
1299
|
+
if (filters.status)
|
|
1300
|
+
newParams.status = filters.status;
|
|
1301
|
+
if (filters.currency)
|
|
1302
|
+
newParams.currency = filters.currency;
|
|
1303
|
+
}
|
|
1304
|
+
const newResponse = await this.request('/brokers/data/accounts', {
|
|
1305
|
+
method: 'GET',
|
|
1306
|
+
headers: {
|
|
1307
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1308
|
+
},
|
|
1309
|
+
params: newParams,
|
|
1310
|
+
});
|
|
1311
|
+
return new PaginatedResult(newResponse.response_data, newResponse.pagination || {
|
|
1312
|
+
has_more: false,
|
|
1313
|
+
next_offset: newOffset,
|
|
1314
|
+
current_offset: newOffset,
|
|
1315
|
+
limit: newLimit,
|
|
1316
|
+
}, navigationCallback);
|
|
1317
|
+
};
|
|
1243
1318
|
return new PaginatedResult(response.response_data, response.pagination || {
|
|
1244
1319
|
has_more: false,
|
|
1245
1320
|
next_offset: offset,
|
|
1246
1321
|
current_offset: offset,
|
|
1247
1322
|
limit: perPage,
|
|
1248
|
-
});
|
|
1323
|
+
}, navigationCallback);
|
|
1249
1324
|
}
|
|
1250
1325
|
async getBrokerPositionsPage(page = 1, perPage = 100, filters) {
|
|
1251
1326
|
const accessToken = await this.getValidAccessToken();
|
|
@@ -1270,25 +1345,49 @@ class ApiClient {
|
|
|
1270
1345
|
const response = await this.request('/brokers/data/positions', {
|
|
1271
1346
|
method: 'GET',
|
|
1272
1347
|
headers: {
|
|
1273
|
-
'Content-Type': 'application/json',
|
|
1274
1348
|
Authorization: `Bearer ${accessToken}`,
|
|
1275
|
-
'X-Company-ID': this.companyId || '',
|
|
1276
|
-
'X-Session-ID': this.currentSessionId || '',
|
|
1277
|
-
'X-CSRF-Token': this.csrfToken || '',
|
|
1278
|
-
'X-Device-Info': JSON.stringify({
|
|
1279
|
-
ip_address: this.deviceInfo?.ip_address || '',
|
|
1280
|
-
user_agent: this.deviceInfo?.user_agent || '',
|
|
1281
|
-
fingerprint: this.deviceInfo?.fingerprint || '',
|
|
1282
|
-
}),
|
|
1283
1349
|
},
|
|
1284
1350
|
params,
|
|
1285
1351
|
});
|
|
1352
|
+
// Create navigation callback for pagination
|
|
1353
|
+
const navigationCallback = async (newOffset, newLimit) => {
|
|
1354
|
+
const newParams = {
|
|
1355
|
+
limit: newLimit.toString(),
|
|
1356
|
+
offset: newOffset.toString(),
|
|
1357
|
+
};
|
|
1358
|
+
// Add filter parameters
|
|
1359
|
+
if (filters) {
|
|
1360
|
+
if (filters.broker_id)
|
|
1361
|
+
newParams.broker_id = filters.broker_id;
|
|
1362
|
+
if (filters.account_id)
|
|
1363
|
+
newParams.account_id = filters.account_id;
|
|
1364
|
+
if (filters.symbol)
|
|
1365
|
+
newParams.symbol = filters.symbol;
|
|
1366
|
+
if (filters.position_status)
|
|
1367
|
+
newParams.position_status = filters.position_status;
|
|
1368
|
+
if (filters.side)
|
|
1369
|
+
newParams.side = filters.side;
|
|
1370
|
+
}
|
|
1371
|
+
const newResponse = await this.request('/brokers/data/positions', {
|
|
1372
|
+
method: 'GET',
|
|
1373
|
+
headers: {
|
|
1374
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1375
|
+
},
|
|
1376
|
+
params: newParams,
|
|
1377
|
+
});
|
|
1378
|
+
return new PaginatedResult(newResponse.response_data, newResponse.pagination || {
|
|
1379
|
+
has_more: false,
|
|
1380
|
+
next_offset: newOffset,
|
|
1381
|
+
current_offset: newOffset,
|
|
1382
|
+
limit: newLimit,
|
|
1383
|
+
}, navigationCallback);
|
|
1384
|
+
};
|
|
1286
1385
|
return new PaginatedResult(response.response_data, response.pagination || {
|
|
1287
1386
|
has_more: false,
|
|
1288
1387
|
next_offset: offset,
|
|
1289
1388
|
current_offset: offset,
|
|
1290
1389
|
limit: perPage,
|
|
1291
|
-
});
|
|
1390
|
+
}, navigationCallback);
|
|
1292
1391
|
}
|
|
1293
1392
|
// Navigation methods
|
|
1294
1393
|
async getNextPage(previousResult, fetchFunction) {
|
|
@@ -1304,6 +1403,20 @@ class ApiClient {
|
|
|
1304
1403
|
isMockClient() {
|
|
1305
1404
|
return false;
|
|
1306
1405
|
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Disconnect a company from a broker connection
|
|
1408
|
+
* @param connectionId - The connection ID to disconnect
|
|
1409
|
+
* @returns Promise with disconnect response
|
|
1410
|
+
*/
|
|
1411
|
+
async disconnectCompany(connectionId) {
|
|
1412
|
+
const accessToken = await this.getValidAccessToken();
|
|
1413
|
+
return this.request(`/brokers/disconnect-company/${connectionId}`, {
|
|
1414
|
+
method: 'DELETE',
|
|
1415
|
+
headers: {
|
|
1416
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
1417
|
+
},
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1307
1420
|
}
|
|
1308
1421
|
|
|
1309
1422
|
class EventEmitter {
|
|
@@ -1809,6 +1922,17 @@ class MockDataProvider {
|
|
|
1809
1922
|
async mockGetBrokerList() {
|
|
1810
1923
|
await this.simulateDelay();
|
|
1811
1924
|
const brokers = [
|
|
1925
|
+
{
|
|
1926
|
+
id: 'alpaca',
|
|
1927
|
+
name: 'alpaca',
|
|
1928
|
+
display_name: 'Alpaca',
|
|
1929
|
+
description: 'Commission-free stock trading and crypto',
|
|
1930
|
+
website: 'https://alpaca.markets',
|
|
1931
|
+
features: ['stocks', 'crypto', 'fractional_shares', 'api_trading'],
|
|
1932
|
+
auth_type: 'api_key',
|
|
1933
|
+
logo_path: '/logos/alpaca.png',
|
|
1934
|
+
is_active: true,
|
|
1935
|
+
},
|
|
1812
1936
|
{
|
|
1813
1937
|
id: 'robinhood',
|
|
1814
1938
|
name: 'robinhood',
|
|
@@ -2122,14 +2246,25 @@ class MockDataProvider {
|
|
|
2122
2246
|
};
|
|
2123
2247
|
}
|
|
2124
2248
|
async mockPlaceOrder(order) {
|
|
2125
|
-
|
|
2249
|
+
// Simulate API delay
|
|
2250
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
2251
|
+
// Generate a mock order ID
|
|
2252
|
+
const orderId = `mock_order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2126
2253
|
return {
|
|
2127
2254
|
success: true,
|
|
2128
2255
|
response_data: {
|
|
2129
|
-
orderId:
|
|
2130
|
-
status: '
|
|
2131
|
-
broker:
|
|
2132
|
-
accountNumber:
|
|
2256
|
+
orderId: orderId,
|
|
2257
|
+
status: 'pending',
|
|
2258
|
+
broker: order.broker,
|
|
2259
|
+
accountNumber: order.accountNumber,
|
|
2260
|
+
symbol: order.symbol,
|
|
2261
|
+
orderType: order.orderType,
|
|
2262
|
+
assetType: order.assetType,
|
|
2263
|
+
action: order.action,
|
|
2264
|
+
quantity: order.orderQty,
|
|
2265
|
+
price: order.price,
|
|
2266
|
+
stopPrice: order.stopPrice,
|
|
2267
|
+
timeInForce: order.timeInForce,
|
|
2133
2268
|
},
|
|
2134
2269
|
message: 'Order placed successfully',
|
|
2135
2270
|
status_code: 200,
|
|
@@ -2225,9 +2360,9 @@ class MockDataProvider {
|
|
|
2225
2360
|
}
|
|
2226
2361
|
applyBrokerAccountFilters(accounts, filter) {
|
|
2227
2362
|
return accounts.filter(account => {
|
|
2228
|
-
if (filter.broker_id && account.
|
|
2363
|
+
if (filter.broker_id && account.user_broker_connection_id !== filter.connection_id)
|
|
2229
2364
|
return false;
|
|
2230
|
-
if (filter.connection_id && account.
|
|
2365
|
+
if (filter.connection_id && account.user_broker_connection_id !== filter.connection_id)
|
|
2231
2366
|
return false;
|
|
2232
2367
|
if (filter.account_type && account.account_type !== filter.account_type)
|
|
2233
2368
|
return false;
|
|
@@ -2367,7 +2502,6 @@ class MockDataProvider {
|
|
|
2367
2502
|
*/
|
|
2368
2503
|
generateMockAccounts(count) {
|
|
2369
2504
|
const accounts = [];
|
|
2370
|
-
const brokers = ['robinhood', 'alpaca', 'tasty_trade', 'ninja_trader'];
|
|
2371
2505
|
const accountTypes = ['margin', 'cash', 'crypto_wallet', 'live', 'sim'];
|
|
2372
2506
|
const statuses = ['active', 'inactive'];
|
|
2373
2507
|
const currencies = ['USD', 'EUR', 'GBP', 'CAD'];
|
|
@@ -2382,26 +2516,22 @@ class MockDataProvider {
|
|
|
2382
2516
|
'Investment Account',
|
|
2383
2517
|
];
|
|
2384
2518
|
for (let i = 0; i < count; i++) {
|
|
2385
|
-
const broker = brokers[Math.floor(Math.random() * brokers.length)];
|
|
2386
2519
|
const accountType = accountTypes[Math.floor(Math.random() * accountTypes.length)];
|
|
2387
2520
|
const status = statuses[Math.floor(Math.random() * statuses.length)];
|
|
2388
2521
|
const currency = currencies[Math.floor(Math.random() * currencies.length)];
|
|
2389
2522
|
const accountName = accountNames[Math.floor(Math.random() * accountNames.length)];
|
|
2390
2523
|
const cashBalance = Math.random() * 100000 + 1000;
|
|
2391
2524
|
const buyingPower = cashBalance * (1 + Math.random() * 2); // 1-3x cash balance
|
|
2392
|
-
const equity = buyingPower * (0.8 + Math.random() * 0.4); // ±20% variation
|
|
2393
2525
|
accounts.push({
|
|
2394
2526
|
id: uuid.v4(),
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
account_id: `account_${Math.floor(Math.random() * 1000000)}`,
|
|
2527
|
+
user_broker_connection_id: uuid.v4(),
|
|
2528
|
+
broker_provided_account_id: `account_${Math.floor(Math.random() * 1000000)}`,
|
|
2398
2529
|
account_name: `${accountName} ${i + 1}`,
|
|
2399
2530
|
account_type: accountType,
|
|
2400
|
-
status,
|
|
2401
2531
|
currency,
|
|
2402
2532
|
cash_balance: cashBalance,
|
|
2403
2533
|
buying_power: buyingPower,
|
|
2404
|
-
|
|
2534
|
+
status,
|
|
2405
2535
|
created_at: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString(), // Random date within last year
|
|
2406
2536
|
updated_at: new Date().toISOString(),
|
|
2407
2537
|
last_synced_at: new Date().toISOString(),
|
|
@@ -2409,6 +2539,42 @@ class MockDataProvider {
|
|
|
2409
2539
|
}
|
|
2410
2540
|
return accounts;
|
|
2411
2541
|
}
|
|
2542
|
+
/**
|
|
2543
|
+
* Mock disconnect company method
|
|
2544
|
+
* @param connectionId - The connection ID to disconnect
|
|
2545
|
+
* @returns Promise with mock disconnect response
|
|
2546
|
+
*/
|
|
2547
|
+
async mockDisconnectCompany(connectionId) {
|
|
2548
|
+
// Simulate API delay
|
|
2549
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
2550
|
+
// Randomly decide if this will delete the connection or just remove company access
|
|
2551
|
+
const willDeleteConnection = Math.random() > 0.5;
|
|
2552
|
+
if (willDeleteConnection) {
|
|
2553
|
+
return {
|
|
2554
|
+
success: true,
|
|
2555
|
+
response_data: {
|
|
2556
|
+
connection_id: connectionId,
|
|
2557
|
+
action: 'connection_deleted',
|
|
2558
|
+
message: 'Company access removed and connection deleted (no companies remaining)',
|
|
2559
|
+
},
|
|
2560
|
+
message: 'Company access removed and connection deleted (no companies remaining)',
|
|
2561
|
+
status_code: 200,
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
else {
|
|
2565
|
+
return {
|
|
2566
|
+
success: true,
|
|
2567
|
+
response_data: {
|
|
2568
|
+
connection_id: connectionId,
|
|
2569
|
+
action: 'company_access_removed',
|
|
2570
|
+
remaining_companies: Math.floor(Math.random() * 5) + 1, // 1-5 remaining companies
|
|
2571
|
+
message: 'Company access removed from connection',
|
|
2572
|
+
},
|
|
2573
|
+
message: 'Company access removed from connection',
|
|
2574
|
+
status_code: 200,
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2412
2578
|
}
|
|
2413
2579
|
|
|
2414
2580
|
/**
|
|
@@ -2618,38 +2784,34 @@ class MockApiClient {
|
|
|
2618
2784
|
return this.mockDataProvider.mockCompletePortalSession(sessionId);
|
|
2619
2785
|
}
|
|
2620
2786
|
// Portfolio Management
|
|
2621
|
-
async getHoldings(
|
|
2787
|
+
async getHoldings(filter) {
|
|
2788
|
+
await this.getValidAccessToken();
|
|
2622
2789
|
return this.mockDataProvider.mockGetHoldings();
|
|
2623
2790
|
}
|
|
2624
|
-
async getOrders(
|
|
2791
|
+
async getOrders(filter) {
|
|
2792
|
+
await this.getValidAccessToken();
|
|
2625
2793
|
return this.mockDataProvider.mockGetOrders(filter);
|
|
2626
2794
|
}
|
|
2627
|
-
async getPortfolio(
|
|
2795
|
+
async getPortfolio() {
|
|
2796
|
+
await this.getValidAccessToken();
|
|
2628
2797
|
return this.mockDataProvider.mockGetPortfolio();
|
|
2629
2798
|
}
|
|
2630
|
-
async placeOrder(
|
|
2799
|
+
async placeOrder(order) {
|
|
2800
|
+
await this.getValidAccessToken();
|
|
2631
2801
|
await this.mockDataProvider.mockPlaceOrder(order);
|
|
2632
2802
|
}
|
|
2633
|
-
// New methods with automatic token management
|
|
2634
|
-
async getHoldingsAuto() {
|
|
2635
|
-
const accessToken = await this.getValidAccessToken();
|
|
2636
|
-
return this.getHoldings(accessToken);
|
|
2637
|
-
}
|
|
2638
|
-
async getOrdersAuto() {
|
|
2639
|
-
const accessToken = await this.getValidAccessToken();
|
|
2640
|
-
return this.getOrders(accessToken);
|
|
2641
|
-
}
|
|
2642
|
-
async getPortfolioAuto() {
|
|
2643
|
-
const accessToken = await this.getValidAccessToken();
|
|
2644
|
-
return this.getPortfolio(accessToken);
|
|
2645
|
-
}
|
|
2646
|
-
async placeOrderAuto(order) {
|
|
2647
|
-
const accessToken = await this.getValidAccessToken();
|
|
2648
|
-
return this.placeOrder(accessToken, order);
|
|
2649
|
-
}
|
|
2650
2803
|
// Enhanced Trading Methods with Session Management
|
|
2651
|
-
async placeBrokerOrder(
|
|
2652
|
-
|
|
2804
|
+
async placeBrokerOrder(params, extras = {}, connection_id) {
|
|
2805
|
+
await this.getValidAccessToken();
|
|
2806
|
+
// Debug logging
|
|
2807
|
+
console.log('MockApiClient.placeBrokerOrder Debug:', {
|
|
2808
|
+
params,
|
|
2809
|
+
tradingContext: this.tradingContext,
|
|
2810
|
+
paramsBroker: params.broker,
|
|
2811
|
+
contextBroker: this.tradingContext.broker,
|
|
2812
|
+
paramsAccountNumber: params.accountNumber,
|
|
2813
|
+
contextAccountNumber: this.tradingContext.accountNumber
|
|
2814
|
+
});
|
|
2653
2815
|
const fullParams = {
|
|
2654
2816
|
broker: (params.broker || this.tradingContext.broker) ||
|
|
2655
2817
|
(() => {
|
|
@@ -2668,40 +2830,33 @@ class MockApiClient {
|
|
|
2668
2830
|
timeInForce: params.timeInForce || 'day',
|
|
2669
2831
|
price: params.price,
|
|
2670
2832
|
stopPrice: params.stopPrice,
|
|
2833
|
+
order_id: params.order_id,
|
|
2671
2834
|
};
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
return this.mockDataProvider.mockPlaceOrder({
|
|
2675
|
-
symbol: fullParams.symbol,
|
|
2676
|
-
side: fullParams.action.toLowerCase(),
|
|
2677
|
-
quantity: fullParams.orderQty,
|
|
2678
|
-
type_: fullParams.orderType.toLowerCase(),
|
|
2679
|
-
timeInForce: timeInForce,
|
|
2680
|
-
price: fullParams.price,
|
|
2681
|
-
stopPrice: fullParams.stopPrice,
|
|
2682
|
-
});
|
|
2835
|
+
console.log('MockApiClient.placeBrokerOrder Debug - Final params:', fullParams);
|
|
2836
|
+
return this.mockDataProvider.mockPlaceOrder(fullParams);
|
|
2683
2837
|
}
|
|
2684
|
-
async cancelBrokerOrder(orderId, broker, extras = {}) {
|
|
2838
|
+
async cancelBrokerOrder(orderId, broker, extras = {}, connection_id) {
|
|
2839
|
+
// Mock successful cancellation
|
|
2685
2840
|
return {
|
|
2686
2841
|
success: true,
|
|
2687
2842
|
response_data: {
|
|
2688
|
-
orderId,
|
|
2843
|
+
orderId: orderId,
|
|
2689
2844
|
status: 'cancelled',
|
|
2690
|
-
broker: broker || this.tradingContext.broker
|
|
2691
|
-
accountNumber: this.tradingContext.accountNumber || '123456789',
|
|
2845
|
+
broker: broker || this.tradingContext.broker,
|
|
2692
2846
|
},
|
|
2693
2847
|
message: 'Order cancelled successfully',
|
|
2694
2848
|
status_code: 200,
|
|
2695
2849
|
};
|
|
2696
2850
|
}
|
|
2697
|
-
async modifyBrokerOrder(orderId, params, broker, extras = {}) {
|
|
2851
|
+
async modifyBrokerOrder(orderId, params, broker, extras = {}, connection_id) {
|
|
2852
|
+
// Mock successful modification
|
|
2698
2853
|
return {
|
|
2699
2854
|
success: true,
|
|
2700
2855
|
response_data: {
|
|
2701
|
-
orderId,
|
|
2856
|
+
orderId: orderId,
|
|
2702
2857
|
status: 'modified',
|
|
2703
|
-
broker: broker || this.tradingContext.broker
|
|
2704
|
-
|
|
2858
|
+
broker: broker || this.tradingContext.broker,
|
|
2859
|
+
...params,
|
|
2705
2860
|
},
|
|
2706
2861
|
message: 'Order modified successfully',
|
|
2707
2862
|
status_code: 200,
|
|
@@ -2715,8 +2870,14 @@ class MockApiClient {
|
|
|
2715
2870
|
this.tradingContext.accountId = undefined;
|
|
2716
2871
|
}
|
|
2717
2872
|
setAccount(accountNumber, accountId) {
|
|
2873
|
+
console.log('MockApiClient.setAccount Debug:', {
|
|
2874
|
+
accountNumber,
|
|
2875
|
+
accountId,
|
|
2876
|
+
previousContext: { ...this.tradingContext }
|
|
2877
|
+
});
|
|
2718
2878
|
this.tradingContext.accountNumber = accountNumber;
|
|
2719
2879
|
this.tradingContext.accountId = accountId;
|
|
2880
|
+
console.log('MockApiClient.setAccount Debug - Updated context:', this.tradingContext);
|
|
2720
2881
|
}
|
|
2721
2882
|
getTradingContext() {
|
|
2722
2883
|
return { ...this.tradingContext };
|
|
@@ -2725,8 +2886,8 @@ class MockApiClient {
|
|
|
2725
2886
|
this.tradingContext = {};
|
|
2726
2887
|
}
|
|
2727
2888
|
// Stock convenience methods
|
|
2728
|
-
async placeStockMarketOrder(
|
|
2729
|
-
return this.placeBrokerOrder(
|
|
2889
|
+
async placeStockMarketOrder(symbol, orderQty, action, broker, accountNumber, extras = {}) {
|
|
2890
|
+
return this.placeBrokerOrder({
|
|
2730
2891
|
broker,
|
|
2731
2892
|
accountNumber,
|
|
2732
2893
|
symbol,
|
|
@@ -2737,8 +2898,8 @@ class MockApiClient {
|
|
|
2737
2898
|
timeInForce: 'day',
|
|
2738
2899
|
}, extras);
|
|
2739
2900
|
}
|
|
2740
|
-
async placeStockLimitOrder(
|
|
2741
|
-
return this.placeBrokerOrder(
|
|
2901
|
+
async placeStockLimitOrder(symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
|
|
2902
|
+
return this.placeBrokerOrder({
|
|
2742
2903
|
broker,
|
|
2743
2904
|
accountNumber,
|
|
2744
2905
|
symbol,
|
|
@@ -2750,8 +2911,8 @@ class MockApiClient {
|
|
|
2750
2911
|
price,
|
|
2751
2912
|
}, extras);
|
|
2752
2913
|
}
|
|
2753
|
-
async placeStockStopOrder(
|
|
2754
|
-
return this.placeBrokerOrder(
|
|
2914
|
+
async placeStockStopOrder(symbol, orderQty, action, stopPrice, timeInForce = 'day', broker, accountNumber, extras = {}) {
|
|
2915
|
+
return this.placeBrokerOrder({
|
|
2755
2916
|
broker,
|
|
2756
2917
|
accountNumber,
|
|
2757
2918
|
symbol,
|
|
@@ -2764,7 +2925,7 @@ class MockApiClient {
|
|
|
2764
2925
|
}, extras);
|
|
2765
2926
|
}
|
|
2766
2927
|
// Crypto convenience methods
|
|
2767
|
-
async placeCryptoMarketOrder(
|
|
2928
|
+
async placeCryptoMarketOrder(symbol, orderQty, action, options = {}, broker, accountNumber, extras = {}) {
|
|
2768
2929
|
const orderParams = {
|
|
2769
2930
|
broker,
|
|
2770
2931
|
accountNumber,
|
|
@@ -2775,9 +2936,9 @@ class MockApiClient {
|
|
|
2775
2936
|
assetType: 'Crypto',
|
|
2776
2937
|
timeInForce: 'gtc', // Crypto typically uses GTC
|
|
2777
2938
|
};
|
|
2778
|
-
return this.placeBrokerOrder(
|
|
2939
|
+
return this.placeBrokerOrder(orderParams, extras);
|
|
2779
2940
|
}
|
|
2780
|
-
async placeCryptoLimitOrder(
|
|
2941
|
+
async placeCryptoLimitOrder(symbol, orderQty, action, price, timeInForce = 'gtc', options = {}, broker, accountNumber, extras = {}) {
|
|
2781
2942
|
const orderParams = {
|
|
2782
2943
|
broker,
|
|
2783
2944
|
accountNumber,
|
|
@@ -2789,10 +2950,10 @@ class MockApiClient {
|
|
|
2789
2950
|
timeInForce,
|
|
2790
2951
|
price,
|
|
2791
2952
|
};
|
|
2792
|
-
return this.placeBrokerOrder(
|
|
2953
|
+
return this.placeBrokerOrder(orderParams, extras);
|
|
2793
2954
|
}
|
|
2794
2955
|
// Options convenience methods
|
|
2795
|
-
async placeOptionsMarketOrder(
|
|
2956
|
+
async placeOptionsMarketOrder(symbol, orderQty, action, options, broker, accountNumber, extras = {}) {
|
|
2796
2957
|
const orderParams = {
|
|
2797
2958
|
broker,
|
|
2798
2959
|
accountNumber,
|
|
@@ -2803,9 +2964,9 @@ class MockApiClient {
|
|
|
2803
2964
|
assetType: 'Option',
|
|
2804
2965
|
timeInForce: 'day',
|
|
2805
2966
|
};
|
|
2806
|
-
return this.placeBrokerOrder(
|
|
2967
|
+
return this.placeBrokerOrder(orderParams, extras);
|
|
2807
2968
|
}
|
|
2808
|
-
async placeOptionsLimitOrder(
|
|
2969
|
+
async placeOptionsLimitOrder(symbol, orderQty, action, price, options, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
|
|
2809
2970
|
const orderParams = {
|
|
2810
2971
|
broker,
|
|
2811
2972
|
accountNumber,
|
|
@@ -2817,30 +2978,30 @@ class MockApiClient {
|
|
|
2817
2978
|
timeInForce,
|
|
2818
2979
|
price,
|
|
2819
2980
|
};
|
|
2820
|
-
return this.placeBrokerOrder(
|
|
2981
|
+
return this.placeBrokerOrder(orderParams, extras);
|
|
2821
2982
|
}
|
|
2822
2983
|
// Futures convenience methods
|
|
2823
|
-
async placeFuturesMarketOrder(
|
|
2824
|
-
return this.placeBrokerOrder(
|
|
2984
|
+
async placeFuturesMarketOrder(symbol, orderQty, action, broker, accountNumber, extras = {}) {
|
|
2985
|
+
return this.placeBrokerOrder({
|
|
2825
2986
|
broker,
|
|
2826
2987
|
accountNumber,
|
|
2827
2988
|
symbol,
|
|
2828
2989
|
orderQty,
|
|
2829
2990
|
action,
|
|
2830
2991
|
orderType: 'Market',
|
|
2831
|
-
assetType: '
|
|
2992
|
+
assetType: 'Future',
|
|
2832
2993
|
timeInForce: 'day',
|
|
2833
2994
|
}, extras);
|
|
2834
2995
|
}
|
|
2835
|
-
async placeFuturesLimitOrder(
|
|
2836
|
-
return this.placeBrokerOrder(
|
|
2996
|
+
async placeFuturesLimitOrder(symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
|
|
2997
|
+
return this.placeBrokerOrder({
|
|
2837
2998
|
broker,
|
|
2838
2999
|
accountNumber,
|
|
2839
3000
|
symbol,
|
|
2840
3001
|
orderQty,
|
|
2841
3002
|
action,
|
|
2842
3003
|
orderType: 'Limit',
|
|
2843
|
-
assetType: '
|
|
3004
|
+
assetType: 'Future',
|
|
2844
3005
|
timeInForce,
|
|
2845
3006
|
price,
|
|
2846
3007
|
}, extras);
|
|
@@ -2860,13 +3021,16 @@ class MockApiClient {
|
|
|
2860
3021
|
return this.currentSessionState;
|
|
2861
3022
|
}
|
|
2862
3023
|
// Broker Data Management
|
|
2863
|
-
async getBrokerList(
|
|
3024
|
+
async getBrokerList() {
|
|
3025
|
+
await this.getValidAccessToken();
|
|
2864
3026
|
return this.mockDataProvider.mockGetBrokerList();
|
|
2865
3027
|
}
|
|
2866
|
-
async getBrokerAccounts(
|
|
3028
|
+
async getBrokerAccounts(options) {
|
|
3029
|
+
await this.getValidAccessToken();
|
|
2867
3030
|
return this.mockDataProvider.mockGetBrokerAccounts();
|
|
2868
3031
|
}
|
|
2869
|
-
async getBrokerOrders(
|
|
3032
|
+
async getBrokerOrders(options) {
|
|
3033
|
+
await this.getValidAccessToken();
|
|
2870
3034
|
// Return empty orders for now - keeping original interface
|
|
2871
3035
|
return {
|
|
2872
3036
|
_id: uuid.v4(),
|
|
@@ -2877,7 +3041,8 @@ class MockApiClient {
|
|
|
2877
3041
|
errors: null,
|
|
2878
3042
|
};
|
|
2879
3043
|
}
|
|
2880
|
-
async getBrokerPositions(
|
|
3044
|
+
async getBrokerPositions(options) {
|
|
3045
|
+
await this.getValidAccessToken();
|
|
2881
3046
|
// Return empty positions for now - keeping original interface
|
|
2882
3047
|
return {
|
|
2883
3048
|
_id: uuid.v4(),
|
|
@@ -2989,46 +3154,18 @@ class MockApiClient {
|
|
|
2989
3154
|
limit: perPage,
|
|
2990
3155
|
}, navigationCallback);
|
|
2991
3156
|
}
|
|
2992
|
-
async getBrokerConnections(
|
|
3157
|
+
async getBrokerConnections() {
|
|
3158
|
+
await this.getValidAccessToken();
|
|
2993
3159
|
return this.mockDataProvider.mockGetBrokerConnections();
|
|
2994
3160
|
}
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
async
|
|
3001
|
-
|
|
3002
|
-
return this.
|
|
3003
|
-
}
|
|
3004
|
-
async getBrokerOrdersAuto(options) {
|
|
3005
|
-
const accessToken = await this.getValidAccessToken();
|
|
3006
|
-
return this.getBrokerOrders(accessToken, options);
|
|
3007
|
-
}
|
|
3008
|
-
async getBrokerPositionsAuto(options) {
|
|
3009
|
-
const accessToken = await this.getValidAccessToken();
|
|
3010
|
-
return this.getBrokerPositions(accessToken, options);
|
|
3011
|
-
}
|
|
3012
|
-
async getBrokerConnectionsAuto() {
|
|
3013
|
-
const accessToken = await this.getValidAccessToken();
|
|
3014
|
-
return this.getBrokerConnections(accessToken);
|
|
3015
|
-
}
|
|
3016
|
-
// Automatic token management versions of trading methods
|
|
3017
|
-
async placeBrokerOrderAuto(params, extras = {}) {
|
|
3018
|
-
const accessToken = await this.getValidAccessToken();
|
|
3019
|
-
return this.placeBrokerOrder(accessToken, params, extras);
|
|
3020
|
-
}
|
|
3021
|
-
async placeStockMarketOrderAuto(symbol, orderQty, action, broker, accountNumber, extras = {}) {
|
|
3022
|
-
const accessToken = await this.getValidAccessToken();
|
|
3023
|
-
return this.placeStockMarketOrder(accessToken, symbol, orderQty, action, broker, accountNumber, extras);
|
|
3024
|
-
}
|
|
3025
|
-
async placeStockLimitOrderAuto(symbol, orderQty, action, price, timeInForce = 'gtc', broker, accountNumber, extras = {}) {
|
|
3026
|
-
const accessToken = await this.getValidAccessToken();
|
|
3027
|
-
return this.placeStockLimitOrder(accessToken, symbol, orderQty, action, price, timeInForce, broker, accountNumber, extras);
|
|
3028
|
-
}
|
|
3029
|
-
async placeStockStopOrderAuto(symbol, orderQty, action, stopPrice, timeInForce = 'day', broker, accountNumber, extras = {}) {
|
|
3030
|
-
const accessToken = await this.getValidAccessToken();
|
|
3031
|
-
return this.placeStockStopOrder(accessToken, symbol, orderQty, action, stopPrice, timeInForce, broker, accountNumber, extras);
|
|
3161
|
+
/**
|
|
3162
|
+
* Mock disconnect company method
|
|
3163
|
+
* @param connectionId - The connection ID to disconnect
|
|
3164
|
+
* @returns Promise with mock disconnect response
|
|
3165
|
+
*/
|
|
3166
|
+
async disconnectCompany(connectionId) {
|
|
3167
|
+
await this.getValidAccessToken();
|
|
3168
|
+
return this.mockDataProvider.mockDisconnectCompany(connectionId);
|
|
3032
3169
|
}
|
|
3033
3170
|
// Utility methods for mock system
|
|
3034
3171
|
getMockDataProvider() {
|
|
@@ -3676,6 +3813,63 @@ function createCustomThemeFromPreset(preset, modifications) {
|
|
|
3676
3813
|
};
|
|
3677
3814
|
}
|
|
3678
3815
|
|
|
3816
|
+
/**
|
|
3817
|
+
* Broker filtering utility functions
|
|
3818
|
+
*/
|
|
3819
|
+
// Supported broker names and their corresponding IDs
|
|
3820
|
+
const SUPPORTED_BROKERS = {
|
|
3821
|
+
'alpaca': 'alpaca',
|
|
3822
|
+
'robinhood': 'robinhood',
|
|
3823
|
+
'tasty_trade': 'tasty_trade',
|
|
3824
|
+
'ninja_trader': 'ninja_trader'
|
|
3825
|
+
};
|
|
3826
|
+
/**
|
|
3827
|
+
* Convert broker names to broker IDs, filtering out unsupported ones
|
|
3828
|
+
* @param brokerNames Array of broker names to convert
|
|
3829
|
+
* @returns Object containing valid broker IDs and any warnings about unsupported names
|
|
3830
|
+
*/
|
|
3831
|
+
function convertBrokerNamesToIds(brokerNames) {
|
|
3832
|
+
const brokerIds = [];
|
|
3833
|
+
const warnings = [];
|
|
3834
|
+
for (const brokerName of brokerNames) {
|
|
3835
|
+
const brokerId = SUPPORTED_BROKERS[brokerName.toLowerCase()];
|
|
3836
|
+
if (brokerId) {
|
|
3837
|
+
brokerIds.push(brokerId);
|
|
3838
|
+
}
|
|
3839
|
+
else {
|
|
3840
|
+
warnings.push(`Broker name '${brokerName}' is not supported. Supported brokers: ${Object.keys(SUPPORTED_BROKERS).join(', ')}`);
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
return { brokerIds, warnings };
|
|
3844
|
+
}
|
|
3845
|
+
/**
|
|
3846
|
+
* Append broker filter parameters to a portal URL
|
|
3847
|
+
* @param baseUrl The base portal URL (may already have query parameters)
|
|
3848
|
+
* @param brokerNames Array of broker names to filter by
|
|
3849
|
+
* @returns The portal URL with broker filter parameters appended
|
|
3850
|
+
*/
|
|
3851
|
+
function appendBrokerFilterToURL(baseUrl, brokerNames) {
|
|
3852
|
+
if (!brokerNames || brokerNames.length === 0) {
|
|
3853
|
+
return baseUrl;
|
|
3854
|
+
}
|
|
3855
|
+
try {
|
|
3856
|
+
const url = new URL(baseUrl);
|
|
3857
|
+
const { brokerIds, warnings } = convertBrokerNamesToIds(brokerNames);
|
|
3858
|
+
// Log warnings for unsupported broker names
|
|
3859
|
+
warnings.forEach(warning => console.warn(`[FinaticConnect] ${warning}`));
|
|
3860
|
+
// Only add broker filter if we have valid broker IDs
|
|
3861
|
+
if (brokerIds.length > 0) {
|
|
3862
|
+
const encodedBrokers = btoa(JSON.stringify(brokerIds));
|
|
3863
|
+
url.searchParams.set('brokers', encodedBrokers);
|
|
3864
|
+
}
|
|
3865
|
+
return url.toString();
|
|
3866
|
+
}
|
|
3867
|
+
catch (error) {
|
|
3868
|
+
console.error('Failed to append broker filter to URL:', error);
|
|
3869
|
+
return baseUrl;
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3679
3873
|
class FinaticConnect extends EventEmitter {
|
|
3680
3874
|
constructor(options, deviceInfo) {
|
|
3681
3875
|
super();
|
|
@@ -3685,6 +3879,12 @@ class FinaticConnect extends EventEmitter {
|
|
|
3685
3879
|
this.BROKER_LIST_CACHE_VERSION = '1.0';
|
|
3686
3880
|
this.BROKER_LIST_CACHE_DURATION = 1000 * 60 * 60 * 24; // 24 hours in milliseconds
|
|
3687
3881
|
this.currentSessionState = null;
|
|
3882
|
+
// Session keep-alive mechanism
|
|
3883
|
+
this.sessionKeepAliveInterval = null;
|
|
3884
|
+
this.SESSION_KEEP_ALIVE_INTERVAL = 1000 * 60 * 5; // 5 minutes
|
|
3885
|
+
this.SESSION_VALIDATION_TIMEOUT = 1000 * 30; // 30 seconds
|
|
3886
|
+
this.SESSION_REFRESH_BUFFER_HOURS = 16; // Refresh session at 16 hours
|
|
3887
|
+
this.sessionStartTime = null;
|
|
3688
3888
|
this.options = options;
|
|
3689
3889
|
this.baseUrl = options.baseUrl || 'https://api.finatic.dev';
|
|
3690
3890
|
this.apiClient = MockFactory.createApiClient(this.baseUrl, deviceInfo);
|
|
@@ -3827,6 +4027,12 @@ class FinaticConnect extends EventEmitter {
|
|
|
3827
4027
|
* @returns FinaticConnect instance
|
|
3828
4028
|
*/
|
|
3829
4029
|
static async init(token, userId, options) {
|
|
4030
|
+
// Safari-specific fix: Clear instance if it exists but has no valid session
|
|
4031
|
+
// This prevents stale instances from interfering with new requests
|
|
4032
|
+
if (FinaticConnect.instance && !FinaticConnect.instance.sessionId) {
|
|
4033
|
+
console.log('[FinaticConnect] Clearing stale instance for Safari compatibility');
|
|
4034
|
+
FinaticConnect.instance = null;
|
|
4035
|
+
}
|
|
3830
4036
|
if (!FinaticConnect.instance) {
|
|
3831
4037
|
const connectOptions = {
|
|
3832
4038
|
token,
|
|
@@ -3856,6 +4062,8 @@ class FinaticConnect extends EventEmitter {
|
|
|
3856
4062
|
const startResponse = await FinaticConnect.instance.apiClient.startSession(token, normalizedUserId);
|
|
3857
4063
|
FinaticConnect.instance.sessionId = startResponse.data.session_id;
|
|
3858
4064
|
FinaticConnect.instance.companyId = startResponse.data.company_id || '';
|
|
4065
|
+
// Record session start time for automatic refresh
|
|
4066
|
+
FinaticConnect.instance.sessionStartTime = Date.now();
|
|
3859
4067
|
// Set session context in API client
|
|
3860
4068
|
if (FinaticConnect.instance.apiClient &&
|
|
3861
4069
|
typeof FinaticConnect.instance.apiClient.setSessionContext === 'function') {
|
|
@@ -3965,7 +4173,9 @@ class FinaticConnect extends EventEmitter {
|
|
|
3965
4173
|
throw new Error('Failed to get portal URL');
|
|
3966
4174
|
}
|
|
3967
4175
|
// Apply theme to portal URL if provided
|
|
3968
|
-
|
|
4176
|
+
let themedPortalUrl = appendThemeToURL(portalResponse.data.portal_url, options?.theme);
|
|
4177
|
+
// Apply broker filter to portal URL if provided
|
|
4178
|
+
themedPortalUrl = appendBrokerFilterToURL(themedPortalUrl, options?.brokers);
|
|
3969
4179
|
// Create portal UI if not exists
|
|
3970
4180
|
if (!this.portalUI) {
|
|
3971
4181
|
this.portalUI = new PortalUI(this.baseUrl);
|
|
@@ -4105,15 +4315,16 @@ class FinaticConnect extends EventEmitter {
|
|
|
4105
4315
|
? 'Limit'
|
|
4106
4316
|
: order.orderType === 'stop'
|
|
4107
4317
|
? 'Stop'
|
|
4108
|
-
: '
|
|
4318
|
+
: 'StopLimit',
|
|
4109
4319
|
assetType: order.assetType || 'Stock',
|
|
4110
4320
|
timeInForce: order.timeInForce,
|
|
4111
4321
|
price: order.price,
|
|
4112
4322
|
stopPrice: order.stopPrice,
|
|
4113
4323
|
broker: order.broker,
|
|
4114
4324
|
accountNumber: order.accountNumber,
|
|
4325
|
+
order_id: order.order_id,
|
|
4115
4326
|
};
|
|
4116
|
-
return await this.apiClient.placeBrokerOrder(this.userToken.accessToken, brokerOrder);
|
|
4327
|
+
return await this.apiClient.placeBrokerOrder(this.userToken.accessToken, brokerOrder, {}, order.connection_id);
|
|
4117
4328
|
}
|
|
4118
4329
|
catch (error) {
|
|
4119
4330
|
this.emit('error', error);
|
|
@@ -4124,13 +4335,14 @@ class FinaticConnect extends EventEmitter {
|
|
|
4124
4335
|
* Cancel a broker order
|
|
4125
4336
|
* @param orderId - The order ID to cancel
|
|
4126
4337
|
* @param broker - Optional broker override
|
|
4338
|
+
* @param connection_id - Optional connection ID for testing bypass
|
|
4127
4339
|
*/
|
|
4128
|
-
async cancelOrder(orderId, broker) {
|
|
4340
|
+
async cancelOrder(orderId, broker, connection_id) {
|
|
4129
4341
|
if (!this.userToken) {
|
|
4130
4342
|
throw new Error('Not initialized with user');
|
|
4131
4343
|
}
|
|
4132
4344
|
try {
|
|
4133
|
-
return await this.apiClient.cancelBrokerOrder(orderId, broker);
|
|
4345
|
+
return await this.apiClient.cancelBrokerOrder(orderId, broker, {}, connection_id);
|
|
4134
4346
|
}
|
|
4135
4347
|
catch (error) {
|
|
4136
4348
|
this.emit('error', error);
|
|
@@ -4142,8 +4354,9 @@ class FinaticConnect extends EventEmitter {
|
|
|
4142
4354
|
* @param orderId - The order ID to modify
|
|
4143
4355
|
* @param modifications - The modifications to apply
|
|
4144
4356
|
* @param broker - Optional broker override
|
|
4357
|
+
* @param connection_id - Optional connection ID for testing bypass
|
|
4145
4358
|
*/
|
|
4146
|
-
async modifyOrder(orderId, modifications, broker) {
|
|
4359
|
+
async modifyOrder(orderId, modifications, broker, connection_id) {
|
|
4147
4360
|
if (!this.userToken) {
|
|
4148
4361
|
throw new Error('Not initialized with user');
|
|
4149
4362
|
}
|
|
@@ -4160,7 +4373,13 @@ class FinaticConnect extends EventEmitter {
|
|
|
4160
4373
|
brokerModifications.stopPrice = modifications.stopPrice;
|
|
4161
4374
|
if (modifications.timeInForce)
|
|
4162
4375
|
brokerModifications.timeInForce = modifications.timeInForce;
|
|
4163
|
-
|
|
4376
|
+
if (modifications.orderType)
|
|
4377
|
+
brokerModifications.orderType = modifications.orderType;
|
|
4378
|
+
if (modifications.side)
|
|
4379
|
+
brokerModifications.action = modifications.side;
|
|
4380
|
+
if (modifications.order_id)
|
|
4381
|
+
brokerModifications.order_id = modifications.order_id;
|
|
4382
|
+
return await this.apiClient.modifyBrokerOrder(orderId, brokerModifications, broker, {}, connection_id);
|
|
4164
4383
|
}
|
|
4165
4384
|
catch (error) {
|
|
4166
4385
|
this.emit('error', error);
|
|
@@ -4202,7 +4421,7 @@ class FinaticConnect extends EventEmitter {
|
|
|
4202
4421
|
throw new Error('Not initialized with user');
|
|
4203
4422
|
}
|
|
4204
4423
|
try {
|
|
4205
|
-
return await this.apiClient.placeStockMarketOrder(
|
|
4424
|
+
return await this.apiClient.placeStockMarketOrder(symbol, quantity, side === 'buy' ? 'Buy' : 'Sell', broker, accountNumber);
|
|
4206
4425
|
}
|
|
4207
4426
|
catch (error) {
|
|
4208
4427
|
this.emit('error', error);
|
|
@@ -4217,7 +4436,7 @@ class FinaticConnect extends EventEmitter {
|
|
|
4217
4436
|
throw new Error('Not initialized with user');
|
|
4218
4437
|
}
|
|
4219
4438
|
try {
|
|
4220
|
-
return await this.apiClient.placeStockLimitOrder(
|
|
4439
|
+
return await this.apiClient.placeStockLimitOrder(symbol, quantity, side === 'buy' ? 'Buy' : 'Sell', price, timeInForce, broker, accountNumber);
|
|
4221
4440
|
}
|
|
4222
4441
|
catch (error) {
|
|
4223
4442
|
this.emit('error', error);
|
|
@@ -4246,7 +4465,7 @@ class FinaticConnect extends EventEmitter {
|
|
|
4246
4465
|
if (!this.isAuthed()) {
|
|
4247
4466
|
throw new AuthenticationError('Not authenticated');
|
|
4248
4467
|
}
|
|
4249
|
-
const response = await this.apiClient.
|
|
4468
|
+
const response = await this.apiClient.getBrokerList();
|
|
4250
4469
|
const baseUrl = this.baseUrl.replace('/api/v1', ''); // Remove /api/v1 to get the base URL
|
|
4251
4470
|
// Transform the broker list to include full logo URLs
|
|
4252
4471
|
return response.response_data.map((broker) => ({
|
|
@@ -4266,7 +4485,7 @@ class FinaticConnect extends EventEmitter {
|
|
|
4266
4485
|
if (!this.userToken?.user_id) {
|
|
4267
4486
|
throw new AuthenticationError('No user ID available. Please connect a broker first.');
|
|
4268
4487
|
}
|
|
4269
|
-
const response = await this.apiClient.
|
|
4488
|
+
const response = await this.apiClient.getBrokerConnections();
|
|
4270
4489
|
if (response.status_code !== 200) {
|
|
4271
4490
|
throw new Error(response.message || 'Failed to retrieve broker connections');
|
|
4272
4491
|
}
|
|
@@ -4482,28 +4701,144 @@ class FinaticConnect extends EventEmitter {
|
|
|
4482
4701
|
return allData;
|
|
4483
4702
|
}
|
|
4484
4703
|
/**
|
|
4485
|
-
* Register
|
|
4704
|
+
* Register session management (but don't auto-cleanup for 24-hour sessions)
|
|
4486
4705
|
*/
|
|
4487
4706
|
registerSessionCleanup() {
|
|
4488
|
-
//
|
|
4707
|
+
// Only cleanup on actual page unload (not visibility changes)
|
|
4708
|
+
// This prevents sessions from being closed when users switch tabs or apps
|
|
4489
4709
|
window.addEventListener('beforeunload', this.handleSessionCleanup.bind(this));
|
|
4490
|
-
//
|
|
4710
|
+
// Handle visibility changes for keep-alive management (but don't complete sessions)
|
|
4491
4711
|
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
|
|
4712
|
+
// Start session keep-alive mechanism
|
|
4713
|
+
this.startSessionKeepAlive();
|
|
4714
|
+
}
|
|
4715
|
+
/**
|
|
4716
|
+
* Start the session keep-alive mechanism
|
|
4717
|
+
*/
|
|
4718
|
+
startSessionKeepAlive() {
|
|
4719
|
+
if (this.sessionKeepAliveInterval) {
|
|
4720
|
+
clearInterval(this.sessionKeepAliveInterval);
|
|
4721
|
+
}
|
|
4722
|
+
this.sessionKeepAliveInterval = setInterval(() => {
|
|
4723
|
+
this.validateSessionKeepAlive();
|
|
4724
|
+
}, this.SESSION_KEEP_ALIVE_INTERVAL);
|
|
4725
|
+
console.log('[FinaticConnect] Session keep-alive started (5-minute intervals)');
|
|
4726
|
+
}
|
|
4727
|
+
/**
|
|
4728
|
+
* Stop the session keep-alive mechanism
|
|
4729
|
+
*/
|
|
4730
|
+
stopSessionKeepAlive() {
|
|
4731
|
+
if (this.sessionKeepAliveInterval) {
|
|
4732
|
+
clearInterval(this.sessionKeepAliveInterval);
|
|
4733
|
+
this.sessionKeepAliveInterval = null;
|
|
4734
|
+
console.log('[FinaticConnect] Session keep-alive stopped');
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
/**
|
|
4738
|
+
* Validate session for keep-alive purposes and handle automatic refresh
|
|
4739
|
+
*/
|
|
4740
|
+
async validateSessionKeepAlive() {
|
|
4741
|
+
if (!this.sessionId || !this.isAuthed()) {
|
|
4742
|
+
console.log('[FinaticConnect] Session keep-alive skipped - no active session');
|
|
4743
|
+
return;
|
|
4744
|
+
}
|
|
4745
|
+
try {
|
|
4746
|
+
console.log('[FinaticConnect] Validating session for keep-alive...');
|
|
4747
|
+
// Check if we need to refresh the session (at 16 hours)
|
|
4748
|
+
if (this.shouldRefreshSession()) {
|
|
4749
|
+
await this.refreshSessionAutomatically();
|
|
4750
|
+
return;
|
|
4751
|
+
}
|
|
4752
|
+
// Use a timeout to prevent hanging requests
|
|
4753
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
4754
|
+
setTimeout(() => reject(new Error('Session validation timeout')), this.SESSION_VALIDATION_TIMEOUT);
|
|
4755
|
+
});
|
|
4756
|
+
const validationPromise = this.apiClient.validatePortalSession(this.sessionId, '');
|
|
4757
|
+
const response = await Promise.race([validationPromise, timeoutPromise]);
|
|
4758
|
+
if (response && response.status === 'active') {
|
|
4759
|
+
console.log('[FinaticConnect] Session keep-alive successful');
|
|
4760
|
+
this.currentSessionState = 'active';
|
|
4761
|
+
}
|
|
4762
|
+
else {
|
|
4763
|
+
console.warn('[FinaticConnect] Session keep-alive failed - session not active');
|
|
4764
|
+
this.currentSessionState = response?.status || 'unknown';
|
|
4765
|
+
}
|
|
4766
|
+
}
|
|
4767
|
+
catch (error) {
|
|
4768
|
+
console.warn('[FinaticConnect] Session keep-alive error:', error);
|
|
4769
|
+
// Don't throw errors during keep-alive - just log them
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
/**
|
|
4773
|
+
* Check if the session should be refreshed (after 16 hours)
|
|
4774
|
+
*/
|
|
4775
|
+
shouldRefreshSession() {
|
|
4776
|
+
if (!this.sessionStartTime) {
|
|
4777
|
+
return false;
|
|
4778
|
+
}
|
|
4779
|
+
const sessionAgeHours = (Date.now() - this.sessionStartTime) / (1000 * 60 * 60);
|
|
4780
|
+
const hoursUntilRefresh = this.SESSION_REFRESH_BUFFER_HOURS - sessionAgeHours;
|
|
4781
|
+
if (hoursUntilRefresh <= 0) {
|
|
4782
|
+
console.log(`[FinaticConnect] Session is ${sessionAgeHours.toFixed(1)} hours old - triggering refresh`);
|
|
4783
|
+
return true;
|
|
4784
|
+
}
|
|
4785
|
+
// Log when refresh will occur (every 5 minutes during keep-alive)
|
|
4786
|
+
if (hoursUntilRefresh <= 1) {
|
|
4787
|
+
console.log(`[FinaticConnect] Session will refresh in ${hoursUntilRefresh.toFixed(1)} hours`);
|
|
4788
|
+
}
|
|
4789
|
+
return false;
|
|
4790
|
+
}
|
|
4791
|
+
/**
|
|
4792
|
+
* Automatically refresh the session to extend its lifetime
|
|
4793
|
+
*/
|
|
4794
|
+
async refreshSessionAutomatically() {
|
|
4795
|
+
if (!this.sessionId) {
|
|
4796
|
+
console.warn('[FinaticConnect] Cannot refresh session - no session ID');
|
|
4797
|
+
return;
|
|
4798
|
+
}
|
|
4799
|
+
try {
|
|
4800
|
+
console.log('[FinaticConnect] Automatically refreshing session (16+ hours old)...');
|
|
4801
|
+
const response = await this.apiClient.refreshSession();
|
|
4802
|
+
if (response.success) {
|
|
4803
|
+
console.log('[FinaticConnect] Session automatically refreshed successfully');
|
|
4804
|
+
console.log('[FinaticConnect] New session expires at:', response.response_data.expires_at);
|
|
4805
|
+
this.currentSessionState = response.response_data.status;
|
|
4806
|
+
// Update session start time to prevent immediate re-refresh
|
|
4807
|
+
this.sessionStartTime = Date.now();
|
|
4808
|
+
}
|
|
4809
|
+
else {
|
|
4810
|
+
console.warn('[FinaticConnect] Automatic session refresh failed');
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
catch (error) {
|
|
4814
|
+
console.warn('[FinaticConnect] Automatic session refresh error:', error);
|
|
4815
|
+
// Don't throw errors during automatic refresh - just log them
|
|
4816
|
+
}
|
|
4492
4817
|
}
|
|
4493
4818
|
/**
|
|
4494
4819
|
* Handle session cleanup when page is unloading
|
|
4495
4820
|
*/
|
|
4496
4821
|
async handleSessionCleanup() {
|
|
4822
|
+
this.stopSessionKeepAlive();
|
|
4497
4823
|
if (this.sessionId) {
|
|
4498
4824
|
await this.completeSession(this.sessionId);
|
|
4499
4825
|
}
|
|
4500
4826
|
}
|
|
4501
4827
|
/**
|
|
4502
4828
|
* Handle visibility change (mobile browsers)
|
|
4829
|
+
* Note: We don't complete sessions on visibility change for 24-hour sessions
|
|
4503
4830
|
*/
|
|
4504
4831
|
async handleVisibilityChange() {
|
|
4505
|
-
|
|
4506
|
-
|
|
4832
|
+
// For 24-hour sessions, we don't want to complete sessions on visibility changes
|
|
4833
|
+
// This prevents sessions from being closed when users switch tabs or apps
|
|
4834
|
+
console.log('[FinaticConnect] Page visibility changed to:', document.visibilityState);
|
|
4835
|
+
// Only pause keep-alive when hidden, but don't complete the session
|
|
4836
|
+
if (document.visibilityState === 'hidden') {
|
|
4837
|
+
this.stopSessionKeepAlive();
|
|
4838
|
+
}
|
|
4839
|
+
else if (document.visibilityState === 'visible') {
|
|
4840
|
+
// Restart keep-alive when page becomes visible again
|
|
4841
|
+
this.startSessionKeepAlive();
|
|
4507
4842
|
}
|
|
4508
4843
|
}
|
|
4509
4844
|
/**
|
|
@@ -4541,242 +4876,23 @@ class FinaticConnect extends EventEmitter {
|
|
|
4541
4876
|
console.warn('[FinaticConnect] Session cleanup failed:', error);
|
|
4542
4877
|
}
|
|
4543
4878
|
}
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
}
|
|
4554
|
-
async getOrders(filter) {
|
|
4555
|
-
return this.getAllOrders(filter);
|
|
4556
|
-
}
|
|
4557
|
-
async getPositions(filter) {
|
|
4558
|
-
return this.getAllPositions(filter);
|
|
4559
|
-
}
|
|
4560
|
-
async placeOrder(order) {
|
|
4561
|
-
const response = await this.apiClient.placeOrder(order);
|
|
4562
|
-
return response.data.orderId;
|
|
4563
|
-
}
|
|
4564
|
-
async cancelOrder(orderId) {
|
|
4565
|
-
const response = await this.apiClient.cancelOrder(orderId);
|
|
4566
|
-
return response.data.success;
|
|
4567
|
-
}
|
|
4568
|
-
async placeOptionsOrder(order) {
|
|
4569
|
-
const response = await this.apiClient.placeOptionsOrder(order);
|
|
4570
|
-
return response.data.orderId;
|
|
4571
|
-
}
|
|
4572
|
-
// Pagination methods
|
|
4573
|
-
async getAccountsPage(page = 1, perPage = 100, filter) {
|
|
4574
|
-
return this.apiClient.getBrokerAccountsPage(page, perPage, filter);
|
|
4575
|
-
}
|
|
4576
|
-
async getOrdersPage(page = 1, perPage = 100, filter) {
|
|
4577
|
-
return this.apiClient.getBrokerOrdersPage(page, perPage, filter);
|
|
4578
|
-
}
|
|
4579
|
-
async getPositionsPage(page = 1, perPage = 100, filter) {
|
|
4580
|
-
return this.apiClient.getBrokerPositionsPage(page, perPage, filter);
|
|
4581
|
-
}
|
|
4582
|
-
// Navigation methods
|
|
4583
|
-
async getNextAccountsPage(previousResult) {
|
|
4584
|
-
return this.apiClient.getNextPage(previousResult, (offset, limit) => this.apiClient.getBrokerAccountsPage(1, limit, { offset }));
|
|
4585
|
-
}
|
|
4586
|
-
async getNextOrdersPage(previousResult) {
|
|
4587
|
-
return this.apiClient.getNextPage(previousResult, (offset, limit) => this.apiClient.getBrokerOrdersPage(1, limit, { offset }));
|
|
4588
|
-
}
|
|
4589
|
-
async getNextPositionsPage(previousResult) {
|
|
4590
|
-
return this.apiClient.getNextPage(previousResult, (offset, limit) => this.apiClient.getBrokerPositionsPage(1, limit, { offset }));
|
|
4591
|
-
}
|
|
4592
|
-
// Get all pages convenience methods
|
|
4593
|
-
async getAllAccounts(filter) {
|
|
4594
|
-
const allData = [];
|
|
4595
|
-
let currentResult = await this.getAccountsPage(1, 100, filter);
|
|
4596
|
-
while (currentResult) {
|
|
4597
|
-
allData.push(...currentResult.data);
|
|
4598
|
-
if (!currentResult.hasNext)
|
|
4599
|
-
break;
|
|
4600
|
-
const nextResult = await this.getNextAccountsPage(currentResult);
|
|
4601
|
-
if (!nextResult)
|
|
4602
|
-
break;
|
|
4603
|
-
currentResult = nextResult;
|
|
4604
|
-
}
|
|
4605
|
-
return allData;
|
|
4606
|
-
}
|
|
4607
|
-
async getAllOrders(filter) {
|
|
4608
|
-
const allData = [];
|
|
4609
|
-
let currentResult = await this.getOrdersPage(1, 100, filter);
|
|
4610
|
-
while (currentResult) {
|
|
4611
|
-
allData.push(...currentResult.data);
|
|
4612
|
-
if (!currentResult.hasNext)
|
|
4613
|
-
break;
|
|
4614
|
-
const nextResult = await this.getNextOrdersPage(currentResult);
|
|
4615
|
-
if (!nextResult)
|
|
4616
|
-
break;
|
|
4617
|
-
currentResult = nextResult;
|
|
4618
|
-
}
|
|
4619
|
-
return allData;
|
|
4620
|
-
}
|
|
4621
|
-
async getAllPositions(filter) {
|
|
4622
|
-
const allData = [];
|
|
4623
|
-
let currentResult = await this.getPositionsPage(1, 100, filter);
|
|
4624
|
-
while (currentResult) {
|
|
4625
|
-
allData.push(...currentResult.data);
|
|
4626
|
-
if (!currentResult.hasNext)
|
|
4627
|
-
break;
|
|
4628
|
-
const nextResult = await this.getNextPositionsPage(currentResult);
|
|
4629
|
-
if (!nextResult)
|
|
4630
|
-
break;
|
|
4631
|
-
currentResult = nextResult;
|
|
4632
|
-
}
|
|
4633
|
-
return allData;
|
|
4634
|
-
}
|
|
4635
|
-
// Abstract convenience methods
|
|
4636
|
-
async getOpenPositions() {
|
|
4637
|
-
return this.getPositions({ position_status: 'open' });
|
|
4638
|
-
}
|
|
4639
|
-
async getFilledOrders() {
|
|
4640
|
-
return this.getOrders({ status: 'filled' });
|
|
4641
|
-
}
|
|
4642
|
-
async getPendingOrders() {
|
|
4643
|
-
return this.getOrders({ status: 'pending' });
|
|
4644
|
-
}
|
|
4645
|
-
async getActiveAccounts() {
|
|
4646
|
-
return this.getAccounts({ status: 'active' });
|
|
4647
|
-
}
|
|
4648
|
-
async getOrdersBySymbol(symbol) {
|
|
4649
|
-
return this.getOrders({ symbol });
|
|
4650
|
-
}
|
|
4651
|
-
async getPositionsBySymbol(symbol) {
|
|
4652
|
-
return this.getPositions({ symbol });
|
|
4653
|
-
}
|
|
4654
|
-
async getOrdersByBroker(brokerId) {
|
|
4655
|
-
return this.getOrders({ broker_id: brokerId });
|
|
4656
|
-
}
|
|
4657
|
-
async getPositionsByBroker(brokerId) {
|
|
4658
|
-
return this.getPositions({ broker_id: brokerId });
|
|
4659
|
-
}
|
|
4660
|
-
}
|
|
4661
|
-
|
|
4662
|
-
class CoreAnalyticsService {
|
|
4663
|
-
constructor(apiClient) {
|
|
4664
|
-
this.apiClient = apiClient;
|
|
4665
|
-
}
|
|
4666
|
-
async getPerformance() {
|
|
4667
|
-
const response = await this.apiClient.getPerformance();
|
|
4668
|
-
return response.data.performance;
|
|
4669
|
-
}
|
|
4670
|
-
async getDailyHistory() {
|
|
4671
|
-
const response = await this.apiClient.getDailyHistory();
|
|
4672
|
-
return response.data.history;
|
|
4673
|
-
}
|
|
4674
|
-
async getWeeklySnapshots() {
|
|
4675
|
-
const response = await this.apiClient.getWeeklySnapshots();
|
|
4676
|
-
return response.data.snapshots;
|
|
4677
|
-
}
|
|
4678
|
-
async getPortfolioDeltas() {
|
|
4679
|
-
const response = await this.apiClient.getPortfolioDeltas();
|
|
4680
|
-
return response.data.deltas;
|
|
4681
|
-
}
|
|
4682
|
-
async getUserLogs(userId) {
|
|
4683
|
-
const response = await this.apiClient.getUserLogs(userId);
|
|
4684
|
-
return response.data.logs;
|
|
4685
|
-
}
|
|
4686
|
-
}
|
|
4687
|
-
|
|
4688
|
-
class CorePortalService {
|
|
4689
|
-
constructor(apiClient) {
|
|
4690
|
-
this.apiClient = apiClient;
|
|
4691
|
-
this.iframe = null;
|
|
4692
|
-
}
|
|
4693
|
-
async createPortal(config) {
|
|
4694
|
-
// Create iframe
|
|
4695
|
-
this.iframe = document.createElement('iframe');
|
|
4696
|
-
this.iframe.style.display = 'none';
|
|
4697
|
-
document.body.appendChild(this.iframe);
|
|
4698
|
-
// Get portal content from API
|
|
4699
|
-
const response = await this.apiClient.getPortalContent();
|
|
4700
|
-
const content = response.data.content;
|
|
4701
|
-
// Set iframe content
|
|
4702
|
-
if (this.iframe.contentWindow) {
|
|
4703
|
-
this.iframe.contentWindow.document.open();
|
|
4704
|
-
this.iframe.contentWindow.document.write(content);
|
|
4705
|
-
this.iframe.contentWindow.document.close();
|
|
4706
|
-
}
|
|
4707
|
-
// Position and style iframe
|
|
4708
|
-
this.positionIframe(config);
|
|
4709
|
-
this.styleIframe(config);
|
|
4710
|
-
// Show iframe
|
|
4711
|
-
this.iframe.style.display = 'block';
|
|
4712
|
-
}
|
|
4713
|
-
async closePortal() {
|
|
4714
|
-
if (this.iframe) {
|
|
4715
|
-
this.iframe.remove();
|
|
4716
|
-
this.iframe = null;
|
|
4717
|
-
}
|
|
4718
|
-
}
|
|
4719
|
-
async updateTheme(theme) {
|
|
4720
|
-
if (this.iframe?.contentWindow) {
|
|
4721
|
-
this.iframe.contentWindow.postMessage({ type: 'update_theme', theme }, '*');
|
|
4722
|
-
}
|
|
4723
|
-
}
|
|
4724
|
-
async getBrokerAccounts() {
|
|
4725
|
-
const response = await this.apiClient.getBrokerAccounts();
|
|
4726
|
-
return response.data.accounts;
|
|
4727
|
-
}
|
|
4728
|
-
async linkBrokerAccount(brokerId) {
|
|
4729
|
-
await this.apiClient.connectBroker(brokerId);
|
|
4730
|
-
}
|
|
4731
|
-
async unlinkBrokerAccount(brokerId) {
|
|
4732
|
-
await this.apiClient.disconnectBroker(brokerId);
|
|
4733
|
-
}
|
|
4734
|
-
positionIframe(config) {
|
|
4735
|
-
if (!this.iframe)
|
|
4736
|
-
return;
|
|
4737
|
-
const position = config.position || 'center';
|
|
4738
|
-
const width = config.width || '600px';
|
|
4739
|
-
const height = config.height || '800px';
|
|
4740
|
-
this.iframe.style.width = width;
|
|
4741
|
-
this.iframe.style.height = height;
|
|
4742
|
-
switch (position) {
|
|
4743
|
-
case 'center':
|
|
4744
|
-
this.iframe.style.position = 'fixed';
|
|
4745
|
-
this.iframe.style.top = '50%';
|
|
4746
|
-
this.iframe.style.left = '50%';
|
|
4747
|
-
this.iframe.style.transform = 'translate(-50%, -50%)';
|
|
4748
|
-
break;
|
|
4749
|
-
case 'top':
|
|
4750
|
-
this.iframe.style.position = 'fixed';
|
|
4751
|
-
this.iframe.style.top = '0';
|
|
4752
|
-
this.iframe.style.left = '50%';
|
|
4753
|
-
this.iframe.style.transform = 'translateX(-50%)';
|
|
4754
|
-
break;
|
|
4755
|
-
case 'bottom':
|
|
4756
|
-
this.iframe.style.position = 'fixed';
|
|
4757
|
-
this.iframe.style.bottom = '0';
|
|
4758
|
-
this.iframe.style.left = '50%';
|
|
4759
|
-
this.iframe.style.transform = 'translateX(-50%)';
|
|
4760
|
-
break;
|
|
4761
|
-
// Note: 'left' and 'right' positions removed from PortalConfig interface
|
|
4762
|
-
// Default to center if invalid position is provided
|
|
4763
|
-
default:
|
|
4764
|
-
this.iframe.style.position = 'fixed';
|
|
4765
|
-
this.iframe.style.top = '50%';
|
|
4766
|
-
this.iframe.style.left = '50%';
|
|
4767
|
-
this.iframe.style.transform = 'translate(-50%, -50%)';
|
|
4768
|
-
break;
|
|
4879
|
+
/**
|
|
4880
|
+
* Disconnect a company from a broker connection
|
|
4881
|
+
* @param connectionId - The connection ID to disconnect
|
|
4882
|
+
* @returns Promise with disconnect response
|
|
4883
|
+
* @throws AuthenticationError if user is not authenticated
|
|
4884
|
+
*/
|
|
4885
|
+
async disconnectCompany(connectionId) {
|
|
4886
|
+
if (!this.isAuthed()) {
|
|
4887
|
+
throw new AuthenticationError('User is not authenticated. Please connect a broker first.');
|
|
4769
4888
|
}
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
if (!this.iframe)
|
|
4773
|
-
return;
|
|
4774
|
-
// Apply z-index if specified
|
|
4775
|
-
if (config.zIndex) {
|
|
4776
|
-
this.iframe.style.zIndex = config.zIndex.toString();
|
|
4889
|
+
if (!this.userToken?.user_id) {
|
|
4890
|
+
throw new AuthenticationError('No user ID available. Please connect a broker first.');
|
|
4777
4891
|
}
|
|
4892
|
+
return this.apiClient.disconnectCompany(connectionId);
|
|
4778
4893
|
}
|
|
4779
4894
|
}
|
|
4895
|
+
FinaticConnect.instance = null;
|
|
4780
4896
|
|
|
4781
4897
|
exports.ApiClient = ApiClient;
|
|
4782
4898
|
exports.ApiError = ApiError;
|
|
@@ -4784,9 +4900,6 @@ exports.AuthenticationError = AuthenticationError;
|
|
|
4784
4900
|
exports.AuthorizationError = AuthorizationError;
|
|
4785
4901
|
exports.BaseError = BaseError;
|
|
4786
4902
|
exports.CompanyAccessError = CompanyAccessError;
|
|
4787
|
-
exports.CoreAnalyticsService = CoreAnalyticsService;
|
|
4788
|
-
exports.CorePortalService = CorePortalService;
|
|
4789
|
-
exports.CoreTradingService = CoreTradingService;
|
|
4790
4903
|
exports.EventEmitter = EventEmitter;
|
|
4791
4904
|
exports.FinaticConnect = FinaticConnect;
|
|
4792
4905
|
exports.MockFactory = MockFactory;
|