new backtesting feature
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user