@one_deploy/sdk 1.0.6 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +339 -0
  2. package/dist/ForexPoolDataGenerator--__twRwl.d.mts +76 -0
  3. package/dist/ForexPoolDataGenerator-eUgwsU_B.d.ts +76 -0
  4. package/dist/OneForexTradeHistory-TlKxjbFF.d.ts +250 -0
  5. package/dist/OneForexTradeHistory-iDySMcw0.d.mts +250 -0
  6. package/dist/components/index.d.mts +539 -0
  7. package/dist/components/index.d.ts +539 -0
  8. package/dist/components/index.js +7295 -0
  9. package/dist/components/index.js.map +1 -0
  10. package/dist/components/index.mjs +7243 -0
  11. package/dist/components/index.mjs.map +1 -0
  12. package/dist/config/index.d.mts +1 -0
  13. package/dist/config/index.d.ts +1 -0
  14. package/dist/console-BfTMA7ah.d.mts +504 -0
  15. package/dist/console-BfTMA7ah.d.ts +504 -0
  16. package/dist/hooks/index.d.mts +323 -1
  17. package/dist/hooks/index.d.ts +323 -1
  18. package/dist/hooks/index.js +3223 -0
  19. package/dist/hooks/index.js.map +1 -1
  20. package/dist/hooks/index.mjs +3204 -1
  21. package/dist/hooks/index.mjs.map +1 -1
  22. package/dist/index.d.mts +18 -352
  23. package/dist/index.d.ts +18 -352
  24. package/dist/index.js +8646 -574
  25. package/dist/index.js.map +1 -1
  26. package/dist/index.mjs +8449 -432
  27. package/dist/index.mjs.map +1 -1
  28. package/dist/providers/index.d.mts +31 -31
  29. package/dist/providers/index.d.ts +31 -31
  30. package/dist/providers/index.js +140 -153
  31. package/dist/providers/index.js.map +1 -1
  32. package/dist/providers/index.mjs +100 -109
  33. package/dist/providers/index.mjs.map +1 -1
  34. package/dist/react-native.d.mts +8 -140
  35. package/dist/react-native.d.ts +8 -140
  36. package/dist/react-native.js +2527 -0
  37. package/dist/react-native.js.map +1 -1
  38. package/dist/react-native.mjs +2497 -2
  39. package/dist/react-native.mjs.map +1 -1
  40. package/dist/services/index.d.mts +85 -4
  41. package/dist/services/index.d.ts +85 -4
  42. package/dist/services/index.js +1621 -0
  43. package/dist/services/index.js.map +1 -1
  44. package/dist/services/index.mjs +1619 -1
  45. package/dist/services/index.mjs.map +1 -1
  46. package/dist/types/index.d.mts +203 -1
  47. package/dist/types/index.d.ts +203 -1
  48. package/dist/types/index.js +275 -0
  49. package/dist/types/index.js.map +1 -1
  50. package/dist/types/index.mjs +251 -0
  51. package/dist/types/index.mjs.map +1 -1
  52. package/dist/useForexTrading-BleeSor8.d.mts +80 -0
  53. package/dist/useForexTrading-ZgW_G40Q.d.ts +80 -0
  54. package/package.json +9 -2
  55. package/src/components/OneConnectButton.tsx +24 -1
  56. package/src/components/OneNFTGallery.tsx +13 -7
  57. package/src/components/OneOfframpWidget.tsx +4 -3
  58. package/src/components/OnePayWidget.tsx +10 -1
  59. package/src/components/OneSendWidget.tsx +3 -3
  60. package/src/components/OneSwapWidget.tsx +4 -4
  61. package/src/components/OneTransactionButton.tsx +28 -3
  62. package/src/components/OneWalletBalance.tsx +1 -1
  63. package/src/components/ai/OneForexCapitalSplit.tsx +112 -0
  64. package/src/components/ai/OneForexConsoleView.tsx +90 -0
  65. package/src/components/ai/OneForexPairSelector.tsx +101 -0
  66. package/src/components/ai/OneForexPoolCard.tsx +105 -0
  67. package/src/components/ai/OneForexTradeHistory.tsx +107 -0
  68. package/src/components/ai/console/OneAIQuantConsole.tsx +423 -0
  69. package/src/components/ai/console/OneAgentCard.tsx +383 -0
  70. package/src/components/ai/console/OneAgentConsole.tsx +469 -0
  71. package/src/components/ai/console/OneDecisionTimeline.tsx +433 -0
  72. package/src/components/ai/console/OneMetricsDashboard.tsx +493 -0
  73. package/src/components/ai/console/OnePositionCard.tsx +406 -0
  74. package/src/components/ai/console/OnePositionDetail.tsx +600 -0
  75. package/src/components/ai/console/OneRiskIndicator.tsx +464 -0
  76. package/src/components/ai/console/OneTradingConsole.tsx +660 -0
  77. package/src/components/ai/console/index.ts +17 -0
  78. package/src/components/ai/index.ts +10 -0
  79. package/src/hooks/index.ts +46 -0
  80. package/src/hooks/useAIDecisions.ts +280 -0
  81. package/src/hooks/useAIPositions.ts +349 -0
  82. package/src/hooks/useAIQuantConsole.ts +283 -0
  83. package/src/hooks/useAIRiskStatus.ts +276 -0
  84. package/src/hooks/useAITrading.ts +190 -0
  85. package/src/hooks/useBotSimulation.ts +201 -0
  86. package/src/hooks/useForexTrading.ts +430 -0
  87. package/src/hooks/useTradingConsole.ts +243 -0
  88. package/src/index.ts +123 -5
  89. package/src/providers/OneProvider.tsx +181 -5
  90. package/src/providers/index.ts +22 -8
  91. package/src/react-native.ts +41 -0
  92. package/src/services/forex/BotSimulationEngine.ts +968 -0
  93. package/src/services/forex/ForexPoolDataGenerator.ts +542 -0
  94. package/src/services/forex/ForexSimulationEngine.ts +482 -0
  95. package/src/services/forex/index.ts +21 -0
  96. package/src/services/index.ts +16 -0
  97. package/src/types/aiTrading.ts +151 -0
  98. package/src/types/console.ts +380 -0
  99. package/src/types/forex.ts +282 -0
  100. package/src/types/index.ts +106 -0
  101. package/dist/price-CgqXPnT3.d.ts +0 -13
  102. package/dist/price-ClbLHHjv.d.mts +0 -13
  103. package/dist/supabase-BT0c7q9e.d.mts +0 -82
  104. package/dist/supabase-BT0c7q9e.d.ts +0 -82
@@ -11,6 +11,10 @@ export {
11
11
  useAITrading,
12
12
  setAITradingAccessToken,
13
13
  clearAITradingAccessToken,
14
+ // AI Agent Hooks
15
+ useAIAgents,
16
+ useAIAgent,
17
+ useAIAgentSubscription,
14
18
  type UseAIStrategiesOptions,
15
19
  type UseAIStrategiesResult,
16
20
  type UseAIStrategyResult,
@@ -19,4 +23,46 @@ export {
19
23
  type UseAIPortfolioResult,
20
24
  type UseAIMarketDataResult,
21
25
  type UseAITradingResult,
26
+ type UseAIAgentsResult,
27
+ type UseAIAgentResult,
28
+ type UseAIAgentSubscriptionResult,
29
+ type AIAgent,
30
+ type AIAgentParams,
22
31
  } from './useAITrading';
32
+
33
+ // Forex Trading Hooks
34
+ export {
35
+ useForexPools,
36
+ useForexInvestments,
37
+ useForexSimulation,
38
+ useForexPoolData,
39
+ useForexTrading,
40
+ setForexAccessToken,
41
+ clearForexAccessToken,
42
+ setForexEngineUrl,
43
+ type UseForexPoolsResult,
44
+ type UseForexInvestmentsResult,
45
+ type UseForexSimulationResult,
46
+ type UseForexPoolDataResult,
47
+ type UseForexTradingResult,
48
+ } from './useForexTrading';
49
+
50
+ // Trading Console Hooks
51
+ export {
52
+ useTradingConsole,
53
+ useAIQuantConsole,
54
+ useBotSimulation,
55
+ useAIPositions,
56
+ useAIDecisions,
57
+ useAIRiskStatus,
58
+ setConsoleAccessToken,
59
+ clearConsoleAccessToken,
60
+ setConsoleEngineUrl,
61
+ } from './useTradingConsole';
62
+
63
+ export type { UseBotSimulationOptions, UseBotSimulationResult } from './useBotSimulation';
64
+ export type { UseAIPositionsOptions, UseAIPositionsResult } from './useAIPositions';
65
+ export type { UseAIDecisionsOptions, UseAIDecisionsResult } from './useAIDecisions';
66
+ export type { UseAIRiskStatusOptions, UseAIRiskStatusResult } from './useAIRiskStatus';
67
+ export type { UseAIQuantConsoleResult } from './useAIQuantConsole';
68
+ export type { UseTradingConsoleResult } from './useTradingConsole';
@@ -0,0 +1,280 @@
1
+ /**
2
+ * ONE SDK - AI Decisions Hook
3
+ * Provides React hook for fetching AI trading decision history with reasoning
4
+ */
5
+
6
+ import { useState, useEffect, useCallback, useMemo } from 'react';
7
+ import type { AIDecision, DecisionAction } from '../types/console';
8
+
9
+ // ── Types ─────────────────────────────────────────────────────────────────────
10
+
11
+ export interface UseAIDecisionsOptions {
12
+ strategyId?: string;
13
+ strategyIds?: string[];
14
+ actions?: DecisionAction[];
15
+ limit?: number;
16
+ pollInterval?: number;
17
+ simulation?: boolean;
18
+ }
19
+
20
+ export interface UseAIDecisionsResult {
21
+ decisions: AIDecision[];
22
+ recentDecisions: AIDecision[];
23
+ executedDecisions: AIDecision[];
24
+ isLoading: boolean;
25
+ error: string | null;
26
+ refresh: () => Promise<void>;
27
+ // Filters
28
+ getByStrategy: (strategyId: string) => AIDecision[];
29
+ getByAction: (action: DecisionAction) => AIDecision[];
30
+ // Stats
31
+ stats: {
32
+ totalDecisions: number;
33
+ executedCount: number;
34
+ executionRate: number;
35
+ byAction: Record<DecisionAction, number>;
36
+ byStrategy: Record<string, number>;
37
+ avgConfidence: number;
38
+ };
39
+ }
40
+
41
+ // ── Simulation Data Generator ─────────────────────────────────────────────────
42
+
43
+ function generateSimulatedDecisions(
44
+ strategyIds?: string[],
45
+ limit: number = 50
46
+ ): AIDecision[] {
47
+ const strategies = [
48
+ { id: 'balanced-01', name: 'Balanced Alpha' },
49
+ { id: 'conservative-01', name: 'Conservative Shield' },
50
+ { id: 'aggressive-01', name: 'Aggressive Momentum' },
51
+ ];
52
+
53
+ const pairs = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'ARB/USDT', 'AVAX/USDT'];
54
+ const actions: DecisionAction[] = ['OPEN_LONG', 'OPEN_SHORT', 'CLOSE_LONG', 'CLOSE_SHORT', 'HOLD', 'SKIP'];
55
+
56
+ const reasonings = {
57
+ OPEN_LONG: [
58
+ 'RSI oversold with MACD bullish crossover',
59
+ 'Golden cross detected on EMA, volume confirming',
60
+ 'Support level held, momentum building',
61
+ 'Bullish divergence on RSI, trend reversal likely',
62
+ ],
63
+ OPEN_SHORT: [
64
+ 'RSI overbought with MACD bearish crossover',
65
+ 'Death cross on EMA, volume increasing',
66
+ 'Resistance level rejected, bearish pressure',
67
+ 'Bearish divergence detected, reversal expected',
68
+ ],
69
+ CLOSE_LONG: [
70
+ 'Take profit target reached',
71
+ 'Momentum weakening, securing profits',
72
+ 'Risk management triggered',
73
+ ],
74
+ CLOSE_SHORT: [
75
+ 'Take profit target reached',
76
+ 'Bearish momentum exhausted',
77
+ 'Stop loss proximity warning',
78
+ ],
79
+ HOLD: [
80
+ 'No clear signal, waiting for confirmation',
81
+ 'Consolidation phase, insufficient momentum',
82
+ 'Mixed indicators, patience advised',
83
+ ],
84
+ SKIP: [
85
+ 'Confidence below threshold',
86
+ 'Risk parameters exceeded',
87
+ 'Position limit reached',
88
+ ],
89
+ };
90
+
91
+ const filteredStrategies = strategyIds
92
+ ? strategies.filter(s => strategyIds.includes(s.id))
93
+ : strategies;
94
+
95
+ const decisions: AIDecision[] = [];
96
+ const now = Date.now();
97
+
98
+ for (let i = 0; i < limit; i++) {
99
+ const strategy = filteredStrategies[Math.floor(Math.random() * filteredStrategies.length)];
100
+ const pair = pairs[Math.floor(Math.random() * pairs.length)];
101
+ const action = actions[Math.floor(Math.random() * actions.length)];
102
+ const confidence = Math.random() * 0.5 + 0.3;
103
+ const executed = action !== 'HOLD' && action !== 'SKIP' && confidence > 0.5;
104
+ const reasoningList = reasonings[action];
105
+
106
+ decisions.push({
107
+ id: `dec_${now - i * 60000}_${Math.random().toString(36).slice(2, 8)}`,
108
+ timestamp: now - i * (Math.random() * 300000 + 60000),
109
+ strategyId: strategy.id,
110
+ strategyName: strategy.name,
111
+ pair,
112
+ action,
113
+ confidence,
114
+ reasoning: reasoningList[Math.floor(Math.random() * reasoningList.length)],
115
+ indicators: {
116
+ rsi: Math.random() * 100,
117
+ macd: (Math.random() - 0.5) * 2,
118
+ ema: Math.random() > 0.5 ? 'bullish' : 'bearish',
119
+ volume: Math.random() * 2 + 0.5,
120
+ bollinger: Math.random() > 0.5 ? 'upper' : 'lower',
121
+ },
122
+ signals: [
123
+ `RSI ${Math.random() > 0.5 ? 'oversold' : 'overbought'}`,
124
+ `MACD ${Math.random() > 0.5 ? 'bullish' : 'bearish'}`,
125
+ ],
126
+ executed,
127
+ positionId: executed ? `pos_${Date.now()}_${Math.random().toString(36).slice(2, 6)}` : undefined,
128
+ price: 50000 * (1 + (Math.random() - 0.5) * 0.1),
129
+ size: executed ? Math.floor(Math.random() * 1000) + 100 : undefined,
130
+ leverage: executed ? Math.floor(Math.random() * 10) + 2 : undefined,
131
+ });
132
+ }
133
+
134
+ return decisions.sort((a, b) => b.timestamp - a.timestamp);
135
+ }
136
+
137
+ // ── Hook Implementation ───────────────────────────────────────────────────────
138
+
139
+ export function useAIDecisions(options?: UseAIDecisionsOptions): UseAIDecisionsResult {
140
+ const limit = options?.limit ?? 100;
141
+ const [decisions, setDecisions] = useState<AIDecision[]>([]);
142
+ const [isLoading, setIsLoading] = useState(false);
143
+ const [error, setError] = useState<string | null>(null);
144
+
145
+ const fetchDecisions = useCallback(async () => {
146
+ setIsLoading(true);
147
+ setError(null);
148
+
149
+ try {
150
+ if (options?.simulation) {
151
+ const simulated = generateSimulatedDecisions(options?.strategyIds, limit);
152
+ setDecisions(simulated);
153
+ } else {
154
+ let path = '/api/v1/ai-quant/decisions';
155
+ const params = new URLSearchParams();
156
+ if (options?.strategyId) {
157
+ params.append('strategyId', options.strategyId);
158
+ }
159
+ if (options?.strategyIds?.length) {
160
+ params.append('strategyIds', options.strategyIds.join(','));
161
+ }
162
+ if (options?.actions?.length) {
163
+ params.append('actions', options.actions.join(','));
164
+ }
165
+ params.append('limit', limit.toString());
166
+ if (params.toString()) {
167
+ path += `?${params.toString()}`;
168
+ }
169
+
170
+ const res = await fetch(`https://api.one23.io${path}`);
171
+ if (res.ok) {
172
+ const data = await res.json();
173
+ if (data?.decisions) {
174
+ setDecisions(data.decisions.map(mapDecision));
175
+ }
176
+ }
177
+ }
178
+ } catch (err) {
179
+ setError(err instanceof Error ? err.message : 'Failed to fetch decisions');
180
+ } finally {
181
+ setIsLoading(false);
182
+ }
183
+ }, [options?.simulation, options?.strategyId, options?.strategyIds, options?.actions, limit]);
184
+
185
+ // Initial fetch and polling
186
+ useEffect(() => {
187
+ fetchDecisions();
188
+ if (options?.pollInterval) {
189
+ const timer = setInterval(fetchDecisions, options.pollInterval);
190
+ return () => clearInterval(timer);
191
+ }
192
+ }, [fetchDecisions, options?.pollInterval]);
193
+
194
+ // Filter functions
195
+ const getByStrategy = useCallback((strategyId: string) =>
196
+ decisions.filter(d => d.strategyId === strategyId),
197
+ [decisions]
198
+ );
199
+
200
+ const getByAction = useCallback((action: DecisionAction) =>
201
+ decisions.filter(d => d.action === action),
202
+ [decisions]
203
+ );
204
+
205
+ // Computed values
206
+ const recentDecisions = useMemo(() =>
207
+ decisions.slice(0, 10),
208
+ [decisions]
209
+ );
210
+
211
+ const executedDecisions = useMemo(() =>
212
+ decisions.filter(d => d.executed),
213
+ [decisions]
214
+ );
215
+
216
+ const stats = useMemo(() => {
217
+ const executed = decisions.filter(d => d.executed);
218
+ const byAction: Record<DecisionAction, number> = {
219
+ OPEN_LONG: 0,
220
+ OPEN_SHORT: 0,
221
+ CLOSE_LONG: 0,
222
+ CLOSE_SHORT: 0,
223
+ HOLD: 0,
224
+ SKIP: 0,
225
+ };
226
+ const byStrategy: Record<string, number> = {};
227
+
228
+ for (const d of decisions) {
229
+ byAction[d.action]++;
230
+ byStrategy[d.strategyId] = (byStrategy[d.strategyId] || 0) + 1;
231
+ }
232
+
233
+ const avgConfidence = decisions.length > 0
234
+ ? decisions.reduce((sum, d) => sum + d.confidence, 0) / decisions.length
235
+ : 0;
236
+
237
+ return {
238
+ totalDecisions: decisions.length,
239
+ executedCount: executed.length,
240
+ executionRate: decisions.length > 0 ? executed.length / decisions.length : 0,
241
+ byAction,
242
+ byStrategy,
243
+ avgConfidence,
244
+ };
245
+ }, [decisions]);
246
+
247
+ return {
248
+ decisions,
249
+ recentDecisions,
250
+ executedDecisions,
251
+ isLoading,
252
+ error,
253
+ refresh: fetchDecisions,
254
+ getByStrategy,
255
+ getByAction,
256
+ stats,
257
+ };
258
+ }
259
+
260
+ // ── Helper Functions ──────────────────────────────────────────────────────────
261
+
262
+ function mapDecision(raw: any): AIDecision {
263
+ return {
264
+ id: raw.id,
265
+ timestamp: raw.timestamp ?? Date.now(),
266
+ strategyId: raw.strategyId ?? raw.strategy_id,
267
+ strategyName: raw.strategyName ?? raw.strategy_name ?? '',
268
+ pair: raw.pair,
269
+ action: raw.action,
270
+ confidence: raw.confidence ?? 0,
271
+ reasoning: raw.reasoning ?? '',
272
+ indicators: raw.indicators ?? {},
273
+ signals: raw.signals ?? [],
274
+ executed: raw.executed ?? false,
275
+ positionId: raw.positionId ?? raw.position_id,
276
+ price: raw.price,
277
+ size: raw.size,
278
+ leverage: raw.leverage,
279
+ };
280
+ }
@@ -0,0 +1,349 @@
1
+ /**
2
+ * ONE SDK - AI Positions Hook
3
+ * Provides React hook for fetching and tracking AI trading positions
4
+ */
5
+
6
+ import { useState, useEffect, useCallback, useMemo } from 'react';
7
+ import type { AIPosition, PositionStatus } from '../types/console';
8
+
9
+ // ── Configuration ─────────────────────────────────────────────────────────────
10
+
11
+ let _consoleAccessToken: string | null = null;
12
+ let _consoleEngineUrl: string = 'https://api.one23.io';
13
+
14
+ export function setConsoleAccessToken(token: string): void {
15
+ _consoleAccessToken = token;
16
+ }
17
+
18
+ export function clearConsoleAccessToken(): void {
19
+ _consoleAccessToken = null;
20
+ }
21
+
22
+ export function setConsoleEngineUrl(url: string): void {
23
+ _consoleEngineUrl = url;
24
+ }
25
+
26
+ // ── Internal API Helper ───────────────────────────────────────────────────────
27
+
28
+ async function consoleApi<T>(path: string, options?: RequestInit): Promise<T | null> {
29
+ try {
30
+ const headers: Record<string, string> = {
31
+ 'Content-Type': 'application/json',
32
+ };
33
+ if (_consoleAccessToken) {
34
+ headers['Authorization'] = `Bearer ${_consoleAccessToken}`;
35
+ }
36
+ const res = await fetch(`${_consoleEngineUrl}${path}`, { ...options, headers });
37
+ if (!res.ok) return null;
38
+ const json = await res.json();
39
+ return json?.data ?? json;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ // ── Types ─────────────────────────────────────────────────────────────────────
46
+
47
+ export interface UseAIPositionsOptions {
48
+ strategyId?: string;
49
+ strategyIds?: string[];
50
+ status?: PositionStatus | PositionStatus[];
51
+ pollInterval?: number;
52
+ simulation?: boolean;
53
+ }
54
+
55
+ export interface UseAIPositionsResult {
56
+ positions: AIPosition[];
57
+ openPositions: AIPosition[];
58
+ closedPositions: AIPosition[];
59
+ isLoading: boolean;
60
+ error: string | null;
61
+ refresh: () => Promise<void>;
62
+ // Position actions
63
+ closePosition: (positionId: string) => Promise<boolean>;
64
+ updateStopLoss: (positionId: string, stopLoss: number) => Promise<boolean>;
65
+ updateTakeProfit: (positionId: string, takeProfit: number) => Promise<boolean>;
66
+ // Computed values
67
+ summary: {
68
+ totalPositions: number;
69
+ openCount: number;
70
+ totalExposure: number;
71
+ totalPnl: number;
72
+ unrealizedPnl: number;
73
+ avgLeverage: number;
74
+ byStrategy: Record<string, { count: number; pnl: number; exposure: number }>;
75
+ };
76
+ }
77
+
78
+ // ── Simulation Data Generator ─────────────────────────────────────────────────
79
+
80
+ function generateSimulatedPositions(strategyIds?: string[]): AIPosition[] {
81
+ const strategies = [
82
+ { id: 'balanced-01', name: 'Balanced Alpha', shortName: 'BAL' },
83
+ { id: 'conservative-01', name: 'Conservative Shield', shortName: 'CON' },
84
+ { id: 'aggressive-01', name: 'Aggressive Momentum', shortName: 'AGG' },
85
+ ];
86
+
87
+ const pairs = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'ARB/USDT', 'AVAX/USDT'];
88
+ const basePrices: Record<string, number> = {
89
+ 'BTC/USDT': 67500,
90
+ 'ETH/USDT': 3450,
91
+ 'SOL/USDT': 178,
92
+ 'ARB/USDT': 1.18,
93
+ 'AVAX/USDT': 38.5,
94
+ };
95
+
96
+ const positions: AIPosition[] = [];
97
+ const filteredStrategies = strategyIds
98
+ ? strategies.filter(s => strategyIds.includes(s.id))
99
+ : strategies;
100
+
101
+ for (const strategy of filteredStrategies) {
102
+ // Generate 0-3 positions per strategy
103
+ const numPositions = Math.floor(Math.random() * 4);
104
+ for (let i = 0; i < numPositions; i++) {
105
+ const pair = pairs[Math.floor(Math.random() * pairs.length)];
106
+ const basePrice = basePrices[pair];
107
+ const side = Math.random() > 0.5 ? 'LONG' : 'SHORT';
108
+ const entryPrice = basePrice * (1 + (Math.random() - 0.5) * 0.02);
109
+ const currentPrice = basePrice * (1 + (Math.random() - 0.5) * 0.03);
110
+ const leverage = Math.floor(Math.random() * 10) + 2;
111
+ const size = Math.floor(Math.random() * 1000) + 100;
112
+ const margin = size / leverage;
113
+
114
+ const priceDiff = side === 'LONG'
115
+ ? (currentPrice - entryPrice) / entryPrice
116
+ : (entryPrice - currentPrice) / entryPrice;
117
+ const pnlPercent = priceDiff * leverage * 100;
118
+ const pnl = size * priceDiff * leverage;
119
+
120
+ positions.push({
121
+ id: `pos_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
122
+ strategyId: strategy.id,
123
+ strategyName: strategy.name,
124
+ pair,
125
+ side,
126
+ entryPrice,
127
+ currentPrice,
128
+ size,
129
+ leverage,
130
+ margin,
131
+ pnl,
132
+ pnlPercent,
133
+ status: 'open',
134
+ stopLoss: entryPrice * (side === 'LONG' ? 0.95 : 1.05),
135
+ takeProfit: entryPrice * (side === 'LONG' ? 1.1 : 0.9),
136
+ liquidationPrice: entryPrice * (side === 'LONG' ? (1 - 1 / leverage) : (1 + 1 / leverage)),
137
+ openTime: Date.now() - Math.floor(Math.random() * 86400000),
138
+ chain: ['ethereum', 'arbitrum', 'base'][Math.floor(Math.random() * 3)],
139
+ aiConfidence: Math.random() * 0.4 + 0.5,
140
+ aiReasoning: `${side === 'LONG' ? 'Bullish' : 'Bearish'} momentum detected on ${pair}`,
141
+ });
142
+ }
143
+ }
144
+
145
+ return positions;
146
+ }
147
+
148
+ // ── Hook Implementation ───────────────────────────────────────────────────────
149
+
150
+ export function useAIPositions(options?: UseAIPositionsOptions): UseAIPositionsResult {
151
+ const [positions, setPositions] = useState<AIPosition[]>([]);
152
+ const [isLoading, setIsLoading] = useState(false);
153
+ const [error, setError] = useState<string | null>(null);
154
+
155
+ const fetchPositions = useCallback(async () => {
156
+ setIsLoading(true);
157
+ setError(null);
158
+
159
+ try {
160
+ if (options?.simulation) {
161
+ // Use simulated data
162
+ const simulated = generateSimulatedPositions(options?.strategyIds);
163
+ setPositions(simulated);
164
+ } else {
165
+ // Fetch from API
166
+ let path = '/api/v1/ai-quant/positions';
167
+ const params = new URLSearchParams();
168
+ if (options?.strategyId) {
169
+ params.append('strategyId', options.strategyId);
170
+ }
171
+ if (options?.strategyIds?.length) {
172
+ params.append('strategyIds', options.strategyIds.join(','));
173
+ }
174
+ if (options?.status) {
175
+ const statuses = Array.isArray(options.status) ? options.status : [options.status];
176
+ params.append('status', statuses.join(','));
177
+ }
178
+ if (params.toString()) {
179
+ path += `?${params.toString()}`;
180
+ }
181
+
182
+ const data = await consoleApi<{ positions: any[] }>(path);
183
+ if (data?.positions) {
184
+ setPositions(data.positions.map(mapPosition));
185
+ }
186
+ }
187
+ } catch (err) {
188
+ setError(err instanceof Error ? err.message : 'Failed to fetch positions');
189
+ } finally {
190
+ setIsLoading(false);
191
+ }
192
+ }, [options?.simulation, options?.strategyId, options?.strategyIds, options?.status]);
193
+
194
+ // Close position
195
+ const closePosition = useCallback(async (positionId: string): Promise<boolean> => {
196
+ try {
197
+ if (options?.simulation) {
198
+ setPositions(prev => prev.map(p =>
199
+ p.id === positionId ? { ...p, status: 'closed' as const, closeTime: Date.now() } : p
200
+ ));
201
+ return true;
202
+ }
203
+ await consoleApi(`/api/v1/ai-quant/positions/${positionId}/close`, { method: 'POST' });
204
+ setPositions(prev => prev.map(p =>
205
+ p.id === positionId ? { ...p, status: 'closed' as const, closeTime: Date.now() } : p
206
+ ));
207
+ return true;
208
+ } catch {
209
+ return false;
210
+ }
211
+ }, [options?.simulation]);
212
+
213
+ // Update stop loss
214
+ const updateStopLoss = useCallback(async (positionId: string, stopLoss: number): Promise<boolean> => {
215
+ try {
216
+ if (options?.simulation) {
217
+ setPositions(prev => prev.map(p =>
218
+ p.id === positionId ? { ...p, stopLoss } : p
219
+ ));
220
+ return true;
221
+ }
222
+ await consoleApi(`/api/v1/ai-quant/positions/${positionId}`, {
223
+ method: 'PATCH',
224
+ body: JSON.stringify({ stopLoss }),
225
+ });
226
+ setPositions(prev => prev.map(p =>
227
+ p.id === positionId ? { ...p, stopLoss } : p
228
+ ));
229
+ return true;
230
+ } catch {
231
+ return false;
232
+ }
233
+ }, [options?.simulation]);
234
+
235
+ // Update take profit
236
+ const updateTakeProfit = useCallback(async (positionId: string, takeProfit: number): Promise<boolean> => {
237
+ try {
238
+ if (options?.simulation) {
239
+ setPositions(prev => prev.map(p =>
240
+ p.id === positionId ? { ...p, takeProfit } : p
241
+ ));
242
+ return true;
243
+ }
244
+ await consoleApi(`/api/v1/ai-quant/positions/${positionId}`, {
245
+ method: 'PATCH',
246
+ body: JSON.stringify({ takeProfit }),
247
+ });
248
+ setPositions(prev => prev.map(p =>
249
+ p.id === positionId ? { ...p, takeProfit } : p
250
+ ));
251
+ return true;
252
+ } catch {
253
+ return false;
254
+ }
255
+ }, [options?.simulation]);
256
+
257
+ // Initial fetch and polling
258
+ useEffect(() => {
259
+ fetchPositions();
260
+ if (options?.pollInterval) {
261
+ const timer = setInterval(fetchPositions, options.pollInterval);
262
+ return () => clearInterval(timer);
263
+ }
264
+ }, [fetchPositions, options?.pollInterval]);
265
+
266
+ // Computed values
267
+ const openPositions = useMemo(() =>
268
+ positions.filter(p => p.status === 'open'),
269
+ [positions]
270
+ );
271
+
272
+ const closedPositions = useMemo(() =>
273
+ positions.filter(p => p.status === 'closed'),
274
+ [positions]
275
+ );
276
+
277
+ const summary = useMemo(() => {
278
+ const open = positions.filter(p => p.status === 'open');
279
+ const byStrategy: Record<string, { count: number; pnl: number; exposure: number }> = {};
280
+
281
+ for (const pos of open) {
282
+ if (!byStrategy[pos.strategyId]) {
283
+ byStrategy[pos.strategyId] = { count: 0, pnl: 0, exposure: 0 };
284
+ }
285
+ byStrategy[pos.strategyId].count++;
286
+ byStrategy[pos.strategyId].pnl += pos.pnl;
287
+ byStrategy[pos.strategyId].exposure += pos.size;
288
+ }
289
+
290
+ const totalExposure = open.reduce((sum, p) => sum + p.size, 0);
291
+ const totalPnl = positions.reduce((sum, p) => sum + p.pnl, 0);
292
+ const unrealizedPnl = open.reduce((sum, p) => sum + p.pnl, 0);
293
+ const avgLeverage = open.length > 0
294
+ ? open.reduce((sum, p) => sum + p.leverage, 0) / open.length
295
+ : 0;
296
+
297
+ return {
298
+ totalPositions: positions.length,
299
+ openCount: open.length,
300
+ totalExposure,
301
+ totalPnl,
302
+ unrealizedPnl,
303
+ avgLeverage,
304
+ byStrategy,
305
+ };
306
+ }, [positions]);
307
+
308
+ return {
309
+ positions,
310
+ openPositions,
311
+ closedPositions,
312
+ isLoading,
313
+ error,
314
+ refresh: fetchPositions,
315
+ closePosition,
316
+ updateStopLoss,
317
+ updateTakeProfit,
318
+ summary,
319
+ };
320
+ }
321
+
322
+ // ── Helper Functions ──────────────────────────────────────────────────────────
323
+
324
+ function mapPosition(raw: any): AIPosition {
325
+ return {
326
+ id: raw.id,
327
+ strategyId: raw.strategyId ?? raw.strategy_id,
328
+ strategyName: raw.strategyName ?? raw.strategy_name ?? '',
329
+ pair: raw.pair,
330
+ side: raw.side,
331
+ entryPrice: raw.entryPrice ?? raw.entry_price ?? 0,
332
+ currentPrice: raw.currentPrice ?? raw.current_price ?? 0,
333
+ size: raw.size ?? 0,
334
+ leverage: raw.leverage ?? 1,
335
+ margin: raw.margin ?? raw.size / (raw.leverage ?? 1),
336
+ pnl: raw.pnl ?? 0,
337
+ pnlPercent: raw.pnlPercent ?? raw.pnl_percent ?? 0,
338
+ status: raw.status ?? 'open',
339
+ stopLoss: raw.stopLoss ?? raw.stop_loss,
340
+ takeProfit: raw.takeProfit ?? raw.take_profit,
341
+ liquidationPrice: raw.liquidationPrice ?? raw.liquidation_price,
342
+ openTime: raw.openTime ?? raw.open_time ?? Date.now(),
343
+ closeTime: raw.closeTime ?? raw.close_time,
344
+ chain: raw.chain,
345
+ orderId: raw.orderId ?? raw.order_id,
346
+ aiConfidence: raw.aiConfidence ?? raw.ai_confidence,
347
+ aiReasoning: raw.aiReasoning ?? raw.ai_reasoning,
348
+ };
349
+ }