@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.
- 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 -140
- package/dist/react-native.d.ts +8 -140
- package/dist/react-native.js +2527 -0
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +2497 -2
- 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/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/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/src/hooks/index.ts
CHANGED
|
@@ -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
|
+
}
|