@finatic/client 0.0.136 → 0.0.138

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/dist/index.js CHANGED
@@ -652,23 +652,6 @@ class ApiClient {
652
652
  },
653
653
  });
654
654
  }
655
- async validatePortalSession(sessionId, signature) {
656
- return this.request('/portal/validate', {
657
- method: 'GET',
658
- headers: {
659
- 'Content-Type': 'application/json',
660
- 'X-Session-ID': sessionId,
661
- 'X-Device-Info': JSON.stringify({
662
- ip_address: this.deviceInfo?.ip_address || '',
663
- user_agent: this.deviceInfo?.user_agent || '',
664
- fingerprint: this.deviceInfo?.fingerprint || '',
665
- }),
666
- },
667
- params: {
668
- signature,
669
- },
670
- });
671
- }
672
655
  async completePortalSession(sessionId) {
673
656
  return this.request(`/portal/${sessionId}/complete`, {
674
657
  method: 'POST',
@@ -678,34 +661,14 @@ class ApiClient {
678
661
  });
679
662
  }
680
663
  // Portfolio Management
681
- async getHoldings() {
682
- const accessToken = await this.getValidAccessToken();
683
- return this.request('/portfolio/holdings', {
684
- method: 'GET',
685
- headers: {
686
- 'Authorization': `Bearer ${accessToken}`,
687
- },
688
- });
689
- }
690
664
  async getOrders() {
691
665
  const accessToken = await this.getValidAccessToken();
692
- return this.request('/data/orders', {
693
- method: 'GET',
694
- headers: {
695
- 'Authorization': `Bearer ${accessToken}`,
696
- },
697
- });
698
- }
699
- async getPortfolio() {
700
- const accessToken = await this.getValidAccessToken();
701
- const response = await this.request('/portfolio/', {
666
+ return this.request('/brokers/data/orders', {
702
667
  method: 'GET',
703
668
  headers: {
704
- 'Authorization': `Bearer ${accessToken}`,
705
- 'Content-Type': 'application/json',
669
+ Authorization: `Bearer ${accessToken}`,
706
670
  },
707
671
  });
708
- return response;
709
672
  }
710
673
  // Enhanced Trading Methods with Session Management
711
674
  async placeBrokerOrder(params, extras = {}, connection_id) {
@@ -768,10 +731,7 @@ class ApiClient {
768
731
  }
769
732
  const accountNumber = this.tradingContext.accountNumber;
770
733
  // Build query parameters as required by API documentation
771
- const queryParams = {
772
- broker: selectedBroker,
773
- order_id: orderId,
774
- };
734
+ const queryParams = {};
775
735
  // Add optional parameters if available
776
736
  if (accountNumber) {
777
737
  queryParams.account_number = accountNumber.toString();
@@ -787,11 +747,11 @@ class ApiClient {
787
747
  order: {
788
748
  order_id: orderId,
789
749
  account_number: accountNumber,
790
- ...extras
791
- }
750
+ ...extras,
751
+ },
792
752
  };
793
753
  }
794
- return this.request('/brokers/orders', {
754
+ return this.request(`/brokers/orders/${orderId}`, {
795
755
  method: 'DELETE',
796
756
  headers: {
797
757
  'Content-Type': 'application/json',
@@ -1061,22 +1021,12 @@ class ApiClient {
1061
1021
  return extras;
1062
1022
  }
1063
1023
  }
1064
- async revokeToken() {
1065
- const accessToken = await this.getValidAccessToken();
1066
- await this.request('/auth/token/revoke', {
1067
- method: 'POST',
1068
- headers: {
1069
- 'Authorization': `Bearer ${accessToken}`,
1070
- },
1071
- });
1072
- this.clearTokens();
1073
- }
1074
- async getUserToken(userId) {
1024
+ async getUserToken(sessionId) {
1075
1025
  const accessToken = await this.getValidAccessToken();
1076
- return this.request(`/auth/token/user/${userId}`, {
1026
+ return this.request(`/auth/session/${sessionId}/user`, {
1077
1027
  method: 'GET',
1078
1028
  headers: {
1079
- 'Authorization': `Bearer ${accessToken}`,
1029
+ Authorization: `Bearer ${accessToken}`,
1080
1030
  },
1081
1031
  });
1082
1032
  }
@@ -1103,12 +1053,9 @@ class ApiClient {
1103
1053
  }
1104
1054
  // Broker Data Management
1105
1055
  async getBrokerList() {
1106
- const accessToken = await this.getValidAccessToken();
1107
- return this.request('/brokers/list', {
1056
+ // Public endpoint - no auth required
1057
+ return this.request('/brokers/', {
1108
1058
  method: 'GET',
1109
- headers: {
1110
- 'Authorization': `Bearer ${accessToken}`,
1111
- },
1112
1059
  });
1113
1060
  }
1114
1061
  async getBrokerAccounts(options) {
@@ -1126,7 +1073,7 @@ class ApiClient {
1126
1073
  return this.request('/brokers/data/accounts', {
1127
1074
  method: 'GET',
1128
1075
  headers: {
1129
- 'Authorization': `Bearer ${accessToken}`,
1076
+ Authorization: `Bearer ${accessToken}`,
1130
1077
  },
1131
1078
  params,
1132
1079
  });
@@ -1146,7 +1093,7 @@ class ApiClient {
1146
1093
  return this.request('/brokers/data/orders', {
1147
1094
  method: 'GET',
1148
1095
  headers: {
1149
- 'Authorization': `Bearer ${accessToken}`,
1096
+ Authorization: `Bearer ${accessToken}`,
1150
1097
  },
1151
1098
  params,
1152
1099
  });
@@ -1166,7 +1113,27 @@ class ApiClient {
1166
1113
  return this.request('/brokers/data/positions', {
1167
1114
  method: 'GET',
1168
1115
  headers: {
1169
- 'Authorization': `Bearer ${accessToken}`,
1116
+ Authorization: `Bearer ${accessToken}`,
1117
+ },
1118
+ params,
1119
+ });
1120
+ }
1121
+ async getBrokerBalances(options) {
1122
+ const accessToken = await this.getValidAccessToken();
1123
+ const params = {};
1124
+ if (options?.broker_name) {
1125
+ params.broker_id = options.broker_name;
1126
+ }
1127
+ if (options?.account_id) {
1128
+ params.account_id = options.account_id;
1129
+ }
1130
+ if (options?.symbol) {
1131
+ params.symbol = options.symbol;
1132
+ }
1133
+ return this.request('/brokers/data/balances', {
1134
+ method: 'GET',
1135
+ headers: {
1136
+ Authorization: `Bearer ${accessToken}`,
1170
1137
  },
1171
1138
  params,
1172
1139
  });
@@ -1176,7 +1143,26 @@ class ApiClient {
1176
1143
  return this.request('/brokers/connections', {
1177
1144
  method: 'GET',
1178
1145
  headers: {
1179
- 'Authorization': `Bearer ${accessToken}`,
1146
+ Authorization: `Bearer ${accessToken}`,
1147
+ },
1148
+ });
1149
+ }
1150
+ async getBalances(filters) {
1151
+ const accessToken = await this.getValidAccessToken();
1152
+ const params = new URLSearchParams();
1153
+ if (filters) {
1154
+ Object.entries(filters).forEach(([key, value]) => {
1155
+ if (value !== undefined && value !== null) {
1156
+ params.append(key, String(value));
1157
+ }
1158
+ });
1159
+ }
1160
+ const queryString = params.toString();
1161
+ const url = queryString ? `/brokers/data/balances?${queryString}` : '/brokers/data/balances';
1162
+ return this.request(url, {
1163
+ method: 'GET',
1164
+ headers: {
1165
+ Authorization: `Bearer ${accessToken}`,
1180
1166
  },
1181
1167
  });
1182
1168
  }
@@ -1398,6 +1384,81 @@ class ApiClient {
1398
1384
  limit: perPage,
1399
1385
  }, navigationCallback);
1400
1386
  }
1387
+ async getBrokerBalancesPage(page = 1, perPage = 100, filters) {
1388
+ const accessToken = await this.getValidAccessToken();
1389
+ const offset = (page - 1) * perPage;
1390
+ const params = {
1391
+ limit: perPage.toString(),
1392
+ offset: offset.toString(),
1393
+ };
1394
+ // Add filter parameters
1395
+ if (filters) {
1396
+ if (filters.broker_id)
1397
+ params.broker_id = filters.broker_id;
1398
+ if (filters.connection_id)
1399
+ params.connection_id = filters.connection_id;
1400
+ if (filters.account_id)
1401
+ params.account_id = filters.account_id;
1402
+ if (filters.is_end_of_day_snapshot !== undefined)
1403
+ params.is_end_of_day_snapshot = filters.is_end_of_day_snapshot.toString();
1404
+ if (filters.balance_created_after)
1405
+ params.balance_created_after = filters.balance_created_after;
1406
+ if (filters.balance_created_before)
1407
+ params.balance_created_before = filters.balance_created_before;
1408
+ if (filters.with_metadata !== undefined)
1409
+ params.with_metadata = filters.with_metadata.toString();
1410
+ }
1411
+ const response = await this.request('/brokers/data/balances', {
1412
+ method: 'GET',
1413
+ headers: {
1414
+ Authorization: `Bearer ${accessToken}`,
1415
+ },
1416
+ params,
1417
+ });
1418
+ // Create navigation callback for pagination
1419
+ const navigationCallback = async (newOffset, newLimit) => {
1420
+ const newParams = {
1421
+ limit: newLimit.toString(),
1422
+ offset: newOffset.toString(),
1423
+ };
1424
+ // Add filter parameters
1425
+ if (filters) {
1426
+ if (filters.broker_id)
1427
+ newParams.broker_id = filters.broker_id;
1428
+ if (filters.connection_id)
1429
+ newParams.connection_id = filters.connection_id;
1430
+ if (filters.account_id)
1431
+ newParams.account_id = filters.account_id;
1432
+ if (filters.is_end_of_day_snapshot !== undefined)
1433
+ newParams.is_end_of_day_snapshot = filters.is_end_of_day_snapshot.toString();
1434
+ if (filters.balance_created_after)
1435
+ newParams.balance_created_after = filters.balance_created_after;
1436
+ if (filters.balance_created_before)
1437
+ newParams.balance_created_before = filters.balance_created_before;
1438
+ if (filters.with_metadata !== undefined)
1439
+ newParams.with_metadata = filters.with_metadata.toString();
1440
+ }
1441
+ const newResponse = await this.request('/brokers/data/balances', {
1442
+ method: 'GET',
1443
+ headers: {
1444
+ Authorization: `Bearer ${accessToken}`,
1445
+ },
1446
+ params: newParams,
1447
+ });
1448
+ return new PaginatedResult(newResponse.response_data, newResponse.pagination || {
1449
+ has_more: false,
1450
+ next_offset: newOffset,
1451
+ current_offset: newOffset,
1452
+ limit: newLimit,
1453
+ }, navigationCallback);
1454
+ };
1455
+ return new PaginatedResult(response.response_data, response.pagination || {
1456
+ has_more: false,
1457
+ next_offset: offset,
1458
+ current_offset: offset,
1459
+ limit: perPage,
1460
+ }, navigationCallback);
1461
+ }
1401
1462
  // Navigation methods
1402
1463
  async getNextPage(previousResult, fetchFunction) {
1403
1464
  if (!previousResult.hasNext) {
@@ -1419,10 +1480,10 @@ class ApiClient {
1419
1480
  */
1420
1481
  async disconnectCompany(connectionId) {
1421
1482
  const accessToken = await this.getValidAccessToken();
1422
- return this.request(`/brokers/disconnect-company/${connectionId}`, {
1483
+ return this.request(`/brokers/disconnect/${connectionId}`, {
1423
1484
  method: 'DELETE',
1424
1485
  headers: {
1425
- 'Authorization': `Bearer ${accessToken}`,
1486
+ Authorization: `Bearer ${accessToken}`,
1426
1487
  },
1427
1488
  });
1428
1489
  }
@@ -1485,7 +1546,6 @@ class PortalUI {
1485
1546
  this.messageHandler = null;
1486
1547
  this.sessionId = null;
1487
1548
  this.portalOrigin = null;
1488
- this.userToken = null;
1489
1549
  this.originalBodyStyle = null;
1490
1550
  this.createContainer();
1491
1551
  }
@@ -1612,15 +1672,13 @@ class PortalUI {
1612
1672
  console.warn('[PortalUI] Received message from unauthorized origin:', event.origin, 'Expected:', this.portalOrigin);
1613
1673
  return;
1614
1674
  }
1615
- const { type, userId, access_token, refresh_token, error, height, data } = event.data;
1675
+ const { type, userId, error, height, data } = event.data;
1616
1676
  console.log('[PortalUI] Received message:', event.data);
1617
1677
  switch (type) {
1618
1678
  case 'portal-success': {
1619
1679
  // Handle both direct userId and data.userId formats
1620
1680
  const successUserId = userId || (data && data.userId);
1621
- const successAccessToken = access_token || (data && data.access_token);
1622
- const successRefreshToken = refresh_token || (data && data.refresh_token);
1623
- this.handlePortalSuccess(successUserId, successAccessToken, successRefreshToken);
1681
+ this.handlePortalSuccess(successUserId);
1624
1682
  break;
1625
1683
  }
1626
1684
  case 'portal-error': {
@@ -1640,7 +1698,7 @@ class PortalUI {
1640
1698
  break;
1641
1699
  // Legacy support for old message types
1642
1700
  case 'success':
1643
- this.handleSuccess(userId, access_token, refresh_token);
1701
+ this.handleSuccess(userId);
1644
1702
  break;
1645
1703
  case 'error':
1646
1704
  this.handleError(error);
@@ -1652,28 +1710,12 @@ class PortalUI {
1652
1710
  console.warn('[PortalUI] Received unhandled message type:', type);
1653
1711
  }
1654
1712
  }
1655
- handlePortalSuccess(userId, accessToken, refreshToken) {
1713
+ handlePortalSuccess(userId) {
1656
1714
  if (!userId) {
1657
1715
  console.error('[PortalUI] Missing userId in portal-success message');
1658
1716
  return;
1659
1717
  }
1660
1718
  console.log('[PortalUI] Portal success - User connected:', userId);
1661
- // If tokens are provided, store them internally
1662
- if (accessToken && refreshToken) {
1663
- const userToken = {
1664
- accessToken: accessToken,
1665
- refreshToken: refreshToken,
1666
- expiresIn: 3600, // Default to 1 hour
1667
- user_id: userId,
1668
- tokenType: 'Bearer',
1669
- scope: 'api:access',
1670
- };
1671
- this.userToken = userToken;
1672
- console.log('[PortalUI] Portal authentication successful');
1673
- }
1674
- else {
1675
- console.warn('[PortalUI] No tokens received from portal');
1676
- }
1677
1719
  // Pass userId to parent (SDK will handle tokens internally)
1678
1720
  this.options?.onSuccess?.(userId);
1679
1721
  }
@@ -1698,22 +1740,11 @@ class PortalUI {
1698
1740
  this.options.onEvent(data.type, data.data);
1699
1741
  }
1700
1742
  }
1701
- handleSuccess(userId, access_token, refresh_token) {
1702
- if (!userId || !access_token || !refresh_token) {
1743
+ handleSuccess(userId) {
1744
+ if (!userId) {
1703
1745
  console.error('[PortalUI] Missing required fields in success message');
1704
1746
  return;
1705
1747
  }
1706
- // Convert portal tokens to UserToken format
1707
- const userToken = {
1708
- accessToken: access_token,
1709
- refreshToken: refresh_token,
1710
- expiresIn: 3600, // Default to 1 hour
1711
- user_id: userId,
1712
- tokenType: 'Bearer',
1713
- scope: 'api:access',
1714
- };
1715
- // Store tokens internally
1716
- this.userToken = userToken;
1717
1748
  // Pass userId to parent
1718
1749
  this.options?.onSuccess?.(userId);
1719
1750
  }
@@ -1732,9 +1763,6 @@ class PortalUI {
1732
1763
  this.iframe.style.height = `${height}px`;
1733
1764
  }
1734
1765
  }
1735
- getTokens() {
1736
- return this.userToken;
1737
- }
1738
1766
  }
1739
1767
 
1740
1768
  /**
@@ -2074,109 +2102,6 @@ class MockDataProvider {
2074
2102
  };
2075
2103
  }
2076
2104
  // Portfolio & Trading Mocks
2077
- async mockGetHoldings() {
2078
- await this.simulateDelay();
2079
- const holdings = [
2080
- {
2081
- symbol: 'AAPL',
2082
- quantity: 100,
2083
- averagePrice: 150.25,
2084
- currentPrice: 175.5,
2085
- marketValue: 17550.0,
2086
- unrealizedPnL: 2525.0,
2087
- realizedPnL: 0,
2088
- costBasis: 15025.0,
2089
- currency: 'USD',
2090
- },
2091
- {
2092
- symbol: 'TSLA',
2093
- quantity: 50,
2094
- averagePrice: 200.0,
2095
- currentPrice: 220.75,
2096
- marketValue: 11037.5,
2097
- unrealizedPnL: 1037.5,
2098
- realizedPnL: 0,
2099
- costBasis: 10000.0,
2100
- currency: 'USD',
2101
- },
2102
- {
2103
- symbol: 'MSFT',
2104
- quantity: 75,
2105
- averagePrice: 300.0,
2106
- currentPrice: 325.25,
2107
- marketValue: 24393.75,
2108
- unrealizedPnL: 1893.75,
2109
- realizedPnL: 0,
2110
- costBasis: 22500.0,
2111
- currency: 'USD',
2112
- },
2113
- ];
2114
- return { data: holdings };
2115
- }
2116
- async mockGetPortfolio() {
2117
- await this.simulateDelay();
2118
- const portfolio = {
2119
- id: uuid.v4(),
2120
- name: 'Main Portfolio',
2121
- type: 'individual',
2122
- status: 'active',
2123
- cash: 15000.5,
2124
- buyingPower: 45000.0,
2125
- equity: 52981.25,
2126
- longMarketValue: 37981.25,
2127
- shortMarketValue: 0,
2128
- initialMargin: 0,
2129
- maintenanceMargin: 0,
2130
- lastEquity: 52000.0,
2131
- positions: [
2132
- {
2133
- symbol: 'AAPL',
2134
- quantity: 100,
2135
- averagePrice: 150.25,
2136
- currentPrice: 175.5,
2137
- marketValue: 17550.0,
2138
- unrealizedPnL: 2525.0,
2139
- realizedPnL: 0,
2140
- costBasis: 15025.0,
2141
- currency: 'USD',
2142
- },
2143
- {
2144
- symbol: 'TSLA',
2145
- quantity: 50,
2146
- averagePrice: 200.0,
2147
- currentPrice: 220.75,
2148
- marketValue: 11037.5,
2149
- unrealizedPnL: 1037.5,
2150
- realizedPnL: 0,
2151
- costBasis: 10000.0,
2152
- currency: 'USD',
2153
- },
2154
- {
2155
- symbol: 'MSFT',
2156
- quantity: 75,
2157
- averagePrice: 300.0,
2158
- currentPrice: 325.25,
2159
- marketValue: 24393.75,
2160
- unrealizedPnL: 1893.75,
2161
- realizedPnL: 0,
2162
- costBasis: 22500.0,
2163
- currency: 'USD',
2164
- },
2165
- ],
2166
- performance: {
2167
- totalReturn: 0.089,
2168
- dailyReturn: 0.002,
2169
- weeklyReturn: 0.015,
2170
- monthlyReturn: 0.045,
2171
- yearlyReturn: 0.089,
2172
- maxDrawdown: -0.05,
2173
- sharpeRatio: 1.2,
2174
- beta: 0.95,
2175
- alpha: 0.02,
2176
- },
2177
- };
2178
- return { data: portfolio };
2179
- }
2180
2105
  async mockGetOrders(filter) {
2181
2106
  await this.simulateDelay();
2182
2107
  const mockOrders = [
@@ -2237,6 +2162,52 @@ class MockDataProvider {
2237
2162
  data: filteredPositions,
2238
2163
  };
2239
2164
  }
2165
+ async mockGetBrokerBalances(filter) {
2166
+ await this.simulateDelay();
2167
+ // Determine how many balances to generate based on limit parameter
2168
+ const limit = filter?.limit || 100;
2169
+ const maxLimit = Math.min(limit, 1000); // Cap at 1000
2170
+ const mockBalances = [];
2171
+ for (let i = 0; i < maxLimit; i++) {
2172
+ const totalCashValue = Math.random() * 100000 + 10000; // $10k - $110k
2173
+ const netLiquidationValue = totalCashValue * (0.8 + Math.random() * 0.4); // ±20% variation
2174
+ const initialMargin = netLiquidationValue * 0.1; // 10% of net liquidation
2175
+ const maintenanceMargin = initialMargin * 0.8; // 80% of initial margin
2176
+ const availableToWithdraw = totalCashValue * 0.9; // 90% of cash available
2177
+ const totalRealizedPnl = (Math.random() - 0.5) * 10000; // -$5k to +$5k
2178
+ const balance = {
2179
+ id: `balance_${i + 1}`,
2180
+ account_id: `account_${Math.floor(Math.random() * 3) + 1}`,
2181
+ total_cash_value: totalCashValue,
2182
+ net_liquidation_value: netLiquidationValue,
2183
+ initial_margin: initialMargin,
2184
+ maintenance_margin: maintenanceMargin,
2185
+ available_to_withdraw: availableToWithdraw,
2186
+ total_realized_pnl: totalRealizedPnl,
2187
+ balance_created_at: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000).toISOString(),
2188
+ balance_updated_at: new Date().toISOString(),
2189
+ is_end_of_day_snapshot: Math.random() > 0.7, // 30% chance of being EOD snapshot
2190
+ raw_payload: {
2191
+ broker_specific_data: {
2192
+ margin_ratio: netLiquidationValue / initialMargin,
2193
+ day_trading_buying_power: availableToWithdraw * 4,
2194
+ overnight_buying_power: availableToWithdraw * 2,
2195
+ },
2196
+ },
2197
+ created_at: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString(),
2198
+ updated_at: new Date().toISOString(),
2199
+ };
2200
+ mockBalances.push(balance);
2201
+ }
2202
+ // Apply filters if provided
2203
+ let filteredBalances = mockBalances;
2204
+ if (filter) {
2205
+ filteredBalances = this.applyBrokerBalanceFilters(mockBalances, filter);
2206
+ }
2207
+ return {
2208
+ data: filteredBalances,
2209
+ };
2210
+ }
2240
2211
  async mockGetBrokerDataAccounts(filter) {
2241
2212
  await this.simulateDelay();
2242
2213
  // Determine how many accounts to generate based on limit parameter
@@ -2289,8 +2260,8 @@ class MockDataProvider {
2289
2260
  /**
2290
2261
  * Get stored user token
2291
2262
  */
2292
- getUserToken(userId) {
2293
- return this.userTokens.get(userId);
2263
+ getUserToken(sessionId) {
2264
+ return this.userTokens.get(sessionId);
2294
2265
  }
2295
2266
  /**
2296
2267
  * Clear all stored data
@@ -2382,6 +2353,19 @@ class MockDataProvider {
2382
2353
  return true;
2383
2354
  });
2384
2355
  }
2356
+ applyBrokerBalanceFilters(balances, filter) {
2357
+ return balances.filter(balance => {
2358
+ if (filter.account_id && balance.account_id !== filter.account_id)
2359
+ return false;
2360
+ if (filter.is_end_of_day_snapshot !== undefined && balance.is_end_of_day_snapshot !== filter.is_end_of_day_snapshot)
2361
+ return false;
2362
+ if (filter.balance_created_after && balance.balance_created_at && new Date(balance.balance_created_at) < new Date(filter.balance_created_after))
2363
+ return false;
2364
+ if (filter.balance_created_before && balance.balance_created_at && new Date(balance.balance_created_at) > new Date(filter.balance_created_before))
2365
+ return false;
2366
+ return true;
2367
+ });
2368
+ }
2385
2369
  /**
2386
2370
  * Generate mock orders with diverse data
2387
2371
  */
@@ -2793,18 +2777,10 @@ class MockApiClient {
2793
2777
  return this.mockDataProvider.mockCompletePortalSession(sessionId);
2794
2778
  }
2795
2779
  // Portfolio Management
2796
- async getHoldings(filter) {
2797
- await this.getValidAccessToken();
2798
- return this.mockDataProvider.mockGetHoldings();
2799
- }
2800
2780
  async getOrders(filter) {
2801
2781
  await this.getValidAccessToken();
2802
2782
  return this.mockDataProvider.mockGetOrders(filter);
2803
2783
  }
2804
- async getPortfolio() {
2805
- await this.getValidAccessToken();
2806
- return this.mockDataProvider.mockGetPortfolio();
2807
- }
2808
2784
  async placeOrder(order) {
2809
2785
  await this.getValidAccessToken();
2810
2786
  await this.mockDataProvider.mockPlaceOrder(order);
@@ -2819,7 +2795,7 @@ class MockApiClient {
2819
2795
  paramsBroker: params.broker,
2820
2796
  contextBroker: this.tradingContext.broker,
2821
2797
  paramsAccountNumber: params.accountNumber,
2822
- contextAccountNumber: this.tradingContext.accountNumber
2798
+ contextAccountNumber: this.tradingContext.accountNumber,
2823
2799
  });
2824
2800
  const fullParams = {
2825
2801
  broker: (params.broker || this.tradingContext.broker) ||
@@ -2882,7 +2858,7 @@ class MockApiClient {
2882
2858
  console.log('MockApiClient.setAccount Debug:', {
2883
2859
  accountNumber,
2884
2860
  accountId,
2885
- previousContext: { ...this.tradingContext }
2861
+ previousContext: { ...this.tradingContext },
2886
2862
  });
2887
2863
  this.tradingContext.accountNumber = accountNumber;
2888
2864
  this.tradingContext.accountId = accountId;
@@ -3015,12 +2991,8 @@ class MockApiClient {
3015
2991
  price,
3016
2992
  }, extras);
3017
2993
  }
3018
- async revokeToken(accessToken) {
3019
- // Clear tokens on revoke
3020
- this.clearTokens();
3021
- }
3022
- async getUserToken(userId) {
3023
- const token = this.mockDataProvider.getUserToken(userId);
2994
+ async getUserToken(sessionId) {
2995
+ const token = this.mockDataProvider.getUserToken(sessionId);
3024
2996
  if (!token) {
3025
2997
  throw new AuthenticationError('User token not found');
3026
2998
  }
@@ -3031,7 +3003,7 @@ class MockApiClient {
3031
3003
  }
3032
3004
  // Broker Data Management
3033
3005
  async getBrokerList() {
3034
- await this.getValidAccessToken();
3006
+ // Public in mock mode as well - no auth required
3035
3007
  return this.mockDataProvider.mockGetBrokerList();
3036
3008
  }
3037
3009
  async getBrokerAccounts(options) {
@@ -3069,6 +3041,9 @@ class MockApiClient {
3069
3041
  async getBrokerPositionsWithFilter(filter) {
3070
3042
  return this.mockDataProvider.mockGetBrokerPositions(filter);
3071
3043
  }
3044
+ async getBrokerBalancesWithFilter(filter) {
3045
+ return this.mockDataProvider.mockGetBrokerBalances(filter);
3046
+ }
3072
3047
  async getBrokerDataAccountsWithFilter(filter) {
3073
3048
  return this.mockDataProvider.mockGetBrokerDataAccounts(filter);
3074
3049
  }
@@ -3163,6 +3138,36 @@ class MockApiClient {
3163
3138
  limit: perPage,
3164
3139
  }, navigationCallback);
3165
3140
  }
3141
+ async getBrokerBalancesPage(page = 1, perPage = 100, filters) {
3142
+ const mockBalances = await this.mockDataProvider.mockGetBrokerBalances(filters);
3143
+ const balances = mockBalances.data;
3144
+ // Simulate pagination
3145
+ const startIndex = (page - 1) * perPage;
3146
+ const endIndex = startIndex + perPage;
3147
+ const paginatedBalances = balances.slice(startIndex, endIndex);
3148
+ const hasMore = endIndex < balances.length;
3149
+ const nextOffset = hasMore ? endIndex : startIndex;
3150
+ // Create navigation callback for mock pagination
3151
+ const navigationCallback = async (newOffset, newLimit) => {
3152
+ const newStartIndex = newOffset;
3153
+ const newEndIndex = newStartIndex + newLimit;
3154
+ const newPaginatedBalances = balances.slice(newStartIndex, newEndIndex);
3155
+ const newHasMore = newEndIndex < balances.length;
3156
+ const newNextOffset = newHasMore ? newEndIndex : newStartIndex;
3157
+ return new PaginatedResult(newPaginatedBalances, {
3158
+ has_more: newHasMore,
3159
+ next_offset: newNextOffset,
3160
+ current_offset: newStartIndex,
3161
+ limit: newLimit,
3162
+ }, navigationCallback);
3163
+ };
3164
+ return new PaginatedResult(paginatedBalances, {
3165
+ has_more: hasMore,
3166
+ next_offset: nextOffset,
3167
+ current_offset: startIndex,
3168
+ limit: perPage,
3169
+ }, navigationCallback);
3170
+ }
3166
3171
  async getBrokerConnections() {
3167
3172
  await this.getValidAccessToken();
3168
3173
  return this.mockDataProvider.mockGetBrokerConnections();
@@ -3401,9 +3406,135 @@ const darkTheme = {
3401
3406
  },
3402
3407
  },
3403
3408
  },
3409
+ typography: {
3410
+ fontFamily: {
3411
+ primary: 'Orbitron, Futura, Inter, system-ui, sans-serif',
3412
+ secondary: 'Orbitron, Futura, Inter, system-ui, sans-serif',
3413
+ },
3414
+ fontSize: {
3415
+ xs: '0.75rem',
3416
+ sm: '0.875rem',
3417
+ base: '1rem',
3418
+ lg: '1.125rem',
3419
+ xl: '1.25rem',
3420
+ '2xl': '1.5rem',
3421
+ '3xl': '1.875rem',
3422
+ '4xl': '2.25rem',
3423
+ },
3424
+ fontWeight: {
3425
+ normal: 400,
3426
+ medium: 500,
3427
+ semibold: 600,
3428
+ bold: 700,
3429
+ extrabold: 800,
3430
+ },
3431
+ lineHeight: {
3432
+ tight: '1.25',
3433
+ normal: '1.5',
3434
+ relaxed: '1.75',
3435
+ },
3436
+ },
3437
+ spacing: {
3438
+ xs: '0.25rem',
3439
+ sm: '0.5rem',
3440
+ md: '1rem',
3441
+ lg: '1.5rem',
3442
+ xl: '2rem',
3443
+ '2xl': '3rem',
3444
+ '3xl': '4rem',
3445
+ },
3446
+ layout: {
3447
+ containerMaxWidth: '1440px',
3448
+ gridGap: '1rem',
3449
+ cardPadding: '1.5rem',
3450
+ borderRadius: {
3451
+ sm: '0.25rem',
3452
+ md: '0.5rem',
3453
+ lg: '0.75rem',
3454
+ xl: '1rem',
3455
+ '2xl': '1.5rem',
3456
+ full: '9999px',
3457
+ },
3458
+ },
3459
+ components: {
3460
+ brokerCard: {
3461
+ width: '100%',
3462
+ height: '180px',
3463
+ logoSize: '64px',
3464
+ padding: '1.5rem',
3465
+ },
3466
+ statusIndicator: {
3467
+ size: '22px',
3468
+ glowIntensity: 0.9,
3469
+ },
3470
+ modal: {
3471
+ background: 'rgba(0, 0, 0, 0.95)',
3472
+ backdrop: 'rgba(0, 0, 0, 0.8)',
3473
+ },
3474
+ brokerCardModern: {
3475
+ width: '150px',
3476
+ height: '150px',
3477
+ padding: '16px',
3478
+ logoSize: '48px',
3479
+ statusSize: '22px',
3480
+ },
3481
+ connectButton: {
3482
+ width: '120px',
3483
+ height: '120px',
3484
+ },
3485
+ themeSwitcher: {
3486
+ indicatorSize: '24px',
3487
+ },
3488
+ },
3489
+ effects: {
3490
+ glassmorphism: {
3491
+ enabled: true,
3492
+ blur: '12px',
3493
+ opacity: 0.15,
3494
+ border: 'rgba(0, 255, 255, 0.3)',
3495
+ },
3496
+ animations: {
3497
+ enabled: true,
3498
+ duration: {
3499
+ fast: '150ms',
3500
+ normal: '300ms',
3501
+ slow: '500ms',
3502
+ },
3503
+ easing: {
3504
+ default: 'cubic-bezier(0.4, 0, 0.2, 1)',
3505
+ smooth: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
3506
+ bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
3507
+ },
3508
+ },
3509
+ shadows: {
3510
+ sm: '0 1px 2px rgba(0, 0, 0, 0.5)',
3511
+ md: '0 4px 6px rgba(0, 0, 0, 0.6)',
3512
+ lg: '0 10px 15px rgba(0, 0, 0, 0.7)',
3513
+ xl: '0 20px 25px rgba(0, 0, 0, 0.8)',
3514
+ card: '0px 4px 12px rgba(0, 0, 0, 0.6), 0 0 20px rgba(0, 255, 255, 0.2)',
3515
+ cardHover: '0px 4px 24px rgba(0, 255, 255, 0.3), 0 0 30px rgba(0, 255, 255, 0.25)',
3516
+ glow: '0 0 20px rgba(0, 255, 255, 0.8)',
3517
+ focus: '0 0 0 2px #00FFFF, 0 0 8px 2px rgba(0, 255, 255, 0.8)',
3518
+ },
3519
+ },
3404
3520
  branding: {
3405
3521
  primaryColor: '#00FFFF',
3406
3522
  },
3523
+ glow: {
3524
+ primary: 'rgba(0, 255, 255, 0.4)',
3525
+ secondary: 'rgba(0, 255, 255, 0.6)',
3526
+ card: 'rgba(0, 255, 255, 0.2)',
3527
+ cardHover: 'rgba(0, 255, 255, 0.3)',
3528
+ button: 'rgba(0, 255, 255, 0.6)',
3529
+ focus: 'rgba(0, 255, 255, 0.8)',
3530
+ scrollbar: 'rgba(0, 255, 255, 0.4)',
3531
+ },
3532
+ gradients: {
3533
+ start: 'rgba(0, 255, 255, 0.08)',
3534
+ end: 'rgba(0, 255, 255, 0.03)',
3535
+ hoverStart: 'rgba(0, 255, 255, 0.15)',
3536
+ hoverEnd: 'rgba(0, 255, 255, 0.08)',
3537
+ },
3407
3538
  };
3408
3539
  // Light theme with cyan accents
3409
3540
  const lightTheme = {
@@ -3460,16 +3591,128 @@ const lightTheme = {
3460
3591
  },
3461
3592
  },
3462
3593
  },
3463
- branding: {
3464
- primaryColor: '#00FFFF',
3594
+ typography: {
3595
+ fontFamily: {
3596
+ primary: 'Orbitron, Futura, Inter, system-ui, sans-serif',
3597
+ secondary: 'Orbitron, Futura, Inter, system-ui, sans-serif',
3598
+ },
3599
+ fontSize: {
3600
+ xs: '0.75rem',
3601
+ sm: '0.875rem',
3602
+ base: '1rem',
3603
+ lg: '1.125rem',
3604
+ xl: '1.25rem',
3605
+ '2xl': '1.5rem',
3606
+ '3xl': '1.875rem',
3607
+ '4xl': '2.25rem',
3608
+ },
3609
+ fontWeight: {
3610
+ normal: 400,
3611
+ medium: 500,
3612
+ semibold: 600,
3613
+ bold: 700,
3614
+ extrabold: 800,
3615
+ },
3616
+ lineHeight: {
3617
+ tight: '1.25',
3618
+ normal: '1.5',
3619
+ relaxed: '1.75',
3620
+ },
3465
3621
  },
3466
- };
3467
- // Corporate blue theme
3468
- const corporateBlueTheme = {
3469
- mode: 'dark',
3470
- colors: {
3471
- background: {
3472
- primary: '#1E293B',
3622
+ spacing: {
3623
+ xs: '0.25rem',
3624
+ sm: '0.5rem',
3625
+ md: '1rem',
3626
+ lg: '1.5rem',
3627
+ xl: '2rem',
3628
+ '2xl': '3rem',
3629
+ '3xl': '4rem',
3630
+ },
3631
+ layout: {
3632
+ containerMaxWidth: '1440px',
3633
+ gridGap: '1rem',
3634
+ cardPadding: '1.5rem',
3635
+ borderRadius: {
3636
+ sm: '0.25rem',
3637
+ md: '0.5rem',
3638
+ lg: '0.75rem',
3639
+ xl: '1rem',
3640
+ '2xl': '1.5rem',
3641
+ full: '9999px',
3642
+ },
3643
+ },
3644
+ components: {
3645
+ brokerCard: {
3646
+ width: '100%',
3647
+ height: '180px',
3648
+ logoSize: '64px',
3649
+ padding: '1.5rem',
3650
+ },
3651
+ statusIndicator: {
3652
+ size: '22px',
3653
+ glowIntensity: 0.9,
3654
+ },
3655
+ modal: {
3656
+ background: 'rgba(255, 255, 255, 0.95)',
3657
+ backdrop: 'rgba(0, 0, 0, 0.5)',
3658
+ },
3659
+ },
3660
+ effects: {
3661
+ glassmorphism: {
3662
+ enabled: true,
3663
+ blur: '12px',
3664
+ opacity: 0.15,
3665
+ border: 'rgba(0, 255, 255, 0.3)',
3666
+ },
3667
+ animations: {
3668
+ enabled: true,
3669
+ duration: {
3670
+ fast: '150ms',
3671
+ normal: '300ms',
3672
+ slow: '500ms',
3673
+ },
3674
+ easing: {
3675
+ default: 'cubic-bezier(0.4, 0, 0.2, 1)',
3676
+ smooth: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
3677
+ bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
3678
+ },
3679
+ },
3680
+ shadows: {
3681
+ sm: '0 1px 2px rgba(0, 0, 0, 0.1)',
3682
+ md: '0 4px 6px rgba(0, 0, 0, 0.1)',
3683
+ lg: '0 10px 15px rgba(0, 0, 0, 0.1)',
3684
+ xl: '0 20px 25px rgba(0, 0, 0, 0.1)',
3685
+ card: '0px 4px 12px rgba(0, 0, 0, 0.1), 0 0 20px rgba(0, 255, 255, 0.2)',
3686
+ cardHover: '0px 4px 24px rgba(0, 255, 255, 0.3), 0 0 30px rgba(0, 255, 255, 0.25)',
3687
+ glow: '0 0 20px rgba(0, 255, 255, 0.8)',
3688
+ focus: '0 0 0 2px #00FFFF, 0 0 8px 2px rgba(0, 255, 255, 0.8)',
3689
+ },
3690
+ },
3691
+ branding: {
3692
+ primaryColor: '#00FFFF',
3693
+ },
3694
+ glow: {
3695
+ primary: 'rgba(0, 255, 255, 0.4)',
3696
+ secondary: 'rgba(0, 255, 255, 0.6)',
3697
+ card: 'rgba(0, 255, 255, 0.2)',
3698
+ cardHover: 'rgba(0, 255, 255, 0.3)',
3699
+ button: 'rgba(0, 255, 255, 0.6)',
3700
+ focus: 'rgba(0, 255, 255, 0.8)',
3701
+ scrollbar: 'rgba(0, 255, 255, 0.4)',
3702
+ },
3703
+ gradients: {
3704
+ start: 'rgba(0, 255, 255, 0.08)',
3705
+ end: 'rgba(0, 255, 255, 0.03)',
3706
+ hoverStart: 'rgba(0, 255, 255, 0.15)',
3707
+ hoverEnd: 'rgba(0, 255, 255, 0.08)',
3708
+ },
3709
+ };
3710
+ // Corporate blue theme
3711
+ const corporateBlueTheme = {
3712
+ mode: 'dark',
3713
+ colors: {
3714
+ background: {
3715
+ primary: '#1E293B',
3473
3716
  secondary: '#334155',
3474
3717
  tertiary: '#475569',
3475
3718
  accent: 'rgba(59, 130, 246, 0.1)',
@@ -3519,9 +3762,135 @@ const corporateBlueTheme = {
3519
3762
  },
3520
3763
  },
3521
3764
  },
3765
+ typography: {
3766
+ fontFamily: {
3767
+ primary: 'Orbitron, Futura, Inter, system-ui, sans-serif',
3768
+ secondary: 'Orbitron, Futura, Inter, system-ui, sans-serif',
3769
+ },
3770
+ fontSize: {
3771
+ xs: '0.75rem',
3772
+ sm: '0.875rem',
3773
+ base: '1rem',
3774
+ lg: '1.125rem',
3775
+ xl: '1.25rem',
3776
+ '2xl': '1.5rem',
3777
+ '3xl': '1.875rem',
3778
+ '4xl': '2.25rem',
3779
+ },
3780
+ fontWeight: {
3781
+ normal: 400,
3782
+ medium: 500,
3783
+ semibold: 600,
3784
+ bold: 700,
3785
+ extrabold: 800,
3786
+ },
3787
+ lineHeight: {
3788
+ tight: '1.25',
3789
+ normal: '1.5',
3790
+ relaxed: '1.75',
3791
+ },
3792
+ },
3793
+ spacing: {
3794
+ xs: '0.25rem',
3795
+ sm: '0.5rem',
3796
+ md: '1rem',
3797
+ lg: '1.5rem',
3798
+ xl: '2rem',
3799
+ '2xl': '3rem',
3800
+ '3xl': '4rem',
3801
+ },
3802
+ layout: {
3803
+ containerMaxWidth: '1440px',
3804
+ gridGap: '1rem',
3805
+ cardPadding: '1.5rem',
3806
+ borderRadius: {
3807
+ sm: '0.25rem',
3808
+ md: '0.5rem',
3809
+ lg: '0.75rem',
3810
+ xl: '1rem',
3811
+ '2xl': '1.5rem',
3812
+ full: '9999px',
3813
+ },
3814
+ },
3815
+ components: {
3816
+ brokerCard: {
3817
+ width: '100%',
3818
+ height: '180px',
3819
+ logoSize: '64px',
3820
+ padding: '1.5rem',
3821
+ },
3822
+ statusIndicator: {
3823
+ size: '22px',
3824
+ glowIntensity: 0.9,
3825
+ },
3826
+ modal: {
3827
+ background: 'rgba(0, 0, 0, 0.95)',
3828
+ backdrop: 'rgba(0, 0, 0, 0.8)',
3829
+ },
3830
+ brokerCardModern: {
3831
+ width: '150px',
3832
+ height: '150px',
3833
+ padding: '16px',
3834
+ logoSize: '48px',
3835
+ statusSize: '22px',
3836
+ },
3837
+ connectButton: {
3838
+ width: '120px',
3839
+ height: '120px',
3840
+ },
3841
+ themeSwitcher: {
3842
+ indicatorSize: '24px',
3843
+ },
3844
+ },
3845
+ effects: {
3846
+ glassmorphism: {
3847
+ enabled: true,
3848
+ blur: '12px',
3849
+ opacity: 0.15,
3850
+ border: 'rgba(59, 130, 246, 0.3)',
3851
+ },
3852
+ animations: {
3853
+ enabled: true,
3854
+ duration: {
3855
+ fast: '150ms',
3856
+ normal: '300ms',
3857
+ slow: '500ms',
3858
+ },
3859
+ easing: {
3860
+ default: 'cubic-bezier(0.4, 0, 0.2, 1)',
3861
+ smooth: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
3862
+ bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
3863
+ },
3864
+ },
3865
+ shadows: {
3866
+ sm: '0 1px 2px rgba(0, 0, 0, 0.5)',
3867
+ md: '0 4px 6px rgba(0, 0, 0, 0.6)',
3868
+ lg: '0 10px 15px rgba(0, 0, 0, 0.7)',
3869
+ xl: '0 20px 25px rgba(0, 0, 0, 0.8)',
3870
+ card: '0px 4px 12px rgba(0, 0, 0, 0.6), 0 0 20px rgba(59, 130, 246, 0.2)',
3871
+ cardHover: '0px 4px 24px rgba(59, 130, 246, 0.3), 0 0 30px rgba(59, 130, 246, 0.25)',
3872
+ glow: '0 0 20px rgba(59, 130, 246, 0.8)',
3873
+ focus: '0 0 0 2px #3B82F6, 0 0 8px 2px rgba(59, 130, 246, 0.8)',
3874
+ },
3875
+ },
3522
3876
  branding: {
3523
3877
  primaryColor: '#3B82F6',
3524
3878
  },
3879
+ glow: {
3880
+ primary: 'rgba(59, 130, 246, 0.4)',
3881
+ secondary: 'rgba(59, 130, 246, 0.6)',
3882
+ card: 'rgba(59, 130, 246, 0.2)',
3883
+ cardHover: 'rgba(59, 130, 246, 0.3)',
3884
+ button: 'rgba(59, 130, 246, 0.6)',
3885
+ focus: 'rgba(59, 130, 246, 0.8)',
3886
+ scrollbar: 'rgba(59, 130, 246, 0.4)',
3887
+ },
3888
+ gradients: {
3889
+ start: 'rgba(59, 130, 246, 0.08)',
3890
+ end: 'rgba(59, 130, 246, 0.03)',
3891
+ hoverStart: 'rgba(59, 130, 246, 0.15)',
3892
+ hoverEnd: 'rgba(59, 130, 246, 0.08)',
3893
+ },
3525
3894
  };
3526
3895
  // Purple theme
3527
3896
  const purpleTheme = {
@@ -3578,9 +3947,135 @@ const purpleTheme = {
3578
3947
  },
3579
3948
  },
3580
3949
  },
3950
+ typography: {
3951
+ fontFamily: {
3952
+ primary: 'Orbitron, Futura, Inter, system-ui, sans-serif',
3953
+ secondary: 'Orbitron, Futura, Inter, system-ui, sans-serif',
3954
+ },
3955
+ fontSize: {
3956
+ xs: '0.75rem',
3957
+ sm: '0.875rem',
3958
+ base: '1rem',
3959
+ lg: '1.125rem',
3960
+ xl: '1.25rem',
3961
+ '2xl': '1.5rem',
3962
+ '3xl': '1.875rem',
3963
+ '4xl': '2.25rem',
3964
+ },
3965
+ fontWeight: {
3966
+ normal: 400,
3967
+ medium: 500,
3968
+ semibold: 600,
3969
+ bold: 700,
3970
+ extrabold: 800,
3971
+ },
3972
+ lineHeight: {
3973
+ tight: '1.25',
3974
+ normal: '1.5',
3975
+ relaxed: '1.75',
3976
+ },
3977
+ },
3978
+ spacing: {
3979
+ xs: '0.25rem',
3980
+ sm: '0.5rem',
3981
+ md: '1rem',
3982
+ lg: '1.5rem',
3983
+ xl: '2rem',
3984
+ '2xl': '3rem',
3985
+ '3xl': '4rem',
3986
+ },
3987
+ layout: {
3988
+ containerMaxWidth: '1440px',
3989
+ gridGap: '1rem',
3990
+ cardPadding: '1.5rem',
3991
+ borderRadius: {
3992
+ sm: '0.25rem',
3993
+ md: '0.5rem',
3994
+ lg: '0.75rem',
3995
+ xl: '1rem',
3996
+ '2xl': '1.5rem',
3997
+ full: '9999px',
3998
+ },
3999
+ },
4000
+ components: {
4001
+ brokerCard: {
4002
+ width: '100%',
4003
+ height: '180px',
4004
+ logoSize: '64px',
4005
+ padding: '1.5rem',
4006
+ },
4007
+ statusIndicator: {
4008
+ size: '22px',
4009
+ glowIntensity: 0.9,
4010
+ },
4011
+ modal: {
4012
+ background: 'rgba(0, 0, 0, 0.95)',
4013
+ backdrop: 'rgba(0, 0, 0, 0.8)',
4014
+ },
4015
+ brokerCardModern: {
4016
+ width: '150px',
4017
+ height: '150px',
4018
+ padding: '16px',
4019
+ logoSize: '48px',
4020
+ statusSize: '22px',
4021
+ },
4022
+ connectButton: {
4023
+ width: '120px',
4024
+ height: '120px',
4025
+ },
4026
+ themeSwitcher: {
4027
+ indicatorSize: '24px',
4028
+ },
4029
+ },
4030
+ effects: {
4031
+ glassmorphism: {
4032
+ enabled: true,
4033
+ blur: '12px',
4034
+ opacity: 0.15,
4035
+ border: 'rgba(168, 85, 247, 0.3)',
4036
+ },
4037
+ animations: {
4038
+ enabled: true,
4039
+ duration: {
4040
+ fast: '150ms',
4041
+ normal: '300ms',
4042
+ slow: '500ms',
4043
+ },
4044
+ easing: {
4045
+ default: 'cubic-bezier(0.4, 0, 0.2, 1)',
4046
+ smooth: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
4047
+ bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
4048
+ },
4049
+ },
4050
+ shadows: {
4051
+ sm: '0 1px 2px rgba(0, 0, 0, 0.5)',
4052
+ md: '0 4px 6px rgba(0, 0, 0, 0.6)',
4053
+ lg: '0 10px 15px rgba(0, 0, 0, 0.7)',
4054
+ xl: '0 20px 25px rgba(0, 0, 0, 0.8)',
4055
+ card: '0px 4px 12px rgba(0, 0, 0, 0.6), 0 0 20px rgba(168, 85, 247, 0.2)',
4056
+ cardHover: '0px 4px 24px rgba(168, 85, 247, 0.3), 0 0 30px rgba(168, 85, 247, 0.25)',
4057
+ glow: '0 0 20px rgba(168, 85, 247, 0.8)',
4058
+ focus: '0 0 0 2px #A855F7, 0 0 8px 2px rgba(168, 85, 247, 0.8)',
4059
+ },
4060
+ },
3581
4061
  branding: {
3582
4062
  primaryColor: '#A855F7',
3583
4063
  },
4064
+ glow: {
4065
+ primary: 'rgba(168, 85, 247, 0.4)',
4066
+ secondary: 'rgba(168, 85, 247, 0.6)',
4067
+ card: 'rgba(168, 85, 247, 0.2)',
4068
+ cardHover: 'rgba(168, 85, 247, 0.3)',
4069
+ button: 'rgba(168, 85, 247, 0.6)',
4070
+ focus: 'rgba(168, 85, 247, 0.8)',
4071
+ scrollbar: 'rgba(168, 85, 247, 0.4)',
4072
+ },
4073
+ gradients: {
4074
+ start: 'rgba(168, 85, 247, 0.08)',
4075
+ end: 'rgba(168, 85, 247, 0.03)',
4076
+ hoverStart: 'rgba(168, 85, 247, 0.15)',
4077
+ hoverEnd: 'rgba(168, 85, 247, 0.08)',
4078
+ },
3584
4079
  };
3585
4080
  // Green theme
3586
4081
  const greenTheme = {
@@ -3637,9 +4132,135 @@ const greenTheme = {
3637
4132
  },
3638
4133
  },
3639
4134
  },
4135
+ typography: {
4136
+ fontFamily: {
4137
+ primary: 'Orbitron, Futura, Inter, system-ui, sans-serif',
4138
+ secondary: 'Orbitron, Futura, Inter, system-ui, sans-serif',
4139
+ },
4140
+ fontSize: {
4141
+ xs: '0.75rem',
4142
+ sm: '0.875rem',
4143
+ base: '1rem',
4144
+ lg: '1.125rem',
4145
+ xl: '1.25rem',
4146
+ '2xl': '1.5rem',
4147
+ '3xl': '1.875rem',
4148
+ '4xl': '2.25rem',
4149
+ },
4150
+ fontWeight: {
4151
+ normal: 400,
4152
+ medium: 500,
4153
+ semibold: 600,
4154
+ bold: 700,
4155
+ extrabold: 800,
4156
+ },
4157
+ lineHeight: {
4158
+ tight: '1.25',
4159
+ normal: '1.5',
4160
+ relaxed: '1.75',
4161
+ },
4162
+ },
4163
+ spacing: {
4164
+ xs: '0.25rem',
4165
+ sm: '0.5rem',
4166
+ md: '1rem',
4167
+ lg: '1.5rem',
4168
+ xl: '2rem',
4169
+ '2xl': '3rem',
4170
+ '3xl': '4rem',
4171
+ },
4172
+ layout: {
4173
+ containerMaxWidth: '1440px',
4174
+ gridGap: '1rem',
4175
+ cardPadding: '1.5rem',
4176
+ borderRadius: {
4177
+ sm: '0.25rem',
4178
+ md: '0.5rem',
4179
+ lg: '0.75rem',
4180
+ xl: '1rem',
4181
+ '2xl': '1.5rem',
4182
+ full: '9999px',
4183
+ },
4184
+ },
4185
+ components: {
4186
+ brokerCard: {
4187
+ width: '100%',
4188
+ height: '180px',
4189
+ logoSize: '64px',
4190
+ padding: '1.5rem',
4191
+ },
4192
+ statusIndicator: {
4193
+ size: '22px',
4194
+ glowIntensity: 0.9,
4195
+ },
4196
+ modal: {
4197
+ background: 'rgba(0, 0, 0, 0.95)',
4198
+ backdrop: 'rgba(0, 0, 0, 0.8)',
4199
+ },
4200
+ brokerCardModern: {
4201
+ width: '150px',
4202
+ height: '150px',
4203
+ padding: '16px',
4204
+ logoSize: '48px',
4205
+ statusSize: '22px',
4206
+ },
4207
+ connectButton: {
4208
+ width: '120px',
4209
+ height: '120px',
4210
+ },
4211
+ themeSwitcher: {
4212
+ indicatorSize: '24px',
4213
+ },
4214
+ },
4215
+ effects: {
4216
+ glassmorphism: {
4217
+ enabled: true,
4218
+ blur: '12px',
4219
+ opacity: 0.15,
4220
+ border: 'rgba(34, 197, 94, 0.3)',
4221
+ },
4222
+ animations: {
4223
+ enabled: true,
4224
+ duration: {
4225
+ fast: '150ms',
4226
+ normal: '300ms',
4227
+ slow: '500ms',
4228
+ },
4229
+ easing: {
4230
+ default: 'cubic-bezier(0.4, 0, 0.2, 1)',
4231
+ smooth: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
4232
+ bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
4233
+ },
4234
+ },
4235
+ shadows: {
4236
+ sm: '0 1px 2px rgba(0, 0, 0, 0.5)',
4237
+ md: '0 4px 6px rgba(0, 0, 0, 0.6)',
4238
+ lg: '0 10px 15px rgba(0, 0, 0, 0.7)',
4239
+ xl: '0 20px 25px rgba(0, 0, 0, 0.8)',
4240
+ card: '0px 4px 12px rgba(0, 0, 0, 0.6), 0 0 20px rgba(34, 197, 94, 0.2)',
4241
+ cardHover: '0px 4px 24px rgba(34, 197, 94, 0.3), 0 0 30px rgba(34, 197, 94, 0.25)',
4242
+ glow: '0 0 20px rgba(34, 197, 94, 0.8)',
4243
+ focus: '0 0 0 2px #22C55E, 0 0 8px 2px rgba(34, 197, 94, 0.8)',
4244
+ },
4245
+ },
3640
4246
  branding: {
3641
4247
  primaryColor: '#22C55E',
3642
4248
  },
4249
+ glow: {
4250
+ primary: 'rgba(34, 197, 94, 0.4)',
4251
+ secondary: 'rgba(34, 197, 94, 0.6)',
4252
+ card: 'rgba(34, 197, 94, 0.2)',
4253
+ cardHover: 'rgba(34, 197, 94, 0.3)',
4254
+ button: 'rgba(34, 197, 94, 0.6)',
4255
+ focus: 'rgba(34, 197, 94, 0.8)',
4256
+ scrollbar: 'rgba(34, 197, 94, 0.4)',
4257
+ },
4258
+ gradients: {
4259
+ start: 'rgba(34, 197, 94, 0.08)',
4260
+ end: 'rgba(34, 197, 94, 0.03)',
4261
+ hoverStart: 'rgba(34, 197, 94, 0.15)',
4262
+ hoverEnd: 'rgba(34, 197, 94, 0.08)',
4263
+ },
3643
4264
  };
3644
4265
  // Orange theme
3645
4266
  const orangeTheme = {
@@ -3696,9 +4317,325 @@ const orangeTheme = {
3696
4317
  },
3697
4318
  },
3698
4319
  },
4320
+ typography: {
4321
+ fontFamily: {
4322
+ primary: 'Orbitron, Futura, Inter, system-ui, sans-serif',
4323
+ secondary: 'Orbitron, Futura, Inter, system-ui, sans-serif',
4324
+ },
4325
+ fontSize: {
4326
+ xs: '0.75rem',
4327
+ sm: '0.875rem',
4328
+ base: '1rem',
4329
+ lg: '1.125rem',
4330
+ xl: '1.25rem',
4331
+ '2xl': '1.5rem',
4332
+ '3xl': '1.875rem',
4333
+ '4xl': '2.25rem',
4334
+ },
4335
+ fontWeight: {
4336
+ normal: 400,
4337
+ medium: 500,
4338
+ semibold: 600,
4339
+ bold: 700,
4340
+ extrabold: 800,
4341
+ },
4342
+ lineHeight: {
4343
+ tight: '1.25',
4344
+ normal: '1.5',
4345
+ relaxed: '1.75',
4346
+ },
4347
+ },
4348
+ spacing: {
4349
+ xs: '0.25rem',
4350
+ sm: '0.5rem',
4351
+ md: '1rem',
4352
+ lg: '1.5rem',
4353
+ xl: '2rem',
4354
+ '2xl': '3rem',
4355
+ '3xl': '4rem',
4356
+ },
4357
+ layout: {
4358
+ containerMaxWidth: '1440px',
4359
+ gridGap: '1rem',
4360
+ cardPadding: '1.5rem',
4361
+ borderRadius: {
4362
+ sm: '0.25rem',
4363
+ md: '0.5rem',
4364
+ lg: '0.75rem',
4365
+ xl: '1rem',
4366
+ '2xl': '1.5rem',
4367
+ full: '9999px',
4368
+ },
4369
+ },
4370
+ components: {
4371
+ brokerCard: {
4372
+ width: '100%',
4373
+ height: '180px',
4374
+ logoSize: '64px',
4375
+ padding: '1.5rem',
4376
+ },
4377
+ statusIndicator: {
4378
+ size: '22px',
4379
+ glowIntensity: 0.9,
4380
+ },
4381
+ modal: {
4382
+ background: 'rgba(0, 0, 0, 0.95)',
4383
+ backdrop: 'rgba(0, 0, 0, 0.8)',
4384
+ },
4385
+ brokerCardModern: {
4386
+ width: '150px',
4387
+ height: '150px',
4388
+ padding: '16px',
4389
+ logoSize: '48px',
4390
+ statusSize: '22px',
4391
+ },
4392
+ connectButton: {
4393
+ width: '120px',
4394
+ height: '120px',
4395
+ },
4396
+ themeSwitcher: {
4397
+ indicatorSize: '24px',
4398
+ },
4399
+ },
4400
+ effects: {
4401
+ glassmorphism: {
4402
+ enabled: true,
4403
+ blur: '12px',
4404
+ opacity: 0.15,
4405
+ border: 'rgba(249, 115, 22, 0.3)',
4406
+ },
4407
+ animations: {
4408
+ enabled: true,
4409
+ duration: {
4410
+ fast: '150ms',
4411
+ normal: '300ms',
4412
+ slow: '500ms',
4413
+ },
4414
+ easing: {
4415
+ default: 'cubic-bezier(0.4, 0, 0.2, 1)',
4416
+ smooth: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
4417
+ bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
4418
+ },
4419
+ },
4420
+ shadows: {
4421
+ sm: '0 1px 2px rgba(0, 0, 0, 0.5)',
4422
+ md: '0 4px 6px rgba(0, 0, 0, 0.6)',
4423
+ lg: '0 10px 15px rgba(0, 0, 0, 0.7)',
4424
+ xl: '0 20px 25px rgba(0, 0, 0, 0.8)',
4425
+ card: '0px 4px 12px rgba(0, 0, 0, 0.6), 0 0 20px rgba(249, 115, 22, 0.2)',
4426
+ cardHover: '0px 4px 24px rgba(249, 115, 22, 0.3), 0 0 30px rgba(249, 115, 22, 0.25)',
4427
+ glow: '0 0 20px rgba(249, 115, 22, 0.8)',
4428
+ focus: '0 0 0 2px #F97316, 0 0 8px 2px rgba(249, 115, 22, 0.8)',
4429
+ },
4430
+ },
3699
4431
  branding: {
3700
4432
  primaryColor: '#F97316',
3701
4433
  },
4434
+ glow: {
4435
+ primary: 'rgba(249, 115, 22, 0.4)',
4436
+ secondary: 'rgba(249, 115, 22, 0.6)',
4437
+ card: 'rgba(249, 115, 22, 0.2)',
4438
+ cardHover: 'rgba(249, 115, 22, 0.3)',
4439
+ button: 'rgba(249, 115, 22, 0.6)',
4440
+ focus: 'rgba(249, 115, 22, 0.8)',
4441
+ scrollbar: 'rgba(249, 115, 22, 0.4)',
4442
+ },
4443
+ gradients: {
4444
+ start: 'rgba(249, 115, 22, 0.08)',
4445
+ end: 'rgba(249, 115, 22, 0.03)',
4446
+ hoverStart: 'rgba(249, 115, 22, 0.15)',
4447
+ hoverEnd: 'rgba(249, 115, 22, 0.08)',
4448
+ },
4449
+ };
4450
+ // StockAlgos theme - Clean professional theme matching StockAlgos website
4451
+ const stockAlgosTheme = {
4452
+ mode: 'light', // Light mode like StockAlgos website
4453
+ colors: {
4454
+ background: {
4455
+ primary: '#FFFFFF', // Clean white background
4456
+ secondary: '#FFFFFF', // Also white for consistency
4457
+ tertiary: '#F8FAFC', // Very light gray for subtle elevation
4458
+ accent: 'rgba(79, 70, 229, 0.05)', // Very subtle blue accent
4459
+ glass: '#FFFFFF', // Pure white, no transparency
4460
+ },
4461
+ status: {
4462
+ connected: '#10B981', // Green for positive/connected
4463
+ disconnected: '#EF4444', // Red for negative/disconnected
4464
+ warning: '#F59E0B', // Amber for warnings
4465
+ pending: '#6366F1', // Indigo for pending states
4466
+ error: '#EF4444', // Red for errors
4467
+ success: '#10B981', // Green for success
4468
+ },
4469
+ text: {
4470
+ primary: '#111827', // Very dark text for maximum contrast
4471
+ secondary: '#374151', // Dark gray for secondary text
4472
+ muted: '#6B7280', // Medium gray for muted text
4473
+ inverse: '#FFFFFF', // White for text on dark backgrounds
4474
+ },
4475
+ border: {
4476
+ primary: '#E5E7EB', // Light gray border
4477
+ secondary: '#F3F4F6', // Very light gray border
4478
+ hover: '#D1D5DB', // Slightly darker on hover
4479
+ focus: '#4F46E5', // Blue focus border
4480
+ accent: '#4F46E5', // Blue accent border
4481
+ },
4482
+ input: {
4483
+ background: '#FFFFFF', // White input background
4484
+ border: '#D1D5DB', // Slightly more visible light gray border
4485
+ borderFocus: '#4F46E5', // Blue focus border
4486
+ text: '#111827', // Darker text for better contrast
4487
+ placeholder: '#6B7280', // Darker placeholder for visibility
4488
+ },
4489
+ button: {
4490
+ primary: {
4491
+ background: '#4F46E5', // Blue primary button
4492
+ text: '#FFFFFF', // White text
4493
+ hover: '#4338CA', // Darker blue on hover
4494
+ active: '#3730A3', // Even darker on active
4495
+ },
4496
+ secondary: {
4497
+ background: '#FFFFFF', // White background
4498
+ text: '#4F46E5', // Blue text
4499
+ border: '#E5E7EB', // Light gray border
4500
+ hover: '#F8FAFC', // Very light gray on hover
4501
+ active: '#F1F5F9', // Light gray on active
4502
+ },
4503
+ },
4504
+ },
4505
+ typography: {
4506
+ fontFamily: {
4507
+ primary: 'Inter, system-ui, -apple-system, sans-serif', // Clean, modern font like StockAlgos
4508
+ secondary: 'Inter, system-ui, -apple-system, sans-serif',
4509
+ },
4510
+ fontSize: {
4511
+ xs: '0.75rem', // 12px
4512
+ sm: '0.875rem', // 14px
4513
+ base: '1rem', // 16px
4514
+ lg: '1.125rem', // 18px
4515
+ xl: '1.25rem', // 20px
4516
+ '2xl': '1.5rem', // 24px
4517
+ '3xl': '1.875rem', // 30px
4518
+ '4xl': '2.25rem', // 36px
4519
+ },
4520
+ fontWeight: {
4521
+ normal: 400,
4522
+ medium: 500,
4523
+ semibold: 600,
4524
+ bold: 700,
4525
+ extrabold: 800,
4526
+ },
4527
+ lineHeight: {
4528
+ tight: '1.25',
4529
+ normal: '1.5',
4530
+ relaxed: '1.75',
4531
+ },
4532
+ },
4533
+ spacing: {
4534
+ xs: '0.25rem', // 4px
4535
+ sm: '0.5rem', // 8px
4536
+ md: '1rem', // 16px
4537
+ lg: '1.5rem', // 24px
4538
+ xl: '2rem', // 32px
4539
+ '2xl': '3rem', // 48px
4540
+ '3xl': '4rem', // 64px
4541
+ },
4542
+ layout: {
4543
+ containerMaxWidth: '1440px',
4544
+ gridGap: '1rem',
4545
+ cardPadding: '1.5rem',
4546
+ borderRadius: {
4547
+ sm: '0.25rem', // 4px
4548
+ md: '0.5rem', // 8px
4549
+ lg: '0.75rem', // 12px
4550
+ xl: '1rem', // 16px
4551
+ '2xl': '1.5rem', // 24px
4552
+ full: '9999px',
4553
+ },
4554
+ },
4555
+ components: {
4556
+ brokerCard: {
4557
+ width: '100%',
4558
+ height: '180px',
4559
+ logoSize: '64px',
4560
+ padding: '1.5rem',
4561
+ },
4562
+ statusIndicator: {
4563
+ size: '22px',
4564
+ glowIntensity: 0.1, // Minimal glow for clean look
4565
+ },
4566
+ modal: {
4567
+ background: '#FFFFFF', // Pure white modal background
4568
+ backdrop: 'rgba(0, 0, 0, 0.4)', // Lighter backdrop
4569
+ },
4570
+ brokerCardModern: {
4571
+ width: '150px',
4572
+ height: '150px',
4573
+ padding: '16px',
4574
+ logoSize: '48px',
4575
+ statusSize: '22px',
4576
+ },
4577
+ connectButton: {
4578
+ width: '120px',
4579
+ height: '120px',
4580
+ },
4581
+ themeSwitcher: {
4582
+ indicatorSize: '24px',
4583
+ },
4584
+ },
4585
+ effects: {
4586
+ glassmorphism: {
4587
+ enabled: false, // Disable glass effects for clean look
4588
+ blur: '0px',
4589
+ opacity: 0,
4590
+ border: 'rgba(0, 0, 0, 0.1)',
4591
+ },
4592
+ animations: {
4593
+ enabled: true,
4594
+ duration: {
4595
+ fast: '150ms',
4596
+ normal: '200ms', // Faster, more subtle animations
4597
+ slow: '300ms',
4598
+ },
4599
+ easing: {
4600
+ default: 'cubic-bezier(0.4, 0, 0.2, 1)',
4601
+ smooth: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
4602
+ bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
4603
+ },
4604
+ },
4605
+ shadows: {
4606
+ sm: '0 1px 2px rgba(0, 0, 0, 0.05)', // Very subtle shadows
4607
+ md: '0 4px 6px rgba(0, 0, 0, 0.07)',
4608
+ lg: '0 10px 15px rgba(0, 0, 0, 0.1)',
4609
+ xl: '0 20px 25px rgba(0, 0, 0, 0.1)',
4610
+ card: '0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06)', // Clean card shadow
4611
+ cardHover: '0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06)', // Subtle hover shadow
4612
+ glow: '0 0 0px transparent', // No glow effects
4613
+ focus: '0 0 0 3px rgba(79, 70, 229, 0.1)', // Subtle focus ring
4614
+ },
4615
+ },
4616
+ branding: {
4617
+ logo: '/stockalgos-logo.png', // Custom logo path
4618
+ companyName: 'StockAlgos', // Company name
4619
+ favicon: '/stockalgos-favicon.ico', // Custom favicon
4620
+ primaryColor: '#4F46E5', // Indigo brand color
4621
+ },
4622
+ // Minimal glow effects
4623
+ glow: {
4624
+ primary: 'transparent',
4625
+ secondary: 'transparent',
4626
+ card: 'transparent',
4627
+ cardHover: 'rgba(79, 70, 229, 0.05)', // Very subtle
4628
+ button: 'transparent',
4629
+ focus: 'rgba(79, 70, 229, 0.1)',
4630
+ scrollbar: 'transparent',
4631
+ },
4632
+ // Minimal gradients
4633
+ gradients: {
4634
+ start: 'rgba(79, 70, 229, 0.02)',
4635
+ end: 'rgba(79, 70, 229, 0.01)',
4636
+ hoverStart: 'rgba(79, 70, 229, 0.05)',
4637
+ hoverEnd: 'rgba(79, 70, 229, 0.02)',
4638
+ },
3702
4639
  };
3703
4640
  // Theme preset mapping
3704
4641
  const portalThemePresets = {
@@ -3708,6 +4645,7 @@ const portalThemePresets = {
3708
4645
  purple: purpleTheme,
3709
4646
  green: greenTheme,
3710
4647
  orange: orangeTheme,
4648
+ stockAlgos: stockAlgosTheme,
3711
4649
  };
3712
4650
 
3713
4651
  /**
@@ -3783,17 +4721,36 @@ function getThemePreset(preset) {
3783
4721
  */
3784
4722
  function validateCustomTheme(theme) {
3785
4723
  try {
3786
- // Check required properties
3787
- if (!theme.mode || !['dark', 'light', 'auto'].includes(theme.mode)) {
4724
+ // Only validate what's provided - everything else gets defaults
4725
+ if (theme.mode && !['dark', 'light', 'auto'].includes(theme.mode)) {
3788
4726
  return false;
3789
4727
  }
3790
- if (!theme.colors) {
3791
- return false;
4728
+ // If colors are provided, validate the structure
4729
+ if (theme.colors) {
4730
+ // Check that any provided color sections have valid structure
4731
+ const colorSections = ['background', 'status', 'text', 'border', 'input', 'button'];
4732
+ for (const section of colorSections) {
4733
+ const colorSection = theme.colors[section];
4734
+ if (colorSection && typeof colorSection !== 'object') {
4735
+ return false;
4736
+ }
4737
+ }
3792
4738
  }
3793
- // Check required color sections
3794
- const requiredSections = ['background', 'status', 'text', 'border', 'input', 'button'];
3795
- for (const section of requiredSections) {
3796
- if (!theme.colors[section]) {
4739
+ // If typography is provided, validate structure
4740
+ if (theme.typography) {
4741
+ if (theme.typography.fontSize && typeof theme.typography.fontSize !== 'object') {
4742
+ return false;
4743
+ }
4744
+ if (theme.typography.fontWeight && typeof theme.typography.fontWeight !== 'object') {
4745
+ return false;
4746
+ }
4747
+ }
4748
+ // If effects are provided, validate structure
4749
+ if (theme.effects) {
4750
+ if (theme.effects.animations && typeof theme.effects.animations !== 'object') {
4751
+ return false;
4752
+ }
4753
+ if (theme.effects.shadows && typeof theme.effects.shadows !== 'object') {
3797
4754
  return false;
3798
4755
  }
3799
4756
  }
@@ -3825,12 +4782,14 @@ function createCustomThemeFromPreset(preset, modifications) {
3825
4782
  /**
3826
4783
  * Broker filtering utility functions
3827
4784
  */
3828
- // Supported broker names and their corresponding IDs
4785
+ // Supported broker names and their corresponding IDs (including aliases)
3829
4786
  const SUPPORTED_BROKERS = {
3830
4787
  'alpaca': 'alpaca',
3831
4788
  'robinhood': 'robinhood',
3832
4789
  'tasty_trade': 'tasty_trade',
3833
- 'ninja_trader': 'ninja_trader'
4790
+ 'ninja_trader': 'ninja_trader',
4791
+ 'tradovate': 'tradovate', // Alias for ninja_trader
4792
+ 'interactive_brokers': 'interactive_brokers',
3834
4793
  };
3835
4794
  /**
3836
4795
  * Convert broker names to broker IDs, filtering out unsupported ones
@@ -3970,6 +4929,13 @@ class FinaticConnect extends EventEmitter {
3970
4929
  this.userToken?.refreshToken &&
3971
4930
  this.userToken?.user_id);
3972
4931
  }
4932
+ /**
4933
+ * Check if the client is authenticated (alias for isAuthed for consistency)
4934
+ * @returns True if authenticated, false otherwise
4935
+ */
4936
+ is_authenticated() {
4937
+ return this.isAuthed();
4938
+ }
3973
4939
  /**
3974
4940
  * Get user's orders with pagination and optional filtering
3975
4941
  * @param params - Query parameters including page, perPage, and filters
@@ -4013,20 +4979,18 @@ class FinaticConnect extends EventEmitter {
4013
4979
  return this.getAccountsPage(page, perPage, filter);
4014
4980
  }
4015
4981
  /**
4016
- * Revoke the current user's access
4982
+ * Get user's balances with pagination and optional filtering
4983
+ * @param params - Query parameters including page, perPage, and filters
4984
+ * @returns Promise with paginated result that supports navigation
4017
4985
  */
4018
- async revokeToken() {
4019
- if (!this.userToken) {
4020
- return;
4021
- }
4022
- try {
4023
- await this.apiClient.revokeToken(this.userToken.accessToken);
4024
- this.userToken = null;
4025
- }
4026
- catch (error) {
4027
- this.emit('error', error);
4028
- throw error;
4986
+ async getBalances(params) {
4987
+ if (!this.isAuthed()) {
4988
+ throw new AuthenticationError('User is not authenticated');
4029
4989
  }
4990
+ const page = params?.page || 1;
4991
+ const perPage = params?.perPage || 100;
4992
+ const filter = params?.filter;
4993
+ return this.getBalancesPage(page, perPage, filter);
4030
4994
  }
4031
4995
  /**
4032
4996
  * Initialize the Finatic Connect SDK
@@ -4115,9 +5079,33 @@ class FinaticConnect extends EventEmitter {
4115
5079
  async setUserId(userId) {
4116
5080
  await this.initializeWithUser(userId);
4117
5081
  }
5082
+ /**
5083
+ * Get the user and tokens for a completed session
5084
+ * @returns Promise with user information and tokens
5085
+ */
5086
+ async getSessionUser() {
5087
+ if (!this.isAuthed()) {
5088
+ throw new AuthenticationError('User is not authenticated');
5089
+ }
5090
+ if (!this.userToken) {
5091
+ throw new AuthenticationError('No user token available');
5092
+ }
5093
+ return {
5094
+ user_id: this.userToken.userId,
5095
+ access_token: this.userToken.accessToken,
5096
+ refresh_token: this.userToken.refreshToken,
5097
+ expires_in: this.userToken.expiresIn,
5098
+ token_type: this.userToken.tokenType,
5099
+ scope: this.userToken.scope,
5100
+ company_id: this.companyId,
5101
+ };
5102
+ }
4118
5103
  async initializeWithUser(userId) {
4119
5104
  try {
4120
- this.userToken = await this.apiClient.getUserToken(userId);
5105
+ if (!this.sessionId) {
5106
+ throw new SessionError('Session not initialized');
5107
+ }
5108
+ this.userToken = await this.apiClient.getUserToken(this.sessionId);
4121
5109
  // Set tokens in ApiClient for automatic token management
4122
5110
  if (this.userToken) {
4123
5111
  const expiresAt = new Date(Date.now() + 3600 * 1000).toISOString(); // 1 hour from now
@@ -4161,20 +5149,8 @@ class FinaticConnect extends EventEmitter {
4161
5149
  this.apiClient.setSessionContext(this.sessionId, this.companyId, startResponse.data.csrf_token // If available in response
4162
5150
  );
4163
5151
  }
4164
- // Wait for session to become active
4165
- const maxAttempts = 5;
4166
- let attempts = 0;
4167
- while (attempts < maxAttempts) {
4168
- const sessionResponse = await this.apiClient.validatePortalSession(this.sessionId, '');
4169
- if (sessionResponse.status === 'active') {
4170
- break;
4171
- }
4172
- await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second between attempts
4173
- attempts++;
4174
- }
4175
- if (attempts === maxAttempts) {
4176
- throw new SessionError('Session failed to become active');
4177
- }
5152
+ // Session is now active
5153
+ this.currentSessionState = SessionState.ACTIVE;
4178
5154
  }
4179
5155
  // Get portal URL
4180
5156
  const portalResponse = await this.apiClient.getPortalUrl(this.sessionId);
@@ -4185,6 +5161,12 @@ class FinaticConnect extends EventEmitter {
4185
5161
  let themedPortalUrl = appendThemeToURL(portalResponse.data.portal_url, options?.theme);
4186
5162
  // Apply broker filter to portal URL if provided
4187
5163
  themedPortalUrl = appendBrokerFilterToURL(themedPortalUrl, options?.brokers);
5164
+ // Apply email parameter to portal URL if provided
5165
+ if (options?.email) {
5166
+ const url = new URL(themedPortalUrl);
5167
+ url.searchParams.set('email', options.email);
5168
+ themedPortalUrl = url.toString();
5169
+ }
4188
5170
  // Create portal UI if not exists
4189
5171
  if (!this.portalUI) {
4190
5172
  this.portalUI = new PortalUI(this.baseUrl);
@@ -4196,16 +5178,6 @@ class FinaticConnect extends EventEmitter {
4196
5178
  if (!this.sessionId) {
4197
5179
  throw new SessionError('Session not initialized');
4198
5180
  }
4199
- // Get tokens from portal UI
4200
- const userToken = this.portalUI.getTokens();
4201
- if (!userToken) {
4202
- throw new Error('No tokens received from portal');
4203
- }
4204
- // Set the tokens internally
4205
- this.userToken = userToken;
4206
- // Set tokens in ApiClient for automatic token management
4207
- const expiresAt = new Date(Date.now() + 3600 * 1000).toISOString(); // 1 hour from now
4208
- this.apiClient.setTokens(userToken.accessToken, userToken.refreshToken, expiresAt, userId);
4209
5181
  // Emit portal success event
4210
5182
  this.emit('portal:success', userId);
4211
5183
  // Emit legacy success event
@@ -4280,21 +5252,8 @@ class FinaticConnect extends EventEmitter {
4280
5252
  }
4281
5253
  // For non-direct auth, we need to wait for the session to be ACTIVE
4282
5254
  if (response.data.state === SessionState.PENDING) {
4283
- // Wait for session to become active
4284
- const maxAttempts = 5;
4285
- let attempts = 0;
4286
- while (attempts < maxAttempts) {
4287
- const sessionResponse = await this.apiClient.validatePortalSession(this.sessionId, '');
4288
- if (sessionResponse.status === 'active') {
4289
- this.currentSessionState = SessionState.ACTIVE;
4290
- break;
4291
- }
4292
- await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second between attempts
4293
- attempts++;
4294
- }
4295
- if (attempts === maxAttempts) {
4296
- throw new SessionError('Session failed to become active');
4297
- }
5255
+ // Session is now active
5256
+ this.currentSessionState = SessionState.ACTIVE;
4298
5257
  }
4299
5258
  }
4300
5259
  catch (error) {
@@ -4399,7 +5358,7 @@ class FinaticConnect extends EventEmitter {
4399
5358
  * Set the broker context for trading
4400
5359
  * @param broker - The broker to use for trading
4401
5360
  */
4402
- setBroker(broker) {
5361
+ setTradingContextBroker(broker) {
4403
5362
  this.apiClient.setBroker(broker);
4404
5363
  }
4405
5364
  /**
@@ -4407,7 +5366,7 @@ class FinaticConnect extends EventEmitter {
4407
5366
  * @param accountNumber - The account number to use for trading
4408
5367
  * @param accountId - Optional account ID
4409
5368
  */
4410
- setAccount(accountNumber, accountId) {
5369
+ setTradingContextAccount(accountNumber, accountId) {
4411
5370
  this.apiClient.setAccount(accountNumber, accountId);
4412
5371
  }
4413
5372
  /**
@@ -4452,6 +5411,111 @@ class FinaticConnect extends EventEmitter {
4452
5411
  throw error;
4453
5412
  }
4454
5413
  }
5414
+ /**
5415
+ * Place a stock stop order (convenience method)
5416
+ */
5417
+ async placeStockStopOrder(symbol, quantity, side, stopPrice, timeInForce = 'gtc', broker, accountNumber) {
5418
+ if (!this.userToken) {
5419
+ throw new Error('Not initialized with user');
5420
+ }
5421
+ try {
5422
+ return await this.apiClient.placeStockStopOrder(symbol, quantity, side === 'buy' ? 'Buy' : 'Sell', stopPrice, timeInForce, broker, accountNumber);
5423
+ }
5424
+ catch (error) {
5425
+ this.emit('error', error);
5426
+ throw error;
5427
+ }
5428
+ }
5429
+ /**
5430
+ * Place a crypto market order (convenience method)
5431
+ */
5432
+ async placeCryptoMarketOrder(symbol, quantity, side, broker, accountNumber) {
5433
+ if (!this.userToken) {
5434
+ throw new Error('Not initialized with user');
5435
+ }
5436
+ try {
5437
+ return await this.apiClient.placeCryptoMarketOrder(symbol, quantity, side === 'buy' ? 'Buy' : 'Sell', broker, accountNumber);
5438
+ }
5439
+ catch (error) {
5440
+ this.emit('error', error);
5441
+ throw error;
5442
+ }
5443
+ }
5444
+ /**
5445
+ * Place a crypto limit order (convenience method)
5446
+ */
5447
+ async placeCryptoLimitOrder(symbol, quantity, side, price, timeInForce = 'gtc', broker, accountNumber) {
5448
+ if (!this.userToken) {
5449
+ throw new Error('Not initialized with user');
5450
+ }
5451
+ try {
5452
+ return await this.apiClient.placeCryptoLimitOrder(symbol, quantity, side === 'buy' ? 'Buy' : 'Sell', price, timeInForce, broker, accountNumber);
5453
+ }
5454
+ catch (error) {
5455
+ this.emit('error', error);
5456
+ throw error;
5457
+ }
5458
+ }
5459
+ /**
5460
+ * Place an options market order (convenience method)
5461
+ */
5462
+ async placeOptionsMarketOrder(symbol, quantity, side, broker, accountNumber) {
5463
+ if (!this.userToken) {
5464
+ throw new Error('Not initialized with user');
5465
+ }
5466
+ try {
5467
+ return await this.apiClient.placeOptionsMarketOrder(symbol, quantity, side === 'buy' ? 'Buy' : 'Sell', broker, accountNumber);
5468
+ }
5469
+ catch (error) {
5470
+ this.emit('error', error);
5471
+ throw error;
5472
+ }
5473
+ }
5474
+ /**
5475
+ * Place an options limit order (convenience method)
5476
+ */
5477
+ async placeOptionsLimitOrder(symbol, quantity, side, price, timeInForce = 'gtc', broker, accountNumber) {
5478
+ if (!this.userToken) {
5479
+ throw new Error('Not initialized with user');
5480
+ }
5481
+ try {
5482
+ return await this.apiClient.placeOptionsLimitOrder(symbol, quantity, side === 'buy' ? 'Buy' : 'Sell', price, timeInForce, broker, accountNumber);
5483
+ }
5484
+ catch (error) {
5485
+ this.emit('error', error);
5486
+ throw error;
5487
+ }
5488
+ }
5489
+ /**
5490
+ * Place a futures market order (convenience method)
5491
+ */
5492
+ async placeFuturesMarketOrder(symbol, quantity, side, broker, accountNumber) {
5493
+ if (!this.userToken) {
5494
+ throw new Error('Not initialized with user');
5495
+ }
5496
+ try {
5497
+ return await this.apiClient.placeFuturesMarketOrder(symbol, quantity, side === 'buy' ? 'Buy' : 'Sell', broker, accountNumber);
5498
+ }
5499
+ catch (error) {
5500
+ this.emit('error', error);
5501
+ throw error;
5502
+ }
5503
+ }
5504
+ /**
5505
+ * Place a futures limit order (convenience method)
5506
+ */
5507
+ async placeFuturesLimitOrder(symbol, quantity, side, price, timeInForce = 'gtc', broker, accountNumber) {
5508
+ if (!this.userToken) {
5509
+ throw new Error('Not initialized with user');
5510
+ }
5511
+ try {
5512
+ return await this.apiClient.placeFuturesLimitOrder(symbol, quantity, side === 'buy' ? 'Buy' : 'Sell', price, timeInForce, broker, accountNumber);
5513
+ }
5514
+ catch (error) {
5515
+ this.emit('error', error);
5516
+ throw error;
5517
+ }
5518
+ }
4455
5519
  /**
4456
5520
  * Get the current user ID
4457
5521
  * @returns The current user ID or undefined if not authenticated
@@ -4471,9 +5535,9 @@ class FinaticConnect extends EventEmitter {
4471
5535
  * @returns Promise with array of broker information
4472
5536
  */
4473
5537
  async getBrokerList() {
4474
- if (!this.isAuthed()) {
4475
- throw new AuthenticationError('Not authenticated');
4476
- }
5538
+ // if (!this.isAuthed()) {
5539
+ // throw new AuthenticationError('Not authenticated');
5540
+ // }
4477
5541
  const response = await this.apiClient.getBrokerList();
4478
5542
  const baseUrl = this.baseUrl.replace('/api/v1', ''); // Remove /api/v1 to get the base URL
4479
5543
  // Transform the broker list to include full logo URLs
@@ -4601,6 +5665,12 @@ class FinaticConnect extends EventEmitter {
4601
5665
  }
4602
5666
  return this.apiClient.getBrokerAccountsPage(page, perPage, filter);
4603
5667
  }
5668
+ async getBalancesPage(page = 1, perPage = 100, filter) {
5669
+ if (!this.isAuthed()) {
5670
+ throw new AuthenticationError('User is not authenticated');
5671
+ }
5672
+ return this.apiClient.getBrokerBalancesPage(page, perPage, filter);
5673
+ }
4604
5674
  /**
4605
5675
  * Get the next page of orders
4606
5676
  * @param previousResult - The previous paginated result
@@ -4643,6 +5713,15 @@ class FinaticConnect extends EventEmitter {
4643
5713
  return this.apiClient.getBrokerAccountsPage(page, limit);
4644
5714
  });
4645
5715
  }
5716
+ async getNextBalancesPage(previousResult) {
5717
+ if (!this.isAuthed()) {
5718
+ throw new AuthenticationError('User is not authenticated');
5719
+ }
5720
+ return this.apiClient.getNextPage(previousResult, (offset, limit) => {
5721
+ const page = Math.floor(offset / limit) + 1;
5722
+ return this.apiClient.getBrokerBalancesPage(page, limit);
5723
+ });
5724
+ }
4646
5725
  /**
4647
5726
  * Get all orders across all pages (convenience method)
4648
5727
  * @param filter - Optional filter parameters
@@ -4709,6 +5788,23 @@ class FinaticConnect extends EventEmitter {
4709
5788
  }
4710
5789
  return allData;
4711
5790
  }
5791
+ async getAllBalances(filter) {
5792
+ if (!this.isAuthed()) {
5793
+ throw new AuthenticationError('User is not authenticated');
5794
+ }
5795
+ const allData = [];
5796
+ let currentResult = await this.getBalancesPage(1, 100, filter);
5797
+ while (currentResult) {
5798
+ allData.push(...currentResult.data);
5799
+ if (!currentResult.hasNext)
5800
+ break;
5801
+ const nextResult = await this.getNextBalancesPage(currentResult);
5802
+ if (!nextResult)
5803
+ break;
5804
+ currentResult = nextResult;
5805
+ }
5806
+ return allData;
5807
+ }
4712
5808
  /**
4713
5809
  * Register session management (but don't auto-cleanup for 24-hour sessions)
4714
5810
  */
@@ -4758,20 +5854,9 @@ class FinaticConnect extends EventEmitter {
4758
5854
  await this.refreshSessionAutomatically();
4759
5855
  return;
4760
5856
  }
4761
- // Use a timeout to prevent hanging requests
4762
- const timeoutPromise = new Promise((_, reject) => {
4763
- setTimeout(() => reject(new Error('Session validation timeout')), this.SESSION_VALIDATION_TIMEOUT);
4764
- });
4765
- const validationPromise = this.apiClient.validatePortalSession(this.sessionId, '');
4766
- const response = await Promise.race([validationPromise, timeoutPromise]);
4767
- if (response && response.status === 'active') {
4768
- console.log('[FinaticConnect] Session keep-alive successful');
4769
- this.currentSessionState = 'active';
4770
- }
4771
- else {
4772
- console.warn('[FinaticConnect] Session keep-alive failed - session not active');
4773
- this.currentSessionState = response?.status || 'unknown';
4774
- }
5857
+ // Session keep-alive - assume session is active if we have a session ID
5858
+ console.log('[FinaticConnect] Session keep-alive successful');
5859
+ this.currentSessionState = 'active';
4775
5860
  }
4776
5861
  catch (error) {
4777
5862
  console.warn('[FinaticConnect] Session keep-alive error:', error);
@@ -4900,6 +5985,24 @@ class FinaticConnect extends EventEmitter {
4900
5985
  }
4901
5986
  return this.apiClient.disconnectCompany(connectionId);
4902
5987
  }
5988
+ /**
5989
+ * Get account balances for the authenticated user
5990
+ * @param filters - Optional filters for balances
5991
+ * @returns Promise with balance data
5992
+ */
5993
+ async getBalances(filters) {
5994
+ if (!this.isAuthed()) {
5995
+ throw new AuthenticationError('User is not authenticated');
5996
+ }
5997
+ try {
5998
+ const response = await this.apiClient.getBalances(filters);
5999
+ return response.response_data || [];
6000
+ }
6001
+ catch (error) {
6002
+ this.emit('error', error);
6003
+ throw error;
6004
+ }
6005
+ }
4903
6006
  }
4904
6007
  FinaticConnect.instance = null;
4905
6008