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