@finatic/client 0.0.142 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +39 -0
  3. package/README.md +54 -425
  4. package/dist/index.d.ts +7329 -1579
  5. package/dist/index.js +8110 -6114
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +8047 -6085
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +77 -33
  10. package/dist/types/core/client/ApiClient.d.ts +0 -270
  11. package/dist/types/core/client/FinaticConnect.d.ts +0 -332
  12. package/dist/types/core/portal/PortalUI.d.ts +0 -37
  13. package/dist/types/index.d.ts +0 -12
  14. package/dist/types/lib/logger/index.d.ts +0 -2
  15. package/dist/types/lib/logger/logger.d.ts +0 -4
  16. package/dist/types/lib/logger/logger.types.d.ts +0 -28
  17. package/dist/types/mocks/MockApiClient.d.ts +0 -171
  18. package/dist/types/mocks/MockDataProvider.d.ts +0 -139
  19. package/dist/types/mocks/MockFactory.d.ts +0 -53
  20. package/dist/types/mocks/utils.d.ts +0 -24
  21. package/dist/types/themes/portalPresets.d.ts +0 -9
  22. package/dist/types/types/api/auth.d.ts +0 -93
  23. package/dist/types/types/api/broker.d.ts +0 -421
  24. package/dist/types/types/api/core.d.ts +0 -46
  25. package/dist/types/types/api/errors.d.ts +0 -31
  26. package/dist/types/types/api/orders.d.ts +0 -39
  27. package/dist/types/types/api/portfolio.d.ts +0 -55
  28. package/dist/types/types/common/pagination.d.ts +0 -33
  29. package/dist/types/types/connect.d.ts +0 -58
  30. package/dist/types/types/index.d.ts +0 -13
  31. package/dist/types/types/portal.d.ts +0 -204
  32. package/dist/types/types/ui/theme.d.ts +0 -104
  33. package/dist/types/utils/brokerUtils.d.ts +0 -30
  34. package/dist/types/utils/errors.d.ts +0 -45
  35. package/dist/types/utils/events.d.ts +0 -12
  36. package/dist/types/utils/themeUtils.d.ts +0 -34
  37. package/src/core/client/ApiClient.ts +0 -2004
  38. package/src/core/client/FinaticConnect.ts +0 -1606
  39. package/src/core/portal/PortalUI.ts +0 -335
  40. package/src/index.d.ts +0 -23
  41. package/src/index.ts +0 -100
  42. package/src/lib/logger/index.ts +0 -3
  43. package/src/lib/logger/logger.ts +0 -332
  44. package/src/lib/logger/logger.types.ts +0 -34
  45. package/src/mocks/MockApiClient.ts +0 -1058
  46. package/src/mocks/MockDataProvider.ts +0 -986
  47. package/src/mocks/MockFactory.ts +0 -97
  48. package/src/mocks/utils.ts +0 -133
  49. package/src/themes/portalPresets.ts +0 -1307
  50. package/src/types/api/auth.ts +0 -112
  51. package/src/types/api/broker.ts +0 -461
  52. package/src/types/api/core.ts +0 -53
  53. package/src/types/api/errors.ts +0 -35
  54. package/src/types/api/orders.ts +0 -45
  55. package/src/types/api/portfolio.ts +0 -59
  56. package/src/types/common/pagination.ts +0 -164
  57. package/src/types/connect.ts +0 -56
  58. package/src/types/index.ts +0 -25
  59. package/src/types/portal.ts +0 -214
  60. package/src/types/ui/theme.ts +0 -105
  61. package/src/utils/brokerUtils.ts +0 -104
  62. package/src/utils/errors.ts +0 -104
  63. package/src/utils/events.ts +0 -66
  64. package/src/utils/themeUtils.ts +0 -165
@@ -1,986 +0,0 @@
1
- import { v4 as uuidv4 } from 'uuid';
2
-
3
- // Import types from the main API types
4
- import { Order, OrderResponse } from '../types/api/orders';
5
- import {
6
- BrokerInfo,
7
- BrokerAccount,
8
- BrokerOrder,
9
- BrokerPosition,
10
- BrokerBalance,
11
- BrokerConnection,
12
- OrdersFilter,
13
- PositionsFilter,
14
- AccountsFilter,
15
- BalancesFilter,
16
- BrokerDataOrder,
17
- BrokerDataPosition,
18
- BrokerDataAccount,
19
- BrokerOrderParams,
20
- DisconnectCompanyResponse,
21
- } from '../types/api/broker';
22
- import {
23
- SessionResponse,
24
- OtpRequestResponse,
25
- OtpVerifyResponse,
26
- SessionValidationResponse,
27
- SessionAuthenticateResponse,
28
- UserToken,
29
- RefreshTokenResponse,
30
- SessionState,
31
- } from '../types/api/auth';
32
- import { PortalUrlResponse } from '../types/api/core';
33
-
34
- /**
35
- * Configuration for mock behavior
36
- */
37
- export interface MockConfig {
38
- delay?: number;
39
- scenario?: MockScenario;
40
- customData?: Record<string, any>;
41
- mockApiOnly?: boolean;
42
- }
43
-
44
- /**
45
- * Different mock scenarios for testing
46
- */
47
- export type MockScenario = 'success' | 'error' | 'network_error' | 'rate_limit' | 'auth_failure';
48
-
49
- /**
50
- * Mock data provider for Finatic API endpoints
51
- */
52
- export class MockDataProvider {
53
- private config: MockConfig;
54
- private sessionData: Map<string, any> = new Map();
55
- private userTokens: Map<string, UserToken> = new Map();
56
-
57
- constructor(config: MockConfig = {}) {
58
- this.config = {
59
- delay: config.delay || this.getRandomDelay(50, 200),
60
- scenario: config.scenario || 'success',
61
- customData: config.customData || {},
62
- };
63
- }
64
-
65
- /**
66
- * Get a random delay between min and max milliseconds
67
- */
68
- private getRandomDelay(min: number, max: number): number {
69
- return Math.floor(Math.random() * (max - min + 1)) + min;
70
- }
71
-
72
- /**
73
- * Simulate network delay
74
- */
75
- async simulateDelay(): Promise<void> {
76
- const delay = this.config.delay || this.getRandomDelay(50, 200);
77
- await new Promise(resolve => setTimeout(resolve, delay));
78
- }
79
-
80
- /**
81
- * Generate a realistic session ID
82
- */
83
- private generateSessionId(): string {
84
- return `session_${uuidv4().replace(/-/g, '')}`;
85
- }
86
-
87
- /**
88
- * Generate a realistic user ID
89
- */
90
- private generateUserId(): string {
91
- return `user_${uuidv4().replace(/-/g, '').substring(0, 8)}`;
92
- }
93
-
94
- /**
95
- * Generate a realistic company ID
96
- */
97
- private generateCompanyId(): string {
98
- return `company_${uuidv4().replace(/-/g, '').substring(0, 8)}`;
99
- }
100
-
101
- /**
102
- * Generate mock tokens
103
- */
104
- private generateTokens(userId: string): UserToken {
105
- const accessToken = `mock_access_${uuidv4().replace(/-/g, '')}`;
106
- const refreshToken = `mock_refresh_${uuidv4().replace(/-/g, '')}`;
107
-
108
- return {
109
- user_id: userId,
110
- // Removed token fields - we no longer use Supabase tokens in the SDK
111
- };
112
- }
113
-
114
- // Authentication & Session Management Mocks
115
-
116
- async mockStartSession(token: string, userId?: string): Promise<SessionResponse> {
117
- await this.simulateDelay();
118
-
119
- const sessionId = this.generateSessionId();
120
- const companyId = this.generateCompanyId();
121
- const actualUserId = userId || this.generateUserId();
122
-
123
- const sessionData = {
124
- session_id: sessionId,
125
- state: SessionState.ACTIVE,
126
- company_id: companyId,
127
- status: 'active',
128
- expires_at: new Date(Date.now() + 30 * 60 * 1000).toISOString(), // 30 minutes
129
- user_id: actualUserId,
130
- auto_login: false,
131
- };
132
-
133
- this.sessionData.set(sessionId, sessionData);
134
-
135
- return {
136
- success: true,
137
- data: sessionData,
138
- message: 'Session started successfully',
139
- };
140
- }
141
-
142
- async mockRequestOtp(sessionId: string, email: string): Promise<OtpRequestResponse> {
143
- await this.simulateDelay();
144
-
145
- return {
146
- success: true,
147
- message: 'OTP sent successfully',
148
- data: true,
149
- };
150
- }
151
-
152
- async mockVerifyOtp(sessionId: string, otp: string): Promise<OtpVerifyResponse> {
153
- await this.simulateDelay();
154
-
155
- const userId = this.generateUserId();
156
- const tokens = this.generateTokens(userId);
157
-
158
- this.userTokens.set(userId, tokens);
159
-
160
- return {
161
- success: true,
162
- message: 'OTP verified successfully',
163
- data: {
164
- access_token: '', // No longer using Supabase tokens
165
- refresh_token: '', // No longer using Supabase tokens
166
- user_id: userId,
167
- expires_in: 0, // No token expiration for session-based auth
168
- scope: 'api:access',
169
- token_type: 'Bearer',
170
- },
171
- };
172
- }
173
-
174
- async mockAuthenticateDirectly(
175
- sessionId: string,
176
- userId: string
177
- ): Promise<SessionAuthenticateResponse> {
178
- await this.simulateDelay();
179
-
180
- const tokens = this.generateTokens(userId);
181
- this.userTokens.set(userId, tokens);
182
-
183
- return {
184
- success: true,
185
- message: 'Authentication successful',
186
- data: {
187
- access_token: '', // No longer using Supabase tokens
188
- refresh_token: '', // No longer using Supabase tokens
189
- },
190
- };
191
- }
192
-
193
- async mockGetPortalUrl(sessionId: string): Promise<PortalUrlResponse> {
194
- await this.simulateDelay();
195
- const scenario = this.getScenario();
196
- if (scenario === 'error') {
197
- return {
198
- success: false,
199
- message: 'Failed to retrieve portal URL (mock error)',
200
- data: { portal_url: '' },
201
- };
202
- }
203
- if (scenario === 'network_error') {
204
- throw new Error('Network error (mock)');
205
- }
206
- if (scenario === 'rate_limit') {
207
- return {
208
- success: false,
209
- message: 'Rate limit exceeded (mock)',
210
- data: { portal_url: '' },
211
- };
212
- }
213
- if (scenario === 'auth_failure') {
214
- return {
215
- success: false,
216
- message: 'Authentication failed (mock)',
217
- data: { portal_url: '' },
218
- };
219
- }
220
- // Default: success
221
- return {
222
- success: true,
223
- message: 'Portal URL retrieved successfully',
224
- data: {
225
- portal_url: 'http://localhost:3000/mock-portal',
226
- },
227
- };
228
- }
229
-
230
- async mockValidatePortalSession(
231
- sessionId: string,
232
- signature: string
233
- ): Promise<SessionValidationResponse> {
234
- await this.simulateDelay();
235
-
236
- return {
237
- valid: true,
238
- company_id: this.generateCompanyId(),
239
- status: 'active',
240
- is_sandbox: false,
241
- environment: 'production',
242
- };
243
- }
244
-
245
- async mockCompletePortalSession(sessionId: string): Promise<PortalUrlResponse> {
246
- await this.simulateDelay();
247
-
248
- return {
249
- success: true,
250
- message: 'Portal session completed successfully',
251
- data: {
252
- portal_url: `https://portal.finatic.dev/complete/${sessionId}`,
253
- },
254
- };
255
- }
256
-
257
- async mockRefreshToken(refreshToken: string): Promise<RefreshTokenResponse> {
258
- await this.simulateDelay();
259
-
260
- const newAccessToken = `mock_access_${uuidv4().replace(/-/g, '')}`;
261
- const newRefreshToken = `mock_refresh_${uuidv4().replace(/-/g, '')}`;
262
-
263
- return {
264
- success: true,
265
- response_data: {
266
- access_token: newAccessToken,
267
- refresh_token: newRefreshToken,
268
- expires_at: new Date(Date.now() + 3600 * 1000).toISOString(),
269
- company_id: this.generateCompanyId(),
270
- company_name: 'Mock Company',
271
- email_verified: true,
272
- },
273
- message: 'Token refreshed successfully',
274
- };
275
- }
276
-
277
- // Broker Management Mocks
278
-
279
- async mockGetBrokerList(): Promise<{
280
- _id: string;
281
- response_data: BrokerInfo[];
282
- message: string;
283
- status_code: number;
284
- warnings: null;
285
- errors: null;
286
- }> {
287
- await this.simulateDelay();
288
-
289
- const brokers: BrokerInfo[] = [
290
- {
291
- id: 'alpaca',
292
- name: 'alpaca',
293
- display_name: 'Alpaca',
294
- description: 'Commission-free stock trading and crypto',
295
- website: 'https://alpaca.markets',
296
- features: ['stocks', 'crypto', 'fractional_shares', 'api_trading'],
297
- auth_type: 'api_key',
298
- logo_path: '/logos/alpaca.png',
299
- is_active: true,
300
- },
301
- {
302
- id: 'robinhood',
303
- name: 'robinhood',
304
- display_name: 'Robinhood',
305
- description: 'Commission-free stock and options trading',
306
- website: 'https://robinhood.com',
307
- features: ['stocks', 'options', 'crypto', 'fractional_shares'],
308
- auth_type: 'oauth',
309
- logo_path: '/logos/robinhood.png',
310
- is_active: true,
311
- },
312
- {
313
- id: 'tasty_trade',
314
- name: 'tasty_trade',
315
- display_name: 'TastyTrade',
316
- description: 'Options and futures trading platform',
317
- website: 'https://tastytrade.com',
318
- features: ['options', 'futures', 'stocks'],
319
- auth_type: 'oauth',
320
- logo_path: '/logos/tastytrade.png',
321
- is_active: true,
322
- },
323
- {
324
- id: 'ninja_trader',
325
- name: 'ninja_trader',
326
- display_name: 'NinjaTrader',
327
- description: 'Advanced futures and forex trading platform',
328
- website: 'https://ninjatrader.com',
329
- features: ['futures', 'forex', 'options'],
330
- auth_type: 'api_key',
331
- logo_path: '/logos/ninjatrader.png',
332
- is_active: true,
333
- },
334
- ];
335
-
336
- return {
337
- _id: uuidv4(),
338
- response_data: brokers,
339
- message: 'Broker list retrieved successfully',
340
- status_code: 200,
341
- warnings: null,
342
- errors: null,
343
- };
344
- }
345
-
346
- async mockGetBrokerAccounts(): Promise<{
347
- _id: string;
348
- response_data: BrokerAccount[];
349
- message: string;
350
- status_code: number;
351
- warnings: null;
352
- errors: null;
353
- }> {
354
- await this.simulateDelay();
355
-
356
- const accounts: BrokerAccount[] = [
357
- {
358
- id: uuidv4(),
359
- user_broker_connection_id: uuidv4(),
360
- broker_provided_account_id: '123456789',
361
- account_name: 'Individual Account',
362
- account_type: 'individual',
363
- currency: 'USD',
364
- cash_balance: 15000.5,
365
- buying_power: 45000.0,
366
- status: 'active',
367
- created_at: new Date().toISOString(),
368
- updated_at: new Date().toISOString(),
369
- last_synced_at: new Date().toISOString(),
370
- positions_synced_at: new Date().toISOString(),
371
- orders_synced_at: new Date().toISOString(),
372
- balances_synced_at: new Date().toISOString(),
373
- account_created_at: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString(), // 1 year ago
374
- account_updated_at: new Date().toISOString(),
375
- account_first_trade_at: new Date(Date.now() - 300 * 24 * 60 * 60 * 1000).toISOString(), // 300 days ago
376
- },
377
- {
378
- id: uuidv4(),
379
- user_broker_connection_id: uuidv4(),
380
- broker_provided_account_id: '987654321',
381
- account_name: 'IRA Account',
382
- account_type: 'ira',
383
- currency: 'USD',
384
- cash_balance: 25000.75,
385
- buying_power: 75000.0,
386
- status: 'active',
387
- created_at: new Date().toISOString(),
388
- updated_at: new Date().toISOString(),
389
- last_synced_at: new Date().toISOString(),
390
- positions_synced_at: new Date().toISOString(),
391
- orders_synced_at: new Date().toISOString(),
392
- balances_synced_at: new Date().toISOString(),
393
- account_created_at: new Date(Date.now() - 730 * 24 * 60 * 60 * 1000).toISOString(), // 2 years ago
394
- account_updated_at: new Date().toISOString(),
395
- account_first_trade_at: new Date(Date.now() - 700 * 24 * 60 * 60 * 1000).toISOString(), // 700 days ago
396
- },
397
- ];
398
-
399
- return {
400
- _id: uuidv4(),
401
- response_data: accounts,
402
- message: 'Broker accounts retrieved successfully',
403
- status_code: 200,
404
- warnings: null,
405
- errors: null,
406
- };
407
- }
408
-
409
- async mockGetBrokerConnections(): Promise<{
410
- _id: string;
411
- response_data: BrokerConnection[];
412
- message: string;
413
- status_code: number;
414
- warnings: null;
415
- errors: null;
416
- }> {
417
- await this.simulateDelay();
418
-
419
- const connections: BrokerConnection[] = [
420
- {
421
- id: uuidv4(),
422
- broker_id: 'robinhood',
423
- user_id: this.generateUserId(),
424
- company_id: this.generateCompanyId(),
425
- status: 'connected',
426
- connected_at: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days ago
427
- last_synced_at: new Date().toISOString(),
428
- permissions: {
429
- read: true,
430
- write: true,
431
- },
432
- metadata: {
433
- nickname: 'My Robinhood',
434
- },
435
- needs_reauth: false,
436
- },
437
- {
438
- id: uuidv4(),
439
- broker_id: 'tasty_trade',
440
- user_id: this.generateUserId(),
441
- company_id: this.generateCompanyId(),
442
- status: 'connected',
443
- connected_at: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), // 3 days ago
444
- last_synced_at: new Date().toISOString(),
445
- permissions: {
446
- read: true,
447
- write: false,
448
- },
449
- metadata: {
450
- nickname: 'Tasty Options',
451
- },
452
- needs_reauth: false,
453
- },
454
- ];
455
-
456
- return {
457
- _id: uuidv4(),
458
- response_data: connections,
459
- message: 'Broker connections retrieved successfully',
460
- status_code: 200,
461
- warnings: null,
462
- errors: null,
463
- };
464
- }
465
-
466
- // Portfolio & Trading Mocks
467
-
468
-
469
-
470
- async mockGetOrders(filter?: OrdersFilter): Promise<{ data: Order[] }> {
471
- await this.simulateDelay();
472
-
473
- const mockOrders: Order[] = [
474
- {
475
- symbol: 'AAPL',
476
- side: 'buy',
477
- quantity: 100,
478
- type_: 'market',
479
- timeInForce: 'day',
480
- },
481
- {
482
- symbol: 'TSLA',
483
- side: 'sell',
484
- quantity: 50,
485
- type_: 'limit',
486
- price: 250.0,
487
- timeInForce: 'day',
488
- },
489
- ];
490
-
491
- // Apply filters if provided
492
- let filteredOrders = mockOrders;
493
- if (filter) {
494
- filteredOrders = this.applyOrderFilters(mockOrders, filter);
495
- }
496
-
497
- return { data: filteredOrders };
498
- }
499
-
500
- async mockGetBrokerOrders(filter?: OrdersFilter): Promise<{ data: BrokerDataOrder[] }> {
501
- await this.simulateDelay();
502
-
503
- // Determine how many orders to generate based on limit parameter
504
- const limit = filter?.limit || 100;
505
- const maxLimit = Math.min(limit, 1000); // Cap at 1000
506
- const count = Math.max(1, maxLimit); // At least 1
507
-
508
- // Generate diverse mock orders based on requested count
509
- const mockOrders: BrokerDataOrder[] = this.generateMockOrders(count);
510
-
511
- // Apply filters if provided
512
- let filteredOrders = mockOrders;
513
- if (filter) {
514
- filteredOrders = this.applyBrokerOrderFilters(mockOrders, filter);
515
- }
516
-
517
- return {
518
- data: filteredOrders,
519
- };
520
- }
521
-
522
- async mockGetBrokerPositions(filter?: PositionsFilter): Promise<{ data: BrokerDataPosition[] }> {
523
- await this.simulateDelay();
524
-
525
- // Determine how many positions to generate based on limit parameter
526
- const limit = filter?.limit || 100;
527
- const maxLimit = Math.min(limit, 1000); // Cap at 1000
528
- const count = Math.max(1, maxLimit); // At least 1
529
-
530
- // Generate diverse mock positions based on requested count
531
- const mockPositions: BrokerDataPosition[] = this.generateMockPositions(count);
532
-
533
- // Apply filters if provided
534
- let filteredPositions = mockPositions;
535
- if (filter) {
536
- filteredPositions = this.applyBrokerPositionFilters(mockPositions, filter);
537
- }
538
-
539
- return {
540
- data: filteredPositions,
541
- };
542
- }
543
-
544
- async mockGetBrokerBalances(filter?: BalancesFilter): Promise<{ data: BrokerBalance[] }> {
545
- await this.simulateDelay();
546
-
547
- // Determine how many balances to generate based on limit parameter
548
- const limit = filter?.limit || 100;
549
- const maxLimit = Math.min(limit, 1000); // Cap at 1000
550
-
551
- const mockBalances: BrokerBalance[] = [];
552
- for (let i = 0; i < maxLimit; i++) {
553
- const totalCashValue = Math.random() * 100000 + 10000; // $10k - $110k
554
- const netLiquidationValue = totalCashValue * (0.8 + Math.random() * 0.4); // ±20% variation
555
- const initialMargin = netLiquidationValue * 0.1; // 10% of net liquidation
556
- const maintenanceMargin = initialMargin * 0.8; // 80% of initial margin
557
- const availableToWithdraw = totalCashValue * 0.9; // 90% of cash available
558
- const totalRealizedPnl = (Math.random() - 0.5) * 10000; // -$5k to +$5k
559
-
560
- const balance: BrokerBalance = {
561
- id: `balance_${i + 1}`,
562
- account_id: `account_${Math.floor(Math.random() * 3) + 1}`,
563
- total_cash_value: totalCashValue,
564
- net_liquidation_value: netLiquidationValue,
565
- initial_margin: initialMargin,
566
- maintenance_margin: maintenanceMargin,
567
- available_to_withdraw: availableToWithdraw,
568
- total_realized_pnl: totalRealizedPnl,
569
- balance_created_at: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000).toISOString(),
570
- balance_updated_at: new Date().toISOString(),
571
- is_end_of_day_snapshot: Math.random() > 0.7, // 30% chance of being EOD snapshot
572
- raw_payload: {
573
- broker_specific_data: {
574
- margin_ratio: netLiquidationValue / initialMargin,
575
- day_trading_buying_power: availableToWithdraw * 4,
576
- overnight_buying_power: availableToWithdraw * 2,
577
- },
578
- },
579
- created_at: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString(),
580
- updated_at: new Date().toISOString(),
581
- };
582
-
583
- mockBalances.push(balance);
584
- }
585
-
586
- // Apply filters if provided
587
- let filteredBalances = mockBalances;
588
- if (filter) {
589
- filteredBalances = this.applyBrokerBalanceFilters(mockBalances, filter);
590
- }
591
-
592
- return {
593
- data: filteredBalances,
594
- };
595
- }
596
-
597
- async mockGetBrokerDataAccounts(filter?: AccountsFilter): Promise<{ data: BrokerAccount[] }> {
598
- await this.simulateDelay();
599
-
600
- // Determine how many accounts to generate based on limit parameter
601
- const limit = filter?.limit || 100;
602
- const maxLimit = Math.min(limit, 1000); // Cap at 1000
603
- const count = Math.max(1, maxLimit); // At least 1
604
-
605
- // Generate diverse mock accounts based on requested count
606
- const mockAccounts: BrokerAccount[] = this.generateMockAccounts(count);
607
-
608
- // Apply filters if provided
609
- let filteredAccounts = mockAccounts;
610
- if (filter) {
611
- filteredAccounts = this.applyBrokerAccountFilters(mockAccounts, filter);
612
- }
613
-
614
- return {
615
- data: filteredAccounts,
616
- };
617
- }
618
-
619
- async mockPlaceOrder(order: BrokerOrderParams): Promise<OrderResponse> {
620
- // Simulate API delay
621
- await new Promise(resolve => setTimeout(resolve, 100));
622
-
623
- // Generate a mock order ID
624
- const orderId = `mock_order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
625
-
626
- return {
627
- success: true,
628
- response_data: {
629
- orderId: orderId,
630
- status: 'pending',
631
- broker: order.broker,
632
- accountNumber: order.accountNumber,
633
- symbol: order.symbol,
634
- orderType: order.orderType,
635
- assetType: order.assetType,
636
- action: order.action,
637
- quantity: order.orderQty,
638
- price: order.price,
639
- stopPrice: order.stopPrice,
640
- timeInForce: order.timeInForce,
641
- },
642
- message: 'Order placed successfully',
643
- status_code: 200,
644
- };
645
- }
646
-
647
- // Utility methods
648
-
649
- /**
650
- * Get stored session data
651
- */
652
- getSessionData(sessionId: string): any {
653
- return this.sessionData.get(sessionId);
654
- }
655
-
656
- /**
657
- * Get stored user token
658
- */
659
- getUserToken(sessionId: string): UserToken | undefined {
660
- return this.userTokens.get(sessionId);
661
- }
662
-
663
- /**
664
- * Clear all stored data
665
- */
666
- clearData(): void {
667
- this.sessionData.clear();
668
- this.userTokens.clear();
669
- }
670
-
671
- /**
672
- * Update configuration
673
- */
674
- updateConfig(config: Partial<MockConfig>): void {
675
- this.config = { ...this.config, ...config };
676
- }
677
-
678
- setScenario(scenario: MockScenario) {
679
- this.config.scenario = scenario;
680
- }
681
- getScenario(): MockScenario {
682
- return this.config.scenario || 'success';
683
- }
684
-
685
- // Helper methods to apply filters
686
- private applyOrderFilters(orders: Order[], filter: OrdersFilter): Order[] {
687
- return orders.filter(order => {
688
- if (filter.symbol && order.symbol !== filter.symbol) return false;
689
- if (filter.side && order.side !== filter.side) return false;
690
- return true;
691
- });
692
- }
693
-
694
- private applyBrokerOrderFilters(
695
- orders: BrokerDataOrder[],
696
- filter: OrdersFilter
697
- ): BrokerDataOrder[] {
698
- return orders.filter(order => {
699
- if (filter.broker_id && order.broker_id !== filter.broker_id) return false;
700
- if (filter.connection_id && order.connection_id !== filter.connection_id) return false;
701
- if (filter.account_id && order.account_id !== filter.account_id) return false;
702
- if (filter.symbol && order.symbol !== filter.symbol) return false;
703
- if (filter.status && order.status !== filter.status) return false;
704
- if (filter.side && order.side !== filter.side) return false;
705
- if (filter.asset_type && order.asset_type !== filter.asset_type) return false;
706
- if (filter.created_after && new Date(order.created_at) < new Date(filter.created_after))
707
- return false;
708
- if (filter.created_before && new Date(order.created_at) > new Date(filter.created_before))
709
- return false;
710
- return true;
711
- });
712
- }
713
-
714
- private applyBrokerPositionFilters(
715
- positions: BrokerDataPosition[],
716
- filter: PositionsFilter
717
- ): BrokerDataPosition[] {
718
- return positions.filter(position => {
719
- if (filter.broker_id && position.broker_id !== filter.broker_id) return false;
720
- if (filter.connection_id && position.connection_id !== filter.connection_id) return false;
721
- if (filter.account_id && position.account_id !== filter.account_id) return false;
722
- if (filter.symbol && position.symbol !== filter.symbol) return false;
723
- if (filter.side && position.side !== filter.side) return false;
724
- if (filter.asset_type && position.asset_type !== filter.asset_type) return false;
725
- if (filter.position_status && position.position_status !== filter.position_status)
726
- return false;
727
- if (filter.updated_after && new Date(position.updated_at) < new Date(filter.updated_after))
728
- return false;
729
- if (filter.updated_before && new Date(position.updated_at) > new Date(filter.updated_before))
730
- return false;
731
- return true;
732
- });
733
- }
734
-
735
- private applyBrokerAccountFilters(
736
- accounts: BrokerAccount[],
737
- filter: AccountsFilter
738
- ): BrokerAccount[] {
739
- return accounts.filter(account => {
740
- if (filter.broker_id && account.user_broker_connection_id !== filter.connection_id) return false;
741
- if (filter.connection_id && account.user_broker_connection_id !== filter.connection_id) return false;
742
- if (filter.account_type && account.account_type !== filter.account_type) return false;
743
- if (filter.status && account.status !== filter.status) return false;
744
- if (filter.currency && account.currency !== filter.currency) return false;
745
- return true;
746
- });
747
- }
748
-
749
- private applyBrokerBalanceFilters(
750
- balances: BrokerBalance[],
751
- filter: BalancesFilter
752
- ): BrokerBalance[] {
753
- return balances.filter(balance => {
754
- if (filter.account_id && balance.account_id !== filter.account_id) return false;
755
- if (filter.is_end_of_day_snapshot !== undefined && balance.is_end_of_day_snapshot !== filter.is_end_of_day_snapshot) return false;
756
- if (filter.balance_created_after && balance.balance_created_at && new Date(balance.balance_created_at) < new Date(filter.balance_created_after)) return false;
757
- if (filter.balance_created_before && balance.balance_created_at && new Date(balance.balance_created_at) > new Date(filter.balance_created_before)) return false;
758
- return true;
759
- });
760
- }
761
-
762
- /**
763
- * Generate mock orders with diverse data
764
- */
765
- private generateMockOrders(count: number): BrokerDataOrder[] {
766
- const orders: BrokerDataOrder[] = [];
767
- const symbols = [
768
- 'AAPL',
769
- 'TSLA',
770
- 'MSFT',
771
- 'GOOGL',
772
- 'AMZN',
773
- 'META',
774
- 'NVDA',
775
- 'NFLX',
776
- 'SPY',
777
- 'QQQ',
778
- 'IWM',
779
- 'VTI',
780
- 'BTC',
781
- 'ETH',
782
- 'ADA',
783
- ];
784
- const brokers = ['robinhood', 'alpaca', 'tasty_trade', 'ninja_trader'];
785
- const orderTypes = ['market', 'limit', 'stop', 'stop_limit'] as const;
786
- const sides = ['buy', 'sell'] as const;
787
- const statuses = ['filled', 'pending', 'cancelled', 'rejected', 'partially_filled'] as const;
788
- const assetTypes = ['stock', 'option', 'crypto', 'future'] as const;
789
-
790
- for (let i = 0; i < count; i++) {
791
- const symbol = symbols[Math.floor(Math.random() * symbols.length)];
792
- const broker = brokers[Math.floor(Math.random() * brokers.length)];
793
- const orderType = orderTypes[Math.floor(Math.random() * orderTypes.length)];
794
- const side = sides[Math.floor(Math.random() * sides.length)];
795
- const status = statuses[Math.floor(Math.random() * statuses.length)];
796
- const assetType = assetTypes[Math.floor(Math.random() * assetTypes.length)];
797
- const quantity = Math.floor(Math.random() * 1000) + 1;
798
- const price = Math.random() * 500 + 10;
799
- const isFilled = status === 'filled' || status === 'partially_filled';
800
- const filledQuantity = isFilled ? Math.floor(quantity * (0.5 + Math.random() * 0.5)) : 0;
801
- const filledAvgPrice = isFilled ? price * (0.95 + Math.random() * 0.1) : 0;
802
-
803
- orders.push({
804
- id: uuidv4(),
805
- broker_id: broker,
806
- connection_id: uuidv4(),
807
- account_id: `account_${Math.floor(Math.random() * 1000000)}`,
808
- order_id: `order_${String(i + 1).padStart(6, '0')}`,
809
- symbol,
810
- order_type: orderType,
811
- side,
812
- quantity,
813
- price,
814
- status,
815
- asset_type: assetType,
816
- created_at: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString(), // Random date within last 30 days
817
- updated_at: new Date().toISOString(),
818
- filled_at: isFilled ? new Date().toISOString() : undefined,
819
- filled_quantity: filledQuantity,
820
- filled_avg_price: filledAvgPrice,
821
- });
822
- }
823
-
824
- return orders;
825
- }
826
-
827
- /**
828
- * Generate mock positions with diverse data
829
- */
830
- private generateMockPositions(count: number): BrokerDataPosition[] {
831
- const positions: BrokerDataPosition[] = [];
832
- const symbols = [
833
- 'AAPL',
834
- 'TSLA',
835
- 'MSFT',
836
- 'GOOGL',
837
- 'AMZN',
838
- 'META',
839
- 'NVDA',
840
- 'NFLX',
841
- 'SPY',
842
- 'QQQ',
843
- 'IWM',
844
- 'VTI',
845
- 'BTC',
846
- 'ETH',
847
- 'ADA',
848
- ];
849
- const brokers = ['robinhood', 'alpaca', 'tasty_trade', 'ninja_trader'];
850
- const sides = ['long', 'short'] as const;
851
- const assetTypes = ['stock', 'option', 'crypto', 'future'] as const;
852
- const positionStatuses = ['open', 'closed'] as const;
853
-
854
- for (let i = 0; i < count; i++) {
855
- const symbol = symbols[Math.floor(Math.random() * symbols.length)];
856
- const broker = brokers[Math.floor(Math.random() * brokers.length)];
857
- const side = sides[Math.floor(Math.random() * sides.length)];
858
- const assetType = assetTypes[Math.floor(Math.random() * assetTypes.length)];
859
- const positionStatus = positionStatuses[Math.floor(Math.random() * positionStatuses.length)];
860
- const quantity = Math.floor(Math.random() * 1000) + 1;
861
- const averagePrice = Math.random() * 500 + 10;
862
- const currentPrice = averagePrice * (0.8 + Math.random() * 0.4); // ±20% variation
863
- const marketValue = quantity * currentPrice;
864
- const costBasis = quantity * averagePrice;
865
- const unrealizedGainLoss = marketValue - costBasis;
866
- const unrealizedGainLossPercent = (unrealizedGainLoss / costBasis) * 100;
867
-
868
- positions.push({
869
- id: uuidv4(),
870
- broker_id: broker,
871
- connection_id: uuidv4(),
872
- account_id: `account_${Math.floor(Math.random() * 1000000)}`,
873
- symbol,
874
- asset_type: assetType,
875
- side,
876
- quantity,
877
- average_price: averagePrice,
878
- market_value: marketValue,
879
- cost_basis: costBasis,
880
- unrealized_gain_loss: unrealizedGainLoss,
881
- unrealized_gain_loss_percent: unrealizedGainLossPercent,
882
- current_price: currentPrice,
883
- last_price: currentPrice,
884
- last_price_updated_at: new Date().toISOString(),
885
- position_status: positionStatus,
886
- created_at: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString(), // Random date within last 30 days
887
- updated_at: new Date().toISOString(),
888
- });
889
- }
890
-
891
- return positions;
892
- }
893
-
894
- /**
895
- * Generate mock accounts with diverse data
896
- */
897
- private generateMockAccounts(count: number): BrokerAccount[] {
898
- const accounts: BrokerAccount[] = [];
899
- const brokers = ['robinhood', 'alpaca', 'tasty_trade', 'ninja_trader'];
900
- const accountTypes = ['margin', 'cash', 'crypto_wallet', 'live', 'sim'] as const;
901
- const statuses = ['active', 'inactive'] as const;
902
- const currencies = ['USD', 'EUR', 'GBP', 'CAD'];
903
- const accountNames = [
904
- 'Individual Account',
905
- 'Paper Trading',
906
- 'Retirement Account',
907
- 'Crypto Wallet',
908
- 'Margin Account',
909
- 'Cash Account',
910
- 'Trading Account',
911
- 'Investment Account',
912
- ];
913
-
914
- for (let i = 0; i < count; i++) {
915
- const broker = brokers[Math.floor(Math.random() * brokers.length)];
916
- const accountType = accountTypes[Math.floor(Math.random() * accountTypes.length)];
917
- const status = statuses[Math.floor(Math.random() * statuses.length)];
918
- const currency = currencies[Math.floor(Math.random() * currencies.length)];
919
- const accountName = accountNames[Math.floor(Math.random() * accountNames.length)];
920
-
921
- const cashBalance = Math.random() * 100000 + 1000;
922
- const buyingPower = cashBalance * (1 + Math.random() * 2); // 1-3x cash balance
923
-
924
- accounts.push({
925
- id: uuidv4(),
926
- user_broker_connection_id: uuidv4(),
927
- broker_provided_account_id: `account_${Math.floor(Math.random() * 1000000)}`,
928
- account_name: `${accountName} ${i + 1}`,
929
- account_type: accountType,
930
- currency,
931
- cash_balance: cashBalance,
932
- buying_power: buyingPower,
933
- status,
934
- created_at: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString(), // Random date within last year
935
- updated_at: new Date().toISOString(),
936
- last_synced_at: new Date().toISOString(),
937
- positions_synced_at: new Date().toISOString(),
938
- orders_synced_at: new Date().toISOString(),
939
- balances_synced_at: new Date().toISOString(),
940
- account_created_at: new Date(Date.now() - Math.random() * 730 * 24 * 60 * 60 * 1000).toISOString(), // Random date within last 2 years
941
- account_updated_at: new Date().toISOString(),
942
- account_first_trade_at: new Date(Date.now() - Math.random() * 500 * 24 * 60 * 60 * 1000).toISOString(), // Random date within last 500 days
943
- });
944
- }
945
-
946
- return accounts;
947
- }
948
-
949
- /**
950
- * Mock disconnect company method
951
- * @param connectionId - The connection ID to disconnect
952
- * @returns Promise with mock disconnect response
953
- */
954
- async mockDisconnectCompany(connectionId: string): Promise<DisconnectCompanyResponse> {
955
- // Simulate API delay
956
- await new Promise(resolve => setTimeout(resolve, 200));
957
-
958
- // Randomly decide if this will delete the connection or just remove company access
959
- const willDeleteConnection = Math.random() > 0.5;
960
-
961
- if (willDeleteConnection) {
962
- return {
963
- success: true,
964
- response_data: {
965
- connection_id: connectionId,
966
- action: 'connection_deleted',
967
- message: 'Company access removed and connection deleted (no companies remaining)',
968
- },
969
- message: 'Company access removed and connection deleted (no companies remaining)',
970
- status_code: 200,
971
- };
972
- } else {
973
- return {
974
- success: true,
975
- response_data: {
976
- connection_id: connectionId,
977
- action: 'company_access_removed',
978
- remaining_companies: Math.floor(Math.random() * 5) + 1, // 1-5 remaining companies
979
- message: 'Company access removed from connection',
980
- },
981
- message: 'Company access removed from connection',
982
- status_code: 200,
983
- };
984
- }
985
- }
986
- }