@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
package/dist/hooks/index.js
CHANGED
|
@@ -2,6 +2,1687 @@
|
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
4
|
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
25
|
+
|
|
26
|
+
// src/types/forex.ts
|
|
27
|
+
var forex_exports = {};
|
|
28
|
+
__export(forex_exports, {
|
|
29
|
+
FOREX_AGENT: () => FOREX_AGENT,
|
|
30
|
+
FOREX_CAPITAL_SPLIT: () => FOREX_CAPITAL_SPLIT,
|
|
31
|
+
FOREX_CURRENCY_PAIRS: () => FOREX_CURRENCY_PAIRS,
|
|
32
|
+
FOREX_CYCLE_OPTIONS: () => FOREX_CYCLE_OPTIONS,
|
|
33
|
+
FOREX_POOL_DEFAULTS: () => FOREX_POOL_DEFAULTS,
|
|
34
|
+
calculateForexNetProfit: () => calculateForexNetProfit,
|
|
35
|
+
computePoolAllocations: () => computePoolAllocations,
|
|
36
|
+
estimateForexProfit: () => estimateForexProfit
|
|
37
|
+
});
|
|
38
|
+
function computePoolAllocations(amount) {
|
|
39
|
+
const totalPoolReserves = amount * FOREX_CAPITAL_SPLIT.poolReserves;
|
|
40
|
+
const tradingCapital = amount * FOREX_CAPITAL_SPLIT.tradingCapital;
|
|
41
|
+
return {
|
|
42
|
+
tradingCapital,
|
|
43
|
+
totalPoolReserves,
|
|
44
|
+
clearing: totalPoolReserves * 0.5,
|
|
45
|
+
hedging: totalPoolReserves * 0.3,
|
|
46
|
+
insurance: totalPoolReserves * 0.2
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function calculateForexNetProfit(grossProfit, cycleOption) {
|
|
50
|
+
const feeAmount = grossProfit * cycleOption.feeRate;
|
|
51
|
+
const postFeeProfit = grossProfit - feeAmount;
|
|
52
|
+
const netProfit = postFeeProfit * cycleOption.commissionRate;
|
|
53
|
+
return { feeAmount, postFeeProfit, netProfit };
|
|
54
|
+
}
|
|
55
|
+
function estimateForexProfit(amount, cycleDays, agent = FOREX_AGENT) {
|
|
56
|
+
const cycleOption = FOREX_CYCLE_OPTIONS.find((c) => c.days === cycleDays) || FOREX_CYCLE_OPTIONS[0];
|
|
57
|
+
const estimatedApy = (agent.dailyRoiMin + agent.dailyRoiMax) / 2 * 365 * 100;
|
|
58
|
+
const dailyRate = estimatedApy / 100 / 365;
|
|
59
|
+
const grossProfit = amount * dailyRate * cycleDays;
|
|
60
|
+
const { feeAmount, netProfit } = calculateForexNetProfit(grossProfit, cycleOption);
|
|
61
|
+
return { grossProfit, feeAmount, netProfit, dailyRate };
|
|
62
|
+
}
|
|
63
|
+
var FOREX_CAPITAL_SPLIT, FOREX_CYCLE_OPTIONS, FOREX_CURRENCY_PAIRS, FOREX_POOL_DEFAULTS, FOREX_AGENT;
|
|
64
|
+
var init_forex = __esm({
|
|
65
|
+
"src/types/forex.ts"() {
|
|
66
|
+
FOREX_CAPITAL_SPLIT = {
|
|
67
|
+
poolReserves: 0.5,
|
|
68
|
+
tradingCapital: 0.5
|
|
69
|
+
};
|
|
70
|
+
FOREX_CYCLE_OPTIONS = [
|
|
71
|
+
{ days: 30, feeRate: 0.1, commissionRate: 0.6, label: "30D" },
|
|
72
|
+
{ days: 60, feeRate: 0.08, commissionRate: 0.7, label: "60D" },
|
|
73
|
+
{ days: 90, feeRate: 0.07, commissionRate: 0.75, label: "90D" },
|
|
74
|
+
{ days: 180, feeRate: 0.05, commissionRate: 0.85, label: "180D" },
|
|
75
|
+
{ days: 360, feeRate: 0.03, commissionRate: 0.9, label: "360D" }
|
|
76
|
+
];
|
|
77
|
+
FOREX_CURRENCY_PAIRS = [
|
|
78
|
+
{ id: "USDC_EURC", base: "USDC", quote: "EURC", symbol: "USDC/EURC", flag: "\u{1F1EA}\u{1F1FA}", name: "Euro", basePrice: 0.923, pipSize: 1e-4, spreadPips: 1.2 },
|
|
79
|
+
{ id: "USDC_GBPC", base: "USDC", quote: "GBPC", symbol: "USDC/GBPC", flag: "\u{1F1EC}\u{1F1E7}", name: "British Pound", basePrice: 0.789, pipSize: 1e-4, spreadPips: 1.5 },
|
|
80
|
+
{ id: "USDC_JPYC", base: "USDC", quote: "JPYC", symbol: "USDC/JPYC", flag: "\u{1F1EF}\u{1F1F5}", name: "Japanese Yen", basePrice: 154.5, pipSize: 0.01, spreadPips: 1 },
|
|
81
|
+
{ id: "USDC_AUDC", base: "USDC", quote: "AUDC", symbol: "USDC/AUDC", flag: "\u{1F1E6}\u{1F1FA}", name: "Australian Dollar", basePrice: 1.538, pipSize: 1e-4, spreadPips: 1.8 },
|
|
82
|
+
{ id: "USDC_CADC", base: "USDC", quote: "CADC", symbol: "USDC/CADC", flag: "\u{1F1E8}\u{1F1E6}", name: "Canadian Dollar", basePrice: 1.364, pipSize: 1e-4, spreadPips: 1.5 },
|
|
83
|
+
{ id: "USDC_CHFC", base: "USDC", quote: "CHFC", symbol: "USDC/CHFC", flag: "\u{1F1E8}\u{1F1ED}", name: "Swiss Franc", basePrice: 0.875, pipSize: 1e-4, spreadPips: 1.3 }
|
|
84
|
+
];
|
|
85
|
+
FOREX_POOL_DEFAULTS = [
|
|
86
|
+
{ id: "clearing", nameKey: "forex.pool_clearing", descriptionKey: "forex.pool_clearing_desc", allocation: 0.5, totalSize: 125e5, utilization: 0.78, color: "#3B82F6", apy7d: 12.8, apy30d: 11.5, netFlow24h: 185e3, txCount24h: 42, txCountTotal: 12840, totalDeposits: 452e5, totalWithdrawals: 327e5, profitDistributed: 185e4, lastUpdated: Date.now() },
|
|
87
|
+
{ id: "hedging", nameKey: "forex.pool_hedging", descriptionKey: "forex.pool_hedging_desc", allocation: 0.3, totalSize: 75e5, utilization: 0.65, color: "#F59E0B", apy7d: 8.1, apy30d: 7.6, netFlow24h: 72e3, txCount24h: 24, txCountTotal: 7620, totalDeposits: 285e5, totalWithdrawals: 21e6, profitDistributed: 98e4, lastUpdated: Date.now() },
|
|
88
|
+
{ id: "insurance", nameKey: "forex.pool_insurance", descriptionKey: "forex.pool_insurance_desc", allocation: 0.2, totalSize: 5e6, utilization: 0.42, color: "#10B981", apy7d: 4.8, apy30d: 4.5, netFlow24h: 35e3, txCount24h: 14, txCountTotal: 4280, totalDeposits: 158e5, totalWithdrawals: 108e5, profitDistributed: 52e4, lastUpdated: Date.now() }
|
|
89
|
+
];
|
|
90
|
+
FOREX_AGENT = {
|
|
91
|
+
id: "stablefx-01",
|
|
92
|
+
nameKey: "forex.agent_name",
|
|
93
|
+
descriptionKey: "forex.agent_description",
|
|
94
|
+
icon: "\u{1F4B1}",
|
|
95
|
+
color: "#0EA5E9",
|
|
96
|
+
supportedPairs: FOREX_CURRENCY_PAIRS.map((p) => p.id),
|
|
97
|
+
dailyRoiMin: 2e-3,
|
|
98
|
+
dailyRoiMax: 5e-3,
|
|
99
|
+
totalManaged: 25e6,
|
|
100
|
+
totalUsers: 3847,
|
|
101
|
+
winRate: 72.5
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// src/services/forex/ForexPoolDataGenerator.ts
|
|
107
|
+
var ForexPoolDataGenerator_exports = {};
|
|
108
|
+
__export(ForexPoolDataGenerator_exports, {
|
|
109
|
+
ForexPoolDataGenerator: () => ForexPoolDataGenerator
|
|
110
|
+
});
|
|
111
|
+
function rand(min, max) {
|
|
112
|
+
return min + Math.random() * (max - min);
|
|
113
|
+
}
|
|
114
|
+
function randInt(min, max) {
|
|
115
|
+
return Math.floor(rand(min, max + 1));
|
|
116
|
+
}
|
|
117
|
+
function pick(arr) {
|
|
118
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
119
|
+
}
|
|
120
|
+
function genTxHash() {
|
|
121
|
+
const chars = "0123456789abcdef";
|
|
122
|
+
let hash = "0x";
|
|
123
|
+
for (let i = 0; i < 64; i++) hash += chars[Math.floor(Math.random() * 16)];
|
|
124
|
+
return hash;
|
|
125
|
+
}
|
|
126
|
+
function formatDate(d) {
|
|
127
|
+
return d.toISOString().split("T")[0];
|
|
128
|
+
}
|
|
129
|
+
function genId(prefix) {
|
|
130
|
+
return `${prefix}_${Date.now().toString(36)}_${(++_idCounter).toString(36)}`;
|
|
131
|
+
}
|
|
132
|
+
var _idCounter, POOL_PARAMS, ForexPoolDataGeneratorClass, _instance, ForexPoolDataGenerator;
|
|
133
|
+
var init_ForexPoolDataGenerator = __esm({
|
|
134
|
+
"src/services/forex/ForexPoolDataGenerator.ts"() {
|
|
135
|
+
init_forex();
|
|
136
|
+
_idCounter = 0;
|
|
137
|
+
POOL_PARAMS = {
|
|
138
|
+
clearing: {
|
|
139
|
+
meanDailyPnlPct: 35e-5,
|
|
140
|
+
stddevPnlPct: 15e-5,
|
|
141
|
+
utilizationMin: 0.7,
|
|
142
|
+
utilizationMax: 0.85,
|
|
143
|
+
depositsPerDay: [15, 25],
|
|
144
|
+
withdrawalsPerDay: [8, 12],
|
|
145
|
+
profitDistPerDay: 5,
|
|
146
|
+
avgDepositSize: 25e3,
|
|
147
|
+
avgWithdrawalSize: 18e3
|
|
148
|
+
},
|
|
149
|
+
hedging: {
|
|
150
|
+
meanDailyPnlPct: 22e-5,
|
|
151
|
+
stddevPnlPct: 2e-4,
|
|
152
|
+
utilizationMin: 0.55,
|
|
153
|
+
utilizationMax: 0.75,
|
|
154
|
+
depositsPerDay: [8, 15],
|
|
155
|
+
withdrawalsPerDay: [5, 8],
|
|
156
|
+
profitDistPerDay: 3,
|
|
157
|
+
avgDepositSize: 18e3,
|
|
158
|
+
avgWithdrawalSize: 12e3
|
|
159
|
+
},
|
|
160
|
+
insurance: {
|
|
161
|
+
meanDailyPnlPct: 13e-5,
|
|
162
|
+
stddevPnlPct: 8e-5,
|
|
163
|
+
utilizationMin: 0.3,
|
|
164
|
+
utilizationMax: 0.5,
|
|
165
|
+
depositsPerDay: [5, 10],
|
|
166
|
+
withdrawalsPerDay: [3, 5],
|
|
167
|
+
profitDistPerDay: 2,
|
|
168
|
+
avgDepositSize: 12e3,
|
|
169
|
+
avgWithdrawalSize: 8e3
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
ForexPoolDataGeneratorClass = class {
|
|
173
|
+
constructor() {
|
|
174
|
+
this.snapshotCache = null;
|
|
175
|
+
this.transactionCache = null;
|
|
176
|
+
this.baseBlockNumber = 195e5;
|
|
177
|
+
}
|
|
178
|
+
// ── Public API ─────────────────────────────────────────────────────────
|
|
179
|
+
generateAllSnapshots() {
|
|
180
|
+
if (this.snapshotCache) return this.snapshotCache;
|
|
181
|
+
const result = {
|
|
182
|
+
clearing: [],
|
|
183
|
+
hedging: [],
|
|
184
|
+
insurance: []
|
|
185
|
+
};
|
|
186
|
+
for (const pool of FOREX_POOL_DEFAULTS) {
|
|
187
|
+
result[pool.id] = this.generatePoolSnapshots(pool.id, pool.totalSize);
|
|
188
|
+
}
|
|
189
|
+
this.snapshotCache = result;
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
generateAllTransactions() {
|
|
193
|
+
if (this.transactionCache) return this.transactionCache;
|
|
194
|
+
const allTx = [];
|
|
195
|
+
for (const pool of FOREX_POOL_DEFAULTS) {
|
|
196
|
+
const snapshots = this.snapshotCache?.[pool.id] || this.generatePoolSnapshots(pool.id, pool.totalSize);
|
|
197
|
+
const txs = this.generatePoolTransactions(pool.id, snapshots);
|
|
198
|
+
allTx.push(...txs);
|
|
199
|
+
}
|
|
200
|
+
const now = /* @__PURE__ */ new Date();
|
|
201
|
+
for (let i = 0; i < 60; i++) {
|
|
202
|
+
const day = new Date(now);
|
|
203
|
+
day.setDate(day.getDate() - (60 - i));
|
|
204
|
+
const dayOfWeek = day.getDay();
|
|
205
|
+
if (dayOfWeek === 2 || dayOfWeek === 4 || dayOfWeek === 5 && Math.random() < 0.4) {
|
|
206
|
+
const fromPool = pick(["clearing", "hedging", "insurance"]);
|
|
207
|
+
const toOptions = ["clearing", "hedging", "insurance"].filter((p) => p !== fromPool);
|
|
208
|
+
const toPool = pick(toOptions);
|
|
209
|
+
const amount = rand(5e4, 2e5);
|
|
210
|
+
const ts = day.getTime() + randInt(10, 18) * 36e5;
|
|
211
|
+
allTx.push({
|
|
212
|
+
id: genId("ipt"),
|
|
213
|
+
poolId: fromPool,
|
|
214
|
+
type: "inter_pool_transfer",
|
|
215
|
+
amount: -amount,
|
|
216
|
+
balanceBefore: 0,
|
|
217
|
+
balanceAfter: 0,
|
|
218
|
+
txHash: genTxHash(),
|
|
219
|
+
blockNumber: this.baseBlockNumber + i * 7200 + randInt(0, 100),
|
|
220
|
+
timestamp: ts,
|
|
221
|
+
description: `Transfer to ${toPool} pool`
|
|
222
|
+
});
|
|
223
|
+
allTx.push({
|
|
224
|
+
id: genId("ipt"),
|
|
225
|
+
poolId: toPool,
|
|
226
|
+
type: "inter_pool_transfer",
|
|
227
|
+
amount,
|
|
228
|
+
balanceBefore: 0,
|
|
229
|
+
balanceAfter: 0,
|
|
230
|
+
txHash: genTxHash(),
|
|
231
|
+
blockNumber: this.baseBlockNumber + i * 7200 + randInt(100, 200),
|
|
232
|
+
timestamp: ts + 5e3,
|
|
233
|
+
description: `Transfer from ${fromPool} pool`
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
if (dayOfWeek === 3 && Math.random() < 0.85) {
|
|
237
|
+
const pool = pick(FOREX_POOL_DEFAULTS);
|
|
238
|
+
const rebalanceAmt = rand(2e4, 1e5);
|
|
239
|
+
const ts = day.getTime() + randInt(2, 6) * 36e5;
|
|
240
|
+
allTx.push({
|
|
241
|
+
id: genId("rrb"),
|
|
242
|
+
poolId: pool.id,
|
|
243
|
+
type: "reserve_rebalance",
|
|
244
|
+
amount: Math.random() < 0.5 ? rebalanceAmt : -rebalanceAmt,
|
|
245
|
+
balanceBefore: 0,
|
|
246
|
+
balanceAfter: 0,
|
|
247
|
+
txHash: genTxHash(),
|
|
248
|
+
blockNumber: this.baseBlockNumber + i * 7200 + randInt(200, 300),
|
|
249
|
+
timestamp: ts,
|
|
250
|
+
description: "Automated reserve rebalance"
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
allTx.sort((a, b) => a.timestamp - b.timestamp);
|
|
255
|
+
const runningBalance = {
|
|
256
|
+
clearing: FOREX_POOL_DEFAULTS[0].totalSize * 0.85,
|
|
257
|
+
hedging: FOREX_POOL_DEFAULTS[1].totalSize * 0.82,
|
|
258
|
+
insurance: FOREX_POOL_DEFAULTS[2].totalSize * 0.88
|
|
259
|
+
};
|
|
260
|
+
for (const tx of allTx) {
|
|
261
|
+
tx.balanceBefore = runningBalance[tx.poolId];
|
|
262
|
+
runningBalance[tx.poolId] += tx.amount;
|
|
263
|
+
tx.balanceAfter = runningBalance[tx.poolId];
|
|
264
|
+
}
|
|
265
|
+
this.transactionCache = allTx;
|
|
266
|
+
return allTx;
|
|
267
|
+
}
|
|
268
|
+
generateTradeHistory(investmentAmount, startDate, selectedPairs) {
|
|
269
|
+
const trades = [];
|
|
270
|
+
const start = new Date(startDate);
|
|
271
|
+
const now = /* @__PURE__ */ new Date();
|
|
272
|
+
const dayCount = Math.min(60, Math.ceil((now.getTime() - start.getTime()) / 864e5));
|
|
273
|
+
let cumulativePnl = 0;
|
|
274
|
+
let blockNum = this.baseBlockNumber + randInt(0, 5e3);
|
|
275
|
+
for (let d = 0; d < dayCount; d++) {
|
|
276
|
+
const day = new Date(start);
|
|
277
|
+
day.setDate(day.getDate() + d);
|
|
278
|
+
const dayOfWeek = day.getDay();
|
|
279
|
+
const baseCount = dayOfWeek === 0 || dayOfWeek === 6 ? randInt(1, 2) : randInt(3, 8);
|
|
280
|
+
for (let t = 0; t < baseCount; t++) {
|
|
281
|
+
const pair = FOREX_CURRENCY_PAIRS.find((p) => p.id === pick(selectedPairs)) || pick(FOREX_CURRENCY_PAIRS);
|
|
282
|
+
const side = Math.random() > 0.5 ? "BUY" : "SELL";
|
|
283
|
+
const lots = parseFloat(rand(0.1, 2.5).toFixed(2));
|
|
284
|
+
const rfqPrice = pair.basePrice * (1 + rand(-3e-3, 3e-3));
|
|
285
|
+
const quoteSpread = rand(0.5, 2) * pair.pipSize;
|
|
286
|
+
const quotePrice = side === "BUY" ? rfqPrice + quoteSpread : rfqPrice - quoteSpread;
|
|
287
|
+
const matched = Math.random() < 0.85;
|
|
288
|
+
if (!matched) continue;
|
|
289
|
+
const matchPrice = quotePrice * (1 + rand(-5e-5, 5e-5));
|
|
290
|
+
const settlePrice = matchPrice * (1 + rand(-2e-5, 2e-5));
|
|
291
|
+
const pips = (settlePrice - rfqPrice) / pair.pipSize * (side === "BUY" ? 1 : -1);
|
|
292
|
+
const pnl = pips * pair.pipSize * lots * 1e5;
|
|
293
|
+
const isWin = Math.random() < FOREX_AGENT.winRate / 100;
|
|
294
|
+
const finalPnl = isWin ? Math.abs(pnl) : -Math.abs(pnl) * rand(0.3, 0.8);
|
|
295
|
+
cumulativePnl += finalPnl;
|
|
296
|
+
const clearingFee = Math.abs(finalPnl) * rand(1e-3, 3e-3);
|
|
297
|
+
const hedgingCost = Math.abs(finalPnl) * rand(5e-4, 2e-3);
|
|
298
|
+
const insuranceReserve = Math.abs(finalPnl) * rand(3e-4, 1e-3);
|
|
299
|
+
blockNum += randInt(1, 20);
|
|
300
|
+
const timestamp = day.getTime() + randInt(0, 23) * 36e5 + randInt(0, 36e5);
|
|
301
|
+
trades.push({
|
|
302
|
+
id: genId("FXT"),
|
|
303
|
+
timestamp,
|
|
304
|
+
pairId: pair.id,
|
|
305
|
+
pairSymbol: pair.symbol,
|
|
306
|
+
side,
|
|
307
|
+
rfqPrice,
|
|
308
|
+
quotePrice,
|
|
309
|
+
matchPrice,
|
|
310
|
+
settlePrice,
|
|
311
|
+
lots,
|
|
312
|
+
pips: isWin ? Math.abs(pips) : -Math.abs(pips) * rand(0.3, 0.8),
|
|
313
|
+
pnl: finalPnl,
|
|
314
|
+
status: "SETTLED",
|
|
315
|
+
pvpSettled: true,
|
|
316
|
+
clearingFee,
|
|
317
|
+
hedgingCost,
|
|
318
|
+
insuranceReserve,
|
|
319
|
+
txHash: genTxHash(),
|
|
320
|
+
blockNumber: blockNum,
|
|
321
|
+
cycleDay: d + 1,
|
|
322
|
+
cumulativePnl
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
trades.sort((a, b) => a.timestamp - b.timestamp);
|
|
327
|
+
return trades;
|
|
328
|
+
}
|
|
329
|
+
generateLiveTransaction(poolId, type, baseAmount) {
|
|
330
|
+
const params = POOL_PARAMS[poolId];
|
|
331
|
+
let amount;
|
|
332
|
+
switch (type) {
|
|
333
|
+
case "deposit":
|
|
334
|
+
amount = baseAmount ?? rand(params.avgDepositSize * 0.5, params.avgDepositSize * 1.5);
|
|
335
|
+
break;
|
|
336
|
+
case "withdrawal":
|
|
337
|
+
amount = -(baseAmount ?? rand(params.avgWithdrawalSize * 0.5, params.avgWithdrawalSize * 1.5));
|
|
338
|
+
break;
|
|
339
|
+
case "profit_distribution":
|
|
340
|
+
amount = baseAmount ?? rand(500, 5e3);
|
|
341
|
+
break;
|
|
342
|
+
case "fee_collection":
|
|
343
|
+
amount = baseAmount ?? rand(100, 2e3);
|
|
344
|
+
break;
|
|
345
|
+
case "loss_absorption":
|
|
346
|
+
amount = -(baseAmount ?? rand(200, 3e3));
|
|
347
|
+
break;
|
|
348
|
+
default:
|
|
349
|
+
amount = baseAmount ?? rand(-5e3, 5e3);
|
|
350
|
+
}
|
|
351
|
+
const pool = FOREX_POOL_DEFAULTS.find((p) => p.id === poolId);
|
|
352
|
+
const balanceBefore = pool.totalSize;
|
|
353
|
+
return {
|
|
354
|
+
id: genId("ptx"),
|
|
355
|
+
poolId,
|
|
356
|
+
type,
|
|
357
|
+
amount,
|
|
358
|
+
balanceBefore,
|
|
359
|
+
balanceAfter: balanceBefore + amount,
|
|
360
|
+
txHash: genTxHash(),
|
|
361
|
+
blockNumber: this.baseBlockNumber + Math.floor(Date.now() / 12e3),
|
|
362
|
+
timestamp: Date.now(),
|
|
363
|
+
description: this.getTxDescription(type, poolId, Math.abs(amount))
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
// ── Private Methods ────────────────────────────────────────────────────
|
|
367
|
+
generatePoolSnapshots(poolId, currentSize) {
|
|
368
|
+
const params = POOL_PARAMS[poolId];
|
|
369
|
+
const snapshots = [];
|
|
370
|
+
const now = /* @__PURE__ */ new Date();
|
|
371
|
+
let balance = currentSize * rand(0.82, 0.88);
|
|
372
|
+
let cumulativePnl = 0;
|
|
373
|
+
for (let i = 59; i >= 0; i--) {
|
|
374
|
+
const day = new Date(now);
|
|
375
|
+
day.setDate(day.getDate() - i);
|
|
376
|
+
const dayOfWeek = day.getDay();
|
|
377
|
+
const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
|
|
378
|
+
const openBalance = balance;
|
|
379
|
+
const activityMultiplier = isWeekend ? 0.2 : 1;
|
|
380
|
+
const isDrawdown = !isWeekend && Math.random() < 0.2;
|
|
381
|
+
let dailyPnlPct;
|
|
382
|
+
if (isDrawdown) {
|
|
383
|
+
dailyPnlPct = -rand(1e-4, params.stddevPnlPct * 2);
|
|
384
|
+
} else {
|
|
385
|
+
dailyPnlPct = this.gaussianRandom(params.meanDailyPnlPct, params.stddevPnlPct);
|
|
386
|
+
}
|
|
387
|
+
dailyPnlPct *= activityMultiplier;
|
|
388
|
+
const dailyPnl = balance * dailyPnlPct;
|
|
389
|
+
cumulativePnl += dailyPnl;
|
|
390
|
+
const depositCount = isWeekend ? randInt(1, Math.ceil(params.depositsPerDay[0] * 0.2)) : randInt(params.depositsPerDay[0], params.depositsPerDay[1]);
|
|
391
|
+
const withdrawalCount = isWeekend ? randInt(0, Math.ceil(params.withdrawalsPerDay[0] * 0.2)) : randInt(params.withdrawalsPerDay[0], params.withdrawalsPerDay[1]);
|
|
392
|
+
const deposits = depositCount * params.avgDepositSize * rand(0.7, 1.3) * activityMultiplier;
|
|
393
|
+
const withdrawals = withdrawalCount * params.avgWithdrawalSize * rand(0.7, 1.3) * activityMultiplier;
|
|
394
|
+
const netFlow = deposits - withdrawals;
|
|
395
|
+
balance += dailyPnl + netFlow;
|
|
396
|
+
const txCount = depositCount + withdrawalCount + Math.round(params.profitDistPerDay * activityMultiplier);
|
|
397
|
+
const utilization = rand(params.utilizationMin, params.utilizationMax);
|
|
398
|
+
const activeUsers = Math.round(rand(80, 400) * activityMultiplier);
|
|
399
|
+
snapshots.push({
|
|
400
|
+
poolId,
|
|
401
|
+
date: formatDate(day),
|
|
402
|
+
openBalance,
|
|
403
|
+
closeBalance: balance,
|
|
404
|
+
deposits,
|
|
405
|
+
withdrawals,
|
|
406
|
+
netFlow,
|
|
407
|
+
dailyPnl,
|
|
408
|
+
dailyPnlPct,
|
|
409
|
+
cumulativePnl,
|
|
410
|
+
utilization,
|
|
411
|
+
txCount,
|
|
412
|
+
activeUsers
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return snapshots;
|
|
416
|
+
}
|
|
417
|
+
generatePoolTransactions(poolId, snapshots) {
|
|
418
|
+
const params = POOL_PARAMS[poolId];
|
|
419
|
+
const transactions = [];
|
|
420
|
+
for (const snap of snapshots) {
|
|
421
|
+
const day = new Date(snap.date);
|
|
422
|
+
const dayOfWeek = day.getDay();
|
|
423
|
+
const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
|
|
424
|
+
const mult = isWeekend ? 0.2 : 1;
|
|
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
|
+
for (let i = 0; i < depositCount; i++) {
|
|
429
|
+
const amount = rand(params.avgDepositSize * 0.3, params.avgDepositSize * 2);
|
|
430
|
+
const ts = day.getTime() + randInt(0, 23) * 36e5 + randInt(0, 36e5);
|
|
431
|
+
transactions.push({
|
|
432
|
+
id: genId("dep"),
|
|
433
|
+
poolId,
|
|
434
|
+
type: "deposit",
|
|
435
|
+
amount,
|
|
436
|
+
balanceBefore: 0,
|
|
437
|
+
balanceAfter: 0,
|
|
438
|
+
txHash: genTxHash(),
|
|
439
|
+
blockNumber: this.baseBlockNumber + Math.floor(ts / 12e3) + randInt(0, 50),
|
|
440
|
+
timestamp: ts,
|
|
441
|
+
description: `Deposit to ${poolId} pool`
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
for (let i = 0; i < withdrawalCount; i++) {
|
|
445
|
+
const amount = rand(params.avgWithdrawalSize * 0.3, params.avgWithdrawalSize * 2);
|
|
446
|
+
const ts = day.getTime() + randInt(0, 23) * 36e5 + randInt(0, 36e5);
|
|
447
|
+
transactions.push({
|
|
448
|
+
id: genId("wdr"),
|
|
449
|
+
poolId,
|
|
450
|
+
type: "withdrawal",
|
|
451
|
+
amount: -amount,
|
|
452
|
+
balanceBefore: 0,
|
|
453
|
+
balanceAfter: 0,
|
|
454
|
+
txHash: genTxHash(),
|
|
455
|
+
blockNumber: this.baseBlockNumber + Math.floor(ts / 12e3) + randInt(0, 50),
|
|
456
|
+
timestamp: ts,
|
|
457
|
+
description: `Withdrawal from ${poolId} pool`
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
for (let i = 0; i < profitCount; i++) {
|
|
461
|
+
const amount = rand(500, 8e3);
|
|
462
|
+
const ts = day.getTime() + randInt(6, 22) * 36e5 + randInt(0, 36e5);
|
|
463
|
+
transactions.push({
|
|
464
|
+
id: genId("prd"),
|
|
465
|
+
poolId,
|
|
466
|
+
type: "profit_distribution",
|
|
467
|
+
amount,
|
|
468
|
+
balanceBefore: 0,
|
|
469
|
+
balanceAfter: 0,
|
|
470
|
+
txHash: genTxHash(),
|
|
471
|
+
blockNumber: this.baseBlockNumber + Math.floor(ts / 12e3) + randInt(0, 50),
|
|
472
|
+
timestamp: ts,
|
|
473
|
+
description: `Profit distribution from ${poolId} pool`
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
if (!isWeekend) {
|
|
477
|
+
const feeCount = randInt(1, 2);
|
|
478
|
+
for (let i = 0; i < feeCount; i++) {
|
|
479
|
+
const amount = rand(100, 3e3);
|
|
480
|
+
const ts = day.getTime() + randInt(8, 20) * 36e5;
|
|
481
|
+
transactions.push({
|
|
482
|
+
id: genId("fee"),
|
|
483
|
+
poolId,
|
|
484
|
+
type: "fee_collection",
|
|
485
|
+
amount,
|
|
486
|
+
balanceBefore: 0,
|
|
487
|
+
balanceAfter: 0,
|
|
488
|
+
txHash: genTxHash(),
|
|
489
|
+
blockNumber: this.baseBlockNumber + Math.floor(ts / 12e3) + randInt(0, 50),
|
|
490
|
+
timestamp: ts,
|
|
491
|
+
description: `Fee collection for ${poolId} pool`
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return transactions;
|
|
497
|
+
}
|
|
498
|
+
gaussianRandom(mean, stddev) {
|
|
499
|
+
let u = 0, v = 0;
|
|
500
|
+
while (u === 0) u = Math.random();
|
|
501
|
+
while (v === 0) v = Math.random();
|
|
502
|
+
const z = Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
|
|
503
|
+
return mean + z * stddev;
|
|
504
|
+
}
|
|
505
|
+
getTxDescription(type, poolId, amount) {
|
|
506
|
+
const fmt = `$${amount.toLocaleString(void 0, { maximumFractionDigits: 0 })}`;
|
|
507
|
+
switch (type) {
|
|
508
|
+
case "deposit":
|
|
509
|
+
return `Deposit ${fmt} to ${poolId} pool`;
|
|
510
|
+
case "withdrawal":
|
|
511
|
+
return `Withdrawal ${fmt} from ${poolId} pool`;
|
|
512
|
+
case "profit_distribution":
|
|
513
|
+
return `Profit distribution ${fmt} from ${poolId}`;
|
|
514
|
+
case "loss_absorption":
|
|
515
|
+
return `Loss absorbed ${fmt} by ${poolId} pool`;
|
|
516
|
+
case "inter_pool_transfer":
|
|
517
|
+
return `Inter-pool transfer ${fmt}`;
|
|
518
|
+
case "fee_collection":
|
|
519
|
+
return `Fee collected ${fmt} in ${poolId}`;
|
|
520
|
+
case "reserve_rebalance":
|
|
521
|
+
return `Reserve rebalance ${fmt} in ${poolId}`;
|
|
522
|
+
default:
|
|
523
|
+
return `${type} ${fmt}`;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
_instance = null;
|
|
528
|
+
ForexPoolDataGenerator = {
|
|
529
|
+
getInstance() {
|
|
530
|
+
if (!_instance) {
|
|
531
|
+
_instance = new ForexPoolDataGeneratorClass();
|
|
532
|
+
}
|
|
533
|
+
return _instance;
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
// src/services/forex/ForexSimulationEngine.ts
|
|
540
|
+
var ForexSimulationEngine_exports = {};
|
|
541
|
+
__export(ForexSimulationEngine_exports, {
|
|
542
|
+
forexSimulationEngine: () => forexSimulationEngine
|
|
543
|
+
});
|
|
544
|
+
function genId2() {
|
|
545
|
+
return `fxlog_${Date.now()}_${++idCounter}`;
|
|
546
|
+
}
|
|
547
|
+
function tradeId() {
|
|
548
|
+
return `FXT_${Date.now().toString(36).toUpperCase()}_${(++idCounter).toString(36).toUpperCase()}`;
|
|
549
|
+
}
|
|
550
|
+
function rand2(min, max) {
|
|
551
|
+
return min + Math.random() * (max - min);
|
|
552
|
+
}
|
|
553
|
+
function randInt2(min, max) {
|
|
554
|
+
return Math.floor(rand2(min, max + 1));
|
|
555
|
+
}
|
|
556
|
+
function pick2(arr) {
|
|
557
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
558
|
+
}
|
|
559
|
+
function formatRate(price, pair) {
|
|
560
|
+
if (pair.pipSize >= 0.01) return price.toFixed(3);
|
|
561
|
+
return price.toFixed(5);
|
|
562
|
+
}
|
|
563
|
+
var idCounter, FX_NEWS, ForexSimulationEngine, forexSimulationEngine;
|
|
564
|
+
var init_ForexSimulationEngine = __esm({
|
|
565
|
+
"src/services/forex/ForexSimulationEngine.ts"() {
|
|
566
|
+
init_forex();
|
|
567
|
+
idCounter = 0;
|
|
568
|
+
FX_NEWS = [
|
|
569
|
+
"ECB signals potential rate adjustment, EUR pairs volatile",
|
|
570
|
+
"BOJ maintains yield curve control, JPY weakens further",
|
|
571
|
+
"Fed minutes reveal hawkish sentiment, USD strengthens",
|
|
572
|
+
"UK CPI data beats expectations, GBP rallies",
|
|
573
|
+
"RBA holds rates steady, AUD consolidates",
|
|
574
|
+
"SNB intervenes in currency markets, CHF stabilizes",
|
|
575
|
+
"Bank of Canada rate decision pending, CAD in focus",
|
|
576
|
+
"Cross-border stablecoin settlement volume hits $2.1B daily",
|
|
577
|
+
"Circle USDC reserves fully backed, attestation report released",
|
|
578
|
+
"DeFi forex protocol TVL reaches new high at $890M",
|
|
579
|
+
"On-chain FX liquidity deepens across major pairs",
|
|
580
|
+
"Institutional adoption of on-chain forex accelerates"
|
|
581
|
+
];
|
|
582
|
+
ForexSimulationEngine = class {
|
|
583
|
+
constructor() {
|
|
584
|
+
this.listeners = [];
|
|
585
|
+
this.poolTxListeners = [];
|
|
586
|
+
this.cycleTimer = null;
|
|
587
|
+
this.pairStates = /* @__PURE__ */ new Map();
|
|
588
|
+
this.running = false;
|
|
589
|
+
this.openPositions = [];
|
|
590
|
+
this.totalPnl = 0;
|
|
591
|
+
this.totalTrades = 0;
|
|
592
|
+
this.totalPips = 0;
|
|
593
|
+
this.totalLots = 0;
|
|
594
|
+
for (const pair of FOREX_CURRENCY_PAIRS) {
|
|
595
|
+
const jitter = pair.basePrice * rand2(-3e-3, 3e-3);
|
|
596
|
+
const price = pair.basePrice + jitter;
|
|
597
|
+
const halfSpread = pair.spreadPips * pair.pipSize / 2;
|
|
598
|
+
this.pairStates.set(pair.id, {
|
|
599
|
+
pair,
|
|
600
|
+
currentPrice: price,
|
|
601
|
+
bidPrice: price - halfSpread,
|
|
602
|
+
askPrice: price + halfSpread,
|
|
603
|
+
lastSpread: pair.spreadPips
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
// ── Public API ─────────────────────────────────────────────────────────────
|
|
608
|
+
start() {
|
|
609
|
+
this.running = true;
|
|
610
|
+
this.scheduleCycle();
|
|
611
|
+
}
|
|
612
|
+
stop() {
|
|
613
|
+
this.running = false;
|
|
614
|
+
if (this.cycleTimer) {
|
|
615
|
+
clearTimeout(this.cycleTimer);
|
|
616
|
+
this.cycleTimer = null;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
onLog(callback) {
|
|
620
|
+
this.listeners.push(callback);
|
|
621
|
+
return () => {
|
|
622
|
+
this.listeners = this.listeners.filter((l) => l !== callback);
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
onPoolTransaction(callback) {
|
|
626
|
+
this.poolTxListeners.push(callback);
|
|
627
|
+
return () => {
|
|
628
|
+
this.poolTxListeners = this.poolTxListeners.filter((l) => l !== callback);
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
isRunning() {
|
|
632
|
+
return this.running;
|
|
633
|
+
}
|
|
634
|
+
getStats() {
|
|
635
|
+
return {
|
|
636
|
+
totalPnl: this.totalPnl,
|
|
637
|
+
totalTrades: this.totalTrades,
|
|
638
|
+
totalPips: this.totalPips,
|
|
639
|
+
totalLots: this.totalLots,
|
|
640
|
+
positions: this.openPositions.length
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
getPairStates() {
|
|
644
|
+
return this.pairStates;
|
|
645
|
+
}
|
|
646
|
+
emitBootSequence() {
|
|
647
|
+
const bootMessages = [
|
|
648
|
+
{ msg: "Initializing StableFX Engine v2.1.0...", delay: 0 },
|
|
649
|
+
{ msg: "Connecting to Circle StableFX RFQ network...", delay: 500 },
|
|
650
|
+
{ msg: "Loading USDC stablecoin pair feeds (6 pairs)...", delay: 1200 },
|
|
651
|
+
{ msg: "Calibrating PvP settlement engine...", delay: 2e3 },
|
|
652
|
+
{ msg: "Initializing clearing pool ($12.5M)...", delay: 2800 },
|
|
653
|
+
{ msg: "Initializing hedging pool ($7.5M)...", delay: 3400 },
|
|
654
|
+
{ msg: "Initializing insurance pool ($5.0M)...", delay: 4e3 },
|
|
655
|
+
{ msg: "Risk management module online (max exposure: 25%)", delay: 4500 },
|
|
656
|
+
{ msg: "=== StableFX Engine ready. Starting RFQ cycles ===", delay: 5e3 }
|
|
657
|
+
];
|
|
658
|
+
for (const { msg, delay } of bootMessages) {
|
|
659
|
+
setTimeout(() => {
|
|
660
|
+
this.emit({
|
|
661
|
+
id: genId2(),
|
|
662
|
+
timestamp: Date.now(),
|
|
663
|
+
type: "SYSTEM",
|
|
664
|
+
message: msg,
|
|
665
|
+
importance: "medium"
|
|
666
|
+
});
|
|
667
|
+
}, delay);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
destroy() {
|
|
671
|
+
this.stop();
|
|
672
|
+
this.listeners = [];
|
|
673
|
+
this.poolTxListeners = [];
|
|
674
|
+
this.pairStates.clear();
|
|
675
|
+
this.openPositions = [];
|
|
676
|
+
}
|
|
677
|
+
// ── Private Methods ────────────────────────────────────────────────────────
|
|
678
|
+
emit(entry) {
|
|
679
|
+
for (const listener of this.listeners) {
|
|
680
|
+
listener(entry);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
emitPoolTx(tx) {
|
|
684
|
+
for (const listener of this.poolTxListeners) {
|
|
685
|
+
listener(tx);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
generatePoolTxFromEvent(type, data) {
|
|
689
|
+
try {
|
|
690
|
+
const { ForexPoolDataGenerator: ForexPoolDataGenerator2 } = (init_ForexPoolDataGenerator(), __toCommonJS(ForexPoolDataGenerator_exports));
|
|
691
|
+
const generator = ForexPoolDataGenerator2.getInstance();
|
|
692
|
+
if (type === "SETTLE" && data.pnl !== void 0) {
|
|
693
|
+
const tx = generator.generateLiveTransaction("clearing", "fee_collection", Math.abs(data.pnl) * rand2(1e-3, 3e-3));
|
|
694
|
+
this.emitPoolTx(tx);
|
|
695
|
+
} else if (type === "HEDGE") {
|
|
696
|
+
const tx = generator.generateLiveTransaction("hedging", data.pnl && data.pnl < 0 ? "loss_absorption" : "profit_distribution", Math.abs(data.lots || 1) * rand2(50, 200));
|
|
697
|
+
this.emitPoolTx(tx);
|
|
698
|
+
} else if (type === "CLEAR") {
|
|
699
|
+
const tx = generator.generateLiveTransaction("clearing", "profit_distribution", (data.volume || 1e5) * rand2(1e-4, 5e-4));
|
|
700
|
+
this.emitPoolTx(tx);
|
|
701
|
+
}
|
|
702
|
+
} catch {
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
scheduleCycle() {
|
|
706
|
+
if (!this.running) return;
|
|
707
|
+
const interval = rand2(8e3, 14e3);
|
|
708
|
+
this.cycleTimer = setTimeout(() => {
|
|
709
|
+
if (this.running) {
|
|
710
|
+
this.runTradeCycle();
|
|
711
|
+
this.scheduleCycle();
|
|
712
|
+
}
|
|
713
|
+
}, interval);
|
|
714
|
+
}
|
|
715
|
+
simulatePrice(pairId) {
|
|
716
|
+
const state = this.pairStates.get(pairId);
|
|
717
|
+
if (!state) return 0;
|
|
718
|
+
const volatility = state.pair.id.includes("JPYC") ? 8e-4 : 4e-4;
|
|
719
|
+
const drift = rand2(-volatility, volatility);
|
|
720
|
+
const newPrice = state.currentPrice * (1 + drift);
|
|
721
|
+
const halfSpread = (state.pair.spreadPips + rand2(-0.3, 0.3)) * state.pair.pipSize / 2;
|
|
722
|
+
state.currentPrice = newPrice;
|
|
723
|
+
state.bidPrice = newPrice - halfSpread;
|
|
724
|
+
state.askPrice = newPrice + halfSpread;
|
|
725
|
+
state.lastSpread = halfSpread * 2 / state.pair.pipSize;
|
|
726
|
+
return newPrice;
|
|
727
|
+
}
|
|
728
|
+
runTradeCycle() {
|
|
729
|
+
const pairState = pick2(Array.from(this.pairStates.values()));
|
|
730
|
+
const pair = pairState.pair;
|
|
731
|
+
const entries = [];
|
|
732
|
+
let delay = 0;
|
|
733
|
+
const price = this.simulatePrice(pair.id);
|
|
734
|
+
const side = Math.random() > 0.5 ? "BUY" : "SELL";
|
|
735
|
+
const lots = parseFloat(rand2(0.1, 2.5).toFixed(2));
|
|
736
|
+
const notional = lots * 1e5;
|
|
737
|
+
const rfqId = tradeId();
|
|
738
|
+
entries.push({
|
|
739
|
+
entry: {
|
|
740
|
+
type: "RFQ",
|
|
741
|
+
message: `RFQ ${rfqId} | ${pair.symbol} ${side} ${lots.toFixed(2)} lots ($${(notional / 1e3).toFixed(0)}K) | Mid: ${formatRate(price, pair)}`,
|
|
742
|
+
data: { rfqId, pair: pair.id, side, lots, price },
|
|
743
|
+
importance: "medium",
|
|
744
|
+
pairId: pair.id
|
|
745
|
+
},
|
|
746
|
+
delay
|
|
747
|
+
});
|
|
748
|
+
delay += rand2(400, 800);
|
|
749
|
+
const quoteSpread = rand2(0.5, 2) * pair.pipSize;
|
|
750
|
+
const quotePrice = side === "BUY" ? price + quoteSpread : price - quoteSpread;
|
|
751
|
+
const quotePips = Math.abs(quotePrice - price) / pair.pipSize;
|
|
752
|
+
entries.push({
|
|
753
|
+
entry: {
|
|
754
|
+
type: "QUOTE",
|
|
755
|
+
message: `QUOTE ${rfqId} | ${pair.symbol} @ ${formatRate(quotePrice, pair)} | Spread: ${quotePips.toFixed(1)} pips | Valid: 3s`,
|
|
756
|
+
data: { rfqId, quotePrice, spread: quotePips },
|
|
757
|
+
importance: "medium",
|
|
758
|
+
pairId: pair.id
|
|
759
|
+
},
|
|
760
|
+
delay
|
|
761
|
+
});
|
|
762
|
+
delay += rand2(500, 1e3);
|
|
763
|
+
const matched = Math.random() < 0.85;
|
|
764
|
+
if (matched) {
|
|
765
|
+
const matchPrice = quotePrice * (1 + rand2(-5e-5, 5e-5));
|
|
766
|
+
entries.push({
|
|
767
|
+
entry: {
|
|
768
|
+
type: "MATCH",
|
|
769
|
+
message: `MATCH ${rfqId} | ${pair.symbol} ${side} @ ${formatRate(matchPrice, pair)} | ${lots.toFixed(2)} lots | Counterparty: LP-${randInt2(1, 8)}`,
|
|
770
|
+
data: { rfqId, matchPrice, counterparty: `LP-${randInt2(1, 8)}` },
|
|
771
|
+
importance: "high",
|
|
772
|
+
pairId: pair.id
|
|
773
|
+
},
|
|
774
|
+
delay
|
|
775
|
+
});
|
|
776
|
+
delay += rand2(1e3, 2500);
|
|
777
|
+
const settlePrice = matchPrice * (1 + rand2(-2e-5, 2e-5));
|
|
778
|
+
const pips = (settlePrice - price) / pair.pipSize * (side === "BUY" ? 1 : -1);
|
|
779
|
+
const pnl = pips * pair.pipSize * lots * 1e5;
|
|
780
|
+
entries.push({
|
|
781
|
+
entry: {
|
|
782
|
+
type: "SETTLE",
|
|
783
|
+
message: `SETTLE ${rfqId} | PvP confirmed | ${pair.symbol} @ ${formatRate(settlePrice, pair)} | P&L: ${pips >= 0 ? "+" : ""}${pips.toFixed(1)} pips ($${pnl >= 0 ? "+" : ""}${pnl.toFixed(2)})`,
|
|
784
|
+
data: { rfqId, settlePrice, pips, pnl, pvp: true },
|
|
785
|
+
importance: "high",
|
|
786
|
+
pairId: pair.id
|
|
787
|
+
},
|
|
788
|
+
delay
|
|
789
|
+
});
|
|
790
|
+
delay += rand2(300, 600);
|
|
791
|
+
entries.push({
|
|
792
|
+
entry: {
|
|
793
|
+
type: "PVP",
|
|
794
|
+
message: `PvP ${rfqId} | Atomic settlement confirmed on-chain | USDC transferred: $${notional.toLocaleString()} | Gas: ~$0.${randInt2(10, 50)}`,
|
|
795
|
+
data: { rfqId, settled: true, gasWei: randInt2(10, 50) },
|
|
796
|
+
importance: "medium",
|
|
797
|
+
pairId: pair.id
|
|
798
|
+
},
|
|
799
|
+
delay
|
|
800
|
+
});
|
|
801
|
+
this.generatePoolTxFromEvent("SETTLE", { pnl });
|
|
802
|
+
this.totalTrades++;
|
|
803
|
+
this.totalPnl += pnl;
|
|
804
|
+
this.totalPips += pips;
|
|
805
|
+
this.totalLots += lots;
|
|
806
|
+
if (Math.random() < 0.4) {
|
|
807
|
+
this.openPositions.push({
|
|
808
|
+
id: rfqId,
|
|
809
|
+
pairId: pair.id,
|
|
810
|
+
side,
|
|
811
|
+
lots,
|
|
812
|
+
pips,
|
|
813
|
+
entryPrice: matchPrice,
|
|
814
|
+
currentPrice: settlePrice,
|
|
815
|
+
pnl,
|
|
816
|
+
openTime: Date.now()
|
|
817
|
+
});
|
|
818
|
+
if (this.openPositions.length > 5) {
|
|
819
|
+
this.openPositions.shift();
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
} else {
|
|
823
|
+
entries.push({
|
|
824
|
+
entry: {
|
|
825
|
+
type: "MATCH",
|
|
826
|
+
message: `MATCH FAILED ${rfqId} | ${pair.symbol} | No counterparty at requested price | Requoting...`,
|
|
827
|
+
data: { rfqId, matched: false },
|
|
828
|
+
importance: "low",
|
|
829
|
+
pairId: pair.id
|
|
830
|
+
},
|
|
831
|
+
delay
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
if (Math.random() < 0.2) {
|
|
835
|
+
delay += rand2(400, 800);
|
|
836
|
+
const hedgePair = pick2(FOREX_CURRENCY_PAIRS);
|
|
837
|
+
const hedgeLots = parseFloat(rand2(0.5, 5).toFixed(2));
|
|
838
|
+
const hedgeDirection = Math.random() > 0.5 ? "LONG" : "SHORT";
|
|
839
|
+
entries.push({
|
|
840
|
+
entry: {
|
|
841
|
+
type: "HEDGE",
|
|
842
|
+
message: `HEDGE | ${hedgePair.symbol} ${hedgeDirection} ${hedgeLots.toFixed(2)} lots | Pool delta neutralization | Exposure: ${rand2(5, 20).toFixed(1)}%`,
|
|
843
|
+
data: { pair: hedgePair.id, direction: hedgeDirection, lots: hedgeLots },
|
|
844
|
+
importance: "medium"
|
|
845
|
+
},
|
|
846
|
+
delay
|
|
847
|
+
});
|
|
848
|
+
this.generatePoolTxFromEvent("HEDGE", { lots: hedgeLots });
|
|
849
|
+
}
|
|
850
|
+
if (Math.random() < 0.15) {
|
|
851
|
+
delay += rand2(300, 600);
|
|
852
|
+
const clearAmount = randInt2(5e4, 5e5);
|
|
853
|
+
entries.push({
|
|
854
|
+
entry: {
|
|
855
|
+
type: "CLEAR",
|
|
856
|
+
message: `CLEAR | Netting cycle complete | Volume: $${(clearAmount / 1e3).toFixed(0)}K | Pairs settled: ${randInt2(2, 6)} | Pool util: ${rand2(60, 85).toFixed(1)}%`,
|
|
857
|
+
data: { volume: clearAmount, pairsSettled: randInt2(2, 6) },
|
|
858
|
+
importance: "low"
|
|
859
|
+
},
|
|
860
|
+
delay
|
|
861
|
+
});
|
|
862
|
+
this.generatePoolTxFromEvent("CLEAR", { volume: clearAmount });
|
|
863
|
+
}
|
|
864
|
+
if (this.openPositions.length > 0 && Math.random() < 0.25) {
|
|
865
|
+
delay += rand2(300, 600);
|
|
866
|
+
const posUpdates = this.openPositions.slice(0, 3).map((pos) => {
|
|
867
|
+
const pState = this.pairStates.get(pos.pairId);
|
|
868
|
+
if (pState) {
|
|
869
|
+
pos.currentPrice = pState.currentPrice;
|
|
870
|
+
pos.pips = (pos.currentPrice - pos.entryPrice) / pState.pair.pipSize * (pos.side === "BUY" ? 1 : -1);
|
|
871
|
+
pos.pnl = pos.pips * pState.pair.pipSize * pos.lots * 1e5;
|
|
872
|
+
}
|
|
873
|
+
return `${pState?.pair.symbol || pos.pairId} ${pos.side}: ${pos.pips >= 0 ? "+" : ""}${pos.pips.toFixed(1)} pips`;
|
|
874
|
+
});
|
|
875
|
+
entries.push({
|
|
876
|
+
entry: {
|
|
877
|
+
type: "POSITION",
|
|
878
|
+
message: `POSITION | ${posUpdates.join(" | ")} | Open: ${this.openPositions.length}`,
|
|
879
|
+
data: { positions: this.openPositions.length },
|
|
880
|
+
importance: "low"
|
|
881
|
+
},
|
|
882
|
+
delay
|
|
883
|
+
});
|
|
884
|
+
if (this.openPositions.length > 2 && Math.random() < 0.3) {
|
|
885
|
+
const closed = this.openPositions.shift();
|
|
886
|
+
this.totalPnl += closed.pnl * rand2(0.01, 0.03);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (Math.random() < 0.3) {
|
|
890
|
+
delay += rand2(300, 600);
|
|
891
|
+
entries.push({
|
|
892
|
+
entry: {
|
|
893
|
+
type: "PNL",
|
|
894
|
+
message: `PNL | Session: $${this.totalPnl >= 0 ? "+" : ""}${this.totalPnl.toFixed(2)} | Trades: ${this.totalTrades} | Pips: ${this.totalPips >= 0 ? "+" : ""}${this.totalPips.toFixed(1)} | Lots: ${this.totalLots.toFixed(2)}`,
|
|
895
|
+
data: { totalPnl: this.totalPnl, totalTrades: this.totalTrades, totalPips: this.totalPips },
|
|
896
|
+
importance: "medium"
|
|
897
|
+
},
|
|
898
|
+
delay
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
if (Math.random() < 0.1) {
|
|
902
|
+
delay += rand2(300, 600);
|
|
903
|
+
entries.push({
|
|
904
|
+
entry: {
|
|
905
|
+
type: "SYSTEM",
|
|
906
|
+
message: `[Market] ${pick2(FX_NEWS)}`,
|
|
907
|
+
importance: "medium"
|
|
908
|
+
},
|
|
909
|
+
delay
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
for (const { entry, delay: d } of entries) {
|
|
913
|
+
setTimeout(() => {
|
|
914
|
+
if (this.running) {
|
|
915
|
+
this.emit({
|
|
916
|
+
...entry,
|
|
917
|
+
id: genId2(),
|
|
918
|
+
timestamp: Date.now()
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
}, d);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
forexSimulationEngine = new ForexSimulationEngine();
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// src/services/forex/BotSimulationEngine.ts
|
|
930
|
+
var BotSimulationEngine_exports = {};
|
|
931
|
+
__export(BotSimulationEngine_exports, {
|
|
932
|
+
STRATEGY_PERSONALITIES: () => STRATEGY_PERSONALITIES,
|
|
933
|
+
botSimulationEngine: () => botSimulationEngine
|
|
934
|
+
});
|
|
935
|
+
function genId3() {
|
|
936
|
+
return `log_${Date.now()}_${++idCounter2}`;
|
|
937
|
+
}
|
|
938
|
+
function rand3(min, max) {
|
|
939
|
+
return min + Math.random() * (max - min);
|
|
940
|
+
}
|
|
941
|
+
function randInt3(min, max) {
|
|
942
|
+
return Math.floor(rand3(min, max + 1));
|
|
943
|
+
}
|
|
944
|
+
function pick3(arr) {
|
|
945
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
946
|
+
}
|
|
947
|
+
function clamp(val, min, max) {
|
|
948
|
+
return Math.max(min, Math.min(max, val));
|
|
949
|
+
}
|
|
950
|
+
function formatPrice(price) {
|
|
951
|
+
if (price >= 1e3) return price.toFixed(2);
|
|
952
|
+
if (price >= 1) return price.toFixed(3);
|
|
953
|
+
return price.toFixed(5);
|
|
954
|
+
}
|
|
955
|
+
var STRATEGY_PERSONALITIES, PAIR_PRICES, CHAIN_INFO, NEWS_HEADLINES, idCounter2, BotSimulationEngine, botSimulationEngine;
|
|
956
|
+
var init_BotSimulationEngine = __esm({
|
|
957
|
+
"src/services/forex/BotSimulationEngine.ts"() {
|
|
958
|
+
STRATEGY_PERSONALITIES = [
|
|
959
|
+
{
|
|
960
|
+
id: "balanced-01",
|
|
961
|
+
name: "Balanced Alpha",
|
|
962
|
+
shortName: "BAL",
|
|
963
|
+
color: "#3B82F6",
|
|
964
|
+
scanIntervalMin: 25e3,
|
|
965
|
+
// Slower: 25-40s between cycles
|
|
966
|
+
scanIntervalMax: 4e4,
|
|
967
|
+
tradeFrequency: 0.4,
|
|
968
|
+
positionSizeMin: 15,
|
|
969
|
+
positionSizeMax: 35,
|
|
970
|
+
leverageMin: 3,
|
|
971
|
+
leverageMax: 10,
|
|
972
|
+
primaryIndicators: ["RSI", "MACD"],
|
|
973
|
+
riskTolerance: "medium",
|
|
974
|
+
preferredPairs: ["BTC/USDT", "ETH/USDT", "SOL/USDT"],
|
|
975
|
+
rsiBias: 50
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
id: "conservative-01",
|
|
979
|
+
name: "Conservative Shield",
|
|
980
|
+
shortName: "CON",
|
|
981
|
+
color: "#10B981",
|
|
982
|
+
scanIntervalMin: 35e3,
|
|
983
|
+
// Slower: 35-55s between cycles
|
|
984
|
+
scanIntervalMax: 55e3,
|
|
985
|
+
tradeFrequency: 0.25,
|
|
986
|
+
positionSizeMin: 10,
|
|
987
|
+
positionSizeMax: 20,
|
|
988
|
+
leverageMin: 2,
|
|
989
|
+
leverageMax: 5,
|
|
990
|
+
primaryIndicators: ["Bollinger", "Volume"],
|
|
991
|
+
riskTolerance: "low",
|
|
992
|
+
preferredPairs: ["BTC/USDT", "ETH/USDT"],
|
|
993
|
+
rsiBias: 45
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
id: "aggressive-01",
|
|
997
|
+
name: "Aggressive Momentum",
|
|
998
|
+
shortName: "AGG",
|
|
999
|
+
color: "#EF4444",
|
|
1000
|
+
scanIntervalMin: 18e3,
|
|
1001
|
+
// Slower: 18-30s between cycles
|
|
1002
|
+
scanIntervalMax: 3e4,
|
|
1003
|
+
tradeFrequency: 0.5,
|
|
1004
|
+
positionSizeMin: 25,
|
|
1005
|
+
positionSizeMax: 50,
|
|
1006
|
+
leverageMin: 5,
|
|
1007
|
+
leverageMax: 20,
|
|
1008
|
+
primaryIndicators: ["RSI", "MACD", "EMA", "Volume"],
|
|
1009
|
+
riskTolerance: "high",
|
|
1010
|
+
preferredPairs: ["BTC/USDT", "ETH/USDT", "SOL/USDT", "DOGE/USDT", "AVAX/USDT"],
|
|
1011
|
+
rsiBias: 55
|
|
1012
|
+
}
|
|
1013
|
+
];
|
|
1014
|
+
PAIR_PRICES = {
|
|
1015
|
+
"BTC/USDT": 67500,
|
|
1016
|
+
"ETH/USDT": 3450,
|
|
1017
|
+
"BNB/USDT": 605,
|
|
1018
|
+
"SOL/USDT": 178,
|
|
1019
|
+
"XRP/USDT": 0.62,
|
|
1020
|
+
"DOGE/USDT": 0.165,
|
|
1021
|
+
"ADA/USDT": 0.45,
|
|
1022
|
+
"AVAX/USDT": 38.5,
|
|
1023
|
+
"ARB/USDT": 1.18,
|
|
1024
|
+
"MATIC/USDT": 0.72,
|
|
1025
|
+
"LINK/USDT": 14.5,
|
|
1026
|
+
"UNI/USDT": 7.8,
|
|
1027
|
+
"AAVE/USDT": 92,
|
|
1028
|
+
"OP/USDT": 2.45,
|
|
1029
|
+
"APT/USDT": 8.9,
|
|
1030
|
+
"INJ/USDT": 24.5,
|
|
1031
|
+
"TIA/USDT": 11.2,
|
|
1032
|
+
"SUI/USDT": 1.65,
|
|
1033
|
+
"DOT/USDT": 7.2,
|
|
1034
|
+
"ATOM/USDT": 9.8,
|
|
1035
|
+
"FIL/USDT": 5.6,
|
|
1036
|
+
"LTC/USDT": 72,
|
|
1037
|
+
"NEAR/USDT": 5.1,
|
|
1038
|
+
"FTM/USDT": 0.42
|
|
1039
|
+
};
|
|
1040
|
+
CHAIN_INFO = {
|
|
1041
|
+
ethereum: { name: "Ethereum", shortName: "ETH", icon: "\u039E" },
|
|
1042
|
+
arbitrum: { name: "Arbitrum", shortName: "ARB", icon: "\u25C6" },
|
|
1043
|
+
bsc: { name: "BSC", shortName: "BSC", icon: "\u25C6" },
|
|
1044
|
+
base: { name: "Base", shortName: "BASE", icon: "\u25CF" },
|
|
1045
|
+
polygon: { name: "Polygon", shortName: "POLY", icon: "\u2B21" },
|
|
1046
|
+
optimism: { name: "Optimism", shortName: "OP", icon: "\u25C9" },
|
|
1047
|
+
avalanche: { name: "Avalanche", shortName: "AVAX", icon: "\u25B2" },
|
|
1048
|
+
linea: { name: "Linea", shortName: "LINEA", icon: "\u2550" },
|
|
1049
|
+
zksync: { name: "zkSync", shortName: "ZK", icon: "\u2B22" },
|
|
1050
|
+
scroll: { name: "Scroll", shortName: "SCRL", icon: "\u25CE" }
|
|
1051
|
+
};
|
|
1052
|
+
NEWS_HEADLINES = [
|
|
1053
|
+
"Fed signals potential rate pause, crypto markets react positively",
|
|
1054
|
+
"Major institutional investor increases BTC allocation by 15%",
|
|
1055
|
+
"On-chain data shows whale accumulation pattern forming",
|
|
1056
|
+
"DeFi TVL reaches new monthly high across major protocols",
|
|
1057
|
+
"Exchange outflows surge as holders move to cold storage",
|
|
1058
|
+
"Options market signals increased volatility expected this week",
|
|
1059
|
+
"Mining difficulty adjustment approaching, hash rate stable",
|
|
1060
|
+
"Regulatory clarity in EU boosts market sentiment",
|
|
1061
|
+
"Stablecoin supply expanding, potential bullish indicator",
|
|
1062
|
+
"Social sentiment score shifts to extreme greed zone",
|
|
1063
|
+
"Cross-chain bridge volume hits record daily high",
|
|
1064
|
+
"Layer 2 adoption metrics show 40% MoM growth"
|
|
1065
|
+
];
|
|
1066
|
+
idCounter2 = 0;
|
|
1067
|
+
BotSimulationEngine = class {
|
|
1068
|
+
constructor() {
|
|
1069
|
+
this.listeners = [];
|
|
1070
|
+
this.botTimers = /* @__PURE__ */ new Map();
|
|
1071
|
+
this.botStates = /* @__PURE__ */ new Map();
|
|
1072
|
+
this.priceState = /* @__PURE__ */ new Map();
|
|
1073
|
+
this.indicatorState = /* @__PURE__ */ new Map();
|
|
1074
|
+
this.running = false;
|
|
1075
|
+
this.userPairs = [];
|
|
1076
|
+
this.userChains = [];
|
|
1077
|
+
for (const [pair, base] of Object.entries(PAIR_PRICES)) {
|
|
1078
|
+
this.priceState.set(pair, base * (1 + rand3(-0.02, 0.02)));
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
// ── Public API ─────────────────────────────────────────────────────────────
|
|
1082
|
+
getStrategies() {
|
|
1083
|
+
return STRATEGY_PERSONALITIES;
|
|
1084
|
+
}
|
|
1085
|
+
start(strategyIds, userPairs, userChains) {
|
|
1086
|
+
this.running = true;
|
|
1087
|
+
this.userPairs = (userPairs || []).map((p) => p.includes("/") ? p : `${p}/USDT`).filter((p) => p in PAIR_PRICES);
|
|
1088
|
+
this.userChains = (userChains || []).filter((c) => c in CHAIN_INFO);
|
|
1089
|
+
const strategies = strategyIds ? STRATEGY_PERSONALITIES.filter((s) => strategyIds.includes(s.id)) : STRATEGY_PERSONALITIES;
|
|
1090
|
+
for (const strategy of strategies) {
|
|
1091
|
+
this.initBotState(strategy);
|
|
1092
|
+
this.scheduleCycle(strategy);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
stop(strategyIds) {
|
|
1096
|
+
const ids = strategyIds || Array.from(this.botTimers.keys());
|
|
1097
|
+
for (const id of ids) {
|
|
1098
|
+
const timer = this.botTimers.get(id);
|
|
1099
|
+
if (timer) {
|
|
1100
|
+
clearTimeout(timer);
|
|
1101
|
+
this.botTimers.delete(id);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
if (!strategyIds) {
|
|
1105
|
+
this.running = false;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
onLog(callback) {
|
|
1109
|
+
this.listeners.push(callback);
|
|
1110
|
+
return () => {
|
|
1111
|
+
this.listeners = this.listeners.filter((l) => l !== callback);
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
getBotState(strategyId) {
|
|
1115
|
+
return this.botStates.get(strategyId);
|
|
1116
|
+
}
|
|
1117
|
+
getAllBotStates() {
|
|
1118
|
+
return this.botStates;
|
|
1119
|
+
}
|
|
1120
|
+
isRunning() {
|
|
1121
|
+
return this.running;
|
|
1122
|
+
}
|
|
1123
|
+
emitBootSequence() {
|
|
1124
|
+
const bootMessages = [
|
|
1125
|
+
{ msg: "Initializing ONE Trading Engine v3.2.1...", delay: 0 },
|
|
1126
|
+
{ msg: "Loading market data feeds...", delay: 500 },
|
|
1127
|
+
{ msg: "Connecting to exchange WebSocket streams...", delay: 1200 },
|
|
1128
|
+
{ msg: "Calibrating indicator engines (RSI, MACD, EMA, Bollinger)...", delay: 2e3 },
|
|
1129
|
+
{ msg: "Loading strategy personalities: balanced-01, conservative-01, aggressive-01", delay: 2800 },
|
|
1130
|
+
{ msg: "Risk management module initialized (max drawdown: 15%)", delay: 3600 },
|
|
1131
|
+
{ msg: "Portfolio allocation engine ready", delay: 4200 },
|
|
1132
|
+
{ msg: "=== All systems online. Starting trading cycles ===", delay: 5e3 }
|
|
1133
|
+
];
|
|
1134
|
+
for (const { msg, delay } of bootMessages) {
|
|
1135
|
+
setTimeout(() => {
|
|
1136
|
+
this.emit({
|
|
1137
|
+
id: genId3(),
|
|
1138
|
+
timestamp: Date.now(),
|
|
1139
|
+
strategyId: "system",
|
|
1140
|
+
strategyName: "SYSTEM",
|
|
1141
|
+
type: "SYSTEM",
|
|
1142
|
+
message: msg,
|
|
1143
|
+
importance: "medium"
|
|
1144
|
+
});
|
|
1145
|
+
}, delay);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
destroy() {
|
|
1149
|
+
this.stop();
|
|
1150
|
+
this.listeners = [];
|
|
1151
|
+
this.botStates.clear();
|
|
1152
|
+
this.priceState.clear();
|
|
1153
|
+
this.indicatorState.clear();
|
|
1154
|
+
this.userPairs = [];
|
|
1155
|
+
this.userChains = [];
|
|
1156
|
+
}
|
|
1157
|
+
// ── Private Methods ────────────────────────────────────────────────────────
|
|
1158
|
+
emit(entry) {
|
|
1159
|
+
for (const listener of this.listeners) {
|
|
1160
|
+
listener(entry);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
getActivePairs(strategy) {
|
|
1164
|
+
return this.userPairs.length > 0 ? this.userPairs : strategy.preferredPairs;
|
|
1165
|
+
}
|
|
1166
|
+
getActiveChain() {
|
|
1167
|
+
const chains = this.userChains.length > 0 ? this.userChains : ["ethereum", "arbitrum", "bsc"];
|
|
1168
|
+
return pick3(chains);
|
|
1169
|
+
}
|
|
1170
|
+
getChainLabel(chainId) {
|
|
1171
|
+
const info = CHAIN_INFO[chainId];
|
|
1172
|
+
return info ? info.shortName : chainId;
|
|
1173
|
+
}
|
|
1174
|
+
initBotState(strategy) {
|
|
1175
|
+
const activePairs = this.getActivePairs(strategy);
|
|
1176
|
+
const pair = pick3(activePairs);
|
|
1177
|
+
const price = this.priceState.get(pair) || PAIR_PRICES[pair] || 5e4;
|
|
1178
|
+
const indicators = this.generateIndicators(strategy, price);
|
|
1179
|
+
this.indicatorState.set(strategy.id, indicators);
|
|
1180
|
+
this.botStates.set(strategy.id, {
|
|
1181
|
+
strategyId: strategy.id,
|
|
1182
|
+
strategyName: strategy.name,
|
|
1183
|
+
isRunning: true,
|
|
1184
|
+
currentPair: pair,
|
|
1185
|
+
currentPrice: price,
|
|
1186
|
+
indicators,
|
|
1187
|
+
openPositions: [],
|
|
1188
|
+
totalPnl: rand3(-50, 200),
|
|
1189
|
+
totalTrades: randInt3(5, 25),
|
|
1190
|
+
winRate: rand3(0.48, 0.68),
|
|
1191
|
+
lastSignal: "HOLD",
|
|
1192
|
+
lastSignalConfidence: 0
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
scheduleCycle(strategy) {
|
|
1196
|
+
if (!this.running) return;
|
|
1197
|
+
const interval = rand3(strategy.scanIntervalMin, strategy.scanIntervalMax);
|
|
1198
|
+
const timer = setTimeout(() => {
|
|
1199
|
+
if (this.running) {
|
|
1200
|
+
this.runBotCycle(strategy);
|
|
1201
|
+
this.scheduleCycle(strategy);
|
|
1202
|
+
}
|
|
1203
|
+
}, interval);
|
|
1204
|
+
this.botTimers.set(strategy.id, timer);
|
|
1205
|
+
}
|
|
1206
|
+
async runBotCycle(strategy) {
|
|
1207
|
+
const state = this.botStates.get(strategy.id);
|
|
1208
|
+
if (!state) return;
|
|
1209
|
+
const activePairs = this.getActivePairs(strategy);
|
|
1210
|
+
const pair = pick3(activePairs);
|
|
1211
|
+
const price = this.simulatePrice(pair);
|
|
1212
|
+
const indicators = this.generateIndicators(strategy, price);
|
|
1213
|
+
this.indicatorState.set(strategy.id, indicators);
|
|
1214
|
+
state.currentPair = pair;
|
|
1215
|
+
state.currentPrice = price;
|
|
1216
|
+
state.indicators = indicators;
|
|
1217
|
+
const entries = [];
|
|
1218
|
+
let delay = 0;
|
|
1219
|
+
const chain = this.getActiveChain();
|
|
1220
|
+
const chainLabel = this.getChainLabel(chain);
|
|
1221
|
+
entries.push({
|
|
1222
|
+
entry: {
|
|
1223
|
+
strategyId: strategy.id,
|
|
1224
|
+
strategyName: strategy.shortName,
|
|
1225
|
+
type: "SCAN",
|
|
1226
|
+
message: `Scanning ${pair} on ${chainLabel} | Price: $${formatPrice(price)}`,
|
|
1227
|
+
data: { pair, chain, chainLabel },
|
|
1228
|
+
importance: "low"
|
|
1229
|
+
},
|
|
1230
|
+
delay
|
|
1231
|
+
});
|
|
1232
|
+
delay += rand3(800, 1500);
|
|
1233
|
+
const thinkingMessages = this.generateThinkingProcess(strategy, pair, price);
|
|
1234
|
+
for (const thinking of thinkingMessages) {
|
|
1235
|
+
entries.push({
|
|
1236
|
+
entry: {
|
|
1237
|
+
strategyId: strategy.id,
|
|
1238
|
+
strategyName: strategy.shortName,
|
|
1239
|
+
type: "THINKING",
|
|
1240
|
+
message: thinking,
|
|
1241
|
+
importance: "low"
|
|
1242
|
+
},
|
|
1243
|
+
delay
|
|
1244
|
+
});
|
|
1245
|
+
delay += rand3(600, 1200);
|
|
1246
|
+
}
|
|
1247
|
+
const indicatorParts = [];
|
|
1248
|
+
if (strategy.primaryIndicators.includes("RSI") || strategy.primaryIndicators.includes("MACD")) {
|
|
1249
|
+
indicatorParts.push(`RSI: ${indicators.rsi.toFixed(1)}`);
|
|
1250
|
+
}
|
|
1251
|
+
if (strategy.primaryIndicators.includes("MACD") || strategy.primaryIndicators.includes("RSI")) {
|
|
1252
|
+
indicatorParts.push(`MACD: ${indicators.macd.histogram > 0 ? "+" : ""}${indicators.macd.histogram.toFixed(3)}`);
|
|
1253
|
+
}
|
|
1254
|
+
if (strategy.primaryIndicators.includes("EMA")) {
|
|
1255
|
+
indicatorParts.push(`EMA: ${indicators.ema.short.toFixed(1)}/${indicators.ema.long.toFixed(1)}`);
|
|
1256
|
+
if (indicators.ema.crossover !== "none") {
|
|
1257
|
+
indicatorParts.push(`[${indicators.ema.crossover.toUpperCase()} CROSS]`);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (strategy.primaryIndicators.includes("Bollinger")) {
|
|
1261
|
+
indicatorParts.push(`BB: ${indicators.bollinger.position.toFixed(1)}% width=${indicators.bollinger.width.toFixed(2)}`);
|
|
1262
|
+
}
|
|
1263
|
+
if (strategy.primaryIndicators.includes("Volume")) {
|
|
1264
|
+
indicatorParts.push(`Vol: ${indicators.volume.ratio.toFixed(2)}x avg`);
|
|
1265
|
+
}
|
|
1266
|
+
entries.push({
|
|
1267
|
+
entry: {
|
|
1268
|
+
strategyId: strategy.id,
|
|
1269
|
+
strategyName: strategy.shortName,
|
|
1270
|
+
type: "INDICATOR",
|
|
1271
|
+
message: indicatorParts.join(" | "),
|
|
1272
|
+
data: { indicators },
|
|
1273
|
+
importance: "low"
|
|
1274
|
+
},
|
|
1275
|
+
delay
|
|
1276
|
+
});
|
|
1277
|
+
delay += rand3(800, 1500);
|
|
1278
|
+
if (Math.random() < 0.12) {
|
|
1279
|
+
const sentiment = Math.random() > 0.4 ? "Bullish" : "Bearish";
|
|
1280
|
+
entries.push({
|
|
1281
|
+
entry: {
|
|
1282
|
+
strategyId: strategy.id,
|
|
1283
|
+
strategyName: strategy.shortName,
|
|
1284
|
+
type: "NEWS",
|
|
1285
|
+
message: `[${sentiment}] ${pick3(NEWS_HEADLINES)}`,
|
|
1286
|
+
importance: "medium"
|
|
1287
|
+
},
|
|
1288
|
+
delay
|
|
1289
|
+
});
|
|
1290
|
+
delay += rand3(1e3, 1800);
|
|
1291
|
+
}
|
|
1292
|
+
if (Math.random() < 0.4) {
|
|
1293
|
+
const analysis = this.generateAnalysis(strategy, indicators, pair);
|
|
1294
|
+
entries.push({
|
|
1295
|
+
entry: {
|
|
1296
|
+
strategyId: strategy.id,
|
|
1297
|
+
strategyName: strategy.shortName,
|
|
1298
|
+
type: "ANALYSIS",
|
|
1299
|
+
message: analysis,
|
|
1300
|
+
importance: "medium"
|
|
1301
|
+
},
|
|
1302
|
+
delay
|
|
1303
|
+
});
|
|
1304
|
+
delay += rand3(1e3, 2e3);
|
|
1305
|
+
}
|
|
1306
|
+
const signal = this.evaluateSignal(strategy, indicators);
|
|
1307
|
+
state.lastSignal = signal.direction;
|
|
1308
|
+
state.lastSignalConfidence = signal.confidence;
|
|
1309
|
+
if (signal.direction !== "HOLD") {
|
|
1310
|
+
const strategyContext = this.generateStrategyContext(strategy, signal, indicators, pair);
|
|
1311
|
+
entries.push({
|
|
1312
|
+
entry: {
|
|
1313
|
+
strategyId: strategy.id,
|
|
1314
|
+
strategyName: strategy.shortName,
|
|
1315
|
+
type: "STRATEGY",
|
|
1316
|
+
message: strategyContext,
|
|
1317
|
+
data: {
|
|
1318
|
+
strategy: strategy.name,
|
|
1319
|
+
riskTolerance: strategy.riskTolerance,
|
|
1320
|
+
primaryIndicators: strategy.primaryIndicators,
|
|
1321
|
+
signal: signal.direction,
|
|
1322
|
+
confidence: signal.confidence
|
|
1323
|
+
},
|
|
1324
|
+
importance: "high"
|
|
1325
|
+
},
|
|
1326
|
+
delay
|
|
1327
|
+
});
|
|
1328
|
+
delay += rand3(1500, 2500);
|
|
1329
|
+
entries.push({
|
|
1330
|
+
entry: {
|
|
1331
|
+
strategyId: strategy.id,
|
|
1332
|
+
strategyName: strategy.shortName,
|
|
1333
|
+
type: "SIGNAL",
|
|
1334
|
+
message: `${signal.direction} signal detected | Confidence: ${(signal.confidence * 100).toFixed(1)}% | ${signal.reason}`,
|
|
1335
|
+
data: { signal },
|
|
1336
|
+
importance: "high"
|
|
1337
|
+
},
|
|
1338
|
+
delay
|
|
1339
|
+
});
|
|
1340
|
+
delay += rand3(1200, 2e3);
|
|
1341
|
+
const decision = this.makeTradeDecision(strategy, signal, state);
|
|
1342
|
+
if (decision.execute) {
|
|
1343
|
+
entries.push({
|
|
1344
|
+
entry: {
|
|
1345
|
+
strategyId: strategy.id,
|
|
1346
|
+
strategyName: strategy.shortName,
|
|
1347
|
+
type: "DECISION",
|
|
1348
|
+
message: `Execute ${signal.direction} | Size: ${decision.positionSize.toFixed(1)}% | Leverage: ${decision.leverage}x | Risk/Reward: 1:${decision.riskReward.toFixed(1)}`,
|
|
1349
|
+
data: {
|
|
1350
|
+
strategyName: strategy.name,
|
|
1351
|
+
strategyId: strategy.id,
|
|
1352
|
+
riskTolerance: strategy.riskTolerance,
|
|
1353
|
+
signalReason: signal.reason,
|
|
1354
|
+
confidence: signal.confidence
|
|
1355
|
+
},
|
|
1356
|
+
importance: "high"
|
|
1357
|
+
},
|
|
1358
|
+
delay
|
|
1359
|
+
});
|
|
1360
|
+
delay += rand3(1e3, 1800);
|
|
1361
|
+
const orderId = `ORD_${Date.now().toString(36).toUpperCase()}`;
|
|
1362
|
+
const orderPrice = signal.direction === "LONG" ? price * (1 - rand3(1e-4, 5e-4)) : price * (1 + rand3(1e-4, 5e-4));
|
|
1363
|
+
entries.push({
|
|
1364
|
+
entry: {
|
|
1365
|
+
strategyId: strategy.id,
|
|
1366
|
+
strategyName: strategy.shortName,
|
|
1367
|
+
type: "ORDER",
|
|
1368
|
+
message: `Submitting ${signal.direction} order | ${pair} @ $${formatPrice(orderPrice)} on ${chainLabel} | ID: ${orderId}`,
|
|
1369
|
+
data: {
|
|
1370
|
+
orderId,
|
|
1371
|
+
pair,
|
|
1372
|
+
side: signal.direction,
|
|
1373
|
+
price: orderPrice,
|
|
1374
|
+
leverage: decision.leverage,
|
|
1375
|
+
chain,
|
|
1376
|
+
chainLabel,
|
|
1377
|
+
strategyName: strategy.name,
|
|
1378
|
+
strategyContext,
|
|
1379
|
+
signalReason: signal.reason
|
|
1380
|
+
},
|
|
1381
|
+
importance: "high"
|
|
1382
|
+
},
|
|
1383
|
+
delay
|
|
1384
|
+
});
|
|
1385
|
+
delay += rand3(2e3, 4e3);
|
|
1386
|
+
const fillPrice = orderPrice * (1 + rand3(-3e-4, 3e-4));
|
|
1387
|
+
const slippage = Math.abs(fillPrice - orderPrice) / orderPrice * 100;
|
|
1388
|
+
entries.push({
|
|
1389
|
+
entry: {
|
|
1390
|
+
strategyId: strategy.id,
|
|
1391
|
+
strategyName: strategy.shortName,
|
|
1392
|
+
type: "FILLED",
|
|
1393
|
+
message: `Order FILLED | ${pair} ${signal.direction} @ $${formatPrice(fillPrice)} on ${chainLabel} | Slippage: ${slippage.toFixed(4)}% | ID: ${orderId}`,
|
|
1394
|
+
data: {
|
|
1395
|
+
orderId,
|
|
1396
|
+
fillPrice,
|
|
1397
|
+
slippage,
|
|
1398
|
+
chain,
|
|
1399
|
+
chainLabel,
|
|
1400
|
+
strategyName: strategy.name,
|
|
1401
|
+
executedBy: strategy.id
|
|
1402
|
+
},
|
|
1403
|
+
importance: "high"
|
|
1404
|
+
},
|
|
1405
|
+
delay
|
|
1406
|
+
});
|
|
1407
|
+
const position = {
|
|
1408
|
+
id: orderId,
|
|
1409
|
+
pair,
|
|
1410
|
+
side: signal.direction,
|
|
1411
|
+
entryPrice: fillPrice,
|
|
1412
|
+
currentPrice: price,
|
|
1413
|
+
size: decision.positionSize,
|
|
1414
|
+
leverage: decision.leverage,
|
|
1415
|
+
pnl: 0,
|
|
1416
|
+
pnlPercent: 0
|
|
1417
|
+
};
|
|
1418
|
+
state.openPositions = [...state.openPositions.slice(-2), position];
|
|
1419
|
+
state.totalTrades++;
|
|
1420
|
+
} else {
|
|
1421
|
+
entries.push({
|
|
1422
|
+
entry: {
|
|
1423
|
+
strategyId: strategy.id,
|
|
1424
|
+
strategyName: strategy.shortName,
|
|
1425
|
+
type: "DECISION",
|
|
1426
|
+
message: `SKIP - ${decision.reason}`,
|
|
1427
|
+
importance: "medium"
|
|
1428
|
+
},
|
|
1429
|
+
delay
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
if (state.openPositions.length > 0 && Math.random() < 0.5) {
|
|
1434
|
+
delay += rand3(1500, 2500);
|
|
1435
|
+
let totalPositionPnl = 0;
|
|
1436
|
+
const pnlParts = [];
|
|
1437
|
+
for (const pos of state.openPositions) {
|
|
1438
|
+
pos.currentPrice = this.simulatePrice(pos.pair);
|
|
1439
|
+
const priceDiff = pos.side === "LONG" ? (pos.currentPrice - pos.entryPrice) / pos.entryPrice : (pos.entryPrice - pos.currentPrice) / pos.entryPrice;
|
|
1440
|
+
pos.pnlPercent = priceDiff * pos.leverage * 100;
|
|
1441
|
+
pos.pnl = priceDiff * pos.leverage * pos.size;
|
|
1442
|
+
totalPositionPnl += pos.pnl;
|
|
1443
|
+
pnlParts.push(`${pos.pair} ${pos.side}: ${pos.pnlPercent >= 0 ? "+" : ""}${pos.pnlPercent.toFixed(2)}%`);
|
|
1444
|
+
}
|
|
1445
|
+
state.totalPnl += totalPositionPnl * rand3(0.01, 0.05);
|
|
1446
|
+
if (state.openPositions.length > 1 && Math.random() < 0.3) {
|
|
1447
|
+
const closed = state.openPositions.shift();
|
|
1448
|
+
const finalPnl = closed.pnlPercent;
|
|
1449
|
+
if (finalPnl > 0) {
|
|
1450
|
+
state.winRate = state.winRate * 0.95 + 0.05;
|
|
1451
|
+
} else {
|
|
1452
|
+
state.winRate = state.winRate * 0.95;
|
|
1453
|
+
}
|
|
1454
|
+
state.winRate = clamp(state.winRate, 0.35, 0.75);
|
|
1455
|
+
pnlParts.push(`CLOSED ${closed.pair}: ${finalPnl >= 0 ? "+" : ""}${finalPnl.toFixed(2)}%`);
|
|
1456
|
+
}
|
|
1457
|
+
entries.push({
|
|
1458
|
+
entry: {
|
|
1459
|
+
strategyId: strategy.id,
|
|
1460
|
+
strategyName: strategy.shortName,
|
|
1461
|
+
type: "PNL",
|
|
1462
|
+
message: pnlParts.join(" | "),
|
|
1463
|
+
data: { totalPnl: state.totalPnl, positions: state.openPositions.length },
|
|
1464
|
+
importance: "medium"
|
|
1465
|
+
},
|
|
1466
|
+
delay
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
if (Math.random() < 0.2) {
|
|
1470
|
+
delay += rand3(1e3, 2e3);
|
|
1471
|
+
const exposure = state.openPositions.reduce((sum, p) => sum + p.size * p.leverage, 0);
|
|
1472
|
+
const maxDrawdown = rand3(2, 12);
|
|
1473
|
+
entries.push({
|
|
1474
|
+
entry: {
|
|
1475
|
+
strategyId: strategy.id,
|
|
1476
|
+
strategyName: strategy.shortName,
|
|
1477
|
+
type: "RISK",
|
|
1478
|
+
message: `Portfolio exposure: ${exposure.toFixed(1)}% | Max drawdown: ${maxDrawdown.toFixed(1)}% | Open positions: ${state.openPositions.length} | Win rate: ${(state.winRate * 100).toFixed(1)}%`,
|
|
1479
|
+
importance: exposure > 80 ? "high" : "low"
|
|
1480
|
+
},
|
|
1481
|
+
delay
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
for (const { entry, delay: d } of entries) {
|
|
1485
|
+
setTimeout(() => {
|
|
1486
|
+
if (this.running) {
|
|
1487
|
+
this.emit({
|
|
1488
|
+
...entry,
|
|
1489
|
+
id: genId3(),
|
|
1490
|
+
timestamp: Date.now()
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
}, d);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
simulatePrice(pair) {
|
|
1497
|
+
const current = this.priceState.get(pair) || PAIR_PRICES[pair] || 5e4;
|
|
1498
|
+
const volatility = pair.includes("DOGE") ? 5e-3 : pair.includes("BTC") ? 2e-3 : 3e-3;
|
|
1499
|
+
const drift = rand3(-volatility, volatility);
|
|
1500
|
+
const newPrice = current * (1 + drift);
|
|
1501
|
+
this.priceState.set(pair, newPrice);
|
|
1502
|
+
return newPrice;
|
|
1503
|
+
}
|
|
1504
|
+
generateIndicators(strategy, price) {
|
|
1505
|
+
const prev = this.indicatorState.get(strategy.id);
|
|
1506
|
+
const prevRsi = prev?.rsi ?? strategy.rsiBias;
|
|
1507
|
+
const rsiMean = strategy.rsiBias;
|
|
1508
|
+
const rsiDrift = rand3(-8, 8);
|
|
1509
|
+
const rsiReversion = (rsiMean - prevRsi) * 0.15;
|
|
1510
|
+
const rsi = clamp(prevRsi + rsiDrift + rsiReversion, 8, 95);
|
|
1511
|
+
const macdBias = rsi > 65 ? 0.3 : rsi < 35 ? -0.3 : 0;
|
|
1512
|
+
const prevHist = prev?.macd.histogram ?? 0;
|
|
1513
|
+
const histogram = clamp(prevHist * 0.7 + rand3(-0.5, 0.5) + macdBias, -2, 2);
|
|
1514
|
+
const macdValue = histogram * rand3(0.8, 1.5);
|
|
1515
|
+
const macdSignal = macdValue - histogram;
|
|
1516
|
+
const prevShort = prev?.ema.short ?? price;
|
|
1517
|
+
const prevLong = prev?.ema.long ?? price;
|
|
1518
|
+
const emaShort = prevShort * 0.9 + price * 0.1;
|
|
1519
|
+
const emaLong = prevLong * 0.95 + price * 0.05;
|
|
1520
|
+
let crossover = "none";
|
|
1521
|
+
if (prevShort <= prevLong && emaShort > emaLong) crossover = "golden";
|
|
1522
|
+
else if (prevShort >= prevLong && emaShort < emaLong) crossover = "death";
|
|
1523
|
+
const bbMiddle = price;
|
|
1524
|
+
const bbWidth = price * rand3(0.01, 0.04);
|
|
1525
|
+
const bbUpper = bbMiddle + bbWidth;
|
|
1526
|
+
const bbLower = bbMiddle - bbWidth;
|
|
1527
|
+
const bbPosition = (price - bbLower) / (bbUpper - bbLower) * 100;
|
|
1528
|
+
const volRatio = rand3(0.3, 2.5);
|
|
1529
|
+
const volCurrent = rand3(1e5, 5e6);
|
|
1530
|
+
return {
|
|
1531
|
+
rsi,
|
|
1532
|
+
macd: { value: macdValue, signal: macdSignal, histogram },
|
|
1533
|
+
ema: { short: emaShort, long: emaLong, crossover },
|
|
1534
|
+
bollinger: { upper: bbUpper, middle: bbMiddle, lower: bbLower, width: bbWidth / price, position: bbPosition },
|
|
1535
|
+
volume: { current: volCurrent, average: volCurrent / volRatio, ratio: volRatio }
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
generateThinkingProcess(strategy, pair, price) {
|
|
1539
|
+
const thoughts = [];
|
|
1540
|
+
const pairBase = pair.split("/")[0];
|
|
1541
|
+
const thinkingTemplates = [
|
|
1542
|
+
`Analyzing ${pairBase} market structure...`,
|
|
1543
|
+
`Checking ${strategy.primaryIndicators.join(", ")} confluence...`,
|
|
1544
|
+
`Evaluating risk parameters for ${strategy.riskTolerance} tolerance...`,
|
|
1545
|
+
`Scanning order book depth at $${formatPrice(price)}...`,
|
|
1546
|
+
`Cross-referencing with historical patterns...`,
|
|
1547
|
+
`Calculating optimal entry zone...`,
|
|
1548
|
+
`Assessing market sentiment indicators...`,
|
|
1549
|
+
`Monitoring whale activity on ${pairBase}...`,
|
|
1550
|
+
`Comparing momentum across timeframes...`,
|
|
1551
|
+
`Validating support/resistance levels...`
|
|
1552
|
+
];
|
|
1553
|
+
const numThoughts = randInt3(1, 3);
|
|
1554
|
+
const shuffled = [...thinkingTemplates].sort(() => Math.random() - 0.5);
|
|
1555
|
+
for (let i = 0; i < numThoughts; i++) {
|
|
1556
|
+
thoughts.push(shuffled[i]);
|
|
1557
|
+
}
|
|
1558
|
+
return thoughts;
|
|
1559
|
+
}
|
|
1560
|
+
generateStrategyContext(strategy, signal, indicators, pair) {
|
|
1561
|
+
const contexts = [];
|
|
1562
|
+
contexts.push(`[${strategy.name}]`);
|
|
1563
|
+
const riskLevel = strategy.riskTolerance === "high" ? "aggressive" : strategy.riskTolerance === "low" ? "conservative" : "balanced";
|
|
1564
|
+
contexts.push(`Risk: ${riskLevel}`);
|
|
1565
|
+
if (indicators.rsi < 35 || indicators.rsi > 65) {
|
|
1566
|
+
contexts.push(`RSI ${indicators.rsi < 35 ? "oversold" : "overbought"} (${indicators.rsi.toFixed(1)})`);
|
|
1567
|
+
}
|
|
1568
|
+
if (indicators.ema.crossover !== "none") {
|
|
1569
|
+
contexts.push(`EMA ${indicators.ema.crossover} cross`);
|
|
1570
|
+
}
|
|
1571
|
+
if (Math.abs(indicators.macd.histogram) > 0.3) {
|
|
1572
|
+
contexts.push(`MACD ${indicators.macd.histogram > 0 ? "bullish" : "bearish"} momentum`);
|
|
1573
|
+
}
|
|
1574
|
+
const confLevel = signal.confidence > 0.7 ? "HIGH" : signal.confidence > 0.5 ? "MEDIUM" : "LOW";
|
|
1575
|
+
contexts.push(`Confidence: ${confLevel}`);
|
|
1576
|
+
return contexts.join(" | ");
|
|
1577
|
+
}
|
|
1578
|
+
generateAnalysis(strategy, indicators, pair) {
|
|
1579
|
+
const analyses = [];
|
|
1580
|
+
if (indicators.rsi > 70) {
|
|
1581
|
+
analyses.push(`RSI at ${indicators.rsi.toFixed(1)} - overbought territory, watching for reversal`);
|
|
1582
|
+
} else if (indicators.rsi < 30) {
|
|
1583
|
+
analyses.push(`RSI at ${indicators.rsi.toFixed(1)} - oversold, potential bounce setup`);
|
|
1584
|
+
} else if (indicators.rsi > 55) {
|
|
1585
|
+
analyses.push(`RSI trending bullish at ${indicators.rsi.toFixed(1)}`);
|
|
1586
|
+
} else {
|
|
1587
|
+
analyses.push(`RSI neutral at ${indicators.rsi.toFixed(1)}, no clear direction`);
|
|
1588
|
+
}
|
|
1589
|
+
if (indicators.macd.histogram > 0.5) {
|
|
1590
|
+
analyses.push("MACD histogram expanding positive - momentum building");
|
|
1591
|
+
} else if (indicators.macd.histogram < -0.5) {
|
|
1592
|
+
analyses.push("MACD histogram expanding negative - bearish pressure");
|
|
1593
|
+
}
|
|
1594
|
+
if (indicators.ema.crossover === "golden") {
|
|
1595
|
+
analyses.push("EMA golden cross detected - strong bullish signal");
|
|
1596
|
+
} else if (indicators.ema.crossover === "death") {
|
|
1597
|
+
analyses.push("EMA death cross detected - bearish warning");
|
|
1598
|
+
}
|
|
1599
|
+
if (indicators.bollinger.position > 90) {
|
|
1600
|
+
analyses.push(`Price near upper Bollinger band (${indicators.bollinger.position.toFixed(0)}%) - potential resistance`);
|
|
1601
|
+
} else if (indicators.bollinger.position < 10) {
|
|
1602
|
+
analyses.push(`Price near lower Bollinger band (${indicators.bollinger.position.toFixed(0)}%) - potential support`);
|
|
1603
|
+
}
|
|
1604
|
+
if (indicators.volume.ratio > 1.8) {
|
|
1605
|
+
analyses.push(`Volume spike ${indicators.volume.ratio.toFixed(1)}x average - high activity`);
|
|
1606
|
+
}
|
|
1607
|
+
return analyses.length > 0 ? analyses.join(" | ") : `${pair} consolidating - waiting for clearer setup`;
|
|
1608
|
+
}
|
|
1609
|
+
evaluateSignal(strategy, indicators) {
|
|
1610
|
+
let bullScore = 0;
|
|
1611
|
+
let bearScore = 0;
|
|
1612
|
+
const reasons = [];
|
|
1613
|
+
if (indicators.rsi < 30) {
|
|
1614
|
+
bullScore += 2;
|
|
1615
|
+
reasons.push("RSI oversold");
|
|
1616
|
+
} else if (indicators.rsi < 40) {
|
|
1617
|
+
bullScore += 1;
|
|
1618
|
+
reasons.push("RSI low");
|
|
1619
|
+
} else if (indicators.rsi > 70) {
|
|
1620
|
+
bearScore += 2;
|
|
1621
|
+
reasons.push("RSI overbought");
|
|
1622
|
+
} else if (indicators.rsi > 60) {
|
|
1623
|
+
bearScore += 1;
|
|
1624
|
+
reasons.push("RSI high");
|
|
1625
|
+
}
|
|
1626
|
+
if (indicators.macd.histogram > 0.3) {
|
|
1627
|
+
bullScore += 1.5;
|
|
1628
|
+
reasons.push("MACD bullish");
|
|
1629
|
+
} else if (indicators.macd.histogram < -0.3) {
|
|
1630
|
+
bearScore += 1.5;
|
|
1631
|
+
reasons.push("MACD bearish");
|
|
1632
|
+
}
|
|
1633
|
+
if (indicators.ema.crossover === "golden") {
|
|
1634
|
+
bullScore += 2.5;
|
|
1635
|
+
reasons.push("Golden cross");
|
|
1636
|
+
} else if (indicators.ema.crossover === "death") {
|
|
1637
|
+
bearScore += 2.5;
|
|
1638
|
+
reasons.push("Death cross");
|
|
1639
|
+
} else if (indicators.ema.short > indicators.ema.long) {
|
|
1640
|
+
bullScore += 0.5;
|
|
1641
|
+
} else {
|
|
1642
|
+
bearScore += 0.5;
|
|
1643
|
+
}
|
|
1644
|
+
if (indicators.bollinger.position < 15) {
|
|
1645
|
+
bullScore += 1;
|
|
1646
|
+
reasons.push("BB support");
|
|
1647
|
+
} else if (indicators.bollinger.position > 85) {
|
|
1648
|
+
bearScore += 1;
|
|
1649
|
+
reasons.push("BB resistance");
|
|
1650
|
+
}
|
|
1651
|
+
if (indicators.volume.ratio > 1.5) {
|
|
1652
|
+
if (bullScore > bearScore) bullScore += 1;
|
|
1653
|
+
else bearScore += 1;
|
|
1654
|
+
reasons.push("Volume confirms");
|
|
1655
|
+
}
|
|
1656
|
+
const netScore = bullScore - bearScore;
|
|
1657
|
+
const confidence = Math.min(Math.abs(netScore) / 6, 0.95);
|
|
1658
|
+
const threshold = strategy.riskTolerance === "high" ? 1.5 : strategy.riskTolerance === "medium" ? 2 : 2.5;
|
|
1659
|
+
if (Math.random() > strategy.tradeFrequency) {
|
|
1660
|
+
return { direction: "HOLD", confidence: 0, reason: "Cycle skip" };
|
|
1661
|
+
}
|
|
1662
|
+
if (netScore > threshold) {
|
|
1663
|
+
return { direction: "LONG", confidence, reason: reasons.slice(0, 3).join(", ") };
|
|
1664
|
+
} else if (netScore < -threshold) {
|
|
1665
|
+
return { direction: "SHORT", confidence, reason: reasons.slice(0, 3).join(", ") };
|
|
1666
|
+
}
|
|
1667
|
+
return { direction: "HOLD", confidence: 0, reason: "No clear signal" };
|
|
1668
|
+
}
|
|
1669
|
+
makeTradeDecision(strategy, signal, state) {
|
|
1670
|
+
if (state.openPositions.length >= 3) {
|
|
1671
|
+
return { execute: false, positionSize: 0, leverage: 0, riskReward: 0, reason: "Max positions reached (3)" };
|
|
1672
|
+
}
|
|
1673
|
+
const minConfidence = strategy.riskTolerance === "high" ? 0.3 : strategy.riskTolerance === "medium" ? 0.45 : 0.6;
|
|
1674
|
+
if (signal.confidence < minConfidence) {
|
|
1675
|
+
return { execute: false, positionSize: 0, leverage: 0, riskReward: 0, reason: `Confidence too low (${(signal.confidence * 100).toFixed(0)}% < ${(minConfidence * 100).toFixed(0)}%)` };
|
|
1676
|
+
}
|
|
1677
|
+
const positionSize = rand3(strategy.positionSizeMin, strategy.positionSizeMax);
|
|
1678
|
+
const leverage = randInt3(strategy.leverageMin, strategy.leverageMax);
|
|
1679
|
+
const riskReward = rand3(1.2, 3.5);
|
|
1680
|
+
return { execute: true, positionSize, leverage, riskReward, reason: "" };
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
botSimulationEngine = new BotSimulationEngine();
|
|
1684
|
+
}
|
|
1685
|
+
});
|
|
5
1686
|
function getConfig() {
|
|
6
1687
|
{
|
|
7
1688
|
throw new Error("ONE SDK not initialized. Call initOneSDK() first.");
|
|
@@ -1695,17 +3376,1559 @@ function useAITrading() {
|
|
|
1695
3376
|
error
|
|
1696
3377
|
};
|
|
1697
3378
|
}
|
|
3379
|
+
function useAIAgents(includeInactive = false) {
|
|
3380
|
+
const [agents, setAgents] = react.useState([]);
|
|
3381
|
+
const [shareRates, setShareRates] = react.useState({});
|
|
3382
|
+
const [isLoading, setIsLoading] = react.useState(true);
|
|
3383
|
+
const [error, setError] = react.useState(null);
|
|
3384
|
+
const fetchAgents = react.useCallback(async () => {
|
|
3385
|
+
setIsLoading(true);
|
|
3386
|
+
setError(null);
|
|
3387
|
+
try {
|
|
3388
|
+
const result = await getClient().getAgentConfigs({ includeInactive });
|
|
3389
|
+
if (result.success && result.data) {
|
|
3390
|
+
setAgents(result.data.agents || []);
|
|
3391
|
+
setShareRates(result.data.shareRates || {});
|
|
3392
|
+
} else {
|
|
3393
|
+
setError(result.error?.message || "Failed to fetch agents");
|
|
3394
|
+
}
|
|
3395
|
+
} catch (err) {
|
|
3396
|
+
setError(err instanceof Error ? err.message : "Unknown error");
|
|
3397
|
+
} finally {
|
|
3398
|
+
setIsLoading(false);
|
|
3399
|
+
}
|
|
3400
|
+
}, [includeInactive]);
|
|
3401
|
+
react.useEffect(() => {
|
|
3402
|
+
fetchAgents();
|
|
3403
|
+
}, [fetchAgents]);
|
|
3404
|
+
return { agents, shareRates, isLoading, error, refresh: fetchAgents };
|
|
3405
|
+
}
|
|
3406
|
+
function useAIAgent(agentId) {
|
|
3407
|
+
const [agent, setAgent] = react.useState(null);
|
|
3408
|
+
const [params, setParams] = react.useState(null);
|
|
3409
|
+
const [isLoading, setIsLoading] = react.useState(true);
|
|
3410
|
+
const [error, setError] = react.useState(null);
|
|
3411
|
+
const fetchAgent = react.useCallback(async () => {
|
|
3412
|
+
if (!agentId) {
|
|
3413
|
+
setIsLoading(false);
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
setIsLoading(true);
|
|
3417
|
+
setError(null);
|
|
3418
|
+
try {
|
|
3419
|
+
const result = await getClient().getAgentConfigs({ agentId });
|
|
3420
|
+
if (result.success && result.data?.agent) {
|
|
3421
|
+
setAgent(result.data.agent);
|
|
3422
|
+
} else {
|
|
3423
|
+
setError(result.error?.message || "Agent not found");
|
|
3424
|
+
}
|
|
3425
|
+
} catch (err) {
|
|
3426
|
+
setError(err instanceof Error ? err.message : "Unknown error");
|
|
3427
|
+
} finally {
|
|
3428
|
+
setIsLoading(false);
|
|
3429
|
+
}
|
|
3430
|
+
}, [agentId]);
|
|
3431
|
+
react.useEffect(() => {
|
|
3432
|
+
fetchAgent();
|
|
3433
|
+
}, [fetchAgent]);
|
|
3434
|
+
const calculateParams = react.useCallback(async (amount, cycleDays) => {
|
|
3435
|
+
if (!agentId) return null;
|
|
3436
|
+
try {
|
|
3437
|
+
const result = await getClient().calculateAgentParams({ agentId, amount, cycleDays });
|
|
3438
|
+
if (result.success && result.data) {
|
|
3439
|
+
setParams(result.data);
|
|
3440
|
+
return result.data;
|
|
3441
|
+
}
|
|
3442
|
+
return null;
|
|
3443
|
+
} catch (err) {
|
|
3444
|
+
setError(err instanceof Error ? err.message : "Failed to calculate params");
|
|
3445
|
+
return null;
|
|
3446
|
+
}
|
|
3447
|
+
}, [agentId]);
|
|
3448
|
+
return { agent, params, isLoading, error, refresh: fetchAgent, calculateParams };
|
|
3449
|
+
}
|
|
3450
|
+
function useAIAgentSubscription() {
|
|
3451
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
3452
|
+
const [error, setError] = react.useState(null);
|
|
3453
|
+
const subscribe = react.useCallback(async (agentId, amount, cycleDays, txHash) => {
|
|
3454
|
+
setIsLoading(true);
|
|
3455
|
+
setError(null);
|
|
3456
|
+
try {
|
|
3457
|
+
const result = await getClient().createAIOrder({
|
|
3458
|
+
strategyId: agentId,
|
|
3459
|
+
amount,
|
|
3460
|
+
lockPeriodDays: cycleDays,
|
|
3461
|
+
txHashDeposit: txHash
|
|
3462
|
+
});
|
|
3463
|
+
if (!result.success) {
|
|
3464
|
+
setError(result.error?.message || "Failed to subscribe");
|
|
3465
|
+
}
|
|
3466
|
+
return result;
|
|
3467
|
+
} catch (err) {
|
|
3468
|
+
const errorMsg = err instanceof Error ? err.message : "Subscription failed";
|
|
3469
|
+
setError(errorMsg);
|
|
3470
|
+
return { success: false, error: { code: "SUBSCRIPTION_ERROR", message: errorMsg } };
|
|
3471
|
+
} finally {
|
|
3472
|
+
setIsLoading(false);
|
|
3473
|
+
}
|
|
3474
|
+
}, []);
|
|
3475
|
+
return { subscribe, isLoading, error };
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
// src/hooks/useForexTrading.ts
|
|
3479
|
+
init_forex();
|
|
3480
|
+
var _forexAccessToken = null;
|
|
3481
|
+
var _forexEngineUrl = "https://api.one23.io";
|
|
3482
|
+
function setForexAccessToken(token) {
|
|
3483
|
+
_forexAccessToken = token;
|
|
3484
|
+
}
|
|
3485
|
+
function clearForexAccessToken() {
|
|
3486
|
+
_forexAccessToken = null;
|
|
3487
|
+
}
|
|
3488
|
+
function setForexEngineUrl(url) {
|
|
3489
|
+
_forexEngineUrl = url;
|
|
3490
|
+
}
|
|
3491
|
+
async function forexApi(path, options) {
|
|
3492
|
+
try {
|
|
3493
|
+
const headers = {
|
|
3494
|
+
"Content-Type": "application/json"
|
|
3495
|
+
};
|
|
3496
|
+
if (_forexAccessToken) {
|
|
3497
|
+
headers["Authorization"] = `Bearer ${_forexAccessToken}`;
|
|
3498
|
+
}
|
|
3499
|
+
const res = await fetch(`${_forexEngineUrl}${path}`, { ...options, headers });
|
|
3500
|
+
if (!res.ok) return null;
|
|
3501
|
+
const json = await res.json();
|
|
3502
|
+
return json?.data ?? json;
|
|
3503
|
+
} catch {
|
|
3504
|
+
return null;
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
function useForexPools(options) {
|
|
3508
|
+
const [pools, setPools] = react.useState(FOREX_POOL_DEFAULTS);
|
|
3509
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
3510
|
+
const [error, setError] = react.useState(null);
|
|
3511
|
+
const fetchPools = react.useCallback(async () => {
|
|
3512
|
+
setIsLoading(true);
|
|
3513
|
+
try {
|
|
3514
|
+
const data = await forexApi("/api/forex/pools");
|
|
3515
|
+
if (data?.pools) {
|
|
3516
|
+
setPools(data.pools.map((p) => ({
|
|
3517
|
+
id: p.type ?? p.id,
|
|
3518
|
+
nameKey: `forex.pool_${p.type ?? p.id}`,
|
|
3519
|
+
descriptionKey: `forex.pool_${p.type ?? p.id}_desc`,
|
|
3520
|
+
allocation: p.allocation ?? (p.type === "clearing" ? 0.5 : p.type === "hedging" ? 0.3 : 0.2),
|
|
3521
|
+
totalSize: p.totalSize ?? p.total_size ?? 0,
|
|
3522
|
+
utilization: p.utilization ?? 0,
|
|
3523
|
+
color: p.type === "clearing" ? "#3B82F6" : p.type === "hedging" ? "#F59E0B" : "#10B981",
|
|
3524
|
+
apy7d: p.apy7d ?? 0,
|
|
3525
|
+
apy30d: p.apy30d ?? 0,
|
|
3526
|
+
netFlow24h: p.netFlow24h ?? 0,
|
|
3527
|
+
txCount24h: p.txCount24h ?? 0,
|
|
3528
|
+
txCountTotal: p.txCountTotal ?? 0,
|
|
3529
|
+
totalDeposits: p.totalDeposits ?? 0,
|
|
3530
|
+
totalWithdrawals: p.totalWithdrawals ?? 0,
|
|
3531
|
+
profitDistributed: p.profitDistributed ?? 0,
|
|
3532
|
+
lastUpdated: p.lastUpdated ?? Date.now()
|
|
3533
|
+
})));
|
|
3534
|
+
}
|
|
3535
|
+
setError(null);
|
|
3536
|
+
} catch (err) {
|
|
3537
|
+
setError(err instanceof Error ? err.message : "Failed to fetch pools");
|
|
3538
|
+
} finally {
|
|
3539
|
+
setIsLoading(false);
|
|
3540
|
+
}
|
|
3541
|
+
}, []);
|
|
3542
|
+
react.useEffect(() => {
|
|
3543
|
+
fetchPools();
|
|
3544
|
+
if (options?.refreshInterval) {
|
|
3545
|
+
const timer = setInterval(fetchPools, options.refreshInterval);
|
|
3546
|
+
return () => clearInterval(timer);
|
|
3547
|
+
}
|
|
3548
|
+
}, [fetchPools, options?.refreshInterval]);
|
|
3549
|
+
return { pools, isLoading, error, refresh: fetchPools };
|
|
3550
|
+
}
|
|
3551
|
+
function useForexInvestments(options) {
|
|
3552
|
+
const [investments, setInvestments] = react.useState([]);
|
|
3553
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
3554
|
+
const [error, setError] = react.useState(null);
|
|
3555
|
+
const fetchInvestments = react.useCallback(async () => {
|
|
3556
|
+
setIsLoading(true);
|
|
3557
|
+
try {
|
|
3558
|
+
const data = await forexApi("/api/forex/investments");
|
|
3559
|
+
if (data?.investments) {
|
|
3560
|
+
setInvestments(data.investments.map(mapInvestment));
|
|
3561
|
+
}
|
|
3562
|
+
setError(null);
|
|
3563
|
+
} catch {
|
|
3564
|
+
setError("Failed to fetch investments");
|
|
3565
|
+
} finally {
|
|
3566
|
+
setIsLoading(false);
|
|
3567
|
+
}
|
|
3568
|
+
}, []);
|
|
3569
|
+
const createInvestment = react.useCallback(async (params) => {
|
|
3570
|
+
setIsLoading(true);
|
|
3571
|
+
try {
|
|
3572
|
+
const data = await forexApi("/api/forex/investments", {
|
|
3573
|
+
method: "POST",
|
|
3574
|
+
body: JSON.stringify(params)
|
|
3575
|
+
});
|
|
3576
|
+
if (data?.investment) {
|
|
3577
|
+
const inv2 = mapInvestment(data.investment);
|
|
3578
|
+
setInvestments((prev) => [inv2, ...prev]);
|
|
3579
|
+
return inv2;
|
|
3580
|
+
}
|
|
3581
|
+
const inv = createLocalInvestment(params);
|
|
3582
|
+
setInvestments((prev) => [inv, ...prev]);
|
|
3583
|
+
return inv;
|
|
3584
|
+
} catch {
|
|
3585
|
+
const inv = createLocalInvestment(params);
|
|
3586
|
+
setInvestments((prev) => [inv, ...prev]);
|
|
3587
|
+
return inv;
|
|
3588
|
+
} finally {
|
|
3589
|
+
setIsLoading(false);
|
|
3590
|
+
}
|
|
3591
|
+
}, []);
|
|
3592
|
+
const redeemInvestment = react.useCallback(async (investmentId) => {
|
|
3593
|
+
try {
|
|
3594
|
+
await forexApi(`/api/forex/investments/${investmentId}/redeem`, { method: "POST" });
|
|
3595
|
+
setInvestments((prev) => prev.map(
|
|
3596
|
+
(inv) => inv.id === investmentId ? { ...inv, status: "redeemed" } : inv
|
|
3597
|
+
));
|
|
3598
|
+
return true;
|
|
3599
|
+
} catch {
|
|
3600
|
+
return false;
|
|
3601
|
+
}
|
|
3602
|
+
}, []);
|
|
3603
|
+
react.useEffect(() => {
|
|
3604
|
+
fetchInvestments();
|
|
3605
|
+
if (options?.refreshInterval) {
|
|
3606
|
+
const timer = setInterval(fetchInvestments, options.refreshInterval);
|
|
3607
|
+
return () => clearInterval(timer);
|
|
3608
|
+
}
|
|
3609
|
+
}, [fetchInvestments, options?.refreshInterval]);
|
|
3610
|
+
const portfolioSummary = react.useMemo(() => {
|
|
3611
|
+
const active = investments.filter((i) => i.status === "active");
|
|
3612
|
+
return {
|
|
3613
|
+
totalInvested: active.reduce((s, i) => s + i.amount, 0),
|
|
3614
|
+
totalValue: active.reduce((s, i) => s + i.currentValue, 0),
|
|
3615
|
+
totalProfit: active.reduce((s, i) => s + i.profit, 0),
|
|
3616
|
+
activeCount: active.length
|
|
3617
|
+
};
|
|
3618
|
+
}, [investments]);
|
|
3619
|
+
return { investments, isLoading, error, createInvestment, redeemInvestment, refresh: fetchInvestments, portfolioSummary };
|
|
3620
|
+
}
|
|
3621
|
+
function useForexSimulation(options) {
|
|
3622
|
+
const maxLogs = options?.maxLogs ?? 500;
|
|
3623
|
+
const [logs, setLogs] = react.useState([]);
|
|
3624
|
+
const [poolTxs, setPoolTxs] = react.useState([]);
|
|
3625
|
+
const [isRunning, setIsRunning] = react.useState(false);
|
|
3626
|
+
const [stats, setStats] = react.useState({ totalPnl: 0, totalTrades: 0, totalPips: 0, totalLots: 0 });
|
|
3627
|
+
const engineRef = react.useRef(null);
|
|
3628
|
+
const getEngine = react.useCallback(() => {
|
|
3629
|
+
if (!engineRef.current) {
|
|
3630
|
+
try {
|
|
3631
|
+
const { forexSimulationEngine: forexSimulationEngine2 } = (init_ForexSimulationEngine(), __toCommonJS(ForexSimulationEngine_exports));
|
|
3632
|
+
engineRef.current = forexSimulationEngine2;
|
|
3633
|
+
} catch {
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
return engineRef.current;
|
|
3637
|
+
}, []);
|
|
3638
|
+
const start = react.useCallback(() => {
|
|
3639
|
+
const engine = getEngine();
|
|
3640
|
+
if (!engine) return;
|
|
3641
|
+
if (!engine.isRunning()) engine.start();
|
|
3642
|
+
setIsRunning(true);
|
|
3643
|
+
}, [getEngine]);
|
|
3644
|
+
const stop = react.useCallback(() => {
|
|
3645
|
+
const engine = getEngine();
|
|
3646
|
+
if (engine) engine.stop();
|
|
3647
|
+
setIsRunning(false);
|
|
3648
|
+
}, [getEngine]);
|
|
3649
|
+
const clearLogs = react.useCallback(() => {
|
|
3650
|
+
setLogs([]);
|
|
3651
|
+
setPoolTxs([]);
|
|
3652
|
+
}, []);
|
|
3653
|
+
react.useEffect(() => {
|
|
3654
|
+
const engine = getEngine();
|
|
3655
|
+
if (!engine) return;
|
|
3656
|
+
const unsubLog = engine.onLog((entry) => {
|
|
3657
|
+
setLogs((prev) => {
|
|
3658
|
+
const next = [...prev, entry];
|
|
3659
|
+
return next.length > maxLogs ? next.slice(-maxLogs) : next;
|
|
3660
|
+
});
|
|
3661
|
+
const s = engine.getStats();
|
|
3662
|
+
setStats({ totalPnl: s.totalPnl, totalTrades: s.totalTrades, totalPips: s.totalPips, totalLots: s.totalLots });
|
|
3663
|
+
});
|
|
3664
|
+
const unsubTx = engine.onPoolTransaction((tx) => {
|
|
3665
|
+
setPoolTxs((prev) => [...prev.slice(-999), tx]);
|
|
3666
|
+
});
|
|
3667
|
+
return () => {
|
|
3668
|
+
unsubLog();
|
|
3669
|
+
unsubTx();
|
|
3670
|
+
};
|
|
3671
|
+
}, [getEngine, maxLogs]);
|
|
3672
|
+
return { logs, poolTransactions: poolTxs, isRunning, stats, start, stop, clearLogs };
|
|
3673
|
+
}
|
|
3674
|
+
function useForexPoolData() {
|
|
3675
|
+
const [snapshots, setSnapshots] = react.useState({ clearing: [], hedging: [], insurance: [] });
|
|
3676
|
+
const [transactions, setTransactions] = react.useState([]);
|
|
3677
|
+
const [isInitialized, setIsInitialized] = react.useState(false);
|
|
3678
|
+
const initialize = react.useCallback(() => {
|
|
3679
|
+
if (isInitialized) return;
|
|
3680
|
+
try {
|
|
3681
|
+
const { ForexPoolDataGenerator: ForexPoolDataGenerator2 } = (init_ForexPoolDataGenerator(), __toCommonJS(ForexPoolDataGenerator_exports));
|
|
3682
|
+
const gen = ForexPoolDataGenerator2.getInstance();
|
|
3683
|
+
setSnapshots(gen.generateAllSnapshots());
|
|
3684
|
+
setTransactions(gen.generateAllTransactions());
|
|
3685
|
+
setIsInitialized(true);
|
|
3686
|
+
} catch {
|
|
3687
|
+
}
|
|
3688
|
+
}, [isInitialized]);
|
|
3689
|
+
return { snapshots, transactions, isInitialized, initialize };
|
|
3690
|
+
}
|
|
3691
|
+
function useForexTrading(options) {
|
|
3692
|
+
const pools = useForexPools({ refreshInterval: options?.poolRefreshInterval });
|
|
3693
|
+
const investments = useForexInvestments({ refreshInterval: options?.investmentRefreshInterval });
|
|
3694
|
+
const simulation = useForexSimulation();
|
|
3695
|
+
const poolData = useForexPoolData();
|
|
3696
|
+
return {
|
|
3697
|
+
pools,
|
|
3698
|
+
investments,
|
|
3699
|
+
simulation,
|
|
3700
|
+
poolData,
|
|
3701
|
+
capitalSplit: FOREX_CAPITAL_SPLIT,
|
|
3702
|
+
agent: FOREX_AGENT,
|
|
3703
|
+
currencyPairs: (init_forex(), __toCommonJS(forex_exports)).FOREX_CURRENCY_PAIRS,
|
|
3704
|
+
cycleOptions: (init_forex(), __toCommonJS(forex_exports)).FOREX_CYCLE_OPTIONS,
|
|
3705
|
+
computePoolAllocations,
|
|
3706
|
+
estimateProfit: estimateForexProfit
|
|
3707
|
+
};
|
|
3708
|
+
}
|
|
3709
|
+
function mapInvestment(raw) {
|
|
3710
|
+
const cycleDays = raw.cycleDays ?? raw.cycle_days ?? 90;
|
|
3711
|
+
const cycleOption = FOREX_CYCLE_OPTIONS.find((c) => c.days === cycleDays) || FOREX_CYCLE_OPTIONS[2];
|
|
3712
|
+
const amount = typeof raw.amount === "string" ? parseFloat(raw.amount) : raw.amount ?? 0;
|
|
3713
|
+
const allocs = computePoolAllocations(amount);
|
|
3714
|
+
return {
|
|
3715
|
+
id: raw.id,
|
|
3716
|
+
userId: raw.userId ?? raw.user_id,
|
|
3717
|
+
amount,
|
|
3718
|
+
currentValue: raw.currentValue ?? raw.current_value ?? amount,
|
|
3719
|
+
profit: raw.profit ?? 0,
|
|
3720
|
+
status: raw.status ?? "active",
|
|
3721
|
+
selectedPairs: raw.selectedPairs ?? raw.selected_pairs ?? [],
|
|
3722
|
+
cycleDays,
|
|
3723
|
+
cycleOption,
|
|
3724
|
+
feeRate: raw.feeRate ?? cycleOption.feeRate,
|
|
3725
|
+
commissionRate: raw.commissionRate ?? cycleOption.commissionRate,
|
|
3726
|
+
startDate: raw.startDate ?? raw.start_date ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3727
|
+
endDate: raw.endDate ?? raw.end_date ?? "",
|
|
3728
|
+
tradingCapital: raw.tradingCapital ?? allocs.tradingCapital,
|
|
3729
|
+
totalPoolReserves: raw.totalPoolReserves ?? allocs.totalPoolReserves,
|
|
3730
|
+
poolAllocations: raw.poolAllocations ?? {
|
|
3731
|
+
clearing: allocs.clearing,
|
|
3732
|
+
hedging: allocs.hedging,
|
|
3733
|
+
insurance: allocs.insurance
|
|
3734
|
+
},
|
|
3735
|
+
tradeWeight: raw.tradeWeight ?? 0,
|
|
3736
|
+
totalLots: raw.totalLots ?? 0,
|
|
3737
|
+
totalPips: raw.totalPips ?? 0,
|
|
3738
|
+
totalTrades: raw.totalTrades ?? 0,
|
|
3739
|
+
positions: raw.positions ?? [],
|
|
3740
|
+
tradeHistory: raw.tradeHistory ?? [],
|
|
3741
|
+
redeemedAt: raw.redeemedAt,
|
|
3742
|
+
createdAt: raw.createdAt,
|
|
3743
|
+
updatedAt: raw.updatedAt
|
|
3744
|
+
};
|
|
3745
|
+
}
|
|
3746
|
+
function createLocalInvestment(params) {
|
|
3747
|
+
const { amount, selectedPairs, cycleDays } = params;
|
|
3748
|
+
const cycleOption = FOREX_CYCLE_OPTIONS.find((c) => c.days === cycleDays) || FOREX_CYCLE_OPTIONS[2];
|
|
3749
|
+
const allocs = computePoolAllocations(amount);
|
|
3750
|
+
const now = /* @__PURE__ */ new Date();
|
|
3751
|
+
const endDate = new Date(now);
|
|
3752
|
+
endDate.setDate(endDate.getDate() + cycleDays);
|
|
3753
|
+
return {
|
|
3754
|
+
id: `fx_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
3755
|
+
amount,
|
|
3756
|
+
currentValue: amount,
|
|
3757
|
+
profit: 0,
|
|
3758
|
+
status: "active",
|
|
3759
|
+
selectedPairs,
|
|
3760
|
+
cycleDays,
|
|
3761
|
+
cycleOption,
|
|
3762
|
+
startDate: now.toISOString(),
|
|
3763
|
+
endDate: endDate.toISOString(),
|
|
3764
|
+
tradingCapital: allocs.tradingCapital,
|
|
3765
|
+
totalPoolReserves: allocs.totalPoolReserves,
|
|
3766
|
+
poolAllocations: { clearing: allocs.clearing, hedging: allocs.hedging, insurance: allocs.insurance },
|
|
3767
|
+
tradeWeight: 0,
|
|
3768
|
+
totalLots: 0,
|
|
3769
|
+
totalPips: 0,
|
|
3770
|
+
totalTrades: 0,
|
|
3771
|
+
positions: [],
|
|
3772
|
+
tradeHistory: []
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3775
|
+
function useBotSimulation(options) {
|
|
3776
|
+
const maxLogs = options?.maxLogs ?? 500;
|
|
3777
|
+
const [logs, setLogs] = react.useState([]);
|
|
3778
|
+
const [isRunning, setIsRunning] = react.useState(false);
|
|
3779
|
+
const [strategies, setStrategies] = react.useState([]);
|
|
3780
|
+
const [botStatesMap, setBotStatesMap] = react.useState(/* @__PURE__ */ new Map());
|
|
3781
|
+
const engineRef = react.useRef(null);
|
|
3782
|
+
const unsubRef = react.useRef(null);
|
|
3783
|
+
const getEngine = react.useCallback(() => {
|
|
3784
|
+
if (!engineRef.current) {
|
|
3785
|
+
try {
|
|
3786
|
+
const { botSimulationEngine: botSimulationEngine2 } = (init_BotSimulationEngine(), __toCommonJS(BotSimulationEngine_exports));
|
|
3787
|
+
engineRef.current = botSimulationEngine2;
|
|
3788
|
+
setStrategies(botSimulationEngine2.getStrategies());
|
|
3789
|
+
} catch {
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
return engineRef.current;
|
|
3793
|
+
}, []);
|
|
3794
|
+
const logsByStrategy = react.useMemo(() => {
|
|
3795
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3796
|
+
for (const log of logs) {
|
|
3797
|
+
const existing = grouped.get(log.strategyId) || [];
|
|
3798
|
+
grouped.set(log.strategyId, [...existing, log]);
|
|
3799
|
+
}
|
|
3800
|
+
return grouped;
|
|
3801
|
+
}, [logs]);
|
|
3802
|
+
const start = react.useCallback((strategyIds, userPairs, userChains) => {
|
|
3803
|
+
const engine = getEngine();
|
|
3804
|
+
if (!engine) return;
|
|
3805
|
+
const ids = strategyIds || options?.strategyIds;
|
|
3806
|
+
const pairs = userPairs || options?.userPairs;
|
|
3807
|
+
const chains = userChains || options?.userChains;
|
|
3808
|
+
if (!engine.isRunning()) {
|
|
3809
|
+
engine.start(ids, pairs, chains);
|
|
3810
|
+
}
|
|
3811
|
+
setIsRunning(true);
|
|
3812
|
+
}, [getEngine, options?.strategyIds, options?.userPairs, options?.userChains]);
|
|
3813
|
+
const stop = react.useCallback((strategyIds) => {
|
|
3814
|
+
const engine = getEngine();
|
|
3815
|
+
if (engine) {
|
|
3816
|
+
engine.stop(strategyIds);
|
|
3817
|
+
if (!strategyIds) {
|
|
3818
|
+
setIsRunning(false);
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
}, [getEngine]);
|
|
3822
|
+
const clearLogs = react.useCallback(() => {
|
|
3823
|
+
setLogs([]);
|
|
3824
|
+
}, []);
|
|
3825
|
+
const emitBootSequence = react.useCallback(() => {
|
|
3826
|
+
const engine = getEngine();
|
|
3827
|
+
if (engine) {
|
|
3828
|
+
engine.emitBootSequence();
|
|
3829
|
+
}
|
|
3830
|
+
}, [getEngine]);
|
|
3831
|
+
const getBotState = react.useCallback((strategyId) => {
|
|
3832
|
+
return botStatesMap.get(strategyId);
|
|
3833
|
+
}, [botStatesMap]);
|
|
3834
|
+
react.useEffect(() => {
|
|
3835
|
+
const engine = getEngine();
|
|
3836
|
+
if (!engine) return;
|
|
3837
|
+
setIsRunning(engine.isRunning());
|
|
3838
|
+
unsubRef.current = engine.onLog((entry) => {
|
|
3839
|
+
setLogs((prev) => {
|
|
3840
|
+
const next = [...prev, entry];
|
|
3841
|
+
return next.length > maxLogs ? next.slice(-maxLogs) : next;
|
|
3842
|
+
});
|
|
3843
|
+
const state = engine.getBotState(entry.strategyId);
|
|
3844
|
+
if (state) {
|
|
3845
|
+
setBotStatesMap((prev) => {
|
|
3846
|
+
const next = new Map(prev);
|
|
3847
|
+
next.set(entry.strategyId, state);
|
|
3848
|
+
return next;
|
|
3849
|
+
});
|
|
3850
|
+
}
|
|
3851
|
+
});
|
|
3852
|
+
return () => {
|
|
3853
|
+
if (unsubRef.current) {
|
|
3854
|
+
unsubRef.current();
|
|
3855
|
+
unsubRef.current = null;
|
|
3856
|
+
}
|
|
3857
|
+
};
|
|
3858
|
+
}, [getEngine, maxLogs]);
|
|
3859
|
+
react.useEffect(() => {
|
|
3860
|
+
if (options?.autoStart) {
|
|
3861
|
+
const engine = getEngine();
|
|
3862
|
+
if (engine && !engine.isRunning()) {
|
|
3863
|
+
emitBootSequence();
|
|
3864
|
+
setTimeout(() => {
|
|
3865
|
+
start();
|
|
3866
|
+
}, 5500);
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
}, [options?.autoStart, getEngine, emitBootSequence, start]);
|
|
3870
|
+
react.useEffect(() => {
|
|
3871
|
+
if (!isRunning) return;
|
|
3872
|
+
const interval = setInterval(() => {
|
|
3873
|
+
const engine = getEngine();
|
|
3874
|
+
if (engine) {
|
|
3875
|
+
const states = engine.getAllBotStates();
|
|
3876
|
+
if (states.size > 0) {
|
|
3877
|
+
setBotStatesMap(new Map(states));
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
}, 1e3);
|
|
3881
|
+
return () => clearInterval(interval);
|
|
3882
|
+
}, [isRunning, getEngine]);
|
|
3883
|
+
return {
|
|
3884
|
+
logs,
|
|
3885
|
+
logsByStrategy,
|
|
3886
|
+
botStates: botStatesMap,
|
|
3887
|
+
isRunning,
|
|
3888
|
+
strategies,
|
|
3889
|
+
start,
|
|
3890
|
+
stop,
|
|
3891
|
+
clearLogs,
|
|
3892
|
+
emitBootSequence,
|
|
3893
|
+
getBotState
|
|
3894
|
+
};
|
|
3895
|
+
}
|
|
3896
|
+
var _consoleAccessToken = null;
|
|
3897
|
+
var _consoleEngineUrl = "https://api.one23.io";
|
|
3898
|
+
function setConsoleAccessToken(token) {
|
|
3899
|
+
_consoleAccessToken = token;
|
|
3900
|
+
}
|
|
3901
|
+
function clearConsoleAccessToken() {
|
|
3902
|
+
_consoleAccessToken = null;
|
|
3903
|
+
}
|
|
3904
|
+
function setConsoleEngineUrl(url) {
|
|
3905
|
+
_consoleEngineUrl = url;
|
|
3906
|
+
}
|
|
3907
|
+
async function consoleApi(path, options) {
|
|
3908
|
+
try {
|
|
3909
|
+
const headers = {
|
|
3910
|
+
"Content-Type": "application/json"
|
|
3911
|
+
};
|
|
3912
|
+
if (_consoleAccessToken) {
|
|
3913
|
+
headers["Authorization"] = `Bearer ${_consoleAccessToken}`;
|
|
3914
|
+
}
|
|
3915
|
+
const res = await fetch(`${_consoleEngineUrl}${path}`, { ...options, headers });
|
|
3916
|
+
if (!res.ok) return null;
|
|
3917
|
+
const json = await res.json();
|
|
3918
|
+
return json?.data ?? json;
|
|
3919
|
+
} catch {
|
|
3920
|
+
return null;
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
function generateSimulatedPositions(strategyIds) {
|
|
3924
|
+
const strategies = [
|
|
3925
|
+
{ id: "balanced-01", name: "Balanced Alpha", shortName: "BAL" },
|
|
3926
|
+
{ id: "conservative-01", name: "Conservative Shield", shortName: "CON" },
|
|
3927
|
+
{ id: "aggressive-01", name: "Aggressive Momentum", shortName: "AGG" }
|
|
3928
|
+
];
|
|
3929
|
+
const pairs = ["BTC/USDT", "ETH/USDT", "SOL/USDT", "ARB/USDT", "AVAX/USDT"];
|
|
3930
|
+
const basePrices = {
|
|
3931
|
+
"BTC/USDT": 67500,
|
|
3932
|
+
"ETH/USDT": 3450,
|
|
3933
|
+
"SOL/USDT": 178,
|
|
3934
|
+
"ARB/USDT": 1.18,
|
|
3935
|
+
"AVAX/USDT": 38.5
|
|
3936
|
+
};
|
|
3937
|
+
const positions = [];
|
|
3938
|
+
const filteredStrategies = strategyIds ? strategies.filter((s) => strategyIds.includes(s.id)) : strategies;
|
|
3939
|
+
for (const strategy of filteredStrategies) {
|
|
3940
|
+
const numPositions = Math.floor(Math.random() * 4);
|
|
3941
|
+
for (let i = 0; i < numPositions; i++) {
|
|
3942
|
+
const pair = pairs[Math.floor(Math.random() * pairs.length)];
|
|
3943
|
+
const basePrice = basePrices[pair];
|
|
3944
|
+
const side = Math.random() > 0.5 ? "LONG" : "SHORT";
|
|
3945
|
+
const entryPrice = basePrice * (1 + (Math.random() - 0.5) * 0.02);
|
|
3946
|
+
const currentPrice = basePrice * (1 + (Math.random() - 0.5) * 0.03);
|
|
3947
|
+
const leverage = Math.floor(Math.random() * 10) + 2;
|
|
3948
|
+
const size = Math.floor(Math.random() * 1e3) + 100;
|
|
3949
|
+
const margin = size / leverage;
|
|
3950
|
+
const priceDiff = side === "LONG" ? (currentPrice - entryPrice) / entryPrice : (entryPrice - currentPrice) / entryPrice;
|
|
3951
|
+
const pnlPercent = priceDiff * leverage * 100;
|
|
3952
|
+
const pnl = size * priceDiff * leverage;
|
|
3953
|
+
positions.push({
|
|
3954
|
+
id: `pos_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
3955
|
+
strategyId: strategy.id,
|
|
3956
|
+
strategyName: strategy.name,
|
|
3957
|
+
pair,
|
|
3958
|
+
side,
|
|
3959
|
+
entryPrice,
|
|
3960
|
+
currentPrice,
|
|
3961
|
+
size,
|
|
3962
|
+
leverage,
|
|
3963
|
+
margin,
|
|
3964
|
+
pnl,
|
|
3965
|
+
pnlPercent,
|
|
3966
|
+
status: "open",
|
|
3967
|
+
stopLoss: entryPrice * (side === "LONG" ? 0.95 : 1.05),
|
|
3968
|
+
takeProfit: entryPrice * (side === "LONG" ? 1.1 : 0.9),
|
|
3969
|
+
liquidationPrice: entryPrice * (side === "LONG" ? 1 - 1 / leverage : 1 + 1 / leverage),
|
|
3970
|
+
openTime: Date.now() - Math.floor(Math.random() * 864e5),
|
|
3971
|
+
chain: ["ethereum", "arbitrum", "base"][Math.floor(Math.random() * 3)],
|
|
3972
|
+
aiConfidence: Math.random() * 0.4 + 0.5,
|
|
3973
|
+
aiReasoning: `${side === "LONG" ? "Bullish" : "Bearish"} momentum detected on ${pair}`
|
|
3974
|
+
});
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
return positions;
|
|
3978
|
+
}
|
|
3979
|
+
function useAIPositions(options) {
|
|
3980
|
+
const [positions, setPositions] = react.useState([]);
|
|
3981
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
3982
|
+
const [error, setError] = react.useState(null);
|
|
3983
|
+
const fetchPositions = react.useCallback(async () => {
|
|
3984
|
+
setIsLoading(true);
|
|
3985
|
+
setError(null);
|
|
3986
|
+
try {
|
|
3987
|
+
if (options?.simulation) {
|
|
3988
|
+
const simulated = generateSimulatedPositions(options?.strategyIds);
|
|
3989
|
+
setPositions(simulated);
|
|
3990
|
+
} else {
|
|
3991
|
+
let path = "/api/v1/ai-quant/positions";
|
|
3992
|
+
const params = new URLSearchParams();
|
|
3993
|
+
if (options?.strategyId) {
|
|
3994
|
+
params.append("strategyId", options.strategyId);
|
|
3995
|
+
}
|
|
3996
|
+
if (options?.strategyIds?.length) {
|
|
3997
|
+
params.append("strategyIds", options.strategyIds.join(","));
|
|
3998
|
+
}
|
|
3999
|
+
if (options?.status) {
|
|
4000
|
+
const statuses = Array.isArray(options.status) ? options.status : [options.status];
|
|
4001
|
+
params.append("status", statuses.join(","));
|
|
4002
|
+
}
|
|
4003
|
+
if (params.toString()) {
|
|
4004
|
+
path += `?${params.toString()}`;
|
|
4005
|
+
}
|
|
4006
|
+
const data = await consoleApi(path);
|
|
4007
|
+
if (data?.positions) {
|
|
4008
|
+
setPositions(data.positions.map(mapPosition));
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
} catch (err) {
|
|
4012
|
+
setError(err instanceof Error ? err.message : "Failed to fetch positions");
|
|
4013
|
+
} finally {
|
|
4014
|
+
setIsLoading(false);
|
|
4015
|
+
}
|
|
4016
|
+
}, [options?.simulation, options?.strategyId, options?.strategyIds, options?.status]);
|
|
4017
|
+
const closePosition = react.useCallback(async (positionId) => {
|
|
4018
|
+
try {
|
|
4019
|
+
if (options?.simulation) {
|
|
4020
|
+
setPositions((prev) => prev.map(
|
|
4021
|
+
(p) => p.id === positionId ? { ...p, status: "closed", closeTime: Date.now() } : p
|
|
4022
|
+
));
|
|
4023
|
+
return true;
|
|
4024
|
+
}
|
|
4025
|
+
await consoleApi(`/api/v1/ai-quant/positions/${positionId}/close`, { method: "POST" });
|
|
4026
|
+
setPositions((prev) => prev.map(
|
|
4027
|
+
(p) => p.id === positionId ? { ...p, status: "closed", closeTime: Date.now() } : p
|
|
4028
|
+
));
|
|
4029
|
+
return true;
|
|
4030
|
+
} catch {
|
|
4031
|
+
return false;
|
|
4032
|
+
}
|
|
4033
|
+
}, [options?.simulation]);
|
|
4034
|
+
const updateStopLoss = react.useCallback(async (positionId, stopLoss) => {
|
|
4035
|
+
try {
|
|
4036
|
+
if (options?.simulation) {
|
|
4037
|
+
setPositions((prev) => prev.map(
|
|
4038
|
+
(p) => p.id === positionId ? { ...p, stopLoss } : p
|
|
4039
|
+
));
|
|
4040
|
+
return true;
|
|
4041
|
+
}
|
|
4042
|
+
await consoleApi(`/api/v1/ai-quant/positions/${positionId}`, {
|
|
4043
|
+
method: "PATCH",
|
|
4044
|
+
body: JSON.stringify({ stopLoss })
|
|
4045
|
+
});
|
|
4046
|
+
setPositions((prev) => prev.map(
|
|
4047
|
+
(p) => p.id === positionId ? { ...p, stopLoss } : p
|
|
4048
|
+
));
|
|
4049
|
+
return true;
|
|
4050
|
+
} catch {
|
|
4051
|
+
return false;
|
|
4052
|
+
}
|
|
4053
|
+
}, [options?.simulation]);
|
|
4054
|
+
const updateTakeProfit = react.useCallback(async (positionId, takeProfit) => {
|
|
4055
|
+
try {
|
|
4056
|
+
if (options?.simulation) {
|
|
4057
|
+
setPositions((prev) => prev.map(
|
|
4058
|
+
(p) => p.id === positionId ? { ...p, takeProfit } : p
|
|
4059
|
+
));
|
|
4060
|
+
return true;
|
|
4061
|
+
}
|
|
4062
|
+
await consoleApi(`/api/v1/ai-quant/positions/${positionId}`, {
|
|
4063
|
+
method: "PATCH",
|
|
4064
|
+
body: JSON.stringify({ takeProfit })
|
|
4065
|
+
});
|
|
4066
|
+
setPositions((prev) => prev.map(
|
|
4067
|
+
(p) => p.id === positionId ? { ...p, takeProfit } : p
|
|
4068
|
+
));
|
|
4069
|
+
return true;
|
|
4070
|
+
} catch {
|
|
4071
|
+
return false;
|
|
4072
|
+
}
|
|
4073
|
+
}, [options?.simulation]);
|
|
4074
|
+
react.useEffect(() => {
|
|
4075
|
+
fetchPositions();
|
|
4076
|
+
if (options?.pollInterval) {
|
|
4077
|
+
const timer = setInterval(fetchPositions, options.pollInterval);
|
|
4078
|
+
return () => clearInterval(timer);
|
|
4079
|
+
}
|
|
4080
|
+
}, [fetchPositions, options?.pollInterval]);
|
|
4081
|
+
const openPositions = react.useMemo(
|
|
4082
|
+
() => positions.filter((p) => p.status === "open"),
|
|
4083
|
+
[positions]
|
|
4084
|
+
);
|
|
4085
|
+
const closedPositions = react.useMemo(
|
|
4086
|
+
() => positions.filter((p) => p.status === "closed"),
|
|
4087
|
+
[positions]
|
|
4088
|
+
);
|
|
4089
|
+
const summary = react.useMemo(() => {
|
|
4090
|
+
const open = positions.filter((p) => p.status === "open");
|
|
4091
|
+
const byStrategy = {};
|
|
4092
|
+
for (const pos of open) {
|
|
4093
|
+
if (!byStrategy[pos.strategyId]) {
|
|
4094
|
+
byStrategy[pos.strategyId] = { count: 0, pnl: 0, exposure: 0 };
|
|
4095
|
+
}
|
|
4096
|
+
byStrategy[pos.strategyId].count++;
|
|
4097
|
+
byStrategy[pos.strategyId].pnl += pos.pnl;
|
|
4098
|
+
byStrategy[pos.strategyId].exposure += pos.size;
|
|
4099
|
+
}
|
|
4100
|
+
const totalExposure = open.reduce((sum, p) => sum + p.size, 0);
|
|
4101
|
+
const totalPnl = positions.reduce((sum, p) => sum + p.pnl, 0);
|
|
4102
|
+
const unrealizedPnl = open.reduce((sum, p) => sum + p.pnl, 0);
|
|
4103
|
+
const avgLeverage = open.length > 0 ? open.reduce((sum, p) => sum + p.leverage, 0) / open.length : 0;
|
|
4104
|
+
return {
|
|
4105
|
+
totalPositions: positions.length,
|
|
4106
|
+
openCount: open.length,
|
|
4107
|
+
totalExposure,
|
|
4108
|
+
totalPnl,
|
|
4109
|
+
unrealizedPnl,
|
|
4110
|
+
avgLeverage,
|
|
4111
|
+
byStrategy
|
|
4112
|
+
};
|
|
4113
|
+
}, [positions]);
|
|
4114
|
+
return {
|
|
4115
|
+
positions,
|
|
4116
|
+
openPositions,
|
|
4117
|
+
closedPositions,
|
|
4118
|
+
isLoading,
|
|
4119
|
+
error,
|
|
4120
|
+
refresh: fetchPositions,
|
|
4121
|
+
closePosition,
|
|
4122
|
+
updateStopLoss,
|
|
4123
|
+
updateTakeProfit,
|
|
4124
|
+
summary
|
|
4125
|
+
};
|
|
4126
|
+
}
|
|
4127
|
+
function mapPosition(raw) {
|
|
4128
|
+
return {
|
|
4129
|
+
id: raw.id,
|
|
4130
|
+
strategyId: raw.strategyId ?? raw.strategy_id,
|
|
4131
|
+
strategyName: raw.strategyName ?? raw.strategy_name ?? "",
|
|
4132
|
+
pair: raw.pair,
|
|
4133
|
+
side: raw.side,
|
|
4134
|
+
entryPrice: raw.entryPrice ?? raw.entry_price ?? 0,
|
|
4135
|
+
currentPrice: raw.currentPrice ?? raw.current_price ?? 0,
|
|
4136
|
+
size: raw.size ?? 0,
|
|
4137
|
+
leverage: raw.leverage ?? 1,
|
|
4138
|
+
margin: raw.margin ?? raw.size / (raw.leverage ?? 1),
|
|
4139
|
+
pnl: raw.pnl ?? 0,
|
|
4140
|
+
pnlPercent: raw.pnlPercent ?? raw.pnl_percent ?? 0,
|
|
4141
|
+
status: raw.status ?? "open",
|
|
4142
|
+
stopLoss: raw.stopLoss ?? raw.stop_loss,
|
|
4143
|
+
takeProfit: raw.takeProfit ?? raw.take_profit,
|
|
4144
|
+
liquidationPrice: raw.liquidationPrice ?? raw.liquidation_price,
|
|
4145
|
+
openTime: raw.openTime ?? raw.open_time ?? Date.now(),
|
|
4146
|
+
closeTime: raw.closeTime ?? raw.close_time,
|
|
4147
|
+
chain: raw.chain,
|
|
4148
|
+
orderId: raw.orderId ?? raw.order_id,
|
|
4149
|
+
aiConfidence: raw.aiConfidence ?? raw.ai_confidence,
|
|
4150
|
+
aiReasoning: raw.aiReasoning ?? raw.ai_reasoning
|
|
4151
|
+
};
|
|
4152
|
+
}
|
|
4153
|
+
function generateSimulatedDecisions(strategyIds, limit = 50) {
|
|
4154
|
+
const strategies = [
|
|
4155
|
+
{ id: "balanced-01", name: "Balanced Alpha" },
|
|
4156
|
+
{ id: "conservative-01", name: "Conservative Shield" },
|
|
4157
|
+
{ id: "aggressive-01", name: "Aggressive Momentum" }
|
|
4158
|
+
];
|
|
4159
|
+
const pairs = ["BTC/USDT", "ETH/USDT", "SOL/USDT", "ARB/USDT", "AVAX/USDT"];
|
|
4160
|
+
const actions = ["OPEN_LONG", "OPEN_SHORT", "CLOSE_LONG", "CLOSE_SHORT", "HOLD", "SKIP"];
|
|
4161
|
+
const reasonings = {
|
|
4162
|
+
OPEN_LONG: [
|
|
4163
|
+
"RSI oversold with MACD bullish crossover",
|
|
4164
|
+
"Golden cross detected on EMA, volume confirming",
|
|
4165
|
+
"Support level held, momentum building",
|
|
4166
|
+
"Bullish divergence on RSI, trend reversal likely"
|
|
4167
|
+
],
|
|
4168
|
+
OPEN_SHORT: [
|
|
4169
|
+
"RSI overbought with MACD bearish crossover",
|
|
4170
|
+
"Death cross on EMA, volume increasing",
|
|
4171
|
+
"Resistance level rejected, bearish pressure",
|
|
4172
|
+
"Bearish divergence detected, reversal expected"
|
|
4173
|
+
],
|
|
4174
|
+
CLOSE_LONG: [
|
|
4175
|
+
"Take profit target reached",
|
|
4176
|
+
"Momentum weakening, securing profits",
|
|
4177
|
+
"Risk management triggered"
|
|
4178
|
+
],
|
|
4179
|
+
CLOSE_SHORT: [
|
|
4180
|
+
"Take profit target reached",
|
|
4181
|
+
"Bearish momentum exhausted",
|
|
4182
|
+
"Stop loss proximity warning"
|
|
4183
|
+
],
|
|
4184
|
+
HOLD: [
|
|
4185
|
+
"No clear signal, waiting for confirmation",
|
|
4186
|
+
"Consolidation phase, insufficient momentum",
|
|
4187
|
+
"Mixed indicators, patience advised"
|
|
4188
|
+
],
|
|
4189
|
+
SKIP: [
|
|
4190
|
+
"Confidence below threshold",
|
|
4191
|
+
"Risk parameters exceeded",
|
|
4192
|
+
"Position limit reached"
|
|
4193
|
+
]
|
|
4194
|
+
};
|
|
4195
|
+
const filteredStrategies = strategyIds ? strategies.filter((s) => strategyIds.includes(s.id)) : strategies;
|
|
4196
|
+
const decisions = [];
|
|
4197
|
+
const now = Date.now();
|
|
4198
|
+
for (let i = 0; i < limit; i++) {
|
|
4199
|
+
const strategy = filteredStrategies[Math.floor(Math.random() * filteredStrategies.length)];
|
|
4200
|
+
const pair = pairs[Math.floor(Math.random() * pairs.length)];
|
|
4201
|
+
const action = actions[Math.floor(Math.random() * actions.length)];
|
|
4202
|
+
const confidence = Math.random() * 0.5 + 0.3;
|
|
4203
|
+
const executed = action !== "HOLD" && action !== "SKIP" && confidence > 0.5;
|
|
4204
|
+
const reasoningList = reasonings[action];
|
|
4205
|
+
decisions.push({
|
|
4206
|
+
id: `dec_${now - i * 6e4}_${Math.random().toString(36).slice(2, 8)}`,
|
|
4207
|
+
timestamp: now - i * (Math.random() * 3e5 + 6e4),
|
|
4208
|
+
strategyId: strategy.id,
|
|
4209
|
+
strategyName: strategy.name,
|
|
4210
|
+
pair,
|
|
4211
|
+
action,
|
|
4212
|
+
confidence,
|
|
4213
|
+
reasoning: reasoningList[Math.floor(Math.random() * reasoningList.length)],
|
|
4214
|
+
indicators: {
|
|
4215
|
+
rsi: Math.random() * 100,
|
|
4216
|
+
macd: (Math.random() - 0.5) * 2,
|
|
4217
|
+
ema: Math.random() > 0.5 ? "bullish" : "bearish",
|
|
4218
|
+
volume: Math.random() * 2 + 0.5,
|
|
4219
|
+
bollinger: Math.random() > 0.5 ? "upper" : "lower"
|
|
4220
|
+
},
|
|
4221
|
+
signals: [
|
|
4222
|
+
`RSI ${Math.random() > 0.5 ? "oversold" : "overbought"}`,
|
|
4223
|
+
`MACD ${Math.random() > 0.5 ? "bullish" : "bearish"}`
|
|
4224
|
+
],
|
|
4225
|
+
executed,
|
|
4226
|
+
positionId: executed ? `pos_${Date.now()}_${Math.random().toString(36).slice(2, 6)}` : void 0,
|
|
4227
|
+
price: 5e4 * (1 + (Math.random() - 0.5) * 0.1),
|
|
4228
|
+
size: executed ? Math.floor(Math.random() * 1e3) + 100 : void 0,
|
|
4229
|
+
leverage: executed ? Math.floor(Math.random() * 10) + 2 : void 0
|
|
4230
|
+
});
|
|
4231
|
+
}
|
|
4232
|
+
return decisions.sort((a, b) => b.timestamp - a.timestamp);
|
|
4233
|
+
}
|
|
4234
|
+
function useAIDecisions(options) {
|
|
4235
|
+
const limit = options?.limit ?? 100;
|
|
4236
|
+
const [decisions, setDecisions] = react.useState([]);
|
|
4237
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
4238
|
+
const [error, setError] = react.useState(null);
|
|
4239
|
+
const fetchDecisions = react.useCallback(async () => {
|
|
4240
|
+
setIsLoading(true);
|
|
4241
|
+
setError(null);
|
|
4242
|
+
try {
|
|
4243
|
+
if (options?.simulation) {
|
|
4244
|
+
const simulated = generateSimulatedDecisions(options?.strategyIds, limit);
|
|
4245
|
+
setDecisions(simulated);
|
|
4246
|
+
} else {
|
|
4247
|
+
let path = "/api/v1/ai-quant/decisions";
|
|
4248
|
+
const params = new URLSearchParams();
|
|
4249
|
+
if (options?.strategyId) {
|
|
4250
|
+
params.append("strategyId", options.strategyId);
|
|
4251
|
+
}
|
|
4252
|
+
if (options?.strategyIds?.length) {
|
|
4253
|
+
params.append("strategyIds", options.strategyIds.join(","));
|
|
4254
|
+
}
|
|
4255
|
+
if (options?.actions?.length) {
|
|
4256
|
+
params.append("actions", options.actions.join(","));
|
|
4257
|
+
}
|
|
4258
|
+
params.append("limit", limit.toString());
|
|
4259
|
+
if (params.toString()) {
|
|
4260
|
+
path += `?${params.toString()}`;
|
|
4261
|
+
}
|
|
4262
|
+
const res = await fetch(`https://api.one23.io${path}`);
|
|
4263
|
+
if (res.ok) {
|
|
4264
|
+
const data = await res.json();
|
|
4265
|
+
if (data?.decisions) {
|
|
4266
|
+
setDecisions(data.decisions.map(mapDecision));
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
} catch (err) {
|
|
4271
|
+
setError(err instanceof Error ? err.message : "Failed to fetch decisions");
|
|
4272
|
+
} finally {
|
|
4273
|
+
setIsLoading(false);
|
|
4274
|
+
}
|
|
4275
|
+
}, [options?.simulation, options?.strategyId, options?.strategyIds, options?.actions, limit]);
|
|
4276
|
+
react.useEffect(() => {
|
|
4277
|
+
fetchDecisions();
|
|
4278
|
+
if (options?.pollInterval) {
|
|
4279
|
+
const timer = setInterval(fetchDecisions, options.pollInterval);
|
|
4280
|
+
return () => clearInterval(timer);
|
|
4281
|
+
}
|
|
4282
|
+
}, [fetchDecisions, options?.pollInterval]);
|
|
4283
|
+
const getByStrategy = react.useCallback(
|
|
4284
|
+
(strategyId) => decisions.filter((d) => d.strategyId === strategyId),
|
|
4285
|
+
[decisions]
|
|
4286
|
+
);
|
|
4287
|
+
const getByAction = react.useCallback(
|
|
4288
|
+
(action) => decisions.filter((d) => d.action === action),
|
|
4289
|
+
[decisions]
|
|
4290
|
+
);
|
|
4291
|
+
const recentDecisions = react.useMemo(
|
|
4292
|
+
() => decisions.slice(0, 10),
|
|
4293
|
+
[decisions]
|
|
4294
|
+
);
|
|
4295
|
+
const executedDecisions = react.useMemo(
|
|
4296
|
+
() => decisions.filter((d) => d.executed),
|
|
4297
|
+
[decisions]
|
|
4298
|
+
);
|
|
4299
|
+
const stats = react.useMemo(() => {
|
|
4300
|
+
const executed = decisions.filter((d) => d.executed);
|
|
4301
|
+
const byAction = {
|
|
4302
|
+
OPEN_LONG: 0,
|
|
4303
|
+
OPEN_SHORT: 0,
|
|
4304
|
+
CLOSE_LONG: 0,
|
|
4305
|
+
CLOSE_SHORT: 0,
|
|
4306
|
+
HOLD: 0,
|
|
4307
|
+
SKIP: 0
|
|
4308
|
+
};
|
|
4309
|
+
const byStrategy = {};
|
|
4310
|
+
for (const d of decisions) {
|
|
4311
|
+
byAction[d.action]++;
|
|
4312
|
+
byStrategy[d.strategyId] = (byStrategy[d.strategyId] || 0) + 1;
|
|
4313
|
+
}
|
|
4314
|
+
const avgConfidence = decisions.length > 0 ? decisions.reduce((sum, d) => sum + d.confidence, 0) / decisions.length : 0;
|
|
4315
|
+
return {
|
|
4316
|
+
totalDecisions: decisions.length,
|
|
4317
|
+
executedCount: executed.length,
|
|
4318
|
+
executionRate: decisions.length > 0 ? executed.length / decisions.length : 0,
|
|
4319
|
+
byAction,
|
|
4320
|
+
byStrategy,
|
|
4321
|
+
avgConfidence
|
|
4322
|
+
};
|
|
4323
|
+
}, [decisions]);
|
|
4324
|
+
return {
|
|
4325
|
+
decisions,
|
|
4326
|
+
recentDecisions,
|
|
4327
|
+
executedDecisions,
|
|
4328
|
+
isLoading,
|
|
4329
|
+
error,
|
|
4330
|
+
refresh: fetchDecisions,
|
|
4331
|
+
getByStrategy,
|
|
4332
|
+
getByAction,
|
|
4333
|
+
stats
|
|
4334
|
+
};
|
|
4335
|
+
}
|
|
4336
|
+
function mapDecision(raw) {
|
|
4337
|
+
return {
|
|
4338
|
+
id: raw.id,
|
|
4339
|
+
timestamp: raw.timestamp ?? Date.now(),
|
|
4340
|
+
strategyId: raw.strategyId ?? raw.strategy_id,
|
|
4341
|
+
strategyName: raw.strategyName ?? raw.strategy_name ?? "",
|
|
4342
|
+
pair: raw.pair,
|
|
4343
|
+
action: raw.action,
|
|
4344
|
+
confidence: raw.confidence ?? 0,
|
|
4345
|
+
reasoning: raw.reasoning ?? "",
|
|
4346
|
+
indicators: raw.indicators ?? {},
|
|
4347
|
+
signals: raw.signals ?? [],
|
|
4348
|
+
executed: raw.executed ?? false,
|
|
4349
|
+
positionId: raw.positionId ?? raw.position_id,
|
|
4350
|
+
price: raw.price,
|
|
4351
|
+
size: raw.size,
|
|
4352
|
+
leverage: raw.leverage
|
|
4353
|
+
};
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4356
|
+
// src/types/console.ts
|
|
4357
|
+
var DEFAULT_RISK_STATUS = {
|
|
4358
|
+
timestamp: Date.now(),
|
|
4359
|
+
totalExposure: 0,
|
|
4360
|
+
maxExposure: 1e5,
|
|
4361
|
+
exposurePercent: 0,
|
|
4362
|
+
dailyPnl: 0,
|
|
4363
|
+
dailyPnlLimit: 5e3,
|
|
4364
|
+
dailyPnlPercent: 0,
|
|
4365
|
+
dailyTradeCount: 0,
|
|
4366
|
+
dailyTradeLimit: 50,
|
|
4367
|
+
currentDrawdown: 0,
|
|
4368
|
+
maxDrawdown: 15,
|
|
4369
|
+
drawdownPercent: 0,
|
|
4370
|
+
openPositions: 0,
|
|
4371
|
+
maxPositions: 10,
|
|
4372
|
+
riskLevel: "low",
|
|
4373
|
+
tradingStatus: "active",
|
|
4374
|
+
warnings: []
|
|
4375
|
+
};
|
|
4376
|
+
function calculateRiskLevel(exposurePercent, drawdownPercent, dailyPnlPercent) {
|
|
4377
|
+
const worstMetric = Math.max(exposurePercent, drawdownPercent, Math.abs(dailyPnlPercent));
|
|
4378
|
+
if (worstMetric >= 90) return "critical";
|
|
4379
|
+
if (worstMetric >= 70) return "high";
|
|
4380
|
+
if (worstMetric >= 40) return "medium";
|
|
4381
|
+
return "low";
|
|
4382
|
+
}
|
|
4383
|
+
|
|
4384
|
+
// src/hooks/useAIRiskStatus.ts
|
|
4385
|
+
function generateSimulatedRiskStatus() {
|
|
4386
|
+
const totalExposure = Math.random() * 8e4 + 1e4;
|
|
4387
|
+
const maxExposure = 1e5;
|
|
4388
|
+
const exposurePercent = totalExposure / maxExposure * 100;
|
|
4389
|
+
const dailyPnl = (Math.random() - 0.3) * 3e3;
|
|
4390
|
+
const dailyPnlLimit = 5e3;
|
|
4391
|
+
const dailyPnlPercent = Math.abs(dailyPnl) / dailyPnlLimit * 100;
|
|
4392
|
+
const currentDrawdown = Math.random() * 10;
|
|
4393
|
+
const maxDrawdown = 15;
|
|
4394
|
+
const drawdownPercent = currentDrawdown / maxDrawdown * 100;
|
|
4395
|
+
const openPositions = Math.floor(Math.random() * 8);
|
|
4396
|
+
const maxPositions = 10;
|
|
4397
|
+
const dailyTradeCount = Math.floor(Math.random() * 30);
|
|
4398
|
+
const dailyTradeLimit = 50;
|
|
4399
|
+
const riskLevel = calculateRiskLevel(exposurePercent, drawdownPercent, dailyPnlPercent);
|
|
4400
|
+
const warnings = [];
|
|
4401
|
+
if (exposurePercent > 70) warnings.push("High portfolio exposure");
|
|
4402
|
+
if (drawdownPercent > 60) warnings.push("Approaching max drawdown");
|
|
4403
|
+
if (dailyPnlPercent > 80 && dailyPnl < 0) warnings.push("Daily loss limit warning");
|
|
4404
|
+
if (openPositions >= maxPositions - 1) warnings.push("Position limit nearly reached");
|
|
4405
|
+
let tradingStatus = "active";
|
|
4406
|
+
if (riskLevel === "critical") tradingStatus = "stopped";
|
|
4407
|
+
else if (riskLevel === "high" && warnings.length > 1) tradingStatus = "paused";
|
|
4408
|
+
const strategyRisks = {
|
|
4409
|
+
"balanced-01": {
|
|
4410
|
+
exposure: Math.random() * 3e4,
|
|
4411
|
+
drawdown: Math.random() * 5,
|
|
4412
|
+
riskLevel: Math.random() > 0.7 ? "medium" : "low"
|
|
4413
|
+
},
|
|
4414
|
+
"conservative-01": {
|
|
4415
|
+
exposure: Math.random() * 2e4,
|
|
4416
|
+
drawdown: Math.random() * 3,
|
|
4417
|
+
riskLevel: "low"
|
|
4418
|
+
},
|
|
4419
|
+
"aggressive-01": {
|
|
4420
|
+
exposure: Math.random() * 4e4,
|
|
4421
|
+
drawdown: Math.random() * 8,
|
|
4422
|
+
riskLevel: Math.random() > 0.5 ? "high" : "medium"
|
|
4423
|
+
}
|
|
4424
|
+
};
|
|
4425
|
+
return {
|
|
4426
|
+
timestamp: Date.now(),
|
|
4427
|
+
totalExposure,
|
|
4428
|
+
maxExposure,
|
|
4429
|
+
exposurePercent,
|
|
4430
|
+
dailyPnl,
|
|
4431
|
+
dailyPnlLimit,
|
|
4432
|
+
dailyPnlPercent,
|
|
4433
|
+
dailyTradeCount,
|
|
4434
|
+
dailyTradeLimit,
|
|
4435
|
+
currentDrawdown,
|
|
4436
|
+
maxDrawdown,
|
|
4437
|
+
drawdownPercent,
|
|
4438
|
+
openPositions,
|
|
4439
|
+
maxPositions,
|
|
4440
|
+
riskLevel,
|
|
4441
|
+
tradingStatus,
|
|
4442
|
+
warnings,
|
|
4443
|
+
strategyRisks
|
|
4444
|
+
};
|
|
4445
|
+
}
|
|
4446
|
+
function useAIRiskStatus(options) {
|
|
4447
|
+
const [riskStatus, setRiskStatus] = react.useState(DEFAULT_RISK_STATUS);
|
|
4448
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
4449
|
+
const [error, setError] = react.useState(null);
|
|
4450
|
+
const fetchRiskStatus = react.useCallback(async () => {
|
|
4451
|
+
setIsLoading(true);
|
|
4452
|
+
setError(null);
|
|
4453
|
+
try {
|
|
4454
|
+
if (options?.simulation) {
|
|
4455
|
+
const simulated = generateSimulatedRiskStatus();
|
|
4456
|
+
setRiskStatus(simulated);
|
|
4457
|
+
} else {
|
|
4458
|
+
let path = "/api/v1/ai-quant/risk-status";
|
|
4459
|
+
if (options?.strategyId) {
|
|
4460
|
+
path += `?strategyId=${options.strategyId}`;
|
|
4461
|
+
}
|
|
4462
|
+
const res = await fetch(`https://api.one23.io${path}`);
|
|
4463
|
+
if (res.ok) {
|
|
4464
|
+
const data = await res.json();
|
|
4465
|
+
if (data) {
|
|
4466
|
+
setRiskStatus(mapRiskStatus(data));
|
|
4467
|
+
}
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
4470
|
+
} catch (err) {
|
|
4471
|
+
setError(err instanceof Error ? err.message : "Failed to fetch risk status");
|
|
4472
|
+
} finally {
|
|
4473
|
+
setIsLoading(false);
|
|
4474
|
+
}
|
|
4475
|
+
}, [options?.simulation, options?.strategyId]);
|
|
4476
|
+
react.useEffect(() => {
|
|
4477
|
+
fetchRiskStatus();
|
|
4478
|
+
if (options?.pollInterval) {
|
|
4479
|
+
const timer = setInterval(fetchRiskStatus, options.pollInterval);
|
|
4480
|
+
return () => clearInterval(timer);
|
|
4481
|
+
}
|
|
4482
|
+
}, [fetchRiskStatus, options?.pollInterval]);
|
|
4483
|
+
const pauseTrading = react.useCallback(async () => {
|
|
4484
|
+
try {
|
|
4485
|
+
if (options?.simulation) {
|
|
4486
|
+
setRiskStatus((prev) => ({ ...prev, tradingStatus: "paused" }));
|
|
4487
|
+
return true;
|
|
4488
|
+
}
|
|
4489
|
+
const res = await fetch("https://api.one23.io/api/v1/ai-quant/trading/pause", {
|
|
4490
|
+
method: "POST"
|
|
4491
|
+
});
|
|
4492
|
+
if (res.ok) {
|
|
4493
|
+
setRiskStatus((prev) => ({ ...prev, tradingStatus: "paused" }));
|
|
4494
|
+
return true;
|
|
4495
|
+
}
|
|
4496
|
+
return false;
|
|
4497
|
+
} catch {
|
|
4498
|
+
return false;
|
|
4499
|
+
}
|
|
4500
|
+
}, [options?.simulation]);
|
|
4501
|
+
const resumeTrading = react.useCallback(async () => {
|
|
4502
|
+
try {
|
|
4503
|
+
if (options?.simulation) {
|
|
4504
|
+
setRiskStatus((prev) => ({ ...prev, tradingStatus: "active" }));
|
|
4505
|
+
return true;
|
|
4506
|
+
}
|
|
4507
|
+
const res = await fetch("https://api.one23.io/api/v1/ai-quant/trading/resume", {
|
|
4508
|
+
method: "POST"
|
|
4509
|
+
});
|
|
4510
|
+
if (res.ok) {
|
|
4511
|
+
setRiskStatus((prev) => ({ ...prev, tradingStatus: "active" }));
|
|
4512
|
+
return true;
|
|
4513
|
+
}
|
|
4514
|
+
return false;
|
|
4515
|
+
} catch {
|
|
4516
|
+
return false;
|
|
4517
|
+
}
|
|
4518
|
+
}, [options?.simulation]);
|
|
4519
|
+
const resetDailyLimits = react.useCallback(async () => {
|
|
4520
|
+
try {
|
|
4521
|
+
if (options?.simulation) {
|
|
4522
|
+
setRiskStatus((prev) => ({
|
|
4523
|
+
...prev,
|
|
4524
|
+
dailyPnl: 0,
|
|
4525
|
+
dailyPnlPercent: 0,
|
|
4526
|
+
dailyTradeCount: 0,
|
|
4527
|
+
warnings: prev.warnings.filter((w) => !w.includes("Daily"))
|
|
4528
|
+
}));
|
|
4529
|
+
return true;
|
|
4530
|
+
}
|
|
4531
|
+
const res = await fetch("https://api.one23.io/api/v1/ai-quant/risk/reset-daily", {
|
|
4532
|
+
method: "POST"
|
|
4533
|
+
});
|
|
4534
|
+
return res.ok;
|
|
4535
|
+
} catch {
|
|
4536
|
+
return false;
|
|
4537
|
+
}
|
|
4538
|
+
}, [options?.simulation]);
|
|
4539
|
+
const isWithinLimits = react.useMemo(() => {
|
|
4540
|
+
return riskStatus.exposurePercent < 90 && riskStatus.drawdownPercent < 90 && riskStatus.dailyPnlPercent < 90 && riskStatus.openPositions < riskStatus.maxPositions;
|
|
4541
|
+
}, [riskStatus]);
|
|
4542
|
+
const canTrade = react.useMemo(() => {
|
|
4543
|
+
return riskStatus.tradingStatus === "active" && isWithinLimits;
|
|
4544
|
+
}, [riskStatus.tradingStatus, isWithinLimits]);
|
|
4545
|
+
return {
|
|
4546
|
+
riskStatus,
|
|
4547
|
+
isLoading,
|
|
4548
|
+
error,
|
|
4549
|
+
refresh: fetchRiskStatus,
|
|
4550
|
+
isWithinLimits,
|
|
4551
|
+
canTrade,
|
|
4552
|
+
riskLevel: riskStatus.riskLevel,
|
|
4553
|
+
tradingStatus: riskStatus.tradingStatus,
|
|
4554
|
+
warnings: riskStatus.warnings,
|
|
4555
|
+
pauseTrading,
|
|
4556
|
+
resumeTrading,
|
|
4557
|
+
resetDailyLimits
|
|
4558
|
+
};
|
|
4559
|
+
}
|
|
4560
|
+
function mapRiskStatus(raw) {
|
|
4561
|
+
const exposurePercent = raw.maxExposure > 0 ? raw.totalExposure / raw.maxExposure * 100 : 0;
|
|
4562
|
+
const dailyPnlPercent = raw.dailyPnlLimit > 0 ? Math.abs(raw.dailyPnl) / raw.dailyPnlLimit * 100 : 0;
|
|
4563
|
+
const drawdownPercent = raw.maxDrawdown > 0 ? raw.currentDrawdown / raw.maxDrawdown * 100 : 0;
|
|
4564
|
+
return {
|
|
4565
|
+
timestamp: raw.timestamp ?? Date.now(),
|
|
4566
|
+
totalExposure: raw.totalExposure ?? raw.total_exposure ?? 0,
|
|
4567
|
+
maxExposure: raw.maxExposure ?? raw.max_exposure ?? 1e5,
|
|
4568
|
+
exposurePercent,
|
|
4569
|
+
dailyPnl: raw.dailyPnl ?? raw.daily_pnl ?? 0,
|
|
4570
|
+
dailyPnlLimit: raw.dailyPnlLimit ?? raw.daily_pnl_limit ?? 5e3,
|
|
4571
|
+
dailyPnlPercent,
|
|
4572
|
+
dailyTradeCount: raw.dailyTradeCount ?? raw.daily_trade_count ?? 0,
|
|
4573
|
+
dailyTradeLimit: raw.dailyTradeLimit ?? raw.daily_trade_limit ?? 50,
|
|
4574
|
+
currentDrawdown: raw.currentDrawdown ?? raw.current_drawdown ?? 0,
|
|
4575
|
+
maxDrawdown: raw.maxDrawdown ?? raw.max_drawdown ?? 15,
|
|
4576
|
+
drawdownPercent,
|
|
4577
|
+
openPositions: raw.openPositions ?? raw.open_positions ?? 0,
|
|
4578
|
+
maxPositions: raw.maxPositions ?? raw.max_positions ?? 10,
|
|
4579
|
+
riskLevel: raw.riskLevel ?? raw.risk_level ?? calculateRiskLevel(exposurePercent, drawdownPercent, dailyPnlPercent),
|
|
4580
|
+
tradingStatus: raw.tradingStatus ?? raw.trading_status ?? "active",
|
|
4581
|
+
warnings: raw.warnings ?? [],
|
|
4582
|
+
strategyRisks: raw.strategyRisks ?? raw.strategy_risks
|
|
4583
|
+
};
|
|
4584
|
+
}
|
|
4585
|
+
|
|
4586
|
+
// src/hooks/useAIQuantConsole.ts
|
|
4587
|
+
function useAIQuantConsole(options) {
|
|
4588
|
+
const simulation = options?.simulation ?? true;
|
|
4589
|
+
const pollInterval = options?.pollInterval ?? 5e3;
|
|
4590
|
+
const maxLogs = options?.maxLogs ?? 500;
|
|
4591
|
+
const strategyIds = options?.strategyIds;
|
|
4592
|
+
const botSim = useBotSimulation({
|
|
4593
|
+
maxLogs,
|
|
4594
|
+
strategyIds,
|
|
4595
|
+
autoStart: false
|
|
4596
|
+
});
|
|
4597
|
+
const positionsHook = useAIPositions({
|
|
4598
|
+
strategyIds,
|
|
4599
|
+
pollInterval,
|
|
4600
|
+
simulation
|
|
4601
|
+
});
|
|
4602
|
+
const decisionsHook = useAIDecisions({
|
|
4603
|
+
strategyIds,
|
|
4604
|
+
pollInterval,
|
|
4605
|
+
simulation,
|
|
4606
|
+
limit: 100
|
|
4607
|
+
});
|
|
4608
|
+
const riskHook = useAIRiskStatus({
|
|
4609
|
+
pollInterval,
|
|
4610
|
+
simulation
|
|
4611
|
+
});
|
|
4612
|
+
const agents = react.useMemo(() => {
|
|
4613
|
+
const agentList = [];
|
|
4614
|
+
for (const strategy of botSim.strategies) {
|
|
4615
|
+
const state = botSim.botStates.get(strategy.id);
|
|
4616
|
+
const strategyPositions = positionsHook.positions.filter((p) => p.strategyId === strategy.id);
|
|
4617
|
+
decisionsHook.decisions.filter((d) => d.strategyId === strategy.id);
|
|
4618
|
+
const openPos = strategyPositions.filter((p) => p.status === "open");
|
|
4619
|
+
const totalPnl = strategyPositions.reduce((sum, p) => sum + p.pnl, 0);
|
|
4620
|
+
const exposure = openPos.reduce((sum, p) => sum + p.size, 0);
|
|
4621
|
+
const winCount = strategyPositions.filter((p) => p.pnl > 0).length;
|
|
4622
|
+
const totalTrades = strategyPositions.length;
|
|
4623
|
+
let riskLevel = "low";
|
|
4624
|
+
if (strategy.riskTolerance === "high") riskLevel = "medium";
|
|
4625
|
+
if (exposure > 5e4) riskLevel = "high";
|
|
4626
|
+
if (state && state.openPositions.length >= 3) riskLevel = "high";
|
|
4627
|
+
agentList.push({
|
|
4628
|
+
id: strategy.id,
|
|
4629
|
+
strategyId: strategy.id,
|
|
4630
|
+
name: strategy.name,
|
|
4631
|
+
shortName: strategy.shortName,
|
|
4632
|
+
color: strategy.color,
|
|
4633
|
+
status: state?.isRunning ? "active" : botSim.isRunning ? "idle" : "paused",
|
|
4634
|
+
totalPnl: state?.totalPnl ?? totalPnl,
|
|
4635
|
+
pnlToday: totalPnl * 0.3,
|
|
4636
|
+
// Simulated
|
|
4637
|
+
winRate: state?.winRate ?? (totalTrades > 0 ? winCount / totalTrades : 0.5),
|
|
4638
|
+
totalTrades: state?.totalTrades ?? totalTrades,
|
|
4639
|
+
tradesToday: Math.floor((state?.totalTrades ?? totalTrades) * 0.2),
|
|
4640
|
+
currentPair: state?.currentPair,
|
|
4641
|
+
currentPrice: state?.currentPrice,
|
|
4642
|
+
lastSignal: state?.lastSignal,
|
|
4643
|
+
lastSignalConfidence: state?.lastSignalConfidence,
|
|
4644
|
+
lastActivity: state ? Date.now() : void 0,
|
|
4645
|
+
openPositions: openPos.length,
|
|
4646
|
+
totalExposure: exposure,
|
|
4647
|
+
riskLevel,
|
|
4648
|
+
drawdown: Math.random() * 5,
|
|
4649
|
+
// Simulated
|
|
4650
|
+
riskTolerance: strategy.riskTolerance,
|
|
4651
|
+
primaryIndicators: strategy.primaryIndicators,
|
|
4652
|
+
preferredPairs: strategy.preferredPairs,
|
|
4653
|
+
leverageRange: [strategy.leverageMin, strategy.leverageMax]
|
|
4654
|
+
});
|
|
4655
|
+
}
|
|
4656
|
+
return agentList;
|
|
4657
|
+
}, [botSim.strategies, botSim.botStates, botSim.isRunning, positionsHook.positions, decisionsHook.decisions]);
|
|
4658
|
+
const metrics = react.useMemo(() => {
|
|
4659
|
+
const positions = positionsHook.positions;
|
|
4660
|
+
const openPos = positions.filter((p) => p.status === "open");
|
|
4661
|
+
const closedPos = positions.filter((p) => p.status === "closed");
|
|
4662
|
+
const totalPnl = positions.reduce((sum, p) => sum + p.pnl, 0);
|
|
4663
|
+
const unrealizedPnl = openPos.reduce((sum, p) => sum + p.pnl, 0);
|
|
4664
|
+
const realizedPnl = closedPos.reduce((sum, p) => sum + p.pnl, 0);
|
|
4665
|
+
const wins = positions.filter((p) => p.pnl > 0);
|
|
4666
|
+
const losses = positions.filter((p) => p.pnl < 0);
|
|
4667
|
+
const avgWin = wins.length > 0 ? wins.reduce((sum, p) => sum + p.pnl, 0) / wins.length : 0;
|
|
4668
|
+
const avgLoss = losses.length > 0 ? Math.abs(losses.reduce((sum, p) => sum + p.pnl, 0)) / losses.length : 0;
|
|
4669
|
+
const profitFactor = avgLoss > 0 ? avgWin / avgLoss : avgWin > 0 ? Infinity : 0;
|
|
4670
|
+
const totalExposure = openPos.reduce((sum, p) => sum + p.size, 0);
|
|
4671
|
+
const avgLeverage = openPos.length > 0 ? openPos.reduce((sum, p) => sum + p.leverage, 0) / openPos.length : 0;
|
|
4672
|
+
const strategyMetrics = {};
|
|
4673
|
+
for (const agent of agents) {
|
|
4674
|
+
const strategyPos = positions.filter((p) => p.strategyId === agent.strategyId);
|
|
4675
|
+
const strategyOpen = strategyPos.filter((p) => p.status === "open");
|
|
4676
|
+
const strategyWins = strategyPos.filter((p) => p.pnl > 0);
|
|
4677
|
+
strategyMetrics[agent.strategyId] = {
|
|
4678
|
+
pnl: strategyPos.reduce((sum, p) => sum + p.pnl, 0),
|
|
4679
|
+
trades: strategyPos.length,
|
|
4680
|
+
winRate: strategyPos.length > 0 ? strategyWins.length / strategyPos.length : 0,
|
|
4681
|
+
exposure: strategyOpen.reduce((sum, p) => sum + p.size, 0)
|
|
4682
|
+
};
|
|
4683
|
+
}
|
|
4684
|
+
return {
|
|
4685
|
+
nav: 1e5 + totalPnl,
|
|
4686
|
+
navChange24h: totalPnl * 0.3,
|
|
4687
|
+
navChangePercent24h: totalPnl * 0.3 / 1e5 * 100,
|
|
4688
|
+
totalPnl,
|
|
4689
|
+
realizedPnl,
|
|
4690
|
+
unrealizedPnl,
|
|
4691
|
+
pnlToday: totalPnl * 0.3,
|
|
4692
|
+
pnl7d: totalPnl * 0.7,
|
|
4693
|
+
pnl30d: totalPnl,
|
|
4694
|
+
totalTrades: positions.length,
|
|
4695
|
+
tradesToday: Math.floor(positions.length * 0.2),
|
|
4696
|
+
winRate: positions.length > 0 ? wins.length / positions.length : 0,
|
|
4697
|
+
winCount: wins.length,
|
|
4698
|
+
lossCount: losses.length,
|
|
4699
|
+
avgWin,
|
|
4700
|
+
avgLoss,
|
|
4701
|
+
profitFactor,
|
|
4702
|
+
openPositions: openPos.length,
|
|
4703
|
+
totalExposure,
|
|
4704
|
+
avgLeverage,
|
|
4705
|
+
strategyMetrics
|
|
4706
|
+
};
|
|
4707
|
+
}, [positionsHook.positions, agents]);
|
|
4708
|
+
const start = react.useCallback((ids) => {
|
|
4709
|
+
botSim.start(ids || strategyIds);
|
|
4710
|
+
}, [botSim, strategyIds]);
|
|
4711
|
+
const stop = react.useCallback((ids) => {
|
|
4712
|
+
botSim.stop(ids);
|
|
4713
|
+
}, [botSim]);
|
|
4714
|
+
const clearLogs = react.useCallback(() => {
|
|
4715
|
+
botSim.clearLogs();
|
|
4716
|
+
}, [botSim]);
|
|
4717
|
+
const refresh = react.useCallback(async () => {
|
|
4718
|
+
await Promise.all([
|
|
4719
|
+
positionsHook.refresh(),
|
|
4720
|
+
decisionsHook.refresh(),
|
|
4721
|
+
riskHook.refresh()
|
|
4722
|
+
]);
|
|
4723
|
+
}, [positionsHook, decisionsHook, riskHook]);
|
|
4724
|
+
const getAgent = react.useCallback(
|
|
4725
|
+
(strategyId) => agents.find((a) => a.strategyId === strategyId),
|
|
4726
|
+
[agents]
|
|
4727
|
+
);
|
|
4728
|
+
const getAgentLogs = react.useCallback(
|
|
4729
|
+
(strategyId) => botSim.logsByStrategy.get(strategyId) || [],
|
|
4730
|
+
[botSim.logsByStrategy]
|
|
4731
|
+
);
|
|
4732
|
+
const getAgentPositions = react.useCallback(
|
|
4733
|
+
(strategyId) => positionsHook.positions.filter((p) => p.strategyId === strategyId),
|
|
4734
|
+
[positionsHook.positions]
|
|
4735
|
+
);
|
|
4736
|
+
const getAgentDecisions = react.useCallback(
|
|
4737
|
+
(strategyId) => decisionsHook.decisions.filter((d) => d.strategyId === strategyId),
|
|
4738
|
+
[decisionsHook.decisions]
|
|
4739
|
+
);
|
|
4740
|
+
const isLoading = positionsHook.isLoading || decisionsHook.isLoading || riskHook.isLoading;
|
|
4741
|
+
const error = positionsHook.error || decisionsHook.error || riskHook.error;
|
|
4742
|
+
return {
|
|
4743
|
+
strategies: botSim.strategies,
|
|
4744
|
+
agents,
|
|
4745
|
+
logs: botSim.logs,
|
|
4746
|
+
logsByStrategy: botSim.logsByStrategy,
|
|
4747
|
+
botStates: botSim.botStates,
|
|
4748
|
+
positions: positionsHook.positions,
|
|
4749
|
+
openPositions: positionsHook.openPositions,
|
|
4750
|
+
decisions: decisionsHook.decisions,
|
|
4751
|
+
recentDecisions: decisionsHook.recentDecisions,
|
|
4752
|
+
riskStatus: riskHook.riskStatus,
|
|
4753
|
+
metrics,
|
|
4754
|
+
isRunning: botSim.isRunning,
|
|
4755
|
+
isLoading,
|
|
4756
|
+
error,
|
|
4757
|
+
start,
|
|
4758
|
+
stop,
|
|
4759
|
+
clearLogs,
|
|
4760
|
+
emitBootSequence: botSim.emitBootSequence,
|
|
4761
|
+
refresh,
|
|
4762
|
+
getAgent,
|
|
4763
|
+
getAgentLogs,
|
|
4764
|
+
getAgentPositions,
|
|
4765
|
+
getAgentDecisions
|
|
4766
|
+
};
|
|
4767
|
+
}
|
|
4768
|
+
|
|
4769
|
+
// src/hooks/useTradingConsole.ts
|
|
4770
|
+
function useTradingConsole(options) {
|
|
4771
|
+
const simulation = options?.simulation ?? true;
|
|
4772
|
+
const pollInterval = options?.pollInterval ?? 5e3;
|
|
4773
|
+
const maxLogs = options?.maxLogs ?? 500;
|
|
4774
|
+
const strategyIds = options?.strategyIds;
|
|
4775
|
+
const autoStart = options?.autoStart ?? false;
|
|
4776
|
+
const aiConsole = useAIQuantConsole({
|
|
4777
|
+
simulation,
|
|
4778
|
+
pollInterval,
|
|
4779
|
+
maxLogs,
|
|
4780
|
+
strategyIds
|
|
4781
|
+
});
|
|
4782
|
+
const forexSim = useForexSimulation({ maxLogs });
|
|
4783
|
+
const forexPools = useForexPools({ refreshInterval: pollInterval });
|
|
4784
|
+
const forexInvestments = useForexInvestments({ refreshInterval: pollInterval });
|
|
4785
|
+
const combinedLogs = react.useMemo(() => {
|
|
4786
|
+
const combined = [];
|
|
4787
|
+
for (const log of aiConsole.logs) {
|
|
4788
|
+
combined.push({
|
|
4789
|
+
id: log.id,
|
|
4790
|
+
timestamp: log.timestamp,
|
|
4791
|
+
source: "ai",
|
|
4792
|
+
strategyId: log.strategyId,
|
|
4793
|
+
strategyName: log.strategyName,
|
|
4794
|
+
type: log.type,
|
|
4795
|
+
message: log.message,
|
|
4796
|
+
data: log.data,
|
|
4797
|
+
importance: log.importance
|
|
4798
|
+
});
|
|
4799
|
+
}
|
|
4800
|
+
for (const log of forexSim.logs) {
|
|
4801
|
+
combined.push({
|
|
4802
|
+
id: log.id,
|
|
4803
|
+
timestamp: log.timestamp,
|
|
4804
|
+
source: "forex",
|
|
4805
|
+
type: log.type,
|
|
4806
|
+
message: log.message,
|
|
4807
|
+
data: log.data,
|
|
4808
|
+
importance: log.importance
|
|
4809
|
+
});
|
|
4810
|
+
}
|
|
4811
|
+
return combined.sort((a, b) => b.timestamp - a.timestamp).slice(0, maxLogs);
|
|
4812
|
+
}, [aiConsole.logs, forexSim.logs, maxLogs]);
|
|
4813
|
+
const combinedMetrics = react.useMemo(() => {
|
|
4814
|
+
const aiMetrics = aiConsole.metrics;
|
|
4815
|
+
return {
|
|
4816
|
+
...aiMetrics,
|
|
4817
|
+
totalPnl: aiMetrics.totalPnl + forexSim.stats.totalPnl,
|
|
4818
|
+
totalTrades: aiMetrics.totalTrades + forexSim.stats.totalTrades
|
|
4819
|
+
};
|
|
4820
|
+
}, [aiConsole.metrics, forexSim.stats]);
|
|
4821
|
+
const controls = react.useMemo(() => ({
|
|
4822
|
+
startAISimulation: (ids) => {
|
|
4823
|
+
aiConsole.emitBootSequence();
|
|
4824
|
+
setTimeout(() => aiConsole.start(ids), 5500);
|
|
4825
|
+
},
|
|
4826
|
+
stopAISimulation: (ids) => {
|
|
4827
|
+
aiConsole.stop(ids);
|
|
4828
|
+
},
|
|
4829
|
+
startForexSimulation: () => {
|
|
4830
|
+
forexSim.start();
|
|
4831
|
+
},
|
|
4832
|
+
stopForexSimulation: () => {
|
|
4833
|
+
forexSim.stop();
|
|
4834
|
+
},
|
|
4835
|
+
startAll: () => {
|
|
4836
|
+
aiConsole.emitBootSequence();
|
|
4837
|
+
setTimeout(() => {
|
|
4838
|
+
aiConsole.start();
|
|
4839
|
+
forexSim.start();
|
|
4840
|
+
}, 5500);
|
|
4841
|
+
},
|
|
4842
|
+
stopAll: () => {
|
|
4843
|
+
aiConsole.stop();
|
|
4844
|
+
forexSim.stop();
|
|
4845
|
+
},
|
|
4846
|
+
clearLogs: () => {
|
|
4847
|
+
aiConsole.clearLogs();
|
|
4848
|
+
forexSim.clearLogs();
|
|
4849
|
+
},
|
|
4850
|
+
emitBootSequence: () => {
|
|
4851
|
+
aiConsole.emitBootSequence();
|
|
4852
|
+
},
|
|
4853
|
+
refreshAll: async () => {
|
|
4854
|
+
await Promise.all([
|
|
4855
|
+
aiConsole.refresh(),
|
|
4856
|
+
forexPools.refresh(),
|
|
4857
|
+
forexInvestments.refresh()
|
|
4858
|
+
]);
|
|
4859
|
+
}
|
|
4860
|
+
}), [aiConsole, forexSim, forexPools, forexInvestments]);
|
|
4861
|
+
const state = react.useMemo(() => ({
|
|
4862
|
+
isAISimulationRunning: aiConsole.isRunning,
|
|
4863
|
+
isForexSimulationRunning: forexSim.isRunning,
|
|
4864
|
+
isAnyRunning: aiConsole.isRunning || forexSim.isRunning,
|
|
4865
|
+
isLoading: aiConsole.isLoading || forexPools.isLoading || forexInvestments.isLoading,
|
|
4866
|
+
error: aiConsole.error || forexPools.error || forexInvestments.error
|
|
4867
|
+
}), [aiConsole.isRunning, aiConsole.isLoading, aiConsole.error, forexSim.isRunning, forexPools, forexInvestments]);
|
|
4868
|
+
react.useEffect(() => {
|
|
4869
|
+
if (autoStart) {
|
|
4870
|
+
controls.startAll();
|
|
4871
|
+
}
|
|
4872
|
+
}, [autoStart, controls]);
|
|
4873
|
+
return {
|
|
4874
|
+
ai: {
|
|
4875
|
+
strategies: aiConsole.strategies,
|
|
4876
|
+
agents: aiConsole.agents,
|
|
4877
|
+
positions: aiConsole.positions,
|
|
4878
|
+
decisions: aiConsole.decisions,
|
|
4879
|
+
riskStatus: aiConsole.riskStatus,
|
|
4880
|
+
simulationLogs: aiConsole.logs,
|
|
4881
|
+
logsByStrategy: aiConsole.logsByStrategy,
|
|
4882
|
+
botStates: aiConsole.botStates
|
|
4883
|
+
},
|
|
4884
|
+
forex: {
|
|
4885
|
+
logs: forexSim.logs,
|
|
4886
|
+
poolTransactions: forexSim.poolTransactions,
|
|
4887
|
+
pools: forexPools.pools,
|
|
4888
|
+
investments: forexInvestments.investments,
|
|
4889
|
+
stats: forexSim.stats
|
|
4890
|
+
},
|
|
4891
|
+
combinedLogs,
|
|
4892
|
+
metrics: combinedMetrics,
|
|
4893
|
+
controls,
|
|
4894
|
+
state,
|
|
4895
|
+
getAgent: aiConsole.getAgent,
|
|
4896
|
+
getAgentLogs: aiConsole.getAgentLogs,
|
|
4897
|
+
getAgentPositions: aiConsole.getAgentPositions,
|
|
4898
|
+
getAgentDecisions: aiConsole.getAgentDecisions
|
|
4899
|
+
};
|
|
4900
|
+
}
|
|
1698
4901
|
|
|
1699
4902
|
exports.clearAITradingAccessToken = clearAITradingAccessToken;
|
|
4903
|
+
exports.clearConsoleAccessToken = clearConsoleAccessToken;
|
|
4904
|
+
exports.clearForexAccessToken = clearForexAccessToken;
|
|
1700
4905
|
exports.setAITradingAccessToken = setAITradingAccessToken;
|
|
4906
|
+
exports.setConsoleAccessToken = setConsoleAccessToken;
|
|
4907
|
+
exports.setConsoleEngineUrl = setConsoleEngineUrl;
|
|
4908
|
+
exports.setForexAccessToken = setForexAccessToken;
|
|
4909
|
+
exports.setForexEngineUrl = setForexEngineUrl;
|
|
4910
|
+
exports.useAIAgent = useAIAgent;
|
|
4911
|
+
exports.useAIAgentSubscription = useAIAgentSubscription;
|
|
4912
|
+
exports.useAIAgents = useAIAgents;
|
|
4913
|
+
exports.useAIDecisions = useAIDecisions;
|
|
1701
4914
|
exports.useAIMarketData = useAIMarketData;
|
|
1702
4915
|
exports.useAIOrders = useAIOrders;
|
|
1703
4916
|
exports.useAIPortfolio = useAIPortfolio;
|
|
4917
|
+
exports.useAIPositions = useAIPositions;
|
|
4918
|
+
exports.useAIQuantConsole = useAIQuantConsole;
|
|
4919
|
+
exports.useAIRiskStatus = useAIRiskStatus;
|
|
1704
4920
|
exports.useAIStrategies = useAIStrategies;
|
|
1705
4921
|
exports.useAIStrategy = useAIStrategy;
|
|
1706
4922
|
exports.useAITrading = useAITrading;
|
|
4923
|
+
exports.useBotSimulation = useBotSimulation;
|
|
4924
|
+
exports.useForexInvestments = useForexInvestments;
|
|
4925
|
+
exports.useForexPoolData = useForexPoolData;
|
|
4926
|
+
exports.useForexPools = useForexPools;
|
|
4927
|
+
exports.useForexSimulation = useForexSimulation;
|
|
4928
|
+
exports.useForexTrading = useForexTrading;
|
|
1707
4929
|
exports.useTokenPrice = useTokenPrice;
|
|
1708
4930
|
exports.useTokenPrices = useTokenPrices;
|
|
4931
|
+
exports.useTradingConsole = useTradingConsole;
|
|
1709
4932
|
exports.useWalletBalance = useWalletBalance;
|
|
1710
4933
|
//# sourceMappingURL=index.js.map
|
|
1711
4934
|
//# sourceMappingURL=index.js.map
|