@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.
- package/CHANGELOG.md +16 -0
- package/LICENSE +39 -0
- package/README.md +54 -425
- package/dist/index.d.ts +7329 -1579
- package/dist/index.js +8110 -6114
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8047 -6085
- package/dist/index.mjs.map +1 -1
- package/package.json +77 -33
- package/dist/types/core/client/ApiClient.d.ts +0 -270
- package/dist/types/core/client/FinaticConnect.d.ts +0 -332
- package/dist/types/core/portal/PortalUI.d.ts +0 -37
- package/dist/types/index.d.ts +0 -12
- package/dist/types/lib/logger/index.d.ts +0 -2
- package/dist/types/lib/logger/logger.d.ts +0 -4
- package/dist/types/lib/logger/logger.types.d.ts +0 -28
- package/dist/types/mocks/MockApiClient.d.ts +0 -171
- package/dist/types/mocks/MockDataProvider.d.ts +0 -139
- package/dist/types/mocks/MockFactory.d.ts +0 -53
- package/dist/types/mocks/utils.d.ts +0 -24
- package/dist/types/themes/portalPresets.d.ts +0 -9
- package/dist/types/types/api/auth.d.ts +0 -93
- package/dist/types/types/api/broker.d.ts +0 -421
- package/dist/types/types/api/core.d.ts +0 -46
- package/dist/types/types/api/errors.d.ts +0 -31
- package/dist/types/types/api/orders.d.ts +0 -39
- package/dist/types/types/api/portfolio.d.ts +0 -55
- package/dist/types/types/common/pagination.d.ts +0 -33
- package/dist/types/types/connect.d.ts +0 -58
- package/dist/types/types/index.d.ts +0 -13
- package/dist/types/types/portal.d.ts +0 -204
- package/dist/types/types/ui/theme.d.ts +0 -104
- package/dist/types/utils/brokerUtils.d.ts +0 -30
- package/dist/types/utils/errors.d.ts +0 -45
- package/dist/types/utils/events.d.ts +0 -12
- package/dist/types/utils/themeUtils.d.ts +0 -34
- package/src/core/client/ApiClient.ts +0 -2004
- package/src/core/client/FinaticConnect.ts +0 -1606
- package/src/core/portal/PortalUI.ts +0 -335
- package/src/index.d.ts +0 -23
- package/src/index.ts +0 -100
- package/src/lib/logger/index.ts +0 -3
- package/src/lib/logger/logger.ts +0 -332
- package/src/lib/logger/logger.types.ts +0 -34
- package/src/mocks/MockApiClient.ts +0 -1058
- package/src/mocks/MockDataProvider.ts +0 -986
- package/src/mocks/MockFactory.ts +0 -97
- package/src/mocks/utils.ts +0 -133
- package/src/themes/portalPresets.ts +0 -1307
- package/src/types/api/auth.ts +0 -112
- package/src/types/api/broker.ts +0 -461
- package/src/types/api/core.ts +0 -53
- package/src/types/api/errors.ts +0 -35
- package/src/types/api/orders.ts +0 -45
- package/src/types/api/portfolio.ts +0 -59
- package/src/types/common/pagination.ts +0 -164
- package/src/types/connect.ts +0 -56
- package/src/types/index.ts +0 -25
- package/src/types/portal.ts +0 -214
- package/src/types/ui/theme.ts +0 -105
- package/src/utils/brokerUtils.ts +0 -104
- package/src/utils/errors.ts +0 -104
- package/src/utils/events.ts +0 -66
- 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
|
-
}
|