hedgequantx 2.9.20 → 2.9.22
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/package.json +1 -1
- package/src/app.js +64 -42
- package/src/menus/connect.js +17 -14
- package/src/menus/dashboard.js +76 -58
- package/src/pages/accounts.js +49 -38
- package/src/pages/ai-agents-ui.js +388 -0
- package/src/pages/ai-agents.js +494 -0
- package/src/pages/ai-models.js +389 -0
- package/src/pages/algo/algo-executor.js +307 -0
- package/src/pages/algo/copy-executor.js +331 -0
- package/src/pages/algo/copy-trading.js +178 -546
- package/src/pages/algo/custom-strategy.js +313 -0
- package/src/pages/algo/index.js +75 -18
- package/src/pages/algo/one-account.js +57 -322
- package/src/pages/algo/ui.js +15 -15
- package/src/pages/orders.js +22 -19
- package/src/pages/positions.js +22 -19
- package/src/pages/stats/index.js +16 -15
- package/src/pages/user.js +11 -7
- package/src/services/ai-supervision/consensus.js +284 -0
- package/src/services/ai-supervision/context.js +275 -0
- package/src/services/ai-supervision/directive.js +167 -0
- package/src/services/ai-supervision/health.js +47 -35
- package/src/services/ai-supervision/index.js +359 -0
- package/src/services/ai-supervision/parser.js +278 -0
- package/src/services/ai-supervision/symbols.js +259 -0
- package/src/services/cliproxy/index.js +256 -0
- package/src/services/cliproxy/installer.js +111 -0
- package/src/services/cliproxy/manager.js +387 -0
- package/src/services/index.js +9 -1
- package/src/services/llmproxy/index.js +166 -0
- package/src/services/llmproxy/manager.js +411 -0
- package/src/services/rithmic/accounts.js +6 -8
- package/src/ui/box.js +5 -9
- package/src/ui/index.js +18 -5
- package/src/ui/menu.js +4 -4
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Builder for AI Supervision
|
|
3
|
+
*
|
|
4
|
+
* Builds the market context from real Rithmic data
|
|
5
|
+
* to send to AI agents for signal analysis.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { getSymbol, getCurrentSession, isGoodSessionForSymbol } = require('./symbols');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build DOM (Depth of Market) summary from raw data
|
|
12
|
+
*/
|
|
13
|
+
const buildDOMSummary = (domData) => {
|
|
14
|
+
if (!domData || !domData.bids || !domData.asks) {
|
|
15
|
+
return { available: false };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const bids = domData.bids.slice(0, 10);
|
|
19
|
+
const asks = domData.asks.slice(0, 10);
|
|
20
|
+
|
|
21
|
+
const totalBidSize = bids.reduce((sum, b) => sum + (b.size || 0), 0);
|
|
22
|
+
const totalAskSize = asks.reduce((sum, a) => sum + (a.size || 0), 0);
|
|
23
|
+
const imbalance = totalBidSize - totalAskSize;
|
|
24
|
+
const imbalanceRatio = totalAskSize > 0 ? totalBidSize / totalAskSize : 1;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
available: true,
|
|
28
|
+
topBid: bids[0]?.price || null,
|
|
29
|
+
topAsk: asks[0]?.price || null,
|
|
30
|
+
spread: asks[0] && bids[0] ? asks[0].price - bids[0].price : null,
|
|
31
|
+
totalBidSize,
|
|
32
|
+
totalAskSize,
|
|
33
|
+
imbalance,
|
|
34
|
+
imbalanceRatio: Math.round(imbalanceRatio * 100) / 100,
|
|
35
|
+
bidLevels: bids.length,
|
|
36
|
+
askLevels: asks.length,
|
|
37
|
+
dominantSide: imbalance > 0 ? 'buyers' : imbalance < 0 ? 'sellers' : 'neutral'
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build Order Flow summary from recent ticks
|
|
43
|
+
*/
|
|
44
|
+
const buildOrderFlowSummary = (recentTicks, windowSize = 50) => {
|
|
45
|
+
if (!recentTicks || recentTicks.length === 0) {
|
|
46
|
+
return { available: false };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const ticks = recentTicks.slice(-windowSize);
|
|
50
|
+
|
|
51
|
+
let buyVolume = 0;
|
|
52
|
+
let sellVolume = 0;
|
|
53
|
+
let totalVolume = 0;
|
|
54
|
+
let highPrice = -Infinity;
|
|
55
|
+
let lowPrice = Infinity;
|
|
56
|
+
|
|
57
|
+
for (const tick of ticks) {
|
|
58
|
+
const vol = tick.volume || 1;
|
|
59
|
+
totalVolume += vol;
|
|
60
|
+
|
|
61
|
+
if (tick.side === 'buy' || tick.lastTradeSide === 'buy') {
|
|
62
|
+
buyVolume += vol;
|
|
63
|
+
} else if (tick.side === 'sell' || tick.lastTradeSide === 'sell') {
|
|
64
|
+
sellVolume += vol;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (tick.price > highPrice) highPrice = tick.price;
|
|
68
|
+
if (tick.price < lowPrice) lowPrice = tick.price;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const delta = buyVolume - sellVolume;
|
|
72
|
+
const deltaPercent = totalVolume > 0 ? (delta / totalVolume) * 100 : 0;
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
available: true,
|
|
76
|
+
tickCount: ticks.length,
|
|
77
|
+
totalVolume,
|
|
78
|
+
buyVolume,
|
|
79
|
+
sellVolume,
|
|
80
|
+
delta,
|
|
81
|
+
deltaPercent: Math.round(deltaPercent),
|
|
82
|
+
highPrice: highPrice === -Infinity ? null : highPrice,
|
|
83
|
+
lowPrice: lowPrice === Infinity ? null : lowPrice,
|
|
84
|
+
range: highPrice !== -Infinity && lowPrice !== Infinity ? highPrice - lowPrice : null,
|
|
85
|
+
lastPrice: ticks[ticks.length - 1]?.price || null,
|
|
86
|
+
trend: delta > 0 ? 'bullish' : delta < 0 ? 'bearish' : 'neutral'
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Build trade history summary from recent signals/trades
|
|
92
|
+
*/
|
|
93
|
+
const buildTradeHistory = (recentSignals, recentTrades) => {
|
|
94
|
+
const signals = recentSignals || [];
|
|
95
|
+
const trades = recentTrades || [];
|
|
96
|
+
|
|
97
|
+
const wins = trades.filter(t => t.pnl > 0).length;
|
|
98
|
+
const losses = trades.filter(t => t.pnl < 0).length;
|
|
99
|
+
const totalTrades = wins + losses;
|
|
100
|
+
const winRate = totalTrades > 0 ? (wins / totalTrades) * 100 : 0;
|
|
101
|
+
|
|
102
|
+
const totalPnL = trades.reduce((sum, t) => sum + (t.pnl || 0), 0);
|
|
103
|
+
const avgWin = wins > 0 ? trades.filter(t => t.pnl > 0).reduce((s, t) => s + t.pnl, 0) / wins : 0;
|
|
104
|
+
const avgLoss = losses > 0 ? Math.abs(trades.filter(t => t.pnl < 0).reduce((s, t) => s + t.pnl, 0) / losses) : 0;
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
recentSignals: signals.length,
|
|
108
|
+
totalTrades,
|
|
109
|
+
wins,
|
|
110
|
+
losses,
|
|
111
|
+
winRate: Math.round(winRate),
|
|
112
|
+
totalPnL: Math.round(totalPnL * 100) / 100,
|
|
113
|
+
avgWin: Math.round(avgWin * 100) / 100,
|
|
114
|
+
avgLoss: Math.round(avgLoss * 100) / 100,
|
|
115
|
+
profitFactor: avgLoss > 0 ? Math.round((avgWin / avgLoss) * 100) / 100 : 0
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Build current position info
|
|
121
|
+
*/
|
|
122
|
+
const buildPositionInfo = (position, currentPrice) => {
|
|
123
|
+
if (!position || position.quantity === 0) {
|
|
124
|
+
return { hasPosition: false, quantity: 0 };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const qty = position.quantity;
|
|
128
|
+
const entryPrice = position.averagePrice || position.entryPrice;
|
|
129
|
+
const unrealizedPnL = position.profitAndLoss || 0;
|
|
130
|
+
const side = qty > 0 ? 'long' : 'short';
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
hasPosition: true,
|
|
134
|
+
side,
|
|
135
|
+
quantity: Math.abs(qty),
|
|
136
|
+
entryPrice,
|
|
137
|
+
currentPrice,
|
|
138
|
+
unrealizedPnL: Math.round(unrealizedPnL * 100) / 100,
|
|
139
|
+
ticksInProfit: entryPrice && currentPrice
|
|
140
|
+
? Math.round((side === 'long' ? currentPrice - entryPrice : entryPrice - currentPrice) * 4)
|
|
141
|
+
: 0
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Build complete market context for AI analysis
|
|
147
|
+
*/
|
|
148
|
+
const buildMarketContext = ({
|
|
149
|
+
symbolId,
|
|
150
|
+
signal,
|
|
151
|
+
recentTicks = [],
|
|
152
|
+
recentSignals = [],
|
|
153
|
+
recentTrades = [],
|
|
154
|
+
domData = null,
|
|
155
|
+
position = null,
|
|
156
|
+
stats = {},
|
|
157
|
+
config = {}
|
|
158
|
+
}) => {
|
|
159
|
+
const symbol = getSymbol(symbolId);
|
|
160
|
+
const session = getCurrentSession();
|
|
161
|
+
const sessionCheck = isGoodSessionForSymbol(symbolId);
|
|
162
|
+
const orderFlow = buildOrderFlowSummary(recentTicks);
|
|
163
|
+
const dom = buildDOMSummary(domData);
|
|
164
|
+
const history = buildTradeHistory(recentSignals, recentTrades);
|
|
165
|
+
const positionInfo = buildPositionInfo(position, orderFlow.lastPrice);
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
timestamp: new Date().toISOString(),
|
|
169
|
+
|
|
170
|
+
// Symbol info
|
|
171
|
+
symbol: {
|
|
172
|
+
id: symbolId,
|
|
173
|
+
name: symbol?.name || symbolId,
|
|
174
|
+
tickSize: symbol?.tickSize || 0.25,
|
|
175
|
+
tickValue: symbol?.tickValue || 12.50,
|
|
176
|
+
characteristics: symbol?.characteristics || {},
|
|
177
|
+
correlations: symbol?.correlations || {}
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
// Current session
|
|
181
|
+
session: {
|
|
182
|
+
name: session.name,
|
|
183
|
+
description: session.description,
|
|
184
|
+
isGoodTime: sessionCheck.good,
|
|
185
|
+
sessionNote: sessionCheck.reason
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
// The signal to analyze
|
|
189
|
+
signal: {
|
|
190
|
+
direction: signal.direction,
|
|
191
|
+
entry: signal.entry,
|
|
192
|
+
stopLoss: signal.stopLoss,
|
|
193
|
+
takeProfit: signal.takeProfit,
|
|
194
|
+
confidence: signal.confidence,
|
|
195
|
+
pattern: signal.pattern || 'unknown',
|
|
196
|
+
timestamp: signal.timestamp || Date.now()
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
// Market data
|
|
200
|
+
orderFlow,
|
|
201
|
+
dom,
|
|
202
|
+
|
|
203
|
+
// Position and history
|
|
204
|
+
position: positionInfo,
|
|
205
|
+
history,
|
|
206
|
+
|
|
207
|
+
// Session stats
|
|
208
|
+
sessionStats: {
|
|
209
|
+
pnl: stats.pnl || 0,
|
|
210
|
+
trades: stats.trades || 0,
|
|
211
|
+
wins: stats.wins || 0,
|
|
212
|
+
losses: stats.losses || 0,
|
|
213
|
+
target: config.dailyTarget || stats.target || 500,
|
|
214
|
+
maxRisk: config.maxRisk || stats.risk || 300,
|
|
215
|
+
progressToTarget: stats.pnl && config.dailyTarget
|
|
216
|
+
? Math.round((stats.pnl / config.dailyTarget) * 100)
|
|
217
|
+
: 0
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Format context as a string for AI prompt
|
|
224
|
+
*/
|
|
225
|
+
const formatContextForPrompt = (context) => {
|
|
226
|
+
return `
|
|
227
|
+
## MARKET CONTEXT
|
|
228
|
+
|
|
229
|
+
**Symbol**: ${context.symbol.name} (${context.symbol.id})
|
|
230
|
+
**Tick Size**: ${context.symbol.tickSize} | **Tick Value**: $${context.symbol.tickValue}
|
|
231
|
+
**Session**: ${context.session.description} ${context.session.isGoodTime ? '✓' : '⚠'}
|
|
232
|
+
|
|
233
|
+
### SIGNAL TO ANALYZE
|
|
234
|
+
- Direction: ${context.signal.direction.toUpperCase()}
|
|
235
|
+
- Entry: ${context.signal.entry}
|
|
236
|
+
- Stop Loss: ${context.signal.stopLoss}
|
|
237
|
+
- Take Profit: ${context.signal.takeProfit}
|
|
238
|
+
- Strategy Confidence: ${Math.round(context.signal.confidence * 100)}%
|
|
239
|
+
|
|
240
|
+
### ORDER FLOW (Last ${context.orderFlow.tickCount || 0} ticks)
|
|
241
|
+
- Delta: ${context.orderFlow.delta || 0} (${context.orderFlow.deltaPercent || 0}%)
|
|
242
|
+
- Buy Volume: ${context.orderFlow.buyVolume || 0}
|
|
243
|
+
- Sell Volume: ${context.orderFlow.sellVolume || 0}
|
|
244
|
+
- Trend: ${context.orderFlow.trend || 'unknown'}
|
|
245
|
+
- Range: ${context.orderFlow.range?.toFixed(2) || 'N/A'}
|
|
246
|
+
|
|
247
|
+
### DOM ANALYSIS
|
|
248
|
+
${context.dom.available
|
|
249
|
+
? `- Spread: ${context.dom.spread?.toFixed(2) || 'N/A'}
|
|
250
|
+
- Bid Size: ${context.dom.totalBidSize} | Ask Size: ${context.dom.totalAskSize}
|
|
251
|
+
- Imbalance Ratio: ${context.dom.imbalanceRatio}x
|
|
252
|
+
- Dominant Side: ${context.dom.dominantSide}`
|
|
253
|
+
: '- DOM data not available'}
|
|
254
|
+
|
|
255
|
+
### POSITION
|
|
256
|
+
${context.position.hasPosition
|
|
257
|
+
? `- ${context.position.side.toUpperCase()} ${context.position.quantity}x @ ${context.position.entryPrice}
|
|
258
|
+
- Unrealized P&L: $${context.position.unrealizedPnL}`
|
|
259
|
+
: '- No open position'}
|
|
260
|
+
|
|
261
|
+
### SESSION PERFORMANCE
|
|
262
|
+
- P&L: $${context.sessionStats.pnl} / $${context.sessionStats.target} target
|
|
263
|
+
- Trades: ${context.sessionStats.trades} (W: ${context.sessionStats.wins} / L: ${context.sessionStats.losses})
|
|
264
|
+
- Progress: ${context.sessionStats.progressToTarget}%
|
|
265
|
+
`;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
module.exports = {
|
|
269
|
+
buildMarketContext,
|
|
270
|
+
formatContextForPrompt,
|
|
271
|
+
buildDOMSummary,
|
|
272
|
+
buildOrderFlowSummary,
|
|
273
|
+
buildTradeHistory,
|
|
274
|
+
buildPositionInfo
|
|
275
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Supervision Directive - The 7 Phases
|
|
3
|
+
*
|
|
4
|
+
* This directive is sent to ALL AI agents for trading supervision.
|
|
5
|
+
* Each agent receives the same instructions and analyzes signals in parallel.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const DIRECTIVE_PHASES = {
|
|
9
|
+
phase1_strategy: `
|
|
10
|
+
## PHASE 1: MAÎTRISER LA STRATÉGIE
|
|
11
|
+
Tu connais cette stratégie par coeur. Tu sais exactement:
|
|
12
|
+
- Les conditions d'entrée (setup patterns, confirmations requises)
|
|
13
|
+
- Les règles de sortie (targets, stops, trailing)
|
|
14
|
+
- La gestion du risque (sizing, max loss, drawdown limits)
|
|
15
|
+
- Les filtres de marché (sessions, volatilité, corrélations)
|
|
16
|
+
Tu ne devines jamais. Tu appliques la stratégie avec précision chirurgicale.
|
|
17
|
+
`,
|
|
18
|
+
|
|
19
|
+
phase2_role: `
|
|
20
|
+
## PHASE 2: TON RÔLE
|
|
21
|
+
Tu es une arme de précision intégrée à l'algo de trading.
|
|
22
|
+
Tu n'es PAS un conseiller - tu es un avantage décisif.
|
|
23
|
+
Ton job: optimiser chaque signal pour maximiser le edge.
|
|
24
|
+
Tu travailles EN TEMPS RÉEL avec latence minimale.
|
|
25
|
+
Chaque milliseconde compte. Chaque tick compte.
|
|
26
|
+
`,
|
|
27
|
+
|
|
28
|
+
phase3_analysis: `
|
|
29
|
+
## PHASE 3: CE QUE TU ANALYSES
|
|
30
|
+
Pour chaque signal reçu, tu analyses:
|
|
31
|
+
- DOM (Depth of Market): déséquilibres bid/ask, absorption, spoofing
|
|
32
|
+
- Order Flow: delta, CVD, volume profile, footprint
|
|
33
|
+
- Manipulations: stop hunts, fakeouts, liquidity grabs
|
|
34
|
+
- Patterns: structure de marché, S/R, pivots, VWAP
|
|
35
|
+
- Historique: trades récents, winrate par setup, performance horaire
|
|
36
|
+
- Corrélations: autres symboles liés (ES/NQ, GC/SI, etc.)
|
|
37
|
+
`,
|
|
38
|
+
|
|
39
|
+
phase4_output: `
|
|
40
|
+
## PHASE 4: CE QUE TU FOURNIS
|
|
41
|
+
Pour chaque signal, tu retournes:
|
|
42
|
+
1. DECISION: approve/reject/modify
|
|
43
|
+
2. CONFIDENCE: score 0-100
|
|
44
|
+
3. OPTIMIZATIONS (si approve/modify):
|
|
45
|
+
- entry: prix d'entrée optimisé (ou null)
|
|
46
|
+
- stopLoss: stop optimisé (ou null)
|
|
47
|
+
- takeProfit: target optimisé (ou null)
|
|
48
|
+
- size: ajustement de taille (-50% à +50%)
|
|
49
|
+
- timing: "now" | "wait" | "cancel"
|
|
50
|
+
4. REASON: explication courte (max 50 chars)
|
|
51
|
+
5. ALERTS: warnings importants (optionnel)
|
|
52
|
+
`,
|
|
53
|
+
|
|
54
|
+
phase5_restrictions: `
|
|
55
|
+
## PHASE 5: CE QUE TU NE FAIS JAMAIS
|
|
56
|
+
- Tu ne BLOQUES jamais l'algo sans raison valide
|
|
57
|
+
- Tu ne RALENTIS jamais l'exécution (réponse < 2 secondes)
|
|
58
|
+
- Tu ne fais pas de VAGUE - décision claire et directe
|
|
59
|
+
- Tu n'INVENTES pas de données - utilise uniquement ce qui est fourni
|
|
60
|
+
- Tu ne CHANGES pas la stratégie - tu l'optimises dans ses règles
|
|
61
|
+
`,
|
|
62
|
+
|
|
63
|
+
phase6_symbols: `
|
|
64
|
+
## PHASE 6: CONNAISSANCE DES SYMBOLES
|
|
65
|
+
Tu trades ces symboles avec leurs caractéristiques:
|
|
66
|
+
- NQ (Nasdaq): volatile, tech-driven, corrélé ES
|
|
67
|
+
- ES (S&P500): référence, plus stable, leader
|
|
68
|
+
- YM (Dow): value stocks, moins volatile
|
|
69
|
+
- RTY (Russell): small caps, plus volatile que ES
|
|
70
|
+
- GC (Gold): safe haven, inverse USD, sessions Asia/London
|
|
71
|
+
- SI (Silver): suit GC avec plus de volatilité
|
|
72
|
+
- CL (Crude): news-driven, inventories, géopolitique
|
|
73
|
+
|
|
74
|
+
Sessions importantes:
|
|
75
|
+
- Asia: 18:00-03:00 ET (GC/SI actifs)
|
|
76
|
+
- London: 03:00-08:00 ET (préparation US)
|
|
77
|
+
- US Open: 09:30-11:30 ET (max volatilité)
|
|
78
|
+
- US Close: 15:00-16:00 ET (rebalancing)
|
|
79
|
+
`,
|
|
80
|
+
|
|
81
|
+
phase7_mindset: `
|
|
82
|
+
## PHASE 7: TA MENTALITÉ
|
|
83
|
+
- OBJECTIF: Gagner. Pas "essayer". Gagner.
|
|
84
|
+
- PRÉCISION: Chaque décision compte
|
|
85
|
+
- RAPIDITÉ: Temps = argent. Sois rapide.
|
|
86
|
+
- RESPONSABILITÉ: Tu assumes tes recommandations
|
|
87
|
+
- ADAPTATION: Le marché change, tu t'adaptes
|
|
88
|
+
- DISCIPLINE: Les règles sont les règles
|
|
89
|
+
`
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Build the complete directive string
|
|
94
|
+
*/
|
|
95
|
+
const buildDirective = () => {
|
|
96
|
+
return Object.values(DIRECTIVE_PHASES).join('\n');
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Expected output format from AI agents
|
|
101
|
+
*/
|
|
102
|
+
const OUTPUT_FORMAT = {
|
|
103
|
+
schema: {
|
|
104
|
+
decision: 'approve | reject | modify',
|
|
105
|
+
confidence: 'number 0-100',
|
|
106
|
+
optimizations: {
|
|
107
|
+
entry: 'number | null',
|
|
108
|
+
stopLoss: 'number | null',
|
|
109
|
+
takeProfit: 'number | null',
|
|
110
|
+
size: 'number (-0.5 to 0.5) | null',
|
|
111
|
+
timing: 'now | wait | cancel'
|
|
112
|
+
},
|
|
113
|
+
reason: 'string (max 50 chars)',
|
|
114
|
+
alerts: 'string[] | null'
|
|
115
|
+
},
|
|
116
|
+
example: {
|
|
117
|
+
decision: 'modify',
|
|
118
|
+
confidence: 85,
|
|
119
|
+
optimizations: {
|
|
120
|
+
entry: 21450.25,
|
|
121
|
+
stopLoss: 21445.00,
|
|
122
|
+
takeProfit: 21462.50,
|
|
123
|
+
size: 0,
|
|
124
|
+
timing: 'now'
|
|
125
|
+
},
|
|
126
|
+
reason: 'Strong bid stack, tighten stop',
|
|
127
|
+
alerts: null
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Build the output format instructions
|
|
133
|
+
*/
|
|
134
|
+
const buildOutputInstructions = () => {
|
|
135
|
+
return `
|
|
136
|
+
## OUTPUT FORMAT (JSON STRICT)
|
|
137
|
+
Tu dois TOUJOURS répondre en JSON valide avec ce format exact:
|
|
138
|
+
|
|
139
|
+
\`\`\`json
|
|
140
|
+
${JSON.stringify(OUTPUT_FORMAT.example, null, 2)}
|
|
141
|
+
\`\`\`
|
|
142
|
+
|
|
143
|
+
IMPORTANT:
|
|
144
|
+
- decision: "approve" (exécuter tel quel), "reject" (ne pas exécuter), "modify" (exécuter avec optimisations)
|
|
145
|
+
- confidence: 0-100, ton niveau de confiance dans la décision
|
|
146
|
+
- optimizations: null si decision="reject", sinon les ajustements
|
|
147
|
+
- size: 0 = garder la taille, -0.5 = réduire de 50%, +0.5 = augmenter de 50%
|
|
148
|
+
- timing: "now" = exécuter immédiatement, "wait" = attendre meilleur prix, "cancel" = annuler
|
|
149
|
+
- reason: TOUJOURS fournir une raison courte
|
|
150
|
+
- Pas de texte avant ou après le JSON
|
|
151
|
+
`;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get the complete directive with output format
|
|
156
|
+
*/
|
|
157
|
+
const getFullDirective = () => {
|
|
158
|
+
return buildDirective() + '\n' + buildOutputInstructions();
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
module.exports = {
|
|
162
|
+
DIRECTIVE_PHASES,
|
|
163
|
+
OUTPUT_FORMAT,
|
|
164
|
+
buildDirective,
|
|
165
|
+
buildOutputInstructions,
|
|
166
|
+
getFullDirective
|
|
167
|
+
};
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const cliproxy = require('../cliproxy');
|
|
13
|
-
const
|
|
13
|
+
const https = require('https');
|
|
14
14
|
|
|
15
15
|
/** Test prompt to verify agent understands directive format */
|
|
16
16
|
const TEST_PROMPT = `You are being tested. Respond ONLY with this exact JSON, nothing else:
|
|
@@ -161,7 +161,6 @@ const testAgentConnection = async (agent) => {
|
|
|
161
161
|
|
|
162
162
|
/**
|
|
163
163
|
* Validate that response matches expected JSON format
|
|
164
|
-
* Uses robust extractJSON from parser.js to handle MiniMax <think> tags and other edge cases
|
|
165
164
|
* @param {string} content - Response content from agent
|
|
166
165
|
* @returns {Object} { valid, error }
|
|
167
166
|
*/
|
|
@@ -170,40 +169,53 @@ const validateResponseFormat = (content) => {
|
|
|
170
169
|
return { valid: false, error: 'Empty response' };
|
|
171
170
|
}
|
|
172
171
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
172
|
+
try {
|
|
173
|
+
// Try to extract JSON from response
|
|
174
|
+
let json;
|
|
175
|
+
|
|
176
|
+
// Direct parse
|
|
177
|
+
try {
|
|
178
|
+
json = JSON.parse(content.trim());
|
|
179
|
+
} catch (e) {
|
|
180
|
+
// Try to find JSON in response
|
|
181
|
+
const match = content.match(/\{[\s\S]*\}/);
|
|
182
|
+
if (match) {
|
|
183
|
+
json = JSON.parse(match[0]);
|
|
184
|
+
} else {
|
|
185
|
+
return { valid: false, error: 'No JSON in response' };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check required fields
|
|
190
|
+
if (!json.decision) {
|
|
191
|
+
return { valid: false, error: 'Missing "decision" field' };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (json.confidence === undefined) {
|
|
195
|
+
return { valid: false, error: 'Missing "confidence" field' };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!json.reason) {
|
|
199
|
+
return { valid: false, error: 'Missing "reason" field' };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Validate decision value
|
|
203
|
+
const validDecisions = ['approve', 'reject', 'modify'];
|
|
204
|
+
if (!validDecisions.includes(json.decision)) {
|
|
205
|
+
return { valid: false, error: `Invalid decision: ${json.decision}` };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Validate confidence is number 0-100
|
|
209
|
+
const conf = Number(json.confidence);
|
|
210
|
+
if (isNaN(conf) || conf < 0 || conf > 100) {
|
|
211
|
+
return { valid: false, error: `Invalid confidence: ${json.confidence}` };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { valid: true, error: null };
|
|
215
|
+
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return { valid: false, error: `Parse error: ${error.message}` };
|
|
204
218
|
}
|
|
205
|
-
|
|
206
|
-
return { valid: true, error: null };
|
|
207
219
|
};
|
|
208
220
|
|
|
209
221
|
/**
|