hedgequantx 2.5.38 → 2.5.40
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/pages/stats.js +133 -70
- package/src/services/ai/strategy-supervisor.js +166 -5
package/package.json
CHANGED
package/src/pages/stats.js
CHANGED
|
@@ -481,84 +481,147 @@ const showStats = async (service) => {
|
|
|
481
481
|
|
|
482
482
|
drawBoxFooter(boxWidth);
|
|
483
483
|
|
|
484
|
-
// ========== AI BEHAVIOR
|
|
485
|
-
const behaviorData = StrategySupervisor.getBehaviorHistory(
|
|
484
|
+
// ========== AI BEHAVIOR DIAGRAM ==========
|
|
485
|
+
const behaviorData = StrategySupervisor.getBehaviorHistory(100);
|
|
486
486
|
const learningStats = StrategySupervisor.getLearningStats();
|
|
487
487
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const newValues = [];
|
|
502
|
-
for (let i = 0; i < scaledValues.length - 1; i++) {
|
|
503
|
-
newValues.push(scaledValues[i]);
|
|
504
|
-
// Smooth interpolation
|
|
505
|
-
const diff = scaledValues[i + 1] - scaledValues[i];
|
|
506
|
-
newValues.push(scaledValues[i] + diff * 0.33);
|
|
507
|
-
newValues.push(scaledValues[i] + diff * 0.66);
|
|
508
|
-
}
|
|
509
|
-
newValues.push(scaledValues[scaledValues.length - 1]);
|
|
510
|
-
scaledValues = newValues;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// Limit data points to chart width
|
|
514
|
-
if (scaledValues.length > chartWidth) {
|
|
515
|
-
const step = Math.ceil(scaledValues.length / chartWidth);
|
|
516
|
-
scaledValues = scaledValues.filter((_, i) => i % step === 0);
|
|
488
|
+
console.log();
|
|
489
|
+
drawBoxHeader('AI AGENTS BEHAVIOR', boxWidth);
|
|
490
|
+
|
|
491
|
+
const behaviorInnerWidth = boxWidth - 2;
|
|
492
|
+
|
|
493
|
+
// Count behavior occurrences
|
|
494
|
+
const behaviorCounts = { AGGRESSIVE: 0, NORMAL: 0, CAUTIOUS: 0, PAUSE: 0 };
|
|
495
|
+
const valueToAction = { 3: 'AGGRESSIVE', 2: 'NORMAL', 1: 'CAUTIOUS', 0: 'PAUSE' };
|
|
496
|
+
|
|
497
|
+
if (behaviorData.values.length > 0) {
|
|
498
|
+
for (const val of behaviorData.values) {
|
|
499
|
+
const action = valueToAction[Math.round(val)] || 'NORMAL';
|
|
500
|
+
behaviorCounts[action]++;
|
|
517
501
|
}
|
|
502
|
+
} else {
|
|
503
|
+
behaviorCounts.NORMAL = 1; // Default
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const total = Object.values(behaviorCounts).reduce((a, b) => a + b, 0) || 1;
|
|
507
|
+
const percentages = {
|
|
508
|
+
AGGRESSIVE: Math.round((behaviorCounts.AGGRESSIVE / total) * 100),
|
|
509
|
+
NORMAL: Math.round((behaviorCounts.NORMAL / total) * 100),
|
|
510
|
+
CAUTIOUS: Math.round((behaviorCounts.CAUTIOUS / total) * 100),
|
|
511
|
+
PAUSE: Math.round((behaviorCounts.PAUSE / total) * 100)
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// Bar chart configuration
|
|
515
|
+
const labelWidth = 12;
|
|
516
|
+
const percentWidth = 6;
|
|
517
|
+
const barMaxWidth = behaviorInnerWidth - labelWidth - percentWidth - 6;
|
|
518
|
+
|
|
519
|
+
// Get current behavior
|
|
520
|
+
const currentValue = behaviorData.values.length > 0 ? behaviorData.values[behaviorData.values.length - 1] : 2;
|
|
521
|
+
const currentAction = valueToAction[Math.round(currentValue)] || 'NORMAL';
|
|
522
|
+
|
|
523
|
+
// Draw vertical bar chart (bars going up)
|
|
524
|
+
const chartHeight = 8;
|
|
525
|
+
const barWidth = Math.floor((barMaxWidth - 12) / 4); // 4 bars with spacing
|
|
526
|
+
|
|
527
|
+
// Calculate bar heights (max height = chartHeight)
|
|
528
|
+
const maxPercent = Math.max(...Object.values(percentages), 1);
|
|
529
|
+
const barHeights = {
|
|
530
|
+
AGGRESSIVE: Math.round((percentages.AGGRESSIVE / 100) * chartHeight),
|
|
531
|
+
NORMAL: Math.round((percentages.NORMAL / 100) * chartHeight),
|
|
532
|
+
CAUTIOUS: Math.round((percentages.CAUTIOUS / 100) * chartHeight),
|
|
533
|
+
PAUSE: Math.round((percentages.PAUSE / 100) * chartHeight)
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
// Colors for each behavior
|
|
537
|
+
const barColors = {
|
|
538
|
+
AGGRESSIVE: chalk.green,
|
|
539
|
+
NORMAL: chalk.cyan,
|
|
540
|
+
CAUTIOUS: chalk.yellow,
|
|
541
|
+
PAUSE: chalk.red
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// Draw bars from top to bottom
|
|
545
|
+
const barLabels = ['AGGRESSIVE', 'NORMAL', 'CAUTIOUS', 'PAUSE'];
|
|
546
|
+
const shortLabels = ['AGR', 'NOR', 'CAU', 'PAU'];
|
|
547
|
+
|
|
548
|
+
// Calculate left padding to center the chart
|
|
549
|
+
const totalBarWidth = (barWidth * 4) + 9; // 4 bars + 3 spaces of 3 chars
|
|
550
|
+
const leftPad = Math.floor((behaviorInnerWidth - totalBarWidth - 4) / 2);
|
|
551
|
+
|
|
552
|
+
for (let row = chartHeight; row >= 1; row--) {
|
|
553
|
+
let line = ' '.repeat(leftPad);
|
|
518
554
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
format: (x) => {
|
|
533
|
-
if (x >= 75) return 'AGGRESSIVE'.padStart(yAxisWidth);
|
|
534
|
-
if (x >= 50) return ' NORMAL'.padStart(yAxisWidth);
|
|
535
|
-
if (x >= 25) return ' CAUTIOUS'.padStart(yAxisWidth);
|
|
536
|
-
return ' PAUSE'.padStart(yAxisWidth);
|
|
555
|
+
for (let i = 0; i < 4; i++) {
|
|
556
|
+
const label = barLabels[i];
|
|
557
|
+
const height = barHeights[label];
|
|
558
|
+
const color = barColors[label];
|
|
559
|
+
const isCurrent = label === currentAction;
|
|
560
|
+
|
|
561
|
+
if (row <= height) {
|
|
562
|
+
// Draw filled bar
|
|
563
|
+
const block = isCurrent ? '█' : '▓';
|
|
564
|
+
line += color(block.repeat(barWidth));
|
|
565
|
+
} else {
|
|
566
|
+
// Empty space
|
|
567
|
+
line += ' '.repeat(barWidth);
|
|
537
568
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
try {
|
|
541
|
-
const behaviorChart = asciichart.plot(scaledValues, behaviorConfig);
|
|
542
|
-
behaviorChart.split('\n').forEach(line => {
|
|
543
|
-
let chartLine = ' ' + line;
|
|
544
|
-
const len = chartLine.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
545
|
-
if (len < behaviorInnerWidth) chartLine += ' '.repeat(behaviorInnerWidth - len);
|
|
546
|
-
console.log(chalk.cyan('\u2551') + chartLine + chalk.cyan('\u2551'));
|
|
547
|
-
});
|
|
548
|
-
} catch (e) {
|
|
549
|
-
// Fallback if chart fails
|
|
550
|
-
const msg = ' BEHAVIOR DATA INSUFFICIENT';
|
|
551
|
-
console.log(chalk.cyan('\u2551') + chalk.white(msg) + ' '.repeat(Math.max(0, behaviorInnerWidth - msg.length)) + chalk.cyan('\u2551'));
|
|
569
|
+
|
|
570
|
+
if (i < 3) line += ' '; // Space between bars
|
|
552
571
|
}
|
|
553
572
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
573
|
+
const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
574
|
+
line += ' '.repeat(Math.max(0, behaviorInnerWidth - lineLen));
|
|
575
|
+
console.log(chalk.cyan('\u2551') + line + chalk.cyan('\u2551'));
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Draw baseline
|
|
579
|
+
let baseLine = ' '.repeat(leftPad) + '─'.repeat(totalBarWidth);
|
|
580
|
+
const baseLen = baseLine.length;
|
|
581
|
+
baseLine += ' '.repeat(Math.max(0, behaviorInnerWidth - baseLen));
|
|
582
|
+
console.log(chalk.cyan('\u2551') + chalk.white(baseLine) + chalk.cyan('\u2551'));
|
|
583
|
+
|
|
584
|
+
// Draw labels
|
|
585
|
+
let labelLine = ' '.repeat(leftPad);
|
|
586
|
+
for (let i = 0; i < 4; i++) {
|
|
587
|
+
const lbl = shortLabels[i];
|
|
588
|
+
const pad = Math.floor((barWidth - lbl.length) / 2);
|
|
589
|
+
labelLine += ' '.repeat(pad) + barColors[barLabels[i]](lbl) + ' '.repeat(barWidth - pad - lbl.length);
|
|
590
|
+
if (i < 3) labelLine += ' ';
|
|
591
|
+
}
|
|
592
|
+
const lblLen = labelLine.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
593
|
+
labelLine += ' '.repeat(Math.max(0, behaviorInnerWidth - lblLen));
|
|
594
|
+
console.log(chalk.cyan('\u2551') + labelLine + chalk.cyan('\u2551'));
|
|
595
|
+
|
|
596
|
+
// Draw percentages
|
|
597
|
+
let pctLine = ' '.repeat(leftPad);
|
|
598
|
+
for (let i = 0; i < 4; i++) {
|
|
599
|
+
const pct = percentages[barLabels[i]] + '%';
|
|
600
|
+
const pad = Math.floor((barWidth - pct.length) / 2);
|
|
601
|
+
pctLine += ' '.repeat(pad) + chalk.white(pct) + ' '.repeat(barWidth - pad - pct.length);
|
|
602
|
+
if (i < 3) pctLine += ' ';
|
|
561
603
|
}
|
|
604
|
+
const pctLen = pctLine.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
605
|
+
pctLine += ' '.repeat(Math.max(0, behaviorInnerWidth - pctLen));
|
|
606
|
+
console.log(chalk.cyan('\u2551') + pctLine + chalk.cyan('\u2551'));
|
|
607
|
+
|
|
608
|
+
// Empty line
|
|
609
|
+
console.log(chalk.cyan('\u2551') + ' '.repeat(behaviorInnerWidth) + chalk.cyan('\u2551'));
|
|
610
|
+
|
|
611
|
+
// Current session stats line
|
|
612
|
+
const statsLine = ` CURRENT: ${barColors[currentAction](currentAction)} | SESSION PATTERNS: ${learningStats.patternsLearned.total} (${learningStats.patternsLearned.winning}W/${learningStats.patternsLearned.losing}L) | OPTIMIZATIONS: ${learningStats.optimizations}`;
|
|
613
|
+
const statsLen = statsLine.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
614
|
+
console.log(chalk.cyan('\u2551') + statsLine + ' '.repeat(Math.max(0, behaviorInnerWidth - statsLen)) + chalk.cyan('\u2551'));
|
|
615
|
+
|
|
616
|
+
// Lifetime stats line (memory across sessions)
|
|
617
|
+
const lifetimeStats = StrategySupervisor.getLifetimeStats();
|
|
618
|
+
if (lifetimeStats.totalSessions > 0) {
|
|
619
|
+
const lifetimeLine = ` LIFETIME: ${lifetimeStats.totalSessions} sessions | ${lifetimeStats.totalTrades} trades | WR: ${lifetimeStats.lifetimeWinRate} | P&L: $${lifetimeStats.lifetimePnL.toFixed(2)} | ${lifetimeStats.patternsLearned.winning + lifetimeStats.patternsLearned.losing} patterns learned`;
|
|
620
|
+
const lifetimeLen = lifetimeLine.length;
|
|
621
|
+
console.log(chalk.cyan('\u2551') + chalk.magenta(lifetimeLine) + ' '.repeat(Math.max(0, behaviorInnerWidth - lifetimeLen)) + chalk.cyan('\u2551'));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
drawBoxFooter(boxWidth);
|
|
562
625
|
}
|
|
563
626
|
|
|
564
627
|
// ========== EQUITY CURVE ==========
|
|
@@ -8,12 +8,114 @@
|
|
|
8
8
|
* 2. LEARN - Analyze winning/losing trades to identify patterns
|
|
9
9
|
* 3. OPTIMIZE - Suggest and apply parameter improvements
|
|
10
10
|
* 4. SUPERVISE - Monitor risk and intervene when necessary
|
|
11
|
+
* 5. PERSIST - Save learned patterns and optimizations between sessions
|
|
11
12
|
*
|
|
12
13
|
* In CONSENSUS mode (2+ agents), ALL agents must agree before applying changes.
|
|
13
14
|
*/
|
|
14
15
|
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const os = require('os');
|
|
15
19
|
const { analyzePerformance, getMarketAdvice, callAI } = require('./client');
|
|
16
20
|
|
|
21
|
+
// Path for persisted learning data
|
|
22
|
+
const DATA_DIR = path.join(os.homedir(), '.hqx');
|
|
23
|
+
const LEARNING_FILE = path.join(DATA_DIR, 'ai-learning.json');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Load persisted learning data from disk
|
|
27
|
+
* Called on startup to restore previous learnings
|
|
28
|
+
*/
|
|
29
|
+
const loadLearningData = () => {
|
|
30
|
+
try {
|
|
31
|
+
if (!fs.existsSync(DATA_DIR)) {
|
|
32
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (fs.existsSync(LEARNING_FILE)) {
|
|
36
|
+
const data = JSON.parse(fs.readFileSync(LEARNING_FILE, 'utf8'));
|
|
37
|
+
return {
|
|
38
|
+
winningPatterns: data.winningPatterns || [],
|
|
39
|
+
losingPatterns: data.losingPatterns || [],
|
|
40
|
+
optimizations: data.optimizations || [],
|
|
41
|
+
totalSessions: data.totalSessions || 0,
|
|
42
|
+
totalTrades: data.totalTrades || 0,
|
|
43
|
+
totalWins: data.totalWins || 0,
|
|
44
|
+
totalLosses: data.totalLosses || 0,
|
|
45
|
+
lifetimePnL: data.lifetimePnL || 0,
|
|
46
|
+
lastUpdated: data.lastUpdated || null
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
} catch (e) {
|
|
50
|
+
// Silent fail - start fresh
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
winningPatterns: [],
|
|
55
|
+
losingPatterns: [],
|
|
56
|
+
optimizations: [],
|
|
57
|
+
totalSessions: 0,
|
|
58
|
+
totalTrades: 0,
|
|
59
|
+
totalWins: 0,
|
|
60
|
+
totalLosses: 0,
|
|
61
|
+
lifetimePnL: 0,
|
|
62
|
+
lastUpdated: null
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Save learning data to disk
|
|
68
|
+
* Called after each trade and on session end
|
|
69
|
+
*/
|
|
70
|
+
const saveLearningData = () => {
|
|
71
|
+
try {
|
|
72
|
+
if (!fs.existsSync(DATA_DIR)) {
|
|
73
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Load existing data first
|
|
77
|
+
const existing = loadLearningData();
|
|
78
|
+
|
|
79
|
+
// Merge with current session data
|
|
80
|
+
const dataToSave = {
|
|
81
|
+
// Patterns - keep last 100 of each type (merge and dedupe by timestamp)
|
|
82
|
+
winningPatterns: mergePatterns(existing.winningPatterns, supervisorState.winningPatterns, 100),
|
|
83
|
+
losingPatterns: mergePatterns(existing.losingPatterns, supervisorState.losingPatterns, 100),
|
|
84
|
+
|
|
85
|
+
// Optimizations history - keep last 50
|
|
86
|
+
optimizations: [...existing.optimizations, ...supervisorState.optimizations].slice(-50),
|
|
87
|
+
|
|
88
|
+
// Lifetime stats
|
|
89
|
+
totalSessions: existing.totalSessions + (supervisorState.active ? 0 : 1),
|
|
90
|
+
totalTrades: existing.totalTrades + supervisorState.performance.trades,
|
|
91
|
+
totalWins: existing.totalWins + supervisorState.performance.wins,
|
|
92
|
+
totalLosses: existing.totalLosses + supervisorState.performance.losses,
|
|
93
|
+
lifetimePnL: existing.lifetimePnL + supervisorState.performance.totalPnL,
|
|
94
|
+
|
|
95
|
+
// Metadata
|
|
96
|
+
lastUpdated: new Date().toISOString()
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
fs.writeFileSync(LEARNING_FILE, JSON.stringify(dataToSave, null, 2));
|
|
100
|
+
return true;
|
|
101
|
+
} catch (e) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Merge pattern arrays, keeping most recent unique entries
|
|
108
|
+
*/
|
|
109
|
+
const mergePatterns = (existing, current, maxCount) => {
|
|
110
|
+
const merged = [...existing, ...current];
|
|
111
|
+
|
|
112
|
+
// Sort by timestamp descending (most recent first)
|
|
113
|
+
merged.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
|
114
|
+
|
|
115
|
+
// Keep only maxCount most recent
|
|
116
|
+
return merged.slice(0, maxCount);
|
|
117
|
+
};
|
|
118
|
+
|
|
17
119
|
// Singleton supervisor state
|
|
18
120
|
let supervisorState = {
|
|
19
121
|
active: false,
|
|
@@ -69,10 +171,14 @@ let analysisInterval = null;
|
|
|
69
171
|
|
|
70
172
|
/**
|
|
71
173
|
* Initialize supervisor with strategy and agents
|
|
174
|
+
* Loads previous learning data to continue improving
|
|
72
175
|
*/
|
|
73
176
|
const initialize = (strategy, agents, service, accountId) => {
|
|
74
177
|
const now = Date.now();
|
|
75
178
|
|
|
179
|
+
// Load previously learned patterns and optimizations
|
|
180
|
+
const previousLearning = loadLearningData();
|
|
181
|
+
|
|
76
182
|
supervisorState = {
|
|
77
183
|
...supervisorState,
|
|
78
184
|
active: true,
|
|
@@ -83,8 +189,17 @@ const initialize = (strategy, agents, service, accountId) => {
|
|
|
83
189
|
ticks: [],
|
|
84
190
|
signals: [],
|
|
85
191
|
trades: [],
|
|
86
|
-
|
|
87
|
-
|
|
192
|
+
// Restore previous learning
|
|
193
|
+
winningPatterns: previousLearning.winningPatterns || [],
|
|
194
|
+
losingPatterns: previousLearning.losingPatterns || [],
|
|
195
|
+
previousOptimizations: previousLearning.optimizations || [],
|
|
196
|
+
lifetimeStats: {
|
|
197
|
+
sessions: previousLearning.totalSessions || 0,
|
|
198
|
+
trades: previousLearning.totalTrades || 0,
|
|
199
|
+
wins: previousLearning.totalWins || 0,
|
|
200
|
+
losses: previousLearning.totalLosses || 0,
|
|
201
|
+
pnl: previousLearning.lifetimePnL || 0
|
|
202
|
+
},
|
|
88
203
|
performance: {
|
|
89
204
|
trades: 0,
|
|
90
205
|
wins: 0,
|
|
@@ -124,7 +239,7 @@ const initialize = (strategy, agents, service, accountId) => {
|
|
|
124
239
|
};
|
|
125
240
|
|
|
126
241
|
/**
|
|
127
|
-
* Stop supervisor
|
|
242
|
+
* Stop supervisor and save learned data
|
|
128
243
|
*/
|
|
129
244
|
const stop = () => {
|
|
130
245
|
if (analysisInterval) {
|
|
@@ -132,11 +247,16 @@ const stop = () => {
|
|
|
132
247
|
analysisInterval = null;
|
|
133
248
|
}
|
|
134
249
|
|
|
250
|
+
// Save all learned data before stopping
|
|
251
|
+
const saved = saveLearningData();
|
|
252
|
+
|
|
135
253
|
const summary = {
|
|
136
254
|
...supervisorState.performance,
|
|
137
255
|
optimizationsApplied: supervisorState.optimizations.length,
|
|
138
256
|
winningPatterns: supervisorState.winningPatterns.length,
|
|
139
|
-
losingPatterns: supervisorState.losingPatterns.length
|
|
257
|
+
losingPatterns: supervisorState.losingPatterns.length,
|
|
258
|
+
dataSaved: saved,
|
|
259
|
+
lifetimeStats: supervisorState.lifetimeStats
|
|
140
260
|
};
|
|
141
261
|
|
|
142
262
|
supervisorState.active = false;
|
|
@@ -768,6 +888,44 @@ const getLearningStats = () => {
|
|
|
768
888
|
};
|
|
769
889
|
};
|
|
770
890
|
|
|
891
|
+
/**
|
|
892
|
+
* Get lifetime stats across all sessions
|
|
893
|
+
* Shows cumulative learning progress
|
|
894
|
+
*/
|
|
895
|
+
const getLifetimeStats = () => {
|
|
896
|
+
const saved = loadLearningData();
|
|
897
|
+
|
|
898
|
+
return {
|
|
899
|
+
totalSessions: saved.totalSessions,
|
|
900
|
+
totalTrades: saved.totalTrades,
|
|
901
|
+
totalWins: saved.totalWins,
|
|
902
|
+
totalLosses: saved.totalLosses,
|
|
903
|
+
lifetimeWinRate: saved.totalTrades > 0 ?
|
|
904
|
+
((saved.totalWins / saved.totalTrades) * 100).toFixed(1) + '%' : 'N/A',
|
|
905
|
+
lifetimePnL: saved.lifetimePnL,
|
|
906
|
+
patternsLearned: {
|
|
907
|
+
winning: saved.winningPatterns?.length || 0,
|
|
908
|
+
losing: saved.losingPatterns?.length || 0
|
|
909
|
+
},
|
|
910
|
+
optimizationsApplied: saved.optimizations?.length || 0,
|
|
911
|
+
lastUpdated: saved.lastUpdated
|
|
912
|
+
};
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* Clear all learned data (reset AI memory)
|
|
917
|
+
*/
|
|
918
|
+
const clearLearningData = () => {
|
|
919
|
+
try {
|
|
920
|
+
if (fs.existsSync(LEARNING_FILE)) {
|
|
921
|
+
fs.unlinkSync(LEARNING_FILE);
|
|
922
|
+
}
|
|
923
|
+
return true;
|
|
924
|
+
} catch (e) {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
|
|
771
929
|
module.exports = {
|
|
772
930
|
initialize,
|
|
773
931
|
stop,
|
|
@@ -779,5 +937,8 @@ module.exports = {
|
|
|
779
937
|
getStatus,
|
|
780
938
|
analyzeAndOptimize,
|
|
781
939
|
getBehaviorHistory,
|
|
782
|
-
getLearningStats
|
|
940
|
+
getLearningStats,
|
|
941
|
+
getLifetimeStats,
|
|
942
|
+
clearLearningData,
|
|
943
|
+
loadLearningData
|
|
783
944
|
};
|