@one_deploy/sdk 1.0.7 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +339 -0
- package/dist/ForexPoolDataGenerator--__twRwl.d.mts +76 -0
- package/dist/ForexPoolDataGenerator-eUgwsU_B.d.ts +76 -0
- package/dist/OneForexTradeHistory-TlKxjbFF.d.ts +250 -0
- package/dist/OneForexTradeHistory-iDySMcw0.d.mts +250 -0
- package/dist/components/index.d.mts +539 -0
- package/dist/components/index.d.ts +539 -0
- package/dist/components/index.js +7295 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +7243 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/config/index.d.mts +1 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/console-BfTMA7ah.d.mts +504 -0
- package/dist/console-BfTMA7ah.d.ts +504 -0
- package/dist/hooks/index.d.mts +323 -1
- package/dist/hooks/index.d.ts +323 -1
- package/dist/hooks/index.js +3223 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +3204 -1
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +18 -352
- package/dist/index.d.ts +18 -352
- package/dist/index.js +8646 -574
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8449 -432
- package/dist/index.mjs.map +1 -1
- package/dist/providers/index.d.mts +31 -31
- package/dist/providers/index.d.ts +31 -31
- package/dist/providers/index.js +140 -153
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/index.mjs +100 -109
- package/dist/providers/index.mjs.map +1 -1
- package/dist/react-native.d.mts +8 -144
- package/dist/react-native.d.ts +8 -144
- package/dist/react-native.js +2640 -689
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +2610 -691
- package/dist/react-native.mjs.map +1 -1
- package/dist/services/index.d.mts +85 -4
- package/dist/services/index.d.ts +85 -4
- package/dist/services/index.js +1621 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/index.mjs +1619 -1
- package/dist/services/index.mjs.map +1 -1
- package/dist/types/index.d.mts +203 -1
- package/dist/types/index.d.ts +203 -1
- package/dist/types/index.js +275 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/index.mjs +251 -0
- package/dist/types/index.mjs.map +1 -1
- package/dist/useForexTrading-BleeSor8.d.mts +80 -0
- package/dist/useForexTrading-ZgW_G40Q.d.ts +80 -0
- package/package.json +9 -2
- package/src/components/OneConnectButton.tsx +24 -1
- package/src/components/OneNFTGallery.tsx +13 -7
- package/src/components/OneOfframpWidget.tsx +4 -3
- package/src/components/OnePayWidget.tsx +10 -1
- package/src/components/OneSendWidget.tsx +3 -3
- package/src/components/OneSwapWidget.tsx +4 -4
- package/src/components/OneTransactionButton.tsx +28 -3
- package/src/components/OneWalletBalance.tsx +1 -1
- package/src/components/ai/OneChainSelector.tsx +63 -336
- package/src/components/ai/OneForexCapitalSplit.tsx +112 -0
- package/src/components/ai/OneForexConsoleView.tsx +90 -0
- package/src/components/ai/OneForexPairSelector.tsx +101 -0
- package/src/components/ai/OneForexPoolCard.tsx +105 -0
- package/src/components/ai/OneForexTradeHistory.tsx +107 -0
- package/src/components/ai/OnePairSelector.tsx +77 -434
- package/src/components/ai/console/OneAIQuantConsole.tsx +423 -0
- package/src/components/ai/console/OneAgentCard.tsx +383 -0
- package/src/components/ai/console/OneAgentConsole.tsx +469 -0
- package/src/components/ai/console/OneDecisionTimeline.tsx +433 -0
- package/src/components/ai/console/OneMetricsDashboard.tsx +493 -0
- package/src/components/ai/console/OnePositionCard.tsx +406 -0
- package/src/components/ai/console/OnePositionDetail.tsx +600 -0
- package/src/components/ai/console/OneRiskIndicator.tsx +464 -0
- package/src/components/ai/console/OneTradingConsole.tsx +660 -0
- package/src/components/ai/console/index.ts +17 -0
- package/src/components/ai/index.ts +10 -0
- package/src/hooks/index.ts +46 -0
- package/src/hooks/useAIDecisions.ts +280 -0
- package/src/hooks/useAIPositions.ts +349 -0
- package/src/hooks/useAIQuantConsole.ts +283 -0
- package/src/hooks/useAIRiskStatus.ts +276 -0
- package/src/hooks/useAITrading.ts +190 -0
- package/src/hooks/useBotSimulation.ts +201 -0
- package/src/hooks/useForexTrading.ts +430 -0
- package/src/hooks/useTradingConsole.ts +243 -0
- package/src/index.ts +123 -5
- package/src/providers/OneProvider.tsx +181 -5
- package/src/providers/index.ts +22 -8
- package/src/react-native.ts +41 -0
- package/src/services/forex/BotSimulationEngine.ts +968 -0
- package/src/services/forex/ForexPoolDataGenerator.ts +542 -0
- package/src/services/forex/ForexSimulationEngine.ts +482 -0
- package/src/services/forex/index.ts +21 -0
- package/src/services/index.ts +16 -0
- package/src/types/aiTrading.ts +151 -0
- package/src/types/console.ts +380 -0
- package/src/types/forex.ts +282 -0
- package/src/types/index.ts +106 -0
- package/dist/price-CgqXPnT3.d.ts +0 -13
- package/dist/price-ClbLHHjv.d.mts +0 -13
- package/dist/supabase-BT0c7q9e.d.mts +0 -82
- package/dist/supabase-BT0c7q9e.d.ts +0 -82
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
// BotSimulationEngine.ts - Core simulation engine for trading bot console
|
|
2
|
+
// Runs independent simulation loops for each strategy bot with distinct personalities
|
|
3
|
+
|
|
4
|
+
// ── Types ──────────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export type BotLogType =
|
|
7
|
+
| 'SCAN'
|
|
8
|
+
| 'INDICATOR'
|
|
9
|
+
| 'NEWS'
|
|
10
|
+
| 'SIGNAL'
|
|
11
|
+
| 'ANALYSIS'
|
|
12
|
+
| 'DECISION'
|
|
13
|
+
| 'ORDER'
|
|
14
|
+
| 'FILLED'
|
|
15
|
+
| 'PNL'
|
|
16
|
+
| 'RISK'
|
|
17
|
+
| 'SYSTEM'
|
|
18
|
+
| 'STRATEGY' // Strategy reasoning and context
|
|
19
|
+
| 'THINKING'; // AI thinking process
|
|
20
|
+
|
|
21
|
+
export interface BotLogEntry {
|
|
22
|
+
id: string;
|
|
23
|
+
timestamp: number;
|
|
24
|
+
strategyId: string;
|
|
25
|
+
strategyName: string;
|
|
26
|
+
type: BotLogType;
|
|
27
|
+
message: string;
|
|
28
|
+
data?: Record<string, any>;
|
|
29
|
+
importance: 'low' | 'medium' | 'high';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface IndicatorSnapshot {
|
|
33
|
+
rsi: number;
|
|
34
|
+
macd: { value: number; signal: number; histogram: number };
|
|
35
|
+
ema: { short: number; long: number; crossover: 'golden' | 'death' | 'none' };
|
|
36
|
+
bollinger: { upper: number; middle: number; lower: number; width: number; position: number };
|
|
37
|
+
volume: { current: number; average: number; ratio: number };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface BotState {
|
|
41
|
+
strategyId: string;
|
|
42
|
+
strategyName: string;
|
|
43
|
+
isRunning: boolean;
|
|
44
|
+
currentPair: string;
|
|
45
|
+
currentPrice: number;
|
|
46
|
+
indicators: IndicatorSnapshot;
|
|
47
|
+
openPositions: OpenPosition[];
|
|
48
|
+
totalPnl: number;
|
|
49
|
+
totalTrades: number;
|
|
50
|
+
winRate: number;
|
|
51
|
+
lastSignal: string;
|
|
52
|
+
lastSignalConfidence: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface OpenPosition {
|
|
56
|
+
id: string;
|
|
57
|
+
pair: string;
|
|
58
|
+
side: 'LONG' | 'SHORT';
|
|
59
|
+
entryPrice: number;
|
|
60
|
+
currentPrice: number;
|
|
61
|
+
size: number;
|
|
62
|
+
leverage: number;
|
|
63
|
+
pnl: number;
|
|
64
|
+
pnlPercent: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface StrategyPersonality {
|
|
68
|
+
id: string;
|
|
69
|
+
name: string;
|
|
70
|
+
shortName: string;
|
|
71
|
+
color: string;
|
|
72
|
+
scanIntervalMin: number;
|
|
73
|
+
scanIntervalMax: number;
|
|
74
|
+
tradeFrequency: number;
|
|
75
|
+
positionSizeMin: number;
|
|
76
|
+
positionSizeMax: number;
|
|
77
|
+
leverageMin: number;
|
|
78
|
+
leverageMax: number;
|
|
79
|
+
primaryIndicators: string[];
|
|
80
|
+
riskTolerance: 'low' | 'medium' | 'high';
|
|
81
|
+
preferredPairs: string[];
|
|
82
|
+
rsiBias: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type LogCallback = (entry: BotLogEntry) => void;
|
|
86
|
+
|
|
87
|
+
// ── Strategy Personalities ─────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
export const STRATEGY_PERSONALITIES: StrategyPersonality[] = [
|
|
90
|
+
{
|
|
91
|
+
id: 'balanced-01',
|
|
92
|
+
name: 'Balanced Alpha',
|
|
93
|
+
shortName: 'BAL',
|
|
94
|
+
color: '#3B82F6',
|
|
95
|
+
scanIntervalMin: 25000, // Slower: 25-40s between cycles
|
|
96
|
+
scanIntervalMax: 40000,
|
|
97
|
+
tradeFrequency: 0.4,
|
|
98
|
+
positionSizeMin: 15,
|
|
99
|
+
positionSizeMax: 35,
|
|
100
|
+
leverageMin: 3,
|
|
101
|
+
leverageMax: 10,
|
|
102
|
+
primaryIndicators: ['RSI', 'MACD'],
|
|
103
|
+
riskTolerance: 'medium',
|
|
104
|
+
preferredPairs: ['BTC/USDT', 'ETH/USDT', 'SOL/USDT'],
|
|
105
|
+
rsiBias: 50,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'conservative-01',
|
|
109
|
+
name: 'Conservative Shield',
|
|
110
|
+
shortName: 'CON',
|
|
111
|
+
color: '#10B981',
|
|
112
|
+
scanIntervalMin: 35000, // Slower: 35-55s between cycles
|
|
113
|
+
scanIntervalMax: 55000,
|
|
114
|
+
tradeFrequency: 0.25,
|
|
115
|
+
positionSizeMin: 10,
|
|
116
|
+
positionSizeMax: 20,
|
|
117
|
+
leverageMin: 2,
|
|
118
|
+
leverageMax: 5,
|
|
119
|
+
primaryIndicators: ['Bollinger', 'Volume'],
|
|
120
|
+
riskTolerance: 'low',
|
|
121
|
+
preferredPairs: ['BTC/USDT', 'ETH/USDT'],
|
|
122
|
+
rsiBias: 45,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: 'aggressive-01',
|
|
126
|
+
name: 'Aggressive Momentum',
|
|
127
|
+
shortName: 'AGG',
|
|
128
|
+
color: '#EF4444',
|
|
129
|
+
scanIntervalMin: 18000, // Slower: 18-30s between cycles
|
|
130
|
+
scanIntervalMax: 30000,
|
|
131
|
+
tradeFrequency: 0.5,
|
|
132
|
+
positionSizeMin: 25,
|
|
133
|
+
positionSizeMax: 50,
|
|
134
|
+
leverageMin: 5,
|
|
135
|
+
leverageMax: 20,
|
|
136
|
+
primaryIndicators: ['RSI', 'MACD', 'EMA', 'Volume'],
|
|
137
|
+
riskTolerance: 'high',
|
|
138
|
+
preferredPairs: ['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'DOGE/USDT', 'AVAX/USDT'],
|
|
139
|
+
rsiBias: 55,
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
// ── Trading pairs with base prices ─────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
const PAIR_PRICES: Record<string, number> = {
|
|
146
|
+
'BTC/USDT': 67500,
|
|
147
|
+
'ETH/USDT': 3450,
|
|
148
|
+
'BNB/USDT': 605,
|
|
149
|
+
'SOL/USDT': 178,
|
|
150
|
+
'XRP/USDT': 0.62,
|
|
151
|
+
'DOGE/USDT': 0.165,
|
|
152
|
+
'ADA/USDT': 0.45,
|
|
153
|
+
'AVAX/USDT': 38.5,
|
|
154
|
+
'ARB/USDT': 1.18,
|
|
155
|
+
'MATIC/USDT': 0.72,
|
|
156
|
+
'LINK/USDT': 14.5,
|
|
157
|
+
'UNI/USDT': 7.8,
|
|
158
|
+
'AAVE/USDT': 92,
|
|
159
|
+
'OP/USDT': 2.45,
|
|
160
|
+
'APT/USDT': 8.9,
|
|
161
|
+
'INJ/USDT': 24.5,
|
|
162
|
+
'TIA/USDT': 11.2,
|
|
163
|
+
'SUI/USDT': 1.65,
|
|
164
|
+
'DOT/USDT': 7.2,
|
|
165
|
+
'ATOM/USDT': 9.8,
|
|
166
|
+
'FIL/USDT': 5.6,
|
|
167
|
+
'LTC/USDT': 72,
|
|
168
|
+
'NEAR/USDT': 5.1,
|
|
169
|
+
'FTM/USDT': 0.42,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const CHAIN_INFO: Record<string, { name: string; shortName: string; icon: string }> = {
|
|
173
|
+
ethereum: { name: 'Ethereum', shortName: 'ETH', icon: 'Ξ' },
|
|
174
|
+
arbitrum: { name: 'Arbitrum', shortName: 'ARB', icon: '◆' },
|
|
175
|
+
bsc: { name: 'BSC', shortName: 'BSC', icon: '◆' },
|
|
176
|
+
base: { name: 'Base', shortName: 'BASE', icon: '●' },
|
|
177
|
+
polygon: { name: 'Polygon', shortName: 'POLY', icon: '⬡' },
|
|
178
|
+
optimism: { name: 'Optimism', shortName: 'OP', icon: '◉' },
|
|
179
|
+
avalanche: { name: 'Avalanche', shortName: 'AVAX', icon: '▲' },
|
|
180
|
+
linea: { name: 'Linea', shortName: 'LINEA', icon: '═' },
|
|
181
|
+
zksync: { name: 'zkSync', shortName: 'ZK', icon: '⬢' },
|
|
182
|
+
scroll: { name: 'Scroll', shortName: 'SCRL', icon: '◎' },
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const NEWS_HEADLINES = [
|
|
186
|
+
'Fed signals potential rate pause, crypto markets react positively',
|
|
187
|
+
'Major institutional investor increases BTC allocation by 15%',
|
|
188
|
+
'On-chain data shows whale accumulation pattern forming',
|
|
189
|
+
'DeFi TVL reaches new monthly high across major protocols',
|
|
190
|
+
'Exchange outflows surge as holders move to cold storage',
|
|
191
|
+
'Options market signals increased volatility expected this week',
|
|
192
|
+
'Mining difficulty adjustment approaching, hash rate stable',
|
|
193
|
+
'Regulatory clarity in EU boosts market sentiment',
|
|
194
|
+
'Stablecoin supply expanding, potential bullish indicator',
|
|
195
|
+
'Social sentiment score shifts to extreme greed zone',
|
|
196
|
+
'Cross-chain bridge volume hits record daily high',
|
|
197
|
+
'Layer 2 adoption metrics show 40% MoM growth',
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
let idCounter = 0;
|
|
203
|
+
function genId(): string {
|
|
204
|
+
return `log_${Date.now()}_${++idCounter}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function rand(min: number, max: number): number {
|
|
208
|
+
return min + Math.random() * (max - min);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function randInt(min: number, max: number): number {
|
|
212
|
+
return Math.floor(rand(min, max + 1));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function pick<T>(arr: T[]): T {
|
|
216
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function clamp(val: number, min: number, max: number): number {
|
|
220
|
+
return Math.max(min, Math.min(max, val));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function formatPrice(price: number): string {
|
|
224
|
+
if (price >= 1000) return price.toFixed(2);
|
|
225
|
+
if (price >= 1) return price.toFixed(3);
|
|
226
|
+
return price.toFixed(5);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── Engine ─────────────────────────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
class BotSimulationEngine {
|
|
232
|
+
private listeners: LogCallback[] = [];
|
|
233
|
+
private botTimers: Map<string, ReturnType<typeof setTimeout>> = new Map();
|
|
234
|
+
private botStates: Map<string, BotState> = new Map();
|
|
235
|
+
private priceState: Map<string, number> = new Map();
|
|
236
|
+
private indicatorState: Map<string, IndicatorSnapshot> = new Map();
|
|
237
|
+
private running = false;
|
|
238
|
+
private userPairs: string[] = [];
|
|
239
|
+
private userChains: string[] = [];
|
|
240
|
+
|
|
241
|
+
constructor() {
|
|
242
|
+
// Initialize prices with some jitter
|
|
243
|
+
for (const [pair, base] of Object.entries(PAIR_PRICES)) {
|
|
244
|
+
this.priceState.set(pair, base * (1 + rand(-0.02, 0.02)));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ── Public API ─────────────────────────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
getStrategies(): StrategyPersonality[] {
|
|
251
|
+
return STRATEGY_PERSONALITIES;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
start(strategyIds?: string[], userPairs?: string[], userChains?: string[]): void {
|
|
255
|
+
this.running = true;
|
|
256
|
+
// Convert pair IDs (e.g. 'BTC') to full symbols (e.g. 'BTC/USDT') if needed
|
|
257
|
+
this.userPairs = (userPairs || [])
|
|
258
|
+
.map(p => p.includes('/') ? p : `${p}/USDT`)
|
|
259
|
+
.filter(p => p in PAIR_PRICES);
|
|
260
|
+
this.userChains = (userChains || []).filter(c => c in CHAIN_INFO);
|
|
261
|
+
const strategies = strategyIds
|
|
262
|
+
? STRATEGY_PERSONALITIES.filter(s => strategyIds.includes(s.id))
|
|
263
|
+
: STRATEGY_PERSONALITIES;
|
|
264
|
+
|
|
265
|
+
for (const strategy of strategies) {
|
|
266
|
+
this.initBotState(strategy);
|
|
267
|
+
this.scheduleCycle(strategy);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
stop(strategyIds?: string[]): void {
|
|
272
|
+
const ids = strategyIds || Array.from(this.botTimers.keys());
|
|
273
|
+
for (const id of ids) {
|
|
274
|
+
const timer = this.botTimers.get(id);
|
|
275
|
+
if (timer) {
|
|
276
|
+
clearTimeout(timer);
|
|
277
|
+
this.botTimers.delete(id);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (!strategyIds) {
|
|
281
|
+
this.running = false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
onLog(callback: LogCallback): () => void {
|
|
286
|
+
this.listeners.push(callback);
|
|
287
|
+
return () => {
|
|
288
|
+
this.listeners = this.listeners.filter(l => l !== callback);
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
getBotState(strategyId: string): BotState | undefined {
|
|
293
|
+
return this.botStates.get(strategyId);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
getAllBotStates(): Map<string, BotState> {
|
|
297
|
+
return this.botStates;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
isRunning(): boolean {
|
|
301
|
+
return this.running;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
emitBootSequence(): void {
|
|
305
|
+
const bootMessages: Array<{ msg: string; delay: number }> = [
|
|
306
|
+
{ msg: 'Initializing ONE Trading Engine v3.2.1...', delay: 0 },
|
|
307
|
+
{ msg: 'Loading market data feeds...', delay: 500 },
|
|
308
|
+
{ msg: 'Connecting to exchange WebSocket streams...', delay: 1200 },
|
|
309
|
+
{ msg: 'Calibrating indicator engines (RSI, MACD, EMA, Bollinger)...', delay: 2000 },
|
|
310
|
+
{ msg: 'Loading strategy personalities: balanced-01, conservative-01, aggressive-01', delay: 2800 },
|
|
311
|
+
{ msg: 'Risk management module initialized (max drawdown: 15%)', delay: 3600 },
|
|
312
|
+
{ msg: 'Portfolio allocation engine ready', delay: 4200 },
|
|
313
|
+
{ msg: '=== All systems online. Starting trading cycles ===', delay: 5000 },
|
|
314
|
+
];
|
|
315
|
+
|
|
316
|
+
for (const { msg, delay } of bootMessages) {
|
|
317
|
+
setTimeout(() => {
|
|
318
|
+
this.emit({
|
|
319
|
+
id: genId(),
|
|
320
|
+
timestamp: Date.now(),
|
|
321
|
+
strategyId: 'system',
|
|
322
|
+
strategyName: 'SYSTEM',
|
|
323
|
+
type: 'SYSTEM',
|
|
324
|
+
message: msg,
|
|
325
|
+
importance: 'medium',
|
|
326
|
+
});
|
|
327
|
+
}, delay);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
destroy(): void {
|
|
332
|
+
this.stop();
|
|
333
|
+
this.listeners = [];
|
|
334
|
+
this.botStates.clear();
|
|
335
|
+
this.priceState.clear();
|
|
336
|
+
this.indicatorState.clear();
|
|
337
|
+
this.userPairs = [];
|
|
338
|
+
this.userChains = [];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ── Private Methods ────────────────────────────────────────────────────────
|
|
342
|
+
|
|
343
|
+
private emit(entry: BotLogEntry): void {
|
|
344
|
+
for (const listener of this.listeners) {
|
|
345
|
+
listener(entry);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private getActivePairs(strategy: StrategyPersonality): string[] {
|
|
350
|
+
return this.userPairs.length > 0 ? this.userPairs : strategy.preferredPairs;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private getActiveChain(): string {
|
|
354
|
+
const chains = this.userChains.length > 0
|
|
355
|
+
? this.userChains
|
|
356
|
+
: ['ethereum', 'arbitrum', 'bsc'];
|
|
357
|
+
return pick(chains);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private getChainLabel(chainId: string): string {
|
|
361
|
+
const info = CHAIN_INFO[chainId];
|
|
362
|
+
return info ? info.shortName : chainId;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private initBotState(strategy: StrategyPersonality): void {
|
|
366
|
+
const activePairs = this.getActivePairs(strategy);
|
|
367
|
+
const pair = pick(activePairs);
|
|
368
|
+
const price = this.priceState.get(pair) || PAIR_PRICES[pair] || 50000;
|
|
369
|
+
const indicators = this.generateIndicators(strategy, price);
|
|
370
|
+
this.indicatorState.set(strategy.id, indicators);
|
|
371
|
+
|
|
372
|
+
this.botStates.set(strategy.id, {
|
|
373
|
+
strategyId: strategy.id,
|
|
374
|
+
strategyName: strategy.name,
|
|
375
|
+
isRunning: true,
|
|
376
|
+
currentPair: pair,
|
|
377
|
+
currentPrice: price,
|
|
378
|
+
indicators,
|
|
379
|
+
openPositions: [],
|
|
380
|
+
totalPnl: rand(-50, 200),
|
|
381
|
+
totalTrades: randInt(5, 25),
|
|
382
|
+
winRate: rand(0.48, 0.68),
|
|
383
|
+
lastSignal: 'HOLD',
|
|
384
|
+
lastSignalConfidence: 0,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private scheduleCycle(strategy: StrategyPersonality): void {
|
|
389
|
+
if (!this.running) return;
|
|
390
|
+
const interval = rand(strategy.scanIntervalMin, strategy.scanIntervalMax);
|
|
391
|
+
const timer = setTimeout(() => {
|
|
392
|
+
if (this.running) {
|
|
393
|
+
this.runBotCycle(strategy);
|
|
394
|
+
this.scheduleCycle(strategy);
|
|
395
|
+
}
|
|
396
|
+
}, interval);
|
|
397
|
+
this.botTimers.set(strategy.id, timer);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private async runBotCycle(strategy: StrategyPersonality): Promise<void> {
|
|
401
|
+
const state = this.botStates.get(strategy.id);
|
|
402
|
+
if (!state) return;
|
|
403
|
+
|
|
404
|
+
const activePairs = this.getActivePairs(strategy);
|
|
405
|
+
const pair = pick(activePairs);
|
|
406
|
+
const price = this.simulatePrice(pair);
|
|
407
|
+
const indicators = this.generateIndicators(strategy, price);
|
|
408
|
+
this.indicatorState.set(strategy.id, indicators);
|
|
409
|
+
|
|
410
|
+
state.currentPair = pair;
|
|
411
|
+
state.currentPrice = price;
|
|
412
|
+
state.indicators = indicators;
|
|
413
|
+
|
|
414
|
+
const entries: Array<{ entry: Omit<BotLogEntry, 'id' | 'timestamp'>; delay: number }> = [];
|
|
415
|
+
let delay = 0;
|
|
416
|
+
|
|
417
|
+
// 1. SCAN - Always (slower delay)
|
|
418
|
+
const chain = this.getActiveChain();
|
|
419
|
+
const chainLabel = this.getChainLabel(chain);
|
|
420
|
+
entries.push({
|
|
421
|
+
entry: {
|
|
422
|
+
strategyId: strategy.id,
|
|
423
|
+
strategyName: strategy.shortName,
|
|
424
|
+
type: 'SCAN',
|
|
425
|
+
message: `Scanning ${pair} on ${chainLabel} | Price: $${formatPrice(price)}`,
|
|
426
|
+
data: { pair, chain, chainLabel },
|
|
427
|
+
importance: 'low',
|
|
428
|
+
},
|
|
429
|
+
delay,
|
|
430
|
+
});
|
|
431
|
+
delay += rand(800, 1500); // Slower: 0.8-1.5s
|
|
432
|
+
|
|
433
|
+
// 2. THINKING - AI reasoning process (new)
|
|
434
|
+
const thinkingMessages = this.generateThinkingProcess(strategy, pair, price);
|
|
435
|
+
for (const thinking of thinkingMessages) {
|
|
436
|
+
entries.push({
|
|
437
|
+
entry: {
|
|
438
|
+
strategyId: strategy.id,
|
|
439
|
+
strategyName: strategy.shortName,
|
|
440
|
+
type: 'THINKING',
|
|
441
|
+
message: thinking,
|
|
442
|
+
importance: 'low',
|
|
443
|
+
},
|
|
444
|
+
delay,
|
|
445
|
+
});
|
|
446
|
+
delay += rand(600, 1200); // 0.6-1.2s between thoughts
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// 3. INDICATOR - Always (slower delay)
|
|
450
|
+
const indicatorParts = [];
|
|
451
|
+
if (strategy.primaryIndicators.includes('RSI') || strategy.primaryIndicators.includes('MACD')) {
|
|
452
|
+
indicatorParts.push(`RSI: ${indicators.rsi.toFixed(1)}`);
|
|
453
|
+
}
|
|
454
|
+
if (strategy.primaryIndicators.includes('MACD') || strategy.primaryIndicators.includes('RSI')) {
|
|
455
|
+
indicatorParts.push(`MACD: ${indicators.macd.histogram > 0 ? '+' : ''}${indicators.macd.histogram.toFixed(3)}`);
|
|
456
|
+
}
|
|
457
|
+
if (strategy.primaryIndicators.includes('EMA')) {
|
|
458
|
+
indicatorParts.push(`EMA: ${indicators.ema.short.toFixed(1)}/${indicators.ema.long.toFixed(1)}`);
|
|
459
|
+
if (indicators.ema.crossover !== 'none') {
|
|
460
|
+
indicatorParts.push(`[${indicators.ema.crossover.toUpperCase()} CROSS]`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (strategy.primaryIndicators.includes('Bollinger')) {
|
|
464
|
+
indicatorParts.push(`BB: ${indicators.bollinger.position.toFixed(1)}% width=${indicators.bollinger.width.toFixed(2)}`);
|
|
465
|
+
}
|
|
466
|
+
if (strategy.primaryIndicators.includes('Volume')) {
|
|
467
|
+
indicatorParts.push(`Vol: ${indicators.volume.ratio.toFixed(2)}x avg`);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
entries.push({
|
|
471
|
+
entry: {
|
|
472
|
+
strategyId: strategy.id,
|
|
473
|
+
strategyName: strategy.shortName,
|
|
474
|
+
type: 'INDICATOR',
|
|
475
|
+
message: indicatorParts.join(' | '),
|
|
476
|
+
data: { indicators },
|
|
477
|
+
importance: 'low',
|
|
478
|
+
},
|
|
479
|
+
delay,
|
|
480
|
+
});
|
|
481
|
+
delay += rand(800, 1500); // Slower: 0.8-1.5s
|
|
482
|
+
|
|
483
|
+
// 4. NEWS - 12% chance (slower)
|
|
484
|
+
if (Math.random() < 0.12) {
|
|
485
|
+
const sentiment = Math.random() > 0.4 ? 'Bullish' : 'Bearish';
|
|
486
|
+
entries.push({
|
|
487
|
+
entry: {
|
|
488
|
+
strategyId: strategy.id,
|
|
489
|
+
strategyName: strategy.shortName,
|
|
490
|
+
type: 'NEWS',
|
|
491
|
+
message: `[${sentiment}] ${pick(NEWS_HEADLINES)}`,
|
|
492
|
+
importance: 'medium',
|
|
493
|
+
},
|
|
494
|
+
delay,
|
|
495
|
+
});
|
|
496
|
+
delay += rand(1000, 1800); // Slower: 1-1.8s
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// 5. ANALYSIS - 40% chance (slower)
|
|
500
|
+
if (Math.random() < 0.4) {
|
|
501
|
+
const analysis = this.generateAnalysis(strategy, indicators, pair);
|
|
502
|
+
entries.push({
|
|
503
|
+
entry: {
|
|
504
|
+
strategyId: strategy.id,
|
|
505
|
+
strategyName: strategy.shortName,
|
|
506
|
+
type: 'ANALYSIS',
|
|
507
|
+
message: analysis,
|
|
508
|
+
importance: 'medium',
|
|
509
|
+
},
|
|
510
|
+
delay,
|
|
511
|
+
});
|
|
512
|
+
delay += rand(1000, 2000); // Slower: 1-2s
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// 6-10. Signal evaluation and potential trade
|
|
516
|
+
const signal = this.evaluateSignal(strategy, indicators);
|
|
517
|
+
state.lastSignal = signal.direction;
|
|
518
|
+
state.lastSignalConfidence = signal.confidence;
|
|
519
|
+
|
|
520
|
+
if (signal.direction !== 'HOLD') {
|
|
521
|
+
// 6. STRATEGY - Show strategy reasoning context (NEW)
|
|
522
|
+
const strategyContext = this.generateStrategyContext(strategy, signal, indicators, pair);
|
|
523
|
+
entries.push({
|
|
524
|
+
entry: {
|
|
525
|
+
strategyId: strategy.id,
|
|
526
|
+
strategyName: strategy.shortName,
|
|
527
|
+
type: 'STRATEGY',
|
|
528
|
+
message: strategyContext,
|
|
529
|
+
data: {
|
|
530
|
+
strategy: strategy.name,
|
|
531
|
+
riskTolerance: strategy.riskTolerance,
|
|
532
|
+
primaryIndicators: strategy.primaryIndicators,
|
|
533
|
+
signal: signal.direction,
|
|
534
|
+
confidence: signal.confidence,
|
|
535
|
+
},
|
|
536
|
+
importance: 'high',
|
|
537
|
+
},
|
|
538
|
+
delay,
|
|
539
|
+
});
|
|
540
|
+
delay += rand(1500, 2500); // Slower: 1.5-2.5s
|
|
541
|
+
|
|
542
|
+
// 7. SIGNAL
|
|
543
|
+
entries.push({
|
|
544
|
+
entry: {
|
|
545
|
+
strategyId: strategy.id,
|
|
546
|
+
strategyName: strategy.shortName,
|
|
547
|
+
type: 'SIGNAL',
|
|
548
|
+
message: `${signal.direction} signal detected | Confidence: ${(signal.confidence * 100).toFixed(1)}% | ${signal.reason}`,
|
|
549
|
+
data: { signal },
|
|
550
|
+
importance: 'high',
|
|
551
|
+
},
|
|
552
|
+
delay,
|
|
553
|
+
});
|
|
554
|
+
delay += rand(1200, 2000); // Slower: 1.2-2s
|
|
555
|
+
|
|
556
|
+
// 8. DECISION
|
|
557
|
+
const decision = this.makeTradeDecision(strategy, signal, state);
|
|
558
|
+
if (decision.execute) {
|
|
559
|
+
entries.push({
|
|
560
|
+
entry: {
|
|
561
|
+
strategyId: strategy.id,
|
|
562
|
+
strategyName: strategy.shortName,
|
|
563
|
+
type: 'DECISION',
|
|
564
|
+
message: `Execute ${signal.direction} | Size: ${decision.positionSize.toFixed(1)}% | Leverage: ${decision.leverage}x | Risk/Reward: 1:${decision.riskReward.toFixed(1)}`,
|
|
565
|
+
data: {
|
|
566
|
+
strategyName: strategy.name,
|
|
567
|
+
strategyId: strategy.id,
|
|
568
|
+
riskTolerance: strategy.riskTolerance,
|
|
569
|
+
signalReason: signal.reason,
|
|
570
|
+
confidence: signal.confidence,
|
|
571
|
+
},
|
|
572
|
+
importance: 'high',
|
|
573
|
+
},
|
|
574
|
+
delay,
|
|
575
|
+
});
|
|
576
|
+
delay += rand(1000, 1800); // Slower: 1-1.8s
|
|
577
|
+
|
|
578
|
+
// 9. ORDER
|
|
579
|
+
const orderId = `ORD_${Date.now().toString(36).toUpperCase()}`;
|
|
580
|
+
const orderPrice = signal.direction === 'LONG'
|
|
581
|
+
? price * (1 - rand(0.0001, 0.0005))
|
|
582
|
+
: price * (1 + rand(0.0001, 0.0005));
|
|
583
|
+
|
|
584
|
+
entries.push({
|
|
585
|
+
entry: {
|
|
586
|
+
strategyId: strategy.id,
|
|
587
|
+
strategyName: strategy.shortName,
|
|
588
|
+
type: 'ORDER',
|
|
589
|
+
message: `Submitting ${signal.direction} order | ${pair} @ $${formatPrice(orderPrice)} on ${chainLabel} | ID: ${orderId}`,
|
|
590
|
+
data: {
|
|
591
|
+
orderId,
|
|
592
|
+
pair,
|
|
593
|
+
side: signal.direction,
|
|
594
|
+
price: orderPrice,
|
|
595
|
+
leverage: decision.leverage,
|
|
596
|
+
chain,
|
|
597
|
+
chainLabel,
|
|
598
|
+
strategyName: strategy.name,
|
|
599
|
+
strategyContext: strategyContext,
|
|
600
|
+
signalReason: signal.reason,
|
|
601
|
+
},
|
|
602
|
+
importance: 'high',
|
|
603
|
+
},
|
|
604
|
+
delay,
|
|
605
|
+
});
|
|
606
|
+
delay += rand(2000, 4000); // Slower: 2-4s for order execution
|
|
607
|
+
|
|
608
|
+
// 10. FILLED
|
|
609
|
+
const fillPrice = orderPrice * (1 + rand(-0.0003, 0.0003));
|
|
610
|
+
const slippage = Math.abs(fillPrice - orderPrice) / orderPrice * 100;
|
|
611
|
+
entries.push({
|
|
612
|
+
entry: {
|
|
613
|
+
strategyId: strategy.id,
|
|
614
|
+
strategyName: strategy.shortName,
|
|
615
|
+
type: 'FILLED',
|
|
616
|
+
message: `Order FILLED | ${pair} ${signal.direction} @ $${formatPrice(fillPrice)} on ${chainLabel} | Slippage: ${slippage.toFixed(4)}% | ID: ${orderId}`,
|
|
617
|
+
data: {
|
|
618
|
+
orderId,
|
|
619
|
+
fillPrice,
|
|
620
|
+
slippage,
|
|
621
|
+
chain,
|
|
622
|
+
chainLabel,
|
|
623
|
+
strategyName: strategy.name,
|
|
624
|
+
executedBy: strategy.id,
|
|
625
|
+
},
|
|
626
|
+
importance: 'high',
|
|
627
|
+
},
|
|
628
|
+
delay,
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
// Update state with new position
|
|
632
|
+
const position: OpenPosition = {
|
|
633
|
+
id: orderId,
|
|
634
|
+
pair,
|
|
635
|
+
side: signal.direction as 'LONG' | 'SHORT',
|
|
636
|
+
entryPrice: fillPrice,
|
|
637
|
+
currentPrice: price,
|
|
638
|
+
size: decision.positionSize,
|
|
639
|
+
leverage: decision.leverage,
|
|
640
|
+
pnl: 0,
|
|
641
|
+
pnlPercent: 0,
|
|
642
|
+
};
|
|
643
|
+
state.openPositions = [...state.openPositions.slice(-2), position];
|
|
644
|
+
state.totalTrades++;
|
|
645
|
+
} else {
|
|
646
|
+
entries.push({
|
|
647
|
+
entry: {
|
|
648
|
+
strategyId: strategy.id,
|
|
649
|
+
strategyName: strategy.shortName,
|
|
650
|
+
type: 'DECISION',
|
|
651
|
+
message: `SKIP - ${decision.reason}`,
|
|
652
|
+
importance: 'medium',
|
|
653
|
+
},
|
|
654
|
+
delay,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// 11. PNL - Update open positions (slower)
|
|
660
|
+
if (state.openPositions.length > 0 && Math.random() < 0.5) {
|
|
661
|
+
delay += rand(1500, 2500); // Slower: 1.5-2.5s
|
|
662
|
+
let totalPositionPnl = 0;
|
|
663
|
+
const pnlParts: string[] = [];
|
|
664
|
+
|
|
665
|
+
for (const pos of state.openPositions) {
|
|
666
|
+
pos.currentPrice = this.simulatePrice(pos.pair);
|
|
667
|
+
const priceDiff = pos.side === 'LONG'
|
|
668
|
+
? (pos.currentPrice - pos.entryPrice) / pos.entryPrice
|
|
669
|
+
: (pos.entryPrice - pos.currentPrice) / pos.entryPrice;
|
|
670
|
+
pos.pnlPercent = priceDiff * pos.leverage * 100;
|
|
671
|
+
pos.pnl = priceDiff * pos.leverage * pos.size;
|
|
672
|
+
totalPositionPnl += pos.pnl;
|
|
673
|
+
pnlParts.push(`${pos.pair} ${pos.side}: ${pos.pnlPercent >= 0 ? '+' : ''}${pos.pnlPercent.toFixed(2)}%`);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
state.totalPnl += totalPositionPnl * rand(0.01, 0.05);
|
|
677
|
+
|
|
678
|
+
// Sometimes close a position
|
|
679
|
+
if (state.openPositions.length > 1 && Math.random() < 0.3) {
|
|
680
|
+
const closed = state.openPositions.shift()!;
|
|
681
|
+
const finalPnl = closed.pnlPercent;
|
|
682
|
+
if (finalPnl > 0) {
|
|
683
|
+
state.winRate = state.winRate * 0.95 + 0.05;
|
|
684
|
+
} else {
|
|
685
|
+
state.winRate = state.winRate * 0.95;
|
|
686
|
+
}
|
|
687
|
+
state.winRate = clamp(state.winRate, 0.35, 0.75);
|
|
688
|
+
pnlParts.push(`CLOSED ${closed.pair}: ${finalPnl >= 0 ? '+' : ''}${finalPnl.toFixed(2)}%`);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
entries.push({
|
|
692
|
+
entry: {
|
|
693
|
+
strategyId: strategy.id,
|
|
694
|
+
strategyName: strategy.shortName,
|
|
695
|
+
type: 'PNL',
|
|
696
|
+
message: pnlParts.join(' | '),
|
|
697
|
+
data: { totalPnl: state.totalPnl, positions: state.openPositions.length },
|
|
698
|
+
importance: 'medium',
|
|
699
|
+
},
|
|
700
|
+
delay,
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// 12. RISK - Periodic check (slower)
|
|
705
|
+
if (Math.random() < 0.2) {
|
|
706
|
+
delay += rand(1000, 2000); // Slower: 1-2s
|
|
707
|
+
const exposure = state.openPositions.reduce((sum, p) => sum + p.size * p.leverage, 0);
|
|
708
|
+
const maxDrawdown = rand(2, 12);
|
|
709
|
+
entries.push({
|
|
710
|
+
entry: {
|
|
711
|
+
strategyId: strategy.id,
|
|
712
|
+
strategyName: strategy.shortName,
|
|
713
|
+
type: 'RISK',
|
|
714
|
+
message: `Portfolio exposure: ${exposure.toFixed(1)}% | Max drawdown: ${maxDrawdown.toFixed(1)}% | Open positions: ${state.openPositions.length} | Win rate: ${(state.winRate * 100).toFixed(1)}%`,
|
|
715
|
+
importance: exposure > 80 ? 'high' : 'low',
|
|
716
|
+
},
|
|
717
|
+
delay,
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Emit entries with delays
|
|
722
|
+
for (const { entry, delay: d } of entries) {
|
|
723
|
+
setTimeout(() => {
|
|
724
|
+
if (this.running) {
|
|
725
|
+
this.emit({
|
|
726
|
+
...entry,
|
|
727
|
+
id: genId(),
|
|
728
|
+
timestamp: Date.now(),
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}, d);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private simulatePrice(pair: string): number {
|
|
736
|
+
const current = this.priceState.get(pair) || PAIR_PRICES[pair] || 50000;
|
|
737
|
+
const volatility = pair.includes('DOGE') ? 0.005 : pair.includes('BTC') ? 0.002 : 0.003;
|
|
738
|
+
const drift = rand(-volatility, volatility);
|
|
739
|
+
const newPrice = current * (1 + drift);
|
|
740
|
+
this.priceState.set(pair, newPrice);
|
|
741
|
+
return newPrice;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
private generateIndicators(strategy: StrategyPersonality, price: number): IndicatorSnapshot {
|
|
745
|
+
const prev = this.indicatorState.get(strategy.id);
|
|
746
|
+
const prevRsi = prev?.rsi ?? strategy.rsiBias;
|
|
747
|
+
|
|
748
|
+
// RSI: mean-reverting around bias
|
|
749
|
+
const rsiMean = strategy.rsiBias;
|
|
750
|
+
const rsiDrift = rand(-8, 8);
|
|
751
|
+
const rsiReversion = (rsiMean - prevRsi) * 0.15;
|
|
752
|
+
const rsi = clamp(prevRsi + rsiDrift + rsiReversion, 8, 95);
|
|
753
|
+
|
|
754
|
+
// MACD: correlated with RSI
|
|
755
|
+
const macdBias = rsi > 65 ? 0.3 : rsi < 35 ? -0.3 : 0;
|
|
756
|
+
const prevHist = prev?.macd.histogram ?? 0;
|
|
757
|
+
const histogram = clamp(prevHist * 0.7 + rand(-0.5, 0.5) + macdBias, -2, 2);
|
|
758
|
+
const macdValue = histogram * rand(0.8, 1.5);
|
|
759
|
+
const macdSignal = macdValue - histogram;
|
|
760
|
+
|
|
761
|
+
// EMA: occasional crossovers
|
|
762
|
+
const prevShort = prev?.ema.short ?? price;
|
|
763
|
+
const prevLong = prev?.ema.long ?? price;
|
|
764
|
+
const emaShort = prevShort * 0.9 + price * 0.1;
|
|
765
|
+
const emaLong = prevLong * 0.95 + price * 0.05;
|
|
766
|
+
let crossover: 'golden' | 'death' | 'none' = 'none';
|
|
767
|
+
if (prevShort <= prevLong && emaShort > emaLong) crossover = 'golden';
|
|
768
|
+
else if (prevShort >= prevLong && emaShort < emaLong) crossover = 'death';
|
|
769
|
+
|
|
770
|
+
// Bollinger
|
|
771
|
+
const bbMiddle = price;
|
|
772
|
+
const bbWidth = price * rand(0.01, 0.04);
|
|
773
|
+
const bbUpper = bbMiddle + bbWidth;
|
|
774
|
+
const bbLower = bbMiddle - bbWidth;
|
|
775
|
+
const bbPosition = ((price - bbLower) / (bbUpper - bbLower)) * 100;
|
|
776
|
+
|
|
777
|
+
// Volume
|
|
778
|
+
const volRatio = rand(0.3, 2.5);
|
|
779
|
+
const volCurrent = rand(100000, 5000000);
|
|
780
|
+
|
|
781
|
+
return {
|
|
782
|
+
rsi,
|
|
783
|
+
macd: { value: macdValue, signal: macdSignal, histogram },
|
|
784
|
+
ema: { short: emaShort, long: emaLong, crossover },
|
|
785
|
+
bollinger: { upper: bbUpper, middle: bbMiddle, lower: bbLower, width: bbWidth / price, position: bbPosition },
|
|
786
|
+
volume: { current: volCurrent, average: volCurrent / volRatio, ratio: volRatio },
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
private generateThinkingProcess(strategy: StrategyPersonality, pair: string, price: number): string[] {
|
|
791
|
+
const thoughts: string[] = [];
|
|
792
|
+
const pairBase = pair.split('/')[0];
|
|
793
|
+
|
|
794
|
+
// Generate 1-3 thinking steps
|
|
795
|
+
const thinkingTemplates = [
|
|
796
|
+
`Analyzing ${pairBase} market structure...`,
|
|
797
|
+
`Checking ${strategy.primaryIndicators.join(', ')} confluence...`,
|
|
798
|
+
`Evaluating risk parameters for ${strategy.riskTolerance} tolerance...`,
|
|
799
|
+
`Scanning order book depth at $${formatPrice(price)}...`,
|
|
800
|
+
`Cross-referencing with historical patterns...`,
|
|
801
|
+
`Calculating optimal entry zone...`,
|
|
802
|
+
`Assessing market sentiment indicators...`,
|
|
803
|
+
`Monitoring whale activity on ${pairBase}...`,
|
|
804
|
+
`Comparing momentum across timeframes...`,
|
|
805
|
+
`Validating support/resistance levels...`,
|
|
806
|
+
];
|
|
807
|
+
|
|
808
|
+
const numThoughts = randInt(1, 3);
|
|
809
|
+
const shuffled = [...thinkingTemplates].sort(() => Math.random() - 0.5);
|
|
810
|
+
|
|
811
|
+
for (let i = 0; i < numThoughts; i++) {
|
|
812
|
+
thoughts.push(shuffled[i]);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
return thoughts;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
private generateStrategyContext(
|
|
819
|
+
strategy: StrategyPersonality,
|
|
820
|
+
signal: { direction: string; confidence: number; reason: string },
|
|
821
|
+
indicators: IndicatorSnapshot,
|
|
822
|
+
pair: string
|
|
823
|
+
): string {
|
|
824
|
+
const contexts = [];
|
|
825
|
+
|
|
826
|
+
// Strategy personality context
|
|
827
|
+
contexts.push(`[${strategy.name}]`);
|
|
828
|
+
|
|
829
|
+
// Risk context
|
|
830
|
+
const riskLevel = strategy.riskTolerance === 'high' ? 'aggressive' : strategy.riskTolerance === 'low' ? 'conservative' : 'balanced';
|
|
831
|
+
contexts.push(`Risk: ${riskLevel}`);
|
|
832
|
+
|
|
833
|
+
// Key indicator that triggered
|
|
834
|
+
if (indicators.rsi < 35 || indicators.rsi > 65) {
|
|
835
|
+
contexts.push(`RSI ${indicators.rsi < 35 ? 'oversold' : 'overbought'} (${indicators.rsi.toFixed(1)})`);
|
|
836
|
+
}
|
|
837
|
+
if (indicators.ema.crossover !== 'none') {
|
|
838
|
+
contexts.push(`EMA ${indicators.ema.crossover} cross`);
|
|
839
|
+
}
|
|
840
|
+
if (Math.abs(indicators.macd.histogram) > 0.3) {
|
|
841
|
+
contexts.push(`MACD ${indicators.macd.histogram > 0 ? 'bullish' : 'bearish'} momentum`);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Confidence interpretation
|
|
845
|
+
const confLevel = signal.confidence > 0.7 ? 'HIGH' : signal.confidence > 0.5 ? 'MEDIUM' : 'LOW';
|
|
846
|
+
contexts.push(`Confidence: ${confLevel}`);
|
|
847
|
+
|
|
848
|
+
return contexts.join(' | ');
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
private generateAnalysis(strategy: StrategyPersonality, indicators: IndicatorSnapshot, pair: string): string {
|
|
852
|
+
const analyses = [];
|
|
853
|
+
|
|
854
|
+
if (indicators.rsi > 70) {
|
|
855
|
+
analyses.push(`RSI at ${indicators.rsi.toFixed(1)} - overbought territory, watching for reversal`);
|
|
856
|
+
} else if (indicators.rsi < 30) {
|
|
857
|
+
analyses.push(`RSI at ${indicators.rsi.toFixed(1)} - oversold, potential bounce setup`);
|
|
858
|
+
} else if (indicators.rsi > 55) {
|
|
859
|
+
analyses.push(`RSI trending bullish at ${indicators.rsi.toFixed(1)}`);
|
|
860
|
+
} else {
|
|
861
|
+
analyses.push(`RSI neutral at ${indicators.rsi.toFixed(1)}, no clear direction`);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (indicators.macd.histogram > 0.5) {
|
|
865
|
+
analyses.push('MACD histogram expanding positive - momentum building');
|
|
866
|
+
} else if (indicators.macd.histogram < -0.5) {
|
|
867
|
+
analyses.push('MACD histogram expanding negative - bearish pressure');
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (indicators.ema.crossover === 'golden') {
|
|
871
|
+
analyses.push('EMA golden cross detected - strong bullish signal');
|
|
872
|
+
} else if (indicators.ema.crossover === 'death') {
|
|
873
|
+
analyses.push('EMA death cross detected - bearish warning');
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (indicators.bollinger.position > 90) {
|
|
877
|
+
analyses.push(`Price near upper Bollinger band (${indicators.bollinger.position.toFixed(0)}%) - potential resistance`);
|
|
878
|
+
} else if (indicators.bollinger.position < 10) {
|
|
879
|
+
analyses.push(`Price near lower Bollinger band (${indicators.bollinger.position.toFixed(0)}%) - potential support`);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (indicators.volume.ratio > 1.8) {
|
|
883
|
+
analyses.push(`Volume spike ${indicators.volume.ratio.toFixed(1)}x average - high activity`);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
return analyses.length > 0 ? analyses.join(' | ') : `${pair} consolidating - waiting for clearer setup`;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
private evaluateSignal(
|
|
890
|
+
strategy: StrategyPersonality,
|
|
891
|
+
indicators: IndicatorSnapshot,
|
|
892
|
+
): { direction: 'LONG' | 'SHORT' | 'HOLD'; confidence: number; reason: string } {
|
|
893
|
+
let bullScore = 0;
|
|
894
|
+
let bearScore = 0;
|
|
895
|
+
const reasons: string[] = [];
|
|
896
|
+
|
|
897
|
+
// RSI
|
|
898
|
+
if (indicators.rsi < 30) { bullScore += 2; reasons.push('RSI oversold'); }
|
|
899
|
+
else if (indicators.rsi < 40) { bullScore += 1; reasons.push('RSI low'); }
|
|
900
|
+
else if (indicators.rsi > 70) { bearScore += 2; reasons.push('RSI overbought'); }
|
|
901
|
+
else if (indicators.rsi > 60) { bearScore += 1; reasons.push('RSI high'); }
|
|
902
|
+
|
|
903
|
+
// MACD
|
|
904
|
+
if (indicators.macd.histogram > 0.3) { bullScore += 1.5; reasons.push('MACD bullish'); }
|
|
905
|
+
else if (indicators.macd.histogram < -0.3) { bearScore += 1.5; reasons.push('MACD bearish'); }
|
|
906
|
+
|
|
907
|
+
// EMA
|
|
908
|
+
if (indicators.ema.crossover === 'golden') { bullScore += 2.5; reasons.push('Golden cross'); }
|
|
909
|
+
else if (indicators.ema.crossover === 'death') { bearScore += 2.5; reasons.push('Death cross'); }
|
|
910
|
+
else if (indicators.ema.short > indicators.ema.long) { bullScore += 0.5; }
|
|
911
|
+
else { bearScore += 0.5; }
|
|
912
|
+
|
|
913
|
+
// Bollinger
|
|
914
|
+
if (indicators.bollinger.position < 15) { bullScore += 1; reasons.push('BB support'); }
|
|
915
|
+
else if (indicators.bollinger.position > 85) { bearScore += 1; reasons.push('BB resistance'); }
|
|
916
|
+
|
|
917
|
+
// Volume confirmation
|
|
918
|
+
if (indicators.volume.ratio > 1.5) {
|
|
919
|
+
if (bullScore > bearScore) bullScore += 1;
|
|
920
|
+
else bearScore += 1;
|
|
921
|
+
reasons.push('Volume confirms');
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const netScore = bullScore - bearScore;
|
|
925
|
+
const confidence = Math.min(Math.abs(netScore) / 6, 0.95);
|
|
926
|
+
const threshold = strategy.riskTolerance === 'high' ? 1.5 : strategy.riskTolerance === 'medium' ? 2.0 : 2.5;
|
|
927
|
+
|
|
928
|
+
// Apply trade frequency filter
|
|
929
|
+
if (Math.random() > strategy.tradeFrequency) {
|
|
930
|
+
return { direction: 'HOLD', confidence: 0, reason: 'Cycle skip' };
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (netScore > threshold) {
|
|
934
|
+
return { direction: 'LONG', confidence, reason: reasons.slice(0, 3).join(', ') };
|
|
935
|
+
} else if (netScore < -threshold) {
|
|
936
|
+
return { direction: 'SHORT', confidence, reason: reasons.slice(0, 3).join(', ') };
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
return { direction: 'HOLD', confidence: 0, reason: 'No clear signal' };
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
private makeTradeDecision(
|
|
943
|
+
strategy: StrategyPersonality,
|
|
944
|
+
signal: { direction: string; confidence: number },
|
|
945
|
+
state: BotState,
|
|
946
|
+
): { execute: boolean; positionSize: number; leverage: number; riskReward: number; reason: string } {
|
|
947
|
+
// Check position limits
|
|
948
|
+
if (state.openPositions.length >= 3) {
|
|
949
|
+
return { execute: false, positionSize: 0, leverage: 0, riskReward: 0, reason: 'Max positions reached (3)' };
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// Confidence threshold
|
|
953
|
+
const minConfidence = strategy.riskTolerance === 'high' ? 0.3 : strategy.riskTolerance === 'medium' ? 0.45 : 0.6;
|
|
954
|
+
if (signal.confidence < minConfidence) {
|
|
955
|
+
return { execute: false, positionSize: 0, leverage: 0, riskReward: 0, reason: `Confidence too low (${(signal.confidence * 100).toFixed(0)}% < ${(minConfidence * 100).toFixed(0)}%)` };
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
const positionSize = rand(strategy.positionSizeMin, strategy.positionSizeMax);
|
|
959
|
+
const leverage = randInt(strategy.leverageMin, strategy.leverageMax);
|
|
960
|
+
const riskReward = rand(1.2, 3.5);
|
|
961
|
+
|
|
962
|
+
return { execute: true, positionSize, leverage, riskReward, reason: '' };
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// ── Singleton Export ────────────────────────────────────────────────────────────
|
|
967
|
+
|
|
968
|
+
export const botSimulationEngine = new BotSimulationEngine();
|