@one_deploy/sdk 1.0.7 → 1.2.0

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 (106) hide show
  1. package/README.md +339 -0
  2. package/dist/ForexPoolDataGenerator--__twRwl.d.mts +76 -0
  3. package/dist/ForexPoolDataGenerator-eUgwsU_B.d.ts +76 -0
  4. package/dist/OneForexTradeHistory-TlKxjbFF.d.ts +250 -0
  5. package/dist/OneForexTradeHistory-iDySMcw0.d.mts +250 -0
  6. package/dist/components/index.d.mts +539 -0
  7. package/dist/components/index.d.ts +539 -0
  8. package/dist/components/index.js +7295 -0
  9. package/dist/components/index.js.map +1 -0
  10. package/dist/components/index.mjs +7243 -0
  11. package/dist/components/index.mjs.map +1 -0
  12. package/dist/config/index.d.mts +1 -0
  13. package/dist/config/index.d.ts +1 -0
  14. package/dist/console-BfTMA7ah.d.mts +504 -0
  15. package/dist/console-BfTMA7ah.d.ts +504 -0
  16. package/dist/hooks/index.d.mts +323 -1
  17. package/dist/hooks/index.d.ts +323 -1
  18. package/dist/hooks/index.js +3223 -0
  19. package/dist/hooks/index.js.map +1 -1
  20. package/dist/hooks/index.mjs +3204 -1
  21. package/dist/hooks/index.mjs.map +1 -1
  22. package/dist/index.d.mts +18 -352
  23. package/dist/index.d.ts +18 -352
  24. package/dist/index.js +8646 -574
  25. package/dist/index.js.map +1 -1
  26. package/dist/index.mjs +8449 -432
  27. package/dist/index.mjs.map +1 -1
  28. package/dist/providers/index.d.mts +31 -31
  29. package/dist/providers/index.d.ts +31 -31
  30. package/dist/providers/index.js +140 -153
  31. package/dist/providers/index.js.map +1 -1
  32. package/dist/providers/index.mjs +100 -109
  33. package/dist/providers/index.mjs.map +1 -1
  34. package/dist/react-native.d.mts +8 -144
  35. package/dist/react-native.d.ts +8 -144
  36. package/dist/react-native.js +2640 -689
  37. package/dist/react-native.js.map +1 -1
  38. package/dist/react-native.mjs +2610 -691
  39. package/dist/react-native.mjs.map +1 -1
  40. package/dist/services/index.d.mts +85 -4
  41. package/dist/services/index.d.ts +85 -4
  42. package/dist/services/index.js +1621 -0
  43. package/dist/services/index.js.map +1 -1
  44. package/dist/services/index.mjs +1619 -1
  45. package/dist/services/index.mjs.map +1 -1
  46. package/dist/types/index.d.mts +203 -1
  47. package/dist/types/index.d.ts +203 -1
  48. package/dist/types/index.js +275 -0
  49. package/dist/types/index.js.map +1 -1
  50. package/dist/types/index.mjs +251 -0
  51. package/dist/types/index.mjs.map +1 -1
  52. package/dist/useForexTrading-BleeSor8.d.mts +80 -0
  53. package/dist/useForexTrading-ZgW_G40Q.d.ts +80 -0
  54. package/package.json +9 -2
  55. package/src/components/OneConnectButton.tsx +24 -1
  56. package/src/components/OneNFTGallery.tsx +13 -7
  57. package/src/components/OneOfframpWidget.tsx +4 -3
  58. package/src/components/OnePayWidget.tsx +10 -1
  59. package/src/components/OneSendWidget.tsx +3 -3
  60. package/src/components/OneSwapWidget.tsx +4 -4
  61. package/src/components/OneTransactionButton.tsx +28 -3
  62. package/src/components/OneWalletBalance.tsx +1 -1
  63. package/src/components/ai/OneChainSelector.tsx +63 -336
  64. package/src/components/ai/OneForexCapitalSplit.tsx +112 -0
  65. package/src/components/ai/OneForexConsoleView.tsx +90 -0
  66. package/src/components/ai/OneForexPairSelector.tsx +101 -0
  67. package/src/components/ai/OneForexPoolCard.tsx +105 -0
  68. package/src/components/ai/OneForexTradeHistory.tsx +107 -0
  69. package/src/components/ai/OnePairSelector.tsx +77 -434
  70. package/src/components/ai/console/OneAIQuantConsole.tsx +423 -0
  71. package/src/components/ai/console/OneAgentCard.tsx +383 -0
  72. package/src/components/ai/console/OneAgentConsole.tsx +469 -0
  73. package/src/components/ai/console/OneDecisionTimeline.tsx +433 -0
  74. package/src/components/ai/console/OneMetricsDashboard.tsx +493 -0
  75. package/src/components/ai/console/OnePositionCard.tsx +406 -0
  76. package/src/components/ai/console/OnePositionDetail.tsx +600 -0
  77. package/src/components/ai/console/OneRiskIndicator.tsx +464 -0
  78. package/src/components/ai/console/OneTradingConsole.tsx +660 -0
  79. package/src/components/ai/console/index.ts +17 -0
  80. package/src/components/ai/index.ts +10 -0
  81. package/src/hooks/index.ts +46 -0
  82. package/src/hooks/useAIDecisions.ts +280 -0
  83. package/src/hooks/useAIPositions.ts +349 -0
  84. package/src/hooks/useAIQuantConsole.ts +283 -0
  85. package/src/hooks/useAIRiskStatus.ts +276 -0
  86. package/src/hooks/useAITrading.ts +190 -0
  87. package/src/hooks/useBotSimulation.ts +201 -0
  88. package/src/hooks/useForexTrading.ts +430 -0
  89. package/src/hooks/useTradingConsole.ts +243 -0
  90. package/src/index.ts +123 -5
  91. package/src/providers/OneProvider.tsx +181 -5
  92. package/src/providers/index.ts +22 -8
  93. package/src/react-native.ts +41 -0
  94. package/src/services/forex/BotSimulationEngine.ts +968 -0
  95. package/src/services/forex/ForexPoolDataGenerator.ts +542 -0
  96. package/src/services/forex/ForexSimulationEngine.ts +482 -0
  97. package/src/services/forex/index.ts +21 -0
  98. package/src/services/index.ts +16 -0
  99. package/src/types/aiTrading.ts +151 -0
  100. package/src/types/console.ts +380 -0
  101. package/src/types/forex.ts +282 -0
  102. package/src/types/index.ts +106 -0
  103. package/dist/price-CgqXPnT3.d.ts +0 -13
  104. package/dist/price-ClbLHHjv.d.mts +0 -13
  105. package/dist/supabase-BT0c7q9e.d.mts +0 -82
  106. package/dist/supabase-BT0c7q9e.d.ts +0 -82
@@ -0,0 +1,542 @@
1
+ // ForexPoolDataGenerator.ts - Generates historical pool snapshots, transactions, and trade history
2
+ // Singleton service following existing pattern (like ForexSimulationEngine)
3
+
4
+ import type {
5
+ ForexPoolType,
6
+ ForexPoolDailySnapshot,
7
+ ForexPoolTransaction,
8
+ ForexPoolTransactionType,
9
+ ForexTradeRecord,
10
+ } from '../../types/forex';
11
+ import { FOREX_CURRENCY_PAIRS, FOREX_POOL_DEFAULTS, FOREX_AGENT } from '../../types/forex';
12
+
13
+ // ── Helpers ──────────────────────────────────────────────────────────────────
14
+
15
+ function rand(min: number, max: number): number {
16
+ return min + Math.random() * (max - min);
17
+ }
18
+
19
+ function randInt(min: number, max: number): number {
20
+ return Math.floor(rand(min, max + 1));
21
+ }
22
+
23
+ function pick<T>(arr: T[]): T {
24
+ return arr[Math.floor(Math.random() * arr.length)];
25
+ }
26
+
27
+ function genTxHash(): string {
28
+ const chars = '0123456789abcdef';
29
+ let hash = '0x';
30
+ for (let i = 0; i < 64; i++) hash += chars[Math.floor(Math.random() * 16)];
31
+ return hash;
32
+ }
33
+
34
+ function formatDate(d: Date): string {
35
+ return d.toISOString().split('T')[0];
36
+ }
37
+
38
+ let _idCounter = 0;
39
+ function genId(prefix: string): string {
40
+ return `${prefix}_${Date.now().toString(36)}_${(++_idCounter).toString(36)}`;
41
+ }
42
+
43
+ // ── Pool Parameters ──────────────────────────────────────────────────────────
44
+
45
+ interface PoolParams {
46
+ meanDailyPnlPct: number;
47
+ stddevPnlPct: number;
48
+ utilizationMin: number;
49
+ utilizationMax: number;
50
+ depositsPerDay: [number, number];
51
+ withdrawalsPerDay: [number, number];
52
+ profitDistPerDay: number;
53
+ avgDepositSize: number;
54
+ avgWithdrawalSize: number;
55
+ }
56
+
57
+ const POOL_PARAMS: Record<ForexPoolType, PoolParams> = {
58
+ clearing: {
59
+ meanDailyPnlPct: 0.00035,
60
+ stddevPnlPct: 0.00015,
61
+ utilizationMin: 0.70,
62
+ utilizationMax: 0.85,
63
+ depositsPerDay: [15, 25],
64
+ withdrawalsPerDay: [8, 12],
65
+ profitDistPerDay: 5,
66
+ avgDepositSize: 25000,
67
+ avgWithdrawalSize: 18000,
68
+ },
69
+ hedging: {
70
+ meanDailyPnlPct: 0.00022,
71
+ stddevPnlPct: 0.00020,
72
+ utilizationMin: 0.55,
73
+ utilizationMax: 0.75,
74
+ depositsPerDay: [8, 15],
75
+ withdrawalsPerDay: [5, 8],
76
+ profitDistPerDay: 3,
77
+ avgDepositSize: 18000,
78
+ avgWithdrawalSize: 12000,
79
+ },
80
+ insurance: {
81
+ meanDailyPnlPct: 0.00013,
82
+ stddevPnlPct: 0.00008,
83
+ utilizationMin: 0.30,
84
+ utilizationMax: 0.50,
85
+ depositsPerDay: [5, 10],
86
+ withdrawalsPerDay: [3, 5],
87
+ profitDistPerDay: 2,
88
+ avgDepositSize: 12000,
89
+ avgWithdrawalSize: 8000,
90
+ },
91
+ };
92
+
93
+ // ── Generator Class ──────────────────────────────────────────────────────────
94
+
95
+ class ForexPoolDataGeneratorClass {
96
+ private snapshotCache: Record<ForexPoolType, ForexPoolDailySnapshot[]> | null = null;
97
+ private transactionCache: ForexPoolTransaction[] | null = null;
98
+ private baseBlockNumber = 19_500_000;
99
+
100
+ // ── Public API ─────────────────────────────────────────────────────────
101
+
102
+ generateAllSnapshots(): Record<ForexPoolType, ForexPoolDailySnapshot[]> {
103
+ if (this.snapshotCache) return this.snapshotCache;
104
+
105
+ const result: Record<ForexPoolType, ForexPoolDailySnapshot[]> = {
106
+ clearing: [],
107
+ hedging: [],
108
+ insurance: [],
109
+ };
110
+
111
+ for (const pool of FOREX_POOL_DEFAULTS) {
112
+ result[pool.id] = this.generatePoolSnapshots(pool.id, pool.totalSize);
113
+ }
114
+
115
+ this.snapshotCache = result;
116
+ return result;
117
+ }
118
+
119
+ generateAllTransactions(): ForexPoolTransaction[] {
120
+ if (this.transactionCache) return this.transactionCache;
121
+
122
+ const allTx: ForexPoolTransaction[] = [];
123
+
124
+ for (const pool of FOREX_POOL_DEFAULTS) {
125
+ const snapshots = this.snapshotCache?.[pool.id] || this.generatePoolSnapshots(pool.id, pool.totalSize);
126
+ const txs = this.generatePoolTransactions(pool.id, snapshots);
127
+ allTx.push(...txs);
128
+ }
129
+
130
+ // Add inter-pool transfers (~2-3/week = ~17-26 over 60 days)
131
+ const now = new Date();
132
+ for (let i = 0; i < 60; i++) {
133
+ const day = new Date(now);
134
+ day.setDate(day.getDate() - (60 - i));
135
+ const dayOfWeek = day.getDay();
136
+
137
+ // Inter-pool transfers: ~2-3 per week
138
+ if (dayOfWeek === 2 || dayOfWeek === 4 || (dayOfWeek === 5 && Math.random() < 0.4)) {
139
+ const fromPool = pick(['clearing', 'hedging', 'insurance'] as ForexPoolType[]);
140
+ const toOptions = (['clearing', 'hedging', 'insurance'] as ForexPoolType[]).filter(p => p !== fromPool);
141
+ const toPool = pick(toOptions);
142
+ const amount = rand(50000, 200000);
143
+ const ts = day.getTime() + randInt(10, 18) * 3600000;
144
+
145
+ allTx.push({
146
+ id: genId('ipt'),
147
+ poolId: fromPool,
148
+ type: 'inter_pool_transfer',
149
+ amount: -amount,
150
+ balanceBefore: 0,
151
+ balanceAfter: 0,
152
+ txHash: genTxHash(),
153
+ blockNumber: this.baseBlockNumber + i * 7200 + randInt(0, 100),
154
+ timestamp: ts,
155
+ description: `Transfer to ${toPool} pool`,
156
+ });
157
+
158
+ allTx.push({
159
+ id: genId('ipt'),
160
+ poolId: toPool,
161
+ type: 'inter_pool_transfer',
162
+ amount: amount,
163
+ balanceBefore: 0,
164
+ balanceAfter: 0,
165
+ txHash: genTxHash(),
166
+ blockNumber: this.baseBlockNumber + i * 7200 + randInt(100, 200),
167
+ timestamp: ts + 5000,
168
+ description: `Transfer from ${fromPool} pool`,
169
+ });
170
+ }
171
+
172
+ // Reserve rebalance: ~1/week
173
+ if (dayOfWeek === 3 && Math.random() < 0.85) {
174
+ const pool = pick(FOREX_POOL_DEFAULTS);
175
+ const rebalanceAmt = rand(20000, 100000);
176
+ const ts = day.getTime() + randInt(2, 6) * 3600000;
177
+ allTx.push({
178
+ id: genId('rrb'),
179
+ poolId: pool.id,
180
+ type: 'reserve_rebalance',
181
+ amount: Math.random() < 0.5 ? rebalanceAmt : -rebalanceAmt,
182
+ balanceBefore: 0,
183
+ balanceAfter: 0,
184
+ txHash: genTxHash(),
185
+ blockNumber: this.baseBlockNumber + i * 7200 + randInt(200, 300),
186
+ timestamp: ts,
187
+ description: 'Automated reserve rebalance',
188
+ });
189
+ }
190
+ }
191
+
192
+ // Sort all transactions by timestamp
193
+ allTx.sort((a, b) => a.timestamp - b.timestamp);
194
+
195
+ // Assign running balances per pool
196
+ const runningBalance: Record<ForexPoolType, number> = {
197
+ clearing: FOREX_POOL_DEFAULTS[0].totalSize * 0.85,
198
+ hedging: FOREX_POOL_DEFAULTS[1].totalSize * 0.82,
199
+ insurance: FOREX_POOL_DEFAULTS[2].totalSize * 0.88,
200
+ };
201
+
202
+ for (const tx of allTx) {
203
+ tx.balanceBefore = runningBalance[tx.poolId];
204
+ runningBalance[tx.poolId] += tx.amount;
205
+ tx.balanceAfter = runningBalance[tx.poolId];
206
+ }
207
+
208
+ this.transactionCache = allTx;
209
+ return allTx;
210
+ }
211
+
212
+ generateTradeHistory(
213
+ investmentAmount: number,
214
+ startDate: string,
215
+ selectedPairs: string[],
216
+ ): ForexTradeRecord[] {
217
+ const trades: ForexTradeRecord[] = [];
218
+ const start = new Date(startDate);
219
+ const now = new Date();
220
+ const dayCount = Math.min(60, Math.ceil((now.getTime() - start.getTime()) / 86400000));
221
+ let cumulativePnl = 0;
222
+ let blockNum = this.baseBlockNumber + randInt(0, 5000);
223
+
224
+ for (let d = 0; d < dayCount; d++) {
225
+ const day = new Date(start);
226
+ day.setDate(day.getDate() + d);
227
+ const dayOfWeek = day.getDay();
228
+
229
+ // Fewer trades on weekends
230
+ const baseCount = (dayOfWeek === 0 || dayOfWeek === 6) ? randInt(1, 2) : randInt(3, 8);
231
+
232
+ for (let t = 0; t < baseCount; t++) {
233
+ const pair = FOREX_CURRENCY_PAIRS.find(p => p.id === pick(selectedPairs)) || pick(FOREX_CURRENCY_PAIRS);
234
+ const side: 'BUY' | 'SELL' = Math.random() > 0.5 ? 'BUY' : 'SELL';
235
+ const lots = parseFloat(rand(0.1, 2.5).toFixed(2));
236
+ const rfqPrice = pair.basePrice * (1 + rand(-0.003, 0.003));
237
+ const quoteSpread = rand(0.5, 2.0) * pair.pipSize;
238
+ const quotePrice = side === 'BUY' ? rfqPrice + quoteSpread : rfqPrice - quoteSpread;
239
+
240
+ // 85% match rate
241
+ const matched = Math.random() < 0.85;
242
+ if (!matched) continue;
243
+
244
+ const matchPrice = quotePrice * (1 + rand(-0.00005, 0.00005));
245
+ const settlePrice = matchPrice * (1 + rand(-0.00002, 0.00002));
246
+ const pips = ((settlePrice - rfqPrice) / pair.pipSize) * (side === 'BUY' ? 1 : -1);
247
+ const pnl = pips * pair.pipSize * lots * 100000;
248
+
249
+ // Win rate matching FOREX_AGENT
250
+ const isWin = Math.random() < (FOREX_AGENT.winRate / 100);
251
+ const finalPnl = isWin ? Math.abs(pnl) : -Math.abs(pnl) * rand(0.3, 0.8);
252
+ cumulativePnl += finalPnl;
253
+
254
+ const clearingFee = Math.abs(finalPnl) * rand(0.001, 0.003);
255
+ const hedgingCost = Math.abs(finalPnl) * rand(0.0005, 0.002);
256
+ const insuranceReserve = Math.abs(finalPnl) * rand(0.0003, 0.001);
257
+
258
+ blockNum += randInt(1, 20);
259
+ const timestamp = day.getTime() + randInt(0, 23) * 3600000 + randInt(0, 3600000);
260
+
261
+ trades.push({
262
+ id: genId('FXT'),
263
+ timestamp,
264
+ pairId: pair.id,
265
+ pairSymbol: pair.symbol,
266
+ side,
267
+ rfqPrice,
268
+ quotePrice,
269
+ matchPrice,
270
+ settlePrice,
271
+ lots,
272
+ pips: isWin ? Math.abs(pips) : -Math.abs(pips) * rand(0.3, 0.8),
273
+ pnl: finalPnl,
274
+ status: 'SETTLED',
275
+ pvpSettled: true,
276
+ clearingFee,
277
+ hedgingCost,
278
+ insuranceReserve,
279
+ txHash: genTxHash(),
280
+ blockNumber: blockNum,
281
+ cycleDay: d + 1,
282
+ cumulativePnl,
283
+ });
284
+ }
285
+ }
286
+
287
+ trades.sort((a, b) => a.timestamp - b.timestamp);
288
+ return trades;
289
+ }
290
+
291
+ generateLiveTransaction(
292
+ poolId: ForexPoolType,
293
+ type: ForexPoolTransactionType,
294
+ baseAmount?: number,
295
+ ): ForexPoolTransaction {
296
+ const params = POOL_PARAMS[poolId];
297
+ let amount: number;
298
+
299
+ switch (type) {
300
+ case 'deposit':
301
+ amount = baseAmount ?? rand(params.avgDepositSize * 0.5, params.avgDepositSize * 1.5);
302
+ break;
303
+ case 'withdrawal':
304
+ amount = -(baseAmount ?? rand(params.avgWithdrawalSize * 0.5, params.avgWithdrawalSize * 1.5));
305
+ break;
306
+ case 'profit_distribution':
307
+ amount = baseAmount ?? rand(500, 5000);
308
+ break;
309
+ case 'fee_collection':
310
+ amount = baseAmount ?? rand(100, 2000);
311
+ break;
312
+ case 'loss_absorption':
313
+ amount = -(baseAmount ?? rand(200, 3000));
314
+ break;
315
+ default:
316
+ amount = baseAmount ?? rand(-5000, 5000);
317
+ }
318
+
319
+ const pool = FOREX_POOL_DEFAULTS.find(p => p.id === poolId)!;
320
+ const balanceBefore = pool.totalSize;
321
+
322
+ return {
323
+ id: genId('ptx'),
324
+ poolId,
325
+ type,
326
+ amount,
327
+ balanceBefore,
328
+ balanceAfter: balanceBefore + amount,
329
+ txHash: genTxHash(),
330
+ blockNumber: this.baseBlockNumber + Math.floor(Date.now() / 12000),
331
+ timestamp: Date.now(),
332
+ description: this.getTxDescription(type, poolId, Math.abs(amount)),
333
+ };
334
+ }
335
+
336
+ // ── Private Methods ────────────────────────────────────────────────────
337
+
338
+ private generatePoolSnapshots(
339
+ poolId: ForexPoolType,
340
+ currentSize: number,
341
+ ): ForexPoolDailySnapshot[] {
342
+ const params = POOL_PARAMS[poolId];
343
+ const snapshots: ForexPoolDailySnapshot[] = [];
344
+ const now = new Date();
345
+
346
+ // Work backward: start with a smaller balance 60 days ago
347
+ let balance = currentSize * rand(0.82, 0.88);
348
+ let cumulativePnl = 0;
349
+
350
+ for (let i = 59; i >= 0; i--) {
351
+ const day = new Date(now);
352
+ day.setDate(day.getDate() - i);
353
+ const dayOfWeek = day.getDay();
354
+ const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
355
+
356
+ const openBalance = balance;
357
+
358
+ // Weekend activity ~20% of weekday
359
+ const activityMultiplier = isWeekend ? 0.2 : 1.0;
360
+
361
+ // Daily PnL with occasional drawdowns (1-2 per week)
362
+ const isDrawdown = !isWeekend && Math.random() < 0.2; // ~1.4/week
363
+ let dailyPnlPct: number;
364
+ if (isDrawdown) {
365
+ dailyPnlPct = -rand(0.0001, params.stddevPnlPct * 2);
366
+ } else {
367
+ dailyPnlPct = this.gaussianRandom(params.meanDailyPnlPct, params.stddevPnlPct);
368
+ }
369
+ dailyPnlPct *= activityMultiplier;
370
+
371
+ const dailyPnl = balance * dailyPnlPct;
372
+ cumulativePnl += dailyPnl;
373
+
374
+ // Net deposits: organic growth with slight positive bias
375
+ const depositCount = isWeekend
376
+ ? randInt(1, Math.ceil(params.depositsPerDay[0] * 0.2))
377
+ : randInt(params.depositsPerDay[0], params.depositsPerDay[1]);
378
+ const withdrawalCount = isWeekend
379
+ ? randInt(0, Math.ceil(params.withdrawalsPerDay[0] * 0.2))
380
+ : randInt(params.withdrawalsPerDay[0], params.withdrawalsPerDay[1]);
381
+
382
+ const deposits = depositCount * params.avgDepositSize * rand(0.7, 1.3) * activityMultiplier;
383
+ const withdrawals = withdrawalCount * params.avgWithdrawalSize * rand(0.7, 1.3) * activityMultiplier;
384
+ const netFlow = deposits - withdrawals;
385
+
386
+ balance += dailyPnl + netFlow;
387
+
388
+ const txCount = depositCount + withdrawalCount + Math.round(params.profitDistPerDay * activityMultiplier);
389
+ const utilization = rand(params.utilizationMin, params.utilizationMax);
390
+ const activeUsers = Math.round(rand(80, 400) * activityMultiplier);
391
+
392
+ snapshots.push({
393
+ poolId,
394
+ date: formatDate(day),
395
+ openBalance,
396
+ closeBalance: balance,
397
+ deposits,
398
+ withdrawals,
399
+ netFlow,
400
+ dailyPnl,
401
+ dailyPnlPct,
402
+ cumulativePnl,
403
+ utilization,
404
+ txCount,
405
+ activeUsers,
406
+ });
407
+ }
408
+
409
+ return snapshots;
410
+ }
411
+
412
+ private generatePoolTransactions(
413
+ poolId: ForexPoolType,
414
+ snapshots: ForexPoolDailySnapshot[],
415
+ ): ForexPoolTransaction[] {
416
+ const params = POOL_PARAMS[poolId];
417
+ const transactions: ForexPoolTransaction[] = [];
418
+
419
+ for (const snap of snapshots) {
420
+ const day = new Date(snap.date);
421
+ const dayOfWeek = day.getDay();
422
+ const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
423
+ const mult = isWeekend ? 0.2 : 1.0;
424
+
425
+ const depositCount = Math.round(randInt(params.depositsPerDay[0], params.depositsPerDay[1]) * mult);
426
+ const withdrawalCount = Math.round(randInt(params.withdrawalsPerDay[0], params.withdrawalsPerDay[1]) * mult);
427
+ const profitCount = Math.round(params.profitDistPerDay * mult);
428
+
429
+ // Deposits
430
+ for (let i = 0; i < depositCount; i++) {
431
+ const amount = rand(params.avgDepositSize * 0.3, params.avgDepositSize * 2.0);
432
+ const ts = day.getTime() + randInt(0, 23) * 3600000 + randInt(0, 3600000);
433
+ transactions.push({
434
+ id: genId('dep'),
435
+ poolId,
436
+ type: 'deposit',
437
+ amount,
438
+ balanceBefore: 0,
439
+ balanceAfter: 0,
440
+ txHash: genTxHash(),
441
+ blockNumber: this.baseBlockNumber + Math.floor(ts / 12000) + randInt(0, 50),
442
+ timestamp: ts,
443
+ description: `Deposit to ${poolId} pool`,
444
+ });
445
+ }
446
+
447
+ // Withdrawals
448
+ for (let i = 0; i < withdrawalCount; i++) {
449
+ const amount = rand(params.avgWithdrawalSize * 0.3, params.avgWithdrawalSize * 2.0);
450
+ const ts = day.getTime() + randInt(0, 23) * 3600000 + randInt(0, 3600000);
451
+ transactions.push({
452
+ id: genId('wdr'),
453
+ poolId,
454
+ type: 'withdrawal',
455
+ amount: -amount,
456
+ balanceBefore: 0,
457
+ balanceAfter: 0,
458
+ txHash: genTxHash(),
459
+ blockNumber: this.baseBlockNumber + Math.floor(ts / 12000) + randInt(0, 50),
460
+ timestamp: ts,
461
+ description: `Withdrawal from ${poolId} pool`,
462
+ });
463
+ }
464
+
465
+ // Profit distributions
466
+ for (let i = 0; i < profitCount; i++) {
467
+ const amount = rand(500, 8000);
468
+ const ts = day.getTime() + randInt(6, 22) * 3600000 + randInt(0, 3600000);
469
+ transactions.push({
470
+ id: genId('prd'),
471
+ poolId,
472
+ type: 'profit_distribution',
473
+ amount,
474
+ balanceBefore: 0,
475
+ balanceAfter: 0,
476
+ txHash: genTxHash(),
477
+ blockNumber: this.baseBlockNumber + Math.floor(ts / 12000) + randInt(0, 50),
478
+ timestamp: ts,
479
+ description: `Profit distribution from ${poolId} pool`,
480
+ });
481
+ }
482
+
483
+ // Fee collection (1-2 per day on weekdays)
484
+ if (!isWeekend) {
485
+ const feeCount = randInt(1, 2);
486
+ for (let i = 0; i < feeCount; i++) {
487
+ const amount = rand(100, 3000);
488
+ const ts = day.getTime() + randInt(8, 20) * 3600000;
489
+ transactions.push({
490
+ id: genId('fee'),
491
+ poolId,
492
+ type: 'fee_collection',
493
+ amount,
494
+ balanceBefore: 0,
495
+ balanceAfter: 0,
496
+ txHash: genTxHash(),
497
+ blockNumber: this.baseBlockNumber + Math.floor(ts / 12000) + randInt(0, 50),
498
+ timestamp: ts,
499
+ description: `Fee collection for ${poolId} pool`,
500
+ });
501
+ }
502
+ }
503
+ }
504
+
505
+ return transactions;
506
+ }
507
+
508
+ private gaussianRandom(mean: number, stddev: number): number {
509
+ let u = 0, v = 0;
510
+ while (u === 0) u = Math.random();
511
+ while (v === 0) v = Math.random();
512
+ const z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
513
+ return mean + z * stddev;
514
+ }
515
+
516
+ private getTxDescription(type: ForexPoolTransactionType, poolId: ForexPoolType, amount: number): string {
517
+ const fmt = `$${amount.toLocaleString(undefined, { maximumFractionDigits: 0 })}`;
518
+ switch (type) {
519
+ case 'deposit': return `Deposit ${fmt} to ${poolId} pool`;
520
+ case 'withdrawal': return `Withdrawal ${fmt} from ${poolId} pool`;
521
+ case 'profit_distribution': return `Profit distribution ${fmt} from ${poolId}`;
522
+ case 'loss_absorption': return `Loss absorbed ${fmt} by ${poolId} pool`;
523
+ case 'inter_pool_transfer': return `Inter-pool transfer ${fmt}`;
524
+ case 'fee_collection': return `Fee collected ${fmt} in ${poolId}`;
525
+ case 'reserve_rebalance': return `Reserve rebalance ${fmt} in ${poolId}`;
526
+ default: return `${type} ${fmt}`;
527
+ }
528
+ }
529
+ }
530
+
531
+ // ── Singleton Export ──────────────────────────────────────────────────────────
532
+
533
+ let _instance: ForexPoolDataGeneratorClass | null = null;
534
+
535
+ export const ForexPoolDataGenerator = {
536
+ getInstance(): ForexPoolDataGeneratorClass {
537
+ if (!_instance) {
538
+ _instance = new ForexPoolDataGeneratorClass();
539
+ }
540
+ return _instance;
541
+ },
542
+ };