@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.
- package/README.md +339 -0
- package/dist/ForexPoolDataGenerator--__twRwl.d.mts +76 -0
- package/dist/ForexPoolDataGenerator-eUgwsU_B.d.ts +76 -0
- package/dist/OneForexTradeHistory-TlKxjbFF.d.ts +250 -0
- package/dist/OneForexTradeHistory-iDySMcw0.d.mts +250 -0
- package/dist/components/index.d.mts +539 -0
- package/dist/components/index.d.ts +539 -0
- package/dist/components/index.js +7295 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +7243 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/config/index.d.mts +1 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/console-BfTMA7ah.d.mts +504 -0
- package/dist/console-BfTMA7ah.d.ts +504 -0
- package/dist/hooks/index.d.mts +323 -1
- package/dist/hooks/index.d.ts +323 -1
- package/dist/hooks/index.js +3223 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +3204 -1
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +18 -352
- package/dist/index.d.ts +18 -352
- package/dist/index.js +8646 -574
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8449 -432
- package/dist/index.mjs.map +1 -1
- package/dist/providers/index.d.mts +31 -31
- package/dist/providers/index.d.ts +31 -31
- package/dist/providers/index.js +140 -153
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/index.mjs +100 -109
- package/dist/providers/index.mjs.map +1 -1
- package/dist/react-native.d.mts +8 -144
- package/dist/react-native.d.ts +8 -144
- package/dist/react-native.js +2640 -689
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +2610 -691
- package/dist/react-native.mjs.map +1 -1
- package/dist/services/index.d.mts +85 -4
- package/dist/services/index.d.ts +85 -4
- package/dist/services/index.js +1621 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/index.mjs +1619 -1
- package/dist/services/index.mjs.map +1 -1
- package/dist/types/index.d.mts +203 -1
- package/dist/types/index.d.ts +203 -1
- package/dist/types/index.js +275 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/index.mjs +251 -0
- package/dist/types/index.mjs.map +1 -1
- package/dist/useForexTrading-BleeSor8.d.mts +80 -0
- package/dist/useForexTrading-ZgW_G40Q.d.ts +80 -0
- package/package.json +9 -2
- package/src/components/OneConnectButton.tsx +24 -1
- package/src/components/OneNFTGallery.tsx +13 -7
- package/src/components/OneOfframpWidget.tsx +4 -3
- package/src/components/OnePayWidget.tsx +10 -1
- package/src/components/OneSendWidget.tsx +3 -3
- package/src/components/OneSwapWidget.tsx +4 -4
- package/src/components/OneTransactionButton.tsx +28 -3
- package/src/components/OneWalletBalance.tsx +1 -1
- package/src/components/ai/OneChainSelector.tsx +63 -336
- package/src/components/ai/OneForexCapitalSplit.tsx +112 -0
- package/src/components/ai/OneForexConsoleView.tsx +90 -0
- package/src/components/ai/OneForexPairSelector.tsx +101 -0
- package/src/components/ai/OneForexPoolCard.tsx +105 -0
- package/src/components/ai/OneForexTradeHistory.tsx +107 -0
- package/src/components/ai/OnePairSelector.tsx +77 -434
- package/src/components/ai/console/OneAIQuantConsole.tsx +423 -0
- package/src/components/ai/console/OneAgentCard.tsx +383 -0
- package/src/components/ai/console/OneAgentConsole.tsx +469 -0
- package/src/components/ai/console/OneDecisionTimeline.tsx +433 -0
- package/src/components/ai/console/OneMetricsDashboard.tsx +493 -0
- package/src/components/ai/console/OnePositionCard.tsx +406 -0
- package/src/components/ai/console/OnePositionDetail.tsx +600 -0
- package/src/components/ai/console/OneRiskIndicator.tsx +464 -0
- package/src/components/ai/console/OneTradingConsole.tsx +660 -0
- package/src/components/ai/console/index.ts +17 -0
- package/src/components/ai/index.ts +10 -0
- package/src/hooks/index.ts +46 -0
- package/src/hooks/useAIDecisions.ts +280 -0
- package/src/hooks/useAIPositions.ts +349 -0
- package/src/hooks/useAIQuantConsole.ts +283 -0
- package/src/hooks/useAIRiskStatus.ts +276 -0
- package/src/hooks/useAITrading.ts +190 -0
- package/src/hooks/useBotSimulation.ts +201 -0
- package/src/hooks/useForexTrading.ts +430 -0
- package/src/hooks/useTradingConsole.ts +243 -0
- package/src/index.ts +123 -5
- package/src/providers/OneProvider.tsx +181 -5
- package/src/providers/index.ts +22 -8
- package/src/react-native.ts +41 -0
- package/src/services/forex/BotSimulationEngine.ts +968 -0
- package/src/services/forex/ForexPoolDataGenerator.ts +542 -0
- package/src/services/forex/ForexSimulationEngine.ts +482 -0
- package/src/services/forex/index.ts +21 -0
- package/src/services/index.ts +16 -0
- package/src/types/aiTrading.ts +151 -0
- package/src/types/console.ts +380 -0
- package/src/types/forex.ts +282 -0
- package/src/types/index.ts +106 -0
- package/dist/price-CgqXPnT3.d.ts +0 -13
- package/dist/price-ClbLHHjv.d.mts +0 -13
- package/dist/supabase-BT0c7q9e.d.mts +0 -82
- 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
|
+
};
|