The test are displaying in the user interface.

This commit is contained in:
Rob 2024-11-13 01:13:29 -04:00
parent 1af55f10a0
commit 0ae835d096
4 changed files with 239 additions and 34 deletions

View File

@ -149,9 +149,8 @@ class Backtester:
logger.error(f"Error during cleanup of backtest '{backtest_key}': {e}", exc_info=True)
def map_user_strategy(self, user_strategy: dict, precomputed_indicators: dict[str, pd.DataFrame],
mode: str = 'testing') -> any:
mode: str = 'testing', socketio=None, socket_conn_id=None, data_length=None) -> any:
"""
Maps user strategy details into a Backtrader-compatible strategy class.
"""
@ -176,6 +175,9 @@ class Backtester:
params = (
('mode', mode),
('strategy_instance', None), # Will be set during instantiation
('socketio', socketio),
('socket_conn_id', socket_conn_id),
('data_length', data_length)
)
def __init__(self):
@ -198,26 +200,35 @@ class Backtester:
# Initialize any other needed variables
self.starting_balance = self.broker.getvalue()
# Existing code...
self.current_step = 0
self.last_progress = 0 # Initialize last_progress
def next(self):
# Increment pointers
for name in self.indicator_names:
self.indicator_pointers[name] += 1
# Increment current step
self.current_step += 1
# Generated strategy logic
# Execute the strategy logic
try:
# Execute the strategy logic via StrategyInstance
execution_result = self.strategy_instance.execute()
if not execution_result.get('success', False):
error_msg = execution_result.get('message', 'Unknown error during strategy execution.')
logger.error(f"Strategy execution failed: {error_msg}")
# Handle the failure (stop the strategy)
self.stop()
except Exception as e:
logger.error(f"Error in strategy execution: {e}")
# Calculate progress
progress = (self.current_step / self.p.data_length) * 100
progress = min(int(progress), 100) # Ensure progress doesn't exceed 100%
# Emit progress only if it has increased by at least 1%
if progress > self.last_progress:
self.p.socketio.emit(
'message',
{'reply': 'progress', 'data': {'progress': progress}},
room=self.p.socket_conn_id
)
self.last_progress = progress
return MappedStrategy
# Add custom handlers to the StrategyInstance
@ -703,7 +714,7 @@ class Backtester:
trades = self.parse_trade_analyzer(trade_analyzer)
# Send 100% completion
self.socketio.emit('progress_update', {"progress": 100}, room=socket_conn_id)
self.socketio.emit('message', {'reply': 'progress', 'data': {'progress': 100}}, room=socket_conn_id)
# Prepare the results to pass into the callback
backtest_results = {
@ -721,7 +732,8 @@ class Backtester:
except Exception as e:
# Handle exceptions and send error messages to the client
error_message = f"Backtest execution failed: {str(e)}"
self.socketio.emit('backtest_error', {"message": error_message}, room=socket_conn_id)
self.socketio.emit('message', {'reply': 'backtest_error', 'data': {'message': error_message}},
room=socket_conn_id)
logger.error(f"[BACKTEST ERROR] {error_message}", exc_info=True)
# Invoke callback with failure details to ensure cleanup
@ -786,8 +798,15 @@ class Backtester:
# Override any methods that access exchanges and market data with custom handlers for backtesting
strategy_instance = self.add_custom_handlers(strategy_instance)
# Map the user strategy to a Backtrader-compatible strategy class
mapped_strategy_class = self.map_user_strategy(user_strategy, precomputed_indicators)
data_length = len(data_feed)
mapped_strategy_class = self.map_user_strategy(
user_strategy,
precomputed_indicators,
socketio=self.socketio,
socket_conn_id=socket_conn_id,
data_length=data_length
)
# Define the backtest key for caching
backtest_key = f"backtest:{user_name}:{backtest_name}"
@ -808,9 +827,9 @@ class Backtester:
self.update_strategy_stats(user_id_int, strategy_name, results)
# Emit the results back to the client
self.socketio.emit(
'backtest_results',
{"test_id": backtest_name, "results": results},
self.socketio.emit('message',
{"reply": 'backtest_results',
"data": {'test_id': backtest_name, "results": results}},
room=socket_conn_id
)
logger.info(f"[BACKTEST COMPLETE] Results emitted to user '{user_name}'.")
@ -830,7 +849,7 @@ class Backtester:
)
logger.info(f"Backtest '{backtest_name}' started for user '{user_name}'.")
return {"reply": "backtest_started"}
return {"status": "started", "backtest_name": backtest_name}
def start_periodic_purge(self, interval_seconds: int = 3600):
"""

View File

@ -6,11 +6,57 @@ class Backtesting {
this.target_id = 'backtest_display'; // The container to display backtests
// Register handlers for backtesting messages
this.comms.on('backtest_error', this.handleBacktestError.bind(this));
this.comms.on('backtest_submitted', this.handleBacktestSubmitted.bind(this));
this.comms.on('backtest_results', this.handleBacktestResults.bind(this));
this.comms.on('progress', this.handleProgress.bind(this));
this.comms.on('backtest_progress', this.handleProgress.bind(this));
this.comms.on('backtests_list', this.handleBacktestsList.bind(this));
this.comms.on('backtest_deleted', this.handleBacktestDeleted.bind(this));
this.comms.on('updates', this.handleUpdates.bind(this));
}
handleBacktestSubmitted(data) {
console.log("Backtest response received:", data.status);
if (data.status === "started") {
// Show the progress bar or any other UI updates
this.showRunningAnimation();
// Add the new backtest to the tests array
const newTest = {
name: data.backtest_name,
strategy: data.strategy_name,
start_date: data.start_date,
// Include any other relevant data from the response if available
};
this.tests.push(newTest);
// Update the HTML to reflect the new backtest
this.updateHTML();
}
}
handleBacktestError(data) {
console.error("Backtest error:", data.message);
// Display error message in the status message area
const statusMessage = document.getElementById('backtest-status-message');
if (statusMessage) {
statusMessage.style.display = 'block';
statusMessage.style.color = 'red'; // Set text color to red for errors
statusMessage.textContent = `Backtest error: ${data.message}`;
} else {
// Fallback to alert if the element is not found
alert(`Backtest error: ${data.message}`);
}
// Optionally hide progress bar and results
const progressContainer = document.getElementById('backtest-progress-container');
if (progressContainer) {
progressContainer.classList.remove('show');
}
const resultsContainer = document.getElementById('backtest-results');
if (resultsContainer) {
resultsContainer.style.display = 'none';
}
}
handleBacktestResults(data) {
@ -25,6 +71,7 @@ class Backtesting {
this.updateProgressBar(data.progress);
}
handleBacktestsList(data) {
console.log("Backtests list received:", data.tests);
// Logic to update backtesting UI
@ -37,48 +84,149 @@ class Backtesting {
this.fetchSavedTests();
}
handleUpdates(data) {
const { trade_updts } = data;
if (trade_updts) {
this.ui.trade.update_received(trade_updts);
}
}
updateProgressBar(progress) {
const progressBar = document.getElementById('progress_bar');
if (progressBar) {
console.log(`Updating progress bar to ${progress}%`);
progressBar.style.width = `${progress}%`;
progressBar.textContent = `${progress}%`;
} else {
console.log('Progress bar element not found');
}
}
showRunningAnimation() {
const resultsContainer = document.getElementById('backtest-results');
const resultsDisplay = document.getElementById('results_display');
const progressContainer = document.getElementById('backtest-progress-container');
const progressBar = document.getElementById('progress_bar');
const statusMessage = document.getElementById('backtest-status-message');
resultsContainer.style.display = 'none';
progressContainer.style.display = 'block';
progressContainer.classList.add('show'); // Use class to control display
progressBar.style.width = '0%';
progressBar.textContent = '0%';
resultsDisplay.innerHTML = '';
statusMessage.style.display = 'block';
statusMessage.style.color = 'blue'; // Reset text color to blue
statusMessage.textContent = 'Backtest started...';
}
displayTestResults(results) {
const resultsContainer = document.getElementById('backtest-results');
const resultsDisplay = document.getElementById('results_display');
resultsContainer.style.display = 'block';
resultsDisplay.innerHTML = `<pre>${JSON.stringify(results, null, 2)}</pre>`;
// Calculate total return
const totalReturn = (((results.final_portfolio_value - results.initial_capital) / results.initial_capital) * 100).toFixed(2);
// Create HTML content
let html = `
<span><strong>Initial Capital:</strong> ${results.initial_capital}</span>
<span><strong>Final Portfolio Value:</strong> ${results.final_portfolio_value}</span>
<span><strong>Total Return:</strong> ${totalReturn}%</span>
<span><strong>Run Duration:</strong> ${results.run_duration.toFixed(2)} seconds</span>
`;
// Add a container for the chart
html += `<h4>Equity Curve</h4>
<div id="equity_curve_chart" style="width: 100%; height: 300px;"></div>`;
// If there are trades, display them
if (results.trades && results.trades.length > 0) {
html += `<h4>Trades Executed</h4>
<div style="max-height: 200px; overflow-y: auto;">
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<th>Trade ID</th>
<th>Size</th>
<th>Price</th>
<th>P&L</th>
</tr>`;
results.trades.forEach(trade => {
html += `<tr>
<td>${trade.ref}</td>
<td>${trade.size}</td>
<td>${trade.price}</td>
<td>${trade.pnl}</td>
</tr>`;
});
html += `</table></div>`;
} else {
html += `<p>No trades were executed.</p>`;
}
resultsDisplay.innerHTML = html;
// Generate the equity curve chart
this.drawEquityCurveChart(results.equity_curve);
}
drawEquityCurveChart(equityCurve) {
const chartContainer = document.getElementById('equity_curve_chart');
if (!chartContainer) {
console.error('Chart container not found');
return;
}
// Get the dimensions of the container
const width = chartContainer.clientWidth || 600;
const height = chartContainer.clientHeight || 300;
const padding = 40;
// Find min and max values
const minValue = Math.min(...equityCurve);
const maxValue = Math.max(...equityCurve);
// Avoid division by zero if all values are the same
const valueRange = maxValue - minValue || 1;
// Normalize data points
const normalizedData = equityCurve.map((value, index) => {
const x = padding + (index / (equityCurve.length - 1)) * (width - 2 * padding);
const y = height - padding - ((value - minValue) / valueRange) * (height - 2 * padding);
return { x, y };
});
// Create SVG content
let svgContent = `<svg width="${width}" height="${height}">`;
// Draw axes
svgContent += `<line x1="${padding}" y1="${height - padding}" x2="${width - padding}" y2="${height - padding}" stroke="black"/>`; // X-axis
svgContent += `<line x1="${padding}" y1="${padding}" x2="${padding}" y2="${height - padding}" stroke="black"/>`; // Y-axis
// Draw equity curve
svgContent += `<polyline points="`;
normalizedData.forEach(point => {
svgContent += `${point.x},${point.y} `;
});
svgContent += `" fill="none" stroke="blue" stroke-width="2"/>`;
// Close SVG
svgContent += `</svg>`;
// Set SVG content
chartContainer.innerHTML = svgContent;
}
stopRunningAnimation(results) {
const progressContainer = document.getElementById('backtest-progress-container');
progressContainer.style.display = 'none';
progressContainer.classList.remove('show');
// Hide the status message
const statusMessage = document.getElementById('backtest-status-message');
statusMessage.style.display = 'none';
statusMessage.textContent = '';
this.displayTestResults(results);
}
fetchSavedTests() {
this.comms.sendToApp('get_backtests', { user_name: this.ui.data.user_name });
}
@ -151,6 +299,10 @@ class Backtesting {
closeForm() {
document.getElementById("backtest_form").style.display = "none";
// Hide and clear the status message
const statusMessage = document.getElementById('backtest-status-message');
statusMessage.style.display = 'none';
statusMessage.textContent = '';
}
clearForm() {

View File

@ -62,12 +62,17 @@ class Comms {
} else if (data.reply === 'error') {
console.error('Socket.IO: Authentication error:', data.data);
// Optionally, handle authentication errors (e.g., redirect to login)
} else if (data.reply === 'progress') {
// Handle progress updates specifically
console.log('Progress update received:', data.data.progress);
this.emit('backtest_progress', data.data);
} else {
// Emit the event to registered handlers
this.emit(data.reply, data.data);
}
});
}
/**
* Flushes the message queue by sending all queued messages.
*/

View File

@ -40,8 +40,11 @@
<button id="backtest-submit-edit" type="button" class="btn next" onclick="UI.backtesting.submitTest('edit')" style="display:none;">Edit Test</button>
</div>
<!-- Status message (Initially Hidden) -->
<div id="backtest-status-message" style="display: none; margin-top: 10px; color: blue;"></div>
<!-- Progress bar (Initially Hidden) -->
<div id="backtest-progress-container" style="display: none; margin-top: 10px;">
<div id="backtest-progress-container">
<div id="progress_bar" style="width: 0%; height: 20px; background-color: green; text-align: center; color: white;">
0%
</div>
@ -101,5 +104,31 @@
width: 20px;
height: 20px;
}
#backtest-progress-container {
display: none; /* Initially hidden */
margin-top: 10px;
}
#backtest-progress-container.show {
display: block; /* Show when backtest is running */
}
#results_display {
margin-top: 10px;
}
#results_display table {
width: 100%;
border-collapse: collapse;
}
#results_display th, #results_display td {
border: 1px solid #ccc;
padding: 5px;
text-align: center;
}
#results_display th {
background-color: #f2f2f2;
}
</style>