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