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)
|
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],
|
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.
|
Maps user strategy details into a Backtrader-compatible strategy class.
|
||||||
"""
|
"""
|
||||||
|
|
@ -176,6 +175,9 @@ class Backtester:
|
||||||
params = (
|
params = (
|
||||||
('mode', mode),
|
('mode', mode),
|
||||||
('strategy_instance', None), # Will be set during instantiation
|
('strategy_instance', None), # Will be set during instantiation
|
||||||
|
('socketio', socketio),
|
||||||
|
('socket_conn_id', socket_conn_id),
|
||||||
|
('data_length', data_length)
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -198,26 +200,35 @@ class Backtester:
|
||||||
# Initialize any other needed variables
|
# Initialize any other needed variables
|
||||||
self.starting_balance = self.broker.getvalue()
|
self.starting_balance = self.broker.getvalue()
|
||||||
|
|
||||||
|
# Existing code...
|
||||||
|
self.current_step = 0
|
||||||
|
self.last_progress = 0 # Initialize last_progress
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
# Increment pointers
|
|
||||||
for name in self.indicator_names:
|
|
||||||
self.indicator_pointers[name] += 1
|
|
||||||
|
|
||||||
# Increment current step
|
|
||||||
self.current_step += 1
|
self.current_step += 1
|
||||||
|
# Execute the strategy logic
|
||||||
# Generated strategy logic
|
|
||||||
try:
|
try:
|
||||||
# Execute the strategy logic via StrategyInstance
|
|
||||||
execution_result = self.strategy_instance.execute()
|
execution_result = self.strategy_instance.execute()
|
||||||
if not execution_result.get('success', False):
|
if not execution_result.get('success', False):
|
||||||
error_msg = execution_result.get('message', 'Unknown error during strategy execution.')
|
error_msg = execution_result.get('message', 'Unknown error during strategy execution.')
|
||||||
logger.error(f"Strategy execution failed: {error_msg}")
|
logger.error(f"Strategy execution failed: {error_msg}")
|
||||||
# Handle the failure (stop the strategy)
|
|
||||||
self.stop()
|
self.stop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in strategy execution: {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
|
return MappedStrategy
|
||||||
|
|
||||||
# Add custom handlers to the StrategyInstance
|
# Add custom handlers to the StrategyInstance
|
||||||
|
|
@ -703,7 +714,7 @@ class Backtester:
|
||||||
trades = self.parse_trade_analyzer(trade_analyzer)
|
trades = self.parse_trade_analyzer(trade_analyzer)
|
||||||
|
|
||||||
# Send 100% completion
|
# 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
|
# Prepare the results to pass into the callback
|
||||||
backtest_results = {
|
backtest_results = {
|
||||||
|
|
@ -721,7 +732,8 @@ class Backtester:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Handle exceptions and send error messages to the client
|
# Handle exceptions and send error messages to the client
|
||||||
error_message = f"Backtest execution failed: {str(e)}"
|
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)
|
logger.error(f"[BACKTEST ERROR] {error_message}", exc_info=True)
|
||||||
|
|
||||||
# Invoke callback with failure details to ensure cleanup
|
# 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
|
# Override any methods that access exchanges and market data with custom handlers for backtesting
|
||||||
strategy_instance = self.add_custom_handlers(strategy_instance)
|
strategy_instance = self.add_custom_handlers(strategy_instance)
|
||||||
|
|
||||||
# Map the user strategy to a Backtrader-compatible strategy class
|
data_length = len(data_feed)
|
||||||
mapped_strategy_class = self.map_user_strategy(user_strategy, precomputed_indicators)
|
|
||||||
|
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
|
# Define the backtest key for caching
|
||||||
backtest_key = f"backtest:{user_name}:{backtest_name}"
|
backtest_key = f"backtest:{user_name}:{backtest_name}"
|
||||||
|
|
@ -808,11 +827,11 @@ class Backtester:
|
||||||
self.update_strategy_stats(user_id_int, strategy_name, results)
|
self.update_strategy_stats(user_id_int, strategy_name, results)
|
||||||
|
|
||||||
# Emit the results back to the client
|
# Emit the results back to the client
|
||||||
self.socketio.emit(
|
self.socketio.emit('message',
|
||||||
'backtest_results',
|
{"reply": 'backtest_results',
|
||||||
{"test_id": backtest_name, "results": results},
|
"data": {'test_id': backtest_name, "results": results}},
|
||||||
room=socket_conn_id
|
room=socket_conn_id
|
||||||
)
|
)
|
||||||
logger.info(f"[BACKTEST COMPLETE] Results emitted to user '{user_name}'.")
|
logger.info(f"[BACKTEST COMPLETE] Results emitted to user '{user_name}'.")
|
||||||
finally:
|
finally:
|
||||||
# Cleanup regardless of success or failure
|
# Cleanup regardless of success or failure
|
||||||
|
|
@ -830,7 +849,7 @@ class Backtester:
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Backtest '{backtest_name}' started for user '{user_name}'.")
|
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):
|
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
|
this.target_id = 'backtest_display'; // The container to display backtests
|
||||||
|
|
||||||
// Register handlers for backtesting messages
|
// 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('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('backtests_list', this.handleBacktestsList.bind(this));
|
||||||
this.comms.on('backtest_deleted', this.handleBacktestDeleted.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) {
|
handleBacktestResults(data) {
|
||||||
|
|
@ -25,6 +71,7 @@ class Backtesting {
|
||||||
this.updateProgressBar(data.progress);
|
this.updateProgressBar(data.progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
handleBacktestsList(data) {
|
handleBacktestsList(data) {
|
||||||
console.log("Backtests list received:", data.tests);
|
console.log("Backtests list received:", data.tests);
|
||||||
// Logic to update backtesting UI
|
// Logic to update backtesting UI
|
||||||
|
|
@ -37,48 +84,149 @@ class Backtesting {
|
||||||
this.fetchSavedTests();
|
this.fetchSavedTests();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdates(data) {
|
|
||||||
const { trade_updts } = data;
|
|
||||||
if (trade_updts) {
|
|
||||||
this.ui.trade.update_received(trade_updts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProgressBar(progress) {
|
updateProgressBar(progress) {
|
||||||
const progressBar = document.getElementById('progress_bar');
|
const progressBar = document.getElementById('progress_bar');
|
||||||
if (progressBar) {
|
if (progressBar) {
|
||||||
|
console.log(`Updating progress bar to ${progress}%`);
|
||||||
progressBar.style.width = `${progress}%`;
|
progressBar.style.width = `${progress}%`;
|
||||||
progressBar.textContent = `${progress}%`;
|
progressBar.textContent = `${progress}%`;
|
||||||
|
} else {
|
||||||
|
console.log('Progress bar element not found');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
showRunningAnimation() {
|
showRunningAnimation() {
|
||||||
const resultsContainer = document.getElementById('backtest-results');
|
const resultsContainer = document.getElementById('backtest-results');
|
||||||
const resultsDisplay = document.getElementById('results_display');
|
const resultsDisplay = document.getElementById('results_display');
|
||||||
const progressContainer = document.getElementById('backtest-progress-container');
|
const progressContainer = document.getElementById('backtest-progress-container');
|
||||||
const progressBar = document.getElementById('progress_bar');
|
const progressBar = document.getElementById('progress_bar');
|
||||||
|
const statusMessage = document.getElementById('backtest-status-message');
|
||||||
|
|
||||||
resultsContainer.style.display = 'none';
|
resultsContainer.style.display = 'none';
|
||||||
progressContainer.style.display = 'block';
|
progressContainer.classList.add('show'); // Use class to control display
|
||||||
progressBar.style.width = '0%';
|
progressBar.style.width = '0%';
|
||||||
progressBar.textContent = '0%';
|
progressBar.textContent = '0%';
|
||||||
resultsDisplay.innerHTML = '';
|
resultsDisplay.innerHTML = '';
|
||||||
|
statusMessage.style.display = 'block';
|
||||||
|
statusMessage.style.color = 'blue'; // Reset text color to blue
|
||||||
|
statusMessage.textContent = 'Backtest started...';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
displayTestResults(results) {
|
displayTestResults(results) {
|
||||||
const resultsContainer = document.getElementById('backtest-results');
|
const resultsContainer = document.getElementById('backtest-results');
|
||||||
const resultsDisplay = document.getElementById('results_display');
|
const resultsDisplay = document.getElementById('results_display');
|
||||||
|
|
||||||
resultsContainer.style.display = 'block';
|
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) {
|
stopRunningAnimation(results) {
|
||||||
const progressContainer = document.getElementById('backtest-progress-container');
|
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);
|
this.displayTestResults(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fetchSavedTests() {
|
fetchSavedTests() {
|
||||||
this.comms.sendToApp('get_backtests', { user_name: this.ui.data.user_name });
|
this.comms.sendToApp('get_backtests', { user_name: this.ui.data.user_name });
|
||||||
}
|
}
|
||||||
|
|
@ -151,6 +299,10 @@ class Backtesting {
|
||||||
|
|
||||||
closeForm() {
|
closeForm() {
|
||||||
document.getElementById("backtest_form").style.display = "none";
|
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() {
|
clearForm() {
|
||||||
|
|
|
||||||
|
|
@ -62,12 +62,17 @@ class Comms {
|
||||||
} else if (data.reply === 'error') {
|
} else if (data.reply === 'error') {
|
||||||
console.error('Socket.IO: Authentication error:', data.data);
|
console.error('Socket.IO: Authentication error:', data.data);
|
||||||
// Optionally, handle authentication errors (e.g., redirect to login)
|
// 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 {
|
} else {
|
||||||
// Emit the event to registered handlers
|
// Emit the event to registered handlers
|
||||||
this.emit(data.reply, data.data);
|
this.emit(data.reply, data.data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flushes the message queue by sending all queued messages.
|
* 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>
|
<button id="backtest-submit-edit" type="button" class="btn next" onclick="UI.backtesting.submitTest('edit')" style="display:none;">Edit Test</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Status message (Initially Hidden) -->
|
||||||
|
<div id="backtest-status-message" style="display: none; margin-top: 10px; color: blue;"></div>
|
||||||
|
|
||||||
<!-- Progress bar (Initially Hidden) -->
|
<!-- 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;">
|
<div id="progress_bar" style="width: 0%; height: 20px; background-color: green; text-align: center; color: white;">
|
||||||
0%
|
0%
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -101,5 +104,31 @@
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 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>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue