Merge branch 'cleanup' of https://git.d-popov.com/popov/gogo2 into cleanup
This commit is contained in:
@@ -101,6 +101,23 @@
|
||||
if (typeof checkActiveTraining === 'function') {
|
||||
checkActiveTraining();
|
||||
}
|
||||
|
||||
// Keyboard shortcuts for chart maximization
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// ESC key to exit maximized mode
|
||||
if (e.key === 'Escape') {
|
||||
const chartArea = document.querySelector('.chart-maximized');
|
||||
if (chartArea) {
|
||||
document.getElementById('maximize-btn').click();
|
||||
}
|
||||
}
|
||||
|
||||
// F key to toggle maximize (when not typing in input)
|
||||
if (e.key === 'f' && !e.ctrlKey && !e.metaKey &&
|
||||
!['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName)) {
|
||||
document.getElementById('maximize-btn').click();
|
||||
}
|
||||
});
|
||||
|
||||
// Setup keyboard shortcuts
|
||||
setupKeyboardShortcuts();
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
<button type="button" class="btn btn-outline-light" id="reset-zoom-btn" title="Reset Zoom">
|
||||
<i class="fas fa-expand"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-light" id="maximize-btn" title="Maximize Chart Area">
|
||||
<i class="fas fa-arrows-alt"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-light" id="fullscreen-btn" title="Fullscreen">
|
||||
<i class="fas fa-expand-arrows-alt"></i>
|
||||
</button>
|
||||
@@ -110,6 +113,41 @@
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('maximize-btn').addEventListener('click', function () {
|
||||
const mainRow = document.querySelector('.row.mt-3');
|
||||
const leftSidebar = mainRow.querySelector('.col-md-2:first-child');
|
||||
const chartArea = mainRow.querySelector('.col-md-8');
|
||||
const rightSidebar = mainRow.querySelector('.col-md-2:last-child');
|
||||
const chartPanel = document.querySelector('.chart-panel');
|
||||
const maximizeIcon = this.querySelector('i');
|
||||
|
||||
// Toggle maximize state
|
||||
if (chartArea.classList.contains('chart-maximized')) {
|
||||
// Restore normal view
|
||||
leftSidebar.style.display = '';
|
||||
rightSidebar.style.display = '';
|
||||
chartArea.classList.remove('chart-maximized');
|
||||
chartPanel.classList.remove('chart-panel-maximized');
|
||||
maximizeIcon.className = 'fas fa-arrows-alt';
|
||||
this.title = 'Maximize Chart Area';
|
||||
} else {
|
||||
// Maximize chart area
|
||||
leftSidebar.style.display = 'none';
|
||||
rightSidebar.style.display = 'none';
|
||||
chartArea.classList.add('chart-maximized');
|
||||
chartPanel.classList.add('chart-panel-maximized');
|
||||
maximizeIcon.className = 'fas fa-compress-arrows-alt';
|
||||
this.title = 'Restore Normal View';
|
||||
}
|
||||
|
||||
// Update chart layouts after transition
|
||||
setTimeout(() => {
|
||||
if (window.appState && window.appState.chartManager) {
|
||||
window.appState.chartManager.updateChartLayout();
|
||||
}
|
||||
}, 350);
|
||||
});
|
||||
|
||||
document.getElementById('fullscreen-btn').addEventListener('click', function () {
|
||||
const chartContainer = document.getElementById('chart-container');
|
||||
if (chartContainer.requestFullscreen) {
|
||||
|
||||
@@ -40,9 +40,13 @@
|
||||
role="progressbar" style="width: 0%"></div>
|
||||
</div>
|
||||
<div class="small">
|
||||
<div>Epoch: <span id="training-epoch">0</span>/<span id="training-total-epochs">0</span></div>
|
||||
<div>Loss: <span id="training-loss">--</span></div>
|
||||
<div>GPU: <span id="training-gpu-util">--</span>% | CPU: <span id="training-cpu-util">--</span>%</div>
|
||||
<div>Annotations: <span id="training-annotation-count" class="fw-bold text-primary">--</span></div>
|
||||
<div>Timeframe: <span id="training-timeframe" class="fw-bold text-info">--</span></div>
|
||||
<div class="mt-1 pt-1 border-top">
|
||||
<div>Epoch: <span id="training-epoch">0</span>/<span id="training-total-epochs">0</span></div>
|
||||
<div>Loss: <span id="training-loss">--</span></div>
|
||||
<div>GPU: <span id="training-gpu-util">--</span>% | CPU: <span id="training-cpu-util">--</span>%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,12 +143,42 @@
|
||||
<!-- Inference Status -->
|
||||
<div id="inference-status" style="display: none;">
|
||||
<div class="alert alert-success py-2 px-2 mb-2">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status">
|
||||
<span class="visually-hidden">Running...</span>
|
||||
<div class="d-flex align-items-center justify-content-between mb-1">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status">
|
||||
<span class="visually-hidden">Running...</span>
|
||||
</div>
|
||||
<strong class="small">🔴 LIVE</strong>
|
||||
</div>
|
||||
<!-- Model Performance -->
|
||||
<div class="small text-end">
|
||||
<div style="font-size: 0.65rem;">Acc: <span id="live-accuracy" class="fw-bold text-success">--</span></div>
|
||||
<div style="font-size: 0.65rem;">Loss: <span id="live-loss" class="fw-bold text-warning">--</span></div>
|
||||
</div>
|
||||
<strong class="small">🔴 LIVE</strong>
|
||||
</div>
|
||||
|
||||
<!-- Position & PnL Status -->
|
||||
<div class="mb-2 p-2" style="background-color: rgba(0,0,0,0.1); border-radius: 4px;">
|
||||
<div class="small">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Position:</span>
|
||||
<span id="position-status" class="fw-bold text-info">NO POSITION</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between" id="floating-pnl-row" style="display: none !important;">
|
||||
<span>Floating PnL:</span>
|
||||
<span id="floating-pnl" class="fw-bold">--</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Session PnL:</span>
|
||||
<span id="session-pnl" class="fw-bold text-success">+$0.00</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between" style="font-size: 0.7rem; color: #9ca3af;">
|
||||
<span>Win Rate:</span>
|
||||
<span id="win-rate">0% (0/0)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="small">
|
||||
<div>Timeframe: <span id="active-timeframe" class="fw-bold text-primary">--</span></div>
|
||||
<div>Signal: <span id="latest-signal" class="fw-bold">--</span></div>
|
||||
@@ -195,6 +229,15 @@
|
||||
// Resume tracking
|
||||
activeTrainingId = data.session.training_id;
|
||||
showTrainingStatus();
|
||||
|
||||
// Populate annotation count and timeframe if available
|
||||
if (data.session.annotation_count) {
|
||||
document.getElementById('training-annotation-count').textContent = data.session.annotation_count;
|
||||
}
|
||||
if (data.session.timeframe) {
|
||||
document.getElementById('training-timeframe').textContent = data.session.timeframe.toUpperCase();
|
||||
}
|
||||
|
||||
pollTrainingProgress(activeTrainingId);
|
||||
} else {
|
||||
console.log('No active training session');
|
||||
@@ -274,6 +317,36 @@
|
||||
|
||||
console.log(`✓ Models available: ${data.available_count}, loaded: ${data.loaded_count}`);
|
||||
|
||||
// Auto-select Transformer (or any loaded model) if available
|
||||
let modelToSelect = null;
|
||||
// First try to find Transformer
|
||||
const transformerModel = data.models.find(m => {
|
||||
const modelName = (m && typeof m === 'object' && m.name) ? m.name : String(m);
|
||||
const isLoaded = (m && typeof m === 'object' && 'loaded' in m) ? m.loaded : false;
|
||||
return modelName === 'Transformer' && isLoaded;
|
||||
});
|
||||
|
||||
if (transformerModel) {
|
||||
modelToSelect = 'Transformer';
|
||||
} else {
|
||||
// If Transformer not loaded, find any loaded model
|
||||
const loadedModel = data.models.find(m => {
|
||||
const isLoaded = (m && typeof m === 'object' && 'loaded' in m) ? m.loaded : false;
|
||||
return isLoaded;
|
||||
});
|
||||
if (loadedModel) {
|
||||
const modelName = (loadedModel && typeof loadedModel === 'object' && loadedModel.name) ? loadedModel.name : String(loadedModel);
|
||||
modelToSelect = modelName;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-select if found
|
||||
if (modelToSelect) {
|
||||
modelSelect.value = modelToSelect;
|
||||
selectedModel = modelToSelect;
|
||||
console.log(`✓ Auto-selected loaded model: ${modelToSelect}`);
|
||||
}
|
||||
|
||||
// Update button state for currently selected model
|
||||
updateButtonState();
|
||||
} else {
|
||||
@@ -418,10 +491,17 @@
|
||||
// Show training status
|
||||
showTrainingStatus();
|
||||
|
||||
// Get primary timeframe for training
|
||||
const primaryTimeframe = document.getElementById('primary-timeframe-select').value;
|
||||
|
||||
// Reset progress
|
||||
document.getElementById('training-progress-bar').style.width = '0%';
|
||||
document.getElementById('training-epoch').textContent = '0';
|
||||
document.getElementById('training-loss').textContent = '--';
|
||||
|
||||
// Set annotation count and timeframe
|
||||
document.getElementById('training-annotation-count').textContent = annotationIds.length;
|
||||
document.getElementById('training-timeframe').textContent = primaryTimeframe.toUpperCase();
|
||||
|
||||
// Start training request
|
||||
fetch('/api/train-model', {
|
||||
@@ -430,7 +510,8 @@
|
||||
body: JSON.stringify({
|
||||
model_name: modelName,
|
||||
annotation_ids: annotationIds,
|
||||
symbol: appState.currentSymbol // CRITICAL: Filter by current symbol
|
||||
symbol: appState.currentSymbol, // CRITICAL: Filter by current symbol
|
||||
timeframe: primaryTimeframe // Primary timeframe for display
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
@@ -977,6 +1058,70 @@
|
||||
}
|
||||
}
|
||||
|
||||
function updatePositionStateDisplay(positionState, sessionMetrics) {
|
||||
/**
|
||||
* Update live trading panel with current position and PnL info
|
||||
*/
|
||||
try {
|
||||
// Update position status
|
||||
const positionStatusEl = document.getElementById('position-status');
|
||||
const floatingPnlRow = document.getElementById('floating-pnl-row');
|
||||
const floatingPnlEl = document.getElementById('floating-pnl');
|
||||
|
||||
if (positionState.has_position) {
|
||||
const posType = positionState.position_type.toUpperCase();
|
||||
const entryPrice = positionState.entry_price.toFixed(2);
|
||||
positionStatusEl.textContent = `${posType} @ $${entryPrice}`;
|
||||
positionStatusEl.className = posType === 'LONG' ? 'fw-bold text-success' : 'fw-bold text-danger';
|
||||
|
||||
// Show floating PnL
|
||||
if (floatingPnlRow) {
|
||||
floatingPnlRow.style.display = 'flex !important';
|
||||
floatingPnlRow.classList.remove('d-none');
|
||||
}
|
||||
const unrealizedPnl = positionState.unrealized_pnl || 0;
|
||||
const pnlColor = unrealizedPnl >= 0 ? 'text-success' : 'text-danger';
|
||||
const pnlSign = unrealizedPnl >= 0 ? '+' : '';
|
||||
floatingPnlEl.textContent = `${pnlSign}${unrealizedPnl.toFixed(2)}%`;
|
||||
floatingPnlEl.className = `fw-bold ${pnlColor}`;
|
||||
} else {
|
||||
positionStatusEl.textContent = 'NO POSITION';
|
||||
positionStatusEl.className = 'fw-bold text-secondary';
|
||||
|
||||
// Hide floating PnL row
|
||||
if (floatingPnlRow) {
|
||||
floatingPnlRow.style.display = 'none !important';
|
||||
floatingPnlRow.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
// Update session PnL
|
||||
const sessionPnlEl = document.getElementById('session-pnl');
|
||||
if (sessionPnlEl && sessionMetrics) {
|
||||
const totalPnl = sessionMetrics.total_pnl || 0;
|
||||
const pnlColor = totalPnl >= 0 ? 'text-success' : 'text-danger';
|
||||
const pnlSign = totalPnl >= 0 ? '+' : '';
|
||||
sessionPnlEl.textContent = `${pnlSign}$${totalPnl.toFixed(2)}`;
|
||||
sessionPnlEl.className = `fw-bold ${pnlColor}`;
|
||||
|
||||
// Update win rate
|
||||
const winRateEl = document.getElementById('win-rate');
|
||||
if (winRateEl) {
|
||||
const winRate = sessionMetrics.win_rate || 0;
|
||||
const winCount = sessionMetrics.win_count || 0;
|
||||
const totalTrades = sessionMetrics.total_trades || 0;
|
||||
winRateEl.textContent = `${winRate.toFixed(1)}% (${winCount}/${totalTrades})`;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error updating position state display:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Make function globally accessible for WebSocket handler
|
||||
window.updatePositionStateDisplay = updatePositionStateDisplay;
|
||||
|
||||
function updatePredictionHistory() {
|
||||
const historyDiv = document.getElementById('prediction-history');
|
||||
if (predictionHistory.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user