new backtesting feature

This commit is contained in:
Dobromir Popov
2025-11-17 19:13:30 +02:00
parent 37e90a1c3c
commit ebb062bdae
5 changed files with 1106 additions and 36 deletions

View File

@@ -87,6 +87,32 @@
</button>
</div>
<!-- Backtest on Visible Chart -->
<div class="mb-3">
<label class="form-label small">Backtest on Visible Data</label>
<button class="btn btn-warning btn-sm w-100" id="start-backtest-btn">
<i class="fas fa-history"></i>
Backtest Visible Chart
</button>
<button class="btn btn-danger btn-sm w-100 mt-1" id="stop-backtest-btn" style="display: none;">
<i class="fas fa-stop"></i>
Stop Backtest
</button>
<!-- Backtest Results -->
<div id="backtest-results" style="display: none;" class="mt-2">
<div class="alert alert-success py-2 px-2 mb-0">
<strong class="small">Backtest Results</strong>
<div class="small mt-1">
<div>PnL: <span id="backtest-pnl" class="fw-bold">--</span></div>
<div>Trades: <span id="backtest-trades">--</span></div>
<div>Win Rate: <span id="backtest-winrate">--</span></div>
<div>Progress: <span id="backtest-progress">0</span>/<span id="backtest-total">0</span></div>
</div>
</div>
</div>
</div>
<!-- Multi-Step Inference Control -->
<div class="mb-3" id="inference-controls" style="display: none;">
<label for="prediction-steps-slider" class="form-label small text-muted">
@@ -569,6 +595,198 @@
});
});
// Backtest controls
let currentBacktestId = null;
let backtestPollInterval = null;
let backtestMarkers = []; // Store markers to clear later
document.getElementById('start-backtest-btn').addEventListener('click', function () {
const modelName = document.getElementById('model-select').value;
if (!modelName) {
showError('Please select a model first');
return;
}
// Get current chart state
const primaryTimeframe = document.getElementById('primary-timeframe-select').value;
const symbol = appState.currentSymbol;
// Get visible chart range from the chart (if available)
const chart = document.getElementById('main-chart');
let startTime = null;
let endTime = null;
// Try to get visible range from chart's x-axis
if (chart && chart.layout && chart.layout.xaxis) {
const xaxis = chart.layout.xaxis;
if (xaxis.range) {
startTime = xaxis.range[0];
endTime = xaxis.range[1];
}
}
// Clear previous backtest markers
if (backtestMarkers.length > 0) {
clearBacktestMarkers();
}
// Start backtest
fetch('/api/backtest', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model_name: modelName,
symbol: symbol,
timeframe: primaryTimeframe,
start_time: startTime,
end_time: endTime
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
currentBacktestId = data.backtest_id;
// Update UI
document.getElementById('start-backtest-btn').style.display = 'none';
document.getElementById('stop-backtest-btn').style.display = 'block';
document.getElementById('backtest-results').style.display = 'block';
// Reset results
document.getElementById('backtest-pnl').textContent = '$0.00';
document.getElementById('backtest-trades').textContent = '0';
document.getElementById('backtest-winrate').textContent = '0%';
document.getElementById('backtest-progress').textContent = '0';
document.getElementById('backtest-total').textContent = data.total_candles || '?';
// Start polling for backtest progress
startBacktestPolling();
showSuccess('Backtest started');
} else {
showError('Failed to start backtest: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
showError('Network error: ' + error.message);
});
});
document.getElementById('stop-backtest-btn').addEventListener('click', function () {
if (!currentBacktestId) return;
// Stop backtest
fetch('/api/backtest/stop', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ backtest_id: currentBacktestId })
})
.then(response => response.json())
.then(data => {
// Update UI
document.getElementById('start-backtest-btn').style.display = 'block';
document.getElementById('stop-backtest-btn').style.display = 'none';
// Stop polling
stopBacktestPolling();
currentBacktestId = null;
showSuccess('Backtest stopped');
})
.catch(error => {
showError('Network error: ' + error.message);
});
});
function startBacktestPolling() {
if (backtestPollInterval) {
clearInterval(backtestPollInterval);
}
backtestPollInterval = setInterval(() => {
if (!currentBacktestId) {
stopBacktestPolling();
return;
}
fetch(`/api/backtest/progress/${currentBacktestId}`)
.then(response => response.json())
.then(data => {
if (data.success) {
updateBacktestUI(data);
// If complete, stop polling
if (data.status === 'complete' || data.status === 'error') {
stopBacktestPolling();
document.getElementById('start-backtest-btn').style.display = 'block';
document.getElementById('stop-backtest-btn').style.display = 'none';
currentBacktestId = null;
if (data.status === 'complete') {
showSuccess('Backtest complete');
} else {
showError('Backtest error: ' + (data.error || 'Unknown'));
}
}
}
})
.catch(error => {
console.error('Backtest polling error:', error);
});
}, 500); // Poll every 500ms for backtest progress
}
function stopBacktestPolling() {
if (backtestPollInterval) {
clearInterval(backtestPollInterval);
backtestPollInterval = null;
}
}
function updateBacktestUI(data) {
// Update progress
document.getElementById('backtest-progress').textContent = data.candles_processed || 0;
document.getElementById('backtest-total').textContent = data.total_candles || 0;
// Update PnL
const pnl = data.pnl || 0;
const pnlElement = document.getElementById('backtest-pnl');
pnlElement.textContent = `$${pnl.toFixed(2)}`;
pnlElement.className = pnl >= 0 ? 'fw-bold text-success' : 'fw-bold text-danger';
// Update trades
document.getElementById('backtest-trades').textContent = data.total_trades || 0;
// Update win rate
const winRate = data.win_rate || 0;
document.getElementById('backtest-winrate').textContent = `${(winRate * 100).toFixed(1)}%`;
// Add new predictions to chart
if (data.new_predictions && data.new_predictions.length > 0) {
addBacktestMarkersToChart(data.new_predictions);
}
}
function addBacktestMarkersToChart(predictions) {
// Store markers for later clearing
predictions.forEach(pred => {
backtestMarkers.push(pred);
});
// Trigger chart update with new markers
if (window.updateBacktestMarkers) {
window.updateBacktestMarkers(backtestMarkers);
}
}
function clearBacktestMarkers() {
backtestMarkers = [];
if (window.clearBacktestMarkers) {
window.clearBacktestMarkers();
}
}
function updatePredictionHistory() {
const historyDiv = document.getElementById('prediction-history');
if (predictionHistory.length === 0) {