The test are displaying in the user interface.
This commit is contained in:
parent
1af55f10a0
commit
0ae835d096
|
|
@ -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,11 +827,11 @@ 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},
|
||||
room=socket_conn_id
|
||||
)
|
||||
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}'.")
|
||||
finally:
|
||||
# Cleanup regardless of success or failure
|
||||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue