hedgequantx 2.9.18 → 2.9.20
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 +42 -64
- package/src/lib/m/hqx-2b.js +7 -0
- package/src/lib/m/index.js +138 -0
- package/src/lib/m/ultra-scalping.js +7 -0
- package/src/menus/connect.js +14 -17
- package/src/menus/dashboard.js +58 -76
- package/src/pages/accounts.js +38 -49
- package/src/pages/algo/copy-trading.js +546 -178
- package/src/pages/algo/index.js +18 -75
- package/src/pages/algo/one-account.js +322 -57
- package/src/pages/algo/ui.js +15 -15
- package/src/pages/orders.js +19 -22
- package/src/pages/positions.js +19 -22
- package/src/pages/stats/index.js +15 -16
- package/src/pages/user.js +7 -11
- package/src/services/ai-supervision/health.js +35 -47
- package/src/services/index.js +1 -9
- package/src/services/rithmic/accounts.js +8 -6
- package/src/ui/box.js +9 -5
- package/src/ui/index.js +5 -18
- package/src/ui/menu.js +4 -4
- package/src/pages/ai-agents-ui.js +0 -388
- package/src/pages/ai-agents.js +0 -494
- package/src/pages/ai-models.js +0 -389
- package/src/pages/algo/algo-executor.js +0 -307
- package/src/pages/algo/copy-executor.js +0 -331
- package/src/pages/algo/custom-strategy.js +0 -313
- package/src/services/ai-supervision/consensus.js +0 -284
- package/src/services/ai-supervision/context.js +0 -275
- package/src/services/ai-supervision/directive.js +0 -167
- package/src/services/ai-supervision/index.js +0 -309
- package/src/services/ai-supervision/parser.js +0 -278
- package/src/services/ai-supervision/symbols.js +0 -259
- package/src/services/cliproxy/index.js +0 -256
- package/src/services/cliproxy/installer.js +0 -111
- package/src/services/cliproxy/manager.js +0 -387
- package/src/services/llmproxy/index.js +0 -166
- package/src/services/llmproxy/manager.js +0 -411
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Consensus Calculator for Multi-Agent Supervision
|
|
3
|
-
*
|
|
4
|
-
* Calculates weighted consensus from multiple AI agent responses.
|
|
5
|
-
* Each agent has a weight, and the final decision is based on
|
|
6
|
-
* the weighted average of all responses.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Default consensus when no valid responses
|
|
11
|
-
*/
|
|
12
|
-
const DEFAULT_CONSENSUS = {
|
|
13
|
-
decision: 'approve',
|
|
14
|
-
confidence: 50,
|
|
15
|
-
optimizations: null,
|
|
16
|
-
reason: 'No consensus - default approve',
|
|
17
|
-
alerts: [],
|
|
18
|
-
agentCount: 0,
|
|
19
|
-
respondedCount: 0,
|
|
20
|
-
unanimous: false
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Calculate weighted average of a numeric field
|
|
25
|
-
*/
|
|
26
|
-
const weightedAverage = (values, weights) => {
|
|
27
|
-
if (values.length === 0) return 0;
|
|
28
|
-
|
|
29
|
-
let sum = 0;
|
|
30
|
-
let totalWeight = 0;
|
|
31
|
-
|
|
32
|
-
for (let i = 0; i < values.length; i++) {
|
|
33
|
-
const val = values[i];
|
|
34
|
-
const weight = weights[i] || 1;
|
|
35
|
-
if (val !== null && val !== undefined && !isNaN(val)) {
|
|
36
|
-
sum += val * weight;
|
|
37
|
-
totalWeight += weight;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return totalWeight > 0 ? sum / totalWeight : 0;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Calculate weighted mode (most common value by weight)
|
|
46
|
-
*/
|
|
47
|
-
const weightedMode = (values, weights) => {
|
|
48
|
-
if (values.length === 0) return null;
|
|
49
|
-
|
|
50
|
-
const weightMap = {};
|
|
51
|
-
|
|
52
|
-
for (let i = 0; i < values.length; i++) {
|
|
53
|
-
const val = values[i];
|
|
54
|
-
const weight = weights[i] || 1;
|
|
55
|
-
if (val !== null && val !== undefined) {
|
|
56
|
-
weightMap[val] = (weightMap[val] || 0) + weight;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let maxWeight = 0;
|
|
61
|
-
let mode = null;
|
|
62
|
-
|
|
63
|
-
for (const [val, w] of Object.entries(weightMap)) {
|
|
64
|
-
if (w > maxWeight) {
|
|
65
|
-
maxWeight = w;
|
|
66
|
-
mode = val;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return mode;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Merge optimizations from multiple agents
|
|
75
|
-
*/
|
|
76
|
-
const mergeOptimizations = (responses, weights) => {
|
|
77
|
-
const validOpts = responses
|
|
78
|
-
.map((r, i) => ({ opt: r.optimizations, weight: weights[i] }))
|
|
79
|
-
.filter(o => o.opt !== null);
|
|
80
|
-
|
|
81
|
-
if (validOpts.length === 0) return null;
|
|
82
|
-
|
|
83
|
-
// Collect values for each field
|
|
84
|
-
const entries = validOpts.filter(o => o.opt.entry !== null).map(o => ({ val: o.opt.entry, w: o.weight }));
|
|
85
|
-
const stops = validOpts.filter(o => o.opt.stopLoss !== null).map(o => ({ val: o.opt.stopLoss, w: o.weight }));
|
|
86
|
-
const targets = validOpts.filter(o => o.opt.takeProfit !== null).map(o => ({ val: o.opt.takeProfit, w: o.weight }));
|
|
87
|
-
const sizes = validOpts.filter(o => o.opt.size !== null).map(o => ({ val: o.opt.size, w: o.weight }));
|
|
88
|
-
const timings = validOpts.map(o => ({ val: o.opt.timing, w: o.weight }));
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
entry: entries.length > 0
|
|
92
|
-
? Math.round(weightedAverage(entries.map(e => e.val), entries.map(e => e.w)) * 100) / 100
|
|
93
|
-
: null,
|
|
94
|
-
stopLoss: stops.length > 0
|
|
95
|
-
? Math.round(weightedAverage(stops.map(s => s.val), stops.map(s => s.w)) * 100) / 100
|
|
96
|
-
: null,
|
|
97
|
-
takeProfit: targets.length > 0
|
|
98
|
-
? Math.round(weightedAverage(targets.map(t => t.val), targets.map(t => t.w)) * 100) / 100
|
|
99
|
-
: null,
|
|
100
|
-
size: sizes.length > 0
|
|
101
|
-
? Math.round(weightedAverage(sizes.map(s => s.val), sizes.map(s => s.w)) * 100) / 100
|
|
102
|
-
: null,
|
|
103
|
-
timing: weightedMode(timings.map(t => t.val), timings.map(t => t.w)) || 'now'
|
|
104
|
-
};
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Collect all alerts from responses
|
|
109
|
-
*/
|
|
110
|
-
const collectAlerts = (responses) => {
|
|
111
|
-
const alerts = [];
|
|
112
|
-
for (const r of responses) {
|
|
113
|
-
if (r.alerts && Array.isArray(r.alerts)) {
|
|
114
|
-
alerts.push(...r.alerts);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return [...new Set(alerts)].slice(0, 10);
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Build reason summary from all responses
|
|
122
|
-
*/
|
|
123
|
-
const buildReasonSummary = (responses, decision) => {
|
|
124
|
-
const reasons = responses
|
|
125
|
-
.filter(r => r.decision === decision && r.reason)
|
|
126
|
-
.map(r => r.reason)
|
|
127
|
-
.slice(0, 3);
|
|
128
|
-
|
|
129
|
-
if (reasons.length === 0) return `Consensus: ${decision}`;
|
|
130
|
-
if (reasons.length === 1) return reasons[0];
|
|
131
|
-
|
|
132
|
-
return reasons[0] + (reasons.length > 1 ? ` (+${reasons.length - 1} more)` : '');
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Calculate consensus from multiple agent responses
|
|
137
|
-
*
|
|
138
|
-
* @param {Array} agentResponses - Array of { agentId, response, weight }
|
|
139
|
-
* @param {Object} options - Consensus options
|
|
140
|
-
* @returns {Object} Consensus result
|
|
141
|
-
*/
|
|
142
|
-
const calculateConsensus = (agentResponses, options = {}) => {
|
|
143
|
-
const {
|
|
144
|
-
minAgents = 1,
|
|
145
|
-
approveThreshold = 0.5,
|
|
146
|
-
rejectThreshold = 0.6,
|
|
147
|
-
minConfidence = 30
|
|
148
|
-
} = options;
|
|
149
|
-
|
|
150
|
-
// Filter valid responses
|
|
151
|
-
const validResponses = agentResponses.filter(ar =>
|
|
152
|
-
ar && ar.response && ar.response.decision
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
if (validResponses.length === 0) {
|
|
156
|
-
return { ...DEFAULT_CONSENSUS, reason: 'No valid agent responses' };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (validResponses.length < minAgents) {
|
|
160
|
-
return {
|
|
161
|
-
...DEFAULT_CONSENSUS,
|
|
162
|
-
reason: `Insufficient agents (${validResponses.length}/${minAgents})`,
|
|
163
|
-
agentCount: agentResponses.length,
|
|
164
|
-
respondedCount: validResponses.length
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Extract responses and weights
|
|
169
|
-
const responses = validResponses.map(ar => ar.response);
|
|
170
|
-
const weights = validResponses.map(ar => ar.weight || 100);
|
|
171
|
-
const totalWeight = weights.reduce((a, b) => a + b, 0);
|
|
172
|
-
|
|
173
|
-
// Calculate weighted votes for each decision
|
|
174
|
-
const votes = { approve: 0, reject: 0, modify: 0 };
|
|
175
|
-
for (let i = 0; i < responses.length; i++) {
|
|
176
|
-
const decision = responses[i].decision;
|
|
177
|
-
votes[decision] = (votes[decision] || 0) + weights[i];
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Normalize votes to percentages
|
|
181
|
-
const approveRatio = votes.approve / totalWeight;
|
|
182
|
-
const rejectRatio = votes.reject / totalWeight;
|
|
183
|
-
const modifyRatio = votes.modify / totalWeight;
|
|
184
|
-
|
|
185
|
-
// Determine consensus decision
|
|
186
|
-
let decision;
|
|
187
|
-
if (rejectRatio >= rejectThreshold) {
|
|
188
|
-
decision = 'reject';
|
|
189
|
-
} else if (approveRatio >= approveThreshold) {
|
|
190
|
-
decision = modifyRatio > 0 ? 'modify' : 'approve';
|
|
191
|
-
} else if (modifyRatio > approveRatio) {
|
|
192
|
-
decision = 'modify';
|
|
193
|
-
} else {
|
|
194
|
-
decision = 'approve';
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Calculate weighted confidence
|
|
198
|
-
const confidences = responses.map(r => r.confidence);
|
|
199
|
-
const avgConfidence = Math.round(weightedAverage(confidences, weights));
|
|
200
|
-
|
|
201
|
-
// Apply minimum confidence check
|
|
202
|
-
if (avgConfidence < minConfidence && decision !== 'reject') {
|
|
203
|
-
decision = 'reject';
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Check unanimity
|
|
207
|
-
const decisions = responses.map(r => r.decision);
|
|
208
|
-
const unanimous = new Set(decisions).size === 1;
|
|
209
|
-
|
|
210
|
-
// Build consensus result
|
|
211
|
-
const consensus = {
|
|
212
|
-
decision,
|
|
213
|
-
confidence: avgConfidence,
|
|
214
|
-
optimizations: decision !== 'reject' ? mergeOptimizations(responses, weights) : null,
|
|
215
|
-
reason: buildReasonSummary(responses, decision),
|
|
216
|
-
alerts: collectAlerts(responses),
|
|
217
|
-
|
|
218
|
-
// Metadata
|
|
219
|
-
agentCount: agentResponses.length,
|
|
220
|
-
respondedCount: validResponses.length,
|
|
221
|
-
unanimous,
|
|
222
|
-
|
|
223
|
-
// Vote breakdown
|
|
224
|
-
votes: {
|
|
225
|
-
approve: Math.round(approveRatio * 100),
|
|
226
|
-
reject: Math.round(rejectRatio * 100),
|
|
227
|
-
modify: Math.round(modifyRatio * 100)
|
|
228
|
-
},
|
|
229
|
-
|
|
230
|
-
// Individual responses for debugging
|
|
231
|
-
agentDetails: validResponses.map(ar => ({
|
|
232
|
-
agentId: ar.agentId,
|
|
233
|
-
decision: ar.response.decision,
|
|
234
|
-
confidence: ar.response.confidence,
|
|
235
|
-
weight: ar.weight
|
|
236
|
-
}))
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
return consensus;
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Quick check if consensus approves the signal
|
|
244
|
-
*/
|
|
245
|
-
const isApproved = (consensus) => {
|
|
246
|
-
return consensus.decision === 'approve' || consensus.decision === 'modify';
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Apply consensus optimizations to original signal
|
|
251
|
-
*/
|
|
252
|
-
const applyOptimizations = (signal, consensus) => {
|
|
253
|
-
if (!isApproved(consensus) || !consensus.optimizations) {
|
|
254
|
-
return signal;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const opts = consensus.optimizations;
|
|
258
|
-
const optimized = { ...signal };
|
|
259
|
-
|
|
260
|
-
if (opts.entry !== null) optimized.entry = opts.entry;
|
|
261
|
-
if (opts.stopLoss !== null) optimized.stopLoss = opts.stopLoss;
|
|
262
|
-
if (opts.takeProfit !== null) optimized.takeProfit = opts.takeProfit;
|
|
263
|
-
|
|
264
|
-
// Apply size adjustment
|
|
265
|
-
if (opts.size !== null && signal.size) {
|
|
266
|
-
optimized.size = Math.max(1, Math.round(signal.size * (1 + opts.size)));
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
optimized.aiOptimized = true;
|
|
270
|
-
optimized.aiConfidence = consensus.confidence;
|
|
271
|
-
optimized.aiTiming = opts.timing;
|
|
272
|
-
|
|
273
|
-
return optimized;
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
module.exports = {
|
|
277
|
-
calculateConsensus,
|
|
278
|
-
isApproved,
|
|
279
|
-
applyOptimizations,
|
|
280
|
-
weightedAverage,
|
|
281
|
-
weightedMode,
|
|
282
|
-
mergeOptimizations,
|
|
283
|
-
DEFAULT_CONSENSUS
|
|
284
|
-
};
|
|
@@ -1,275 +0,0 @@
|
|
|
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
|
-
};
|