feat: Add comprehensive multi-ticker testing with batch processing and advanced filtering

This commit is contained in:
Bobby (aider) 2025-02-14 00:12:07 -08:00
parent 1204b18a76
commit a4e828da8c

View File

@ -2,6 +2,7 @@ import streamlit as st
import pandas_ta as ta import pandas_ta as ta
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from utils.common_utils import get_qualified_stocks
from backtesting import Backtest, Strategy from backtesting import Backtest, Strategy
from typing import Dict, List, Union from typing import Dict, List, Union
import itertools import itertools
@ -232,27 +233,59 @@ def backtesting_page():
with left_col: with left_col:
st.subheader("Backtest Settings") st.subheader("Backtest Settings")
# Add radio button for single/multiple ticker mode # Add radio button for test mode
test_mode = st.radio("Testing Mode", ["Single Ticker", "Multiple Tickers"]) test_mode = st.radio("Testing Mode", ["Single Ticker", "Multiple Tickers", "All Available Tickers"])
if test_mode == "Single Ticker": if test_mode == "Single Ticker":
# Single ticker input # Single ticker input
ticker = st.text_input("Enter Ticker Symbol", value="AAPL").upper() ticker = st.text_input("Enter Ticker Symbol", value="AAPL").upper()
tickers = [ticker] tickers = [ticker]
else: elif test_mode == "Multiple Tickers":
# Multiple ticker input # Multiple ticker input
ticker_input = st.text_area( ticker_input = st.text_area(
"Enter Ticker Symbols (one per line)", "Enter Ticker Symbols (one per line)",
value="AAPL\nMSFT\nGOOG" value="AAPL\nMSFT\nGOOG"
) )
tickers = [t.strip().upper() for t in ticker_input.split('\n') if t.strip()] tickers = [t.strip().upper() for t in ticker_input.split('\n') if t.strip()]
else: # All Available Tickers
st.subheader("Filter Settings")
min_price = st.number_input("Minimum Price", value=5.0)
max_price = st.number_input("Maximum Price", value=1000.0)
min_volume = st.number_input("Minimum Volume", value=100000)
# Add minimum performance filters # Get all qualified stocks based on filters
try:
qualified_stocks = get_qualified_stocks(
start_date=start_datetime,
end_date=end_datetime,
min_price=min_price,
max_price=max_price,
min_volume=min_volume
)
st.info(f"Found {len(qualified_stocks)} qualified stocks for testing")
tickers = qualified_stocks
except Exception as e:
st.error(f"Error getting qualified stocks: {str(e)}")
tickers = []
# Add performance filters for multiple and all tickers modes
if test_mode in ["Multiple Tickers", "All Available Tickers"]:
st.subheader("Performance Filters") st.subheader("Performance Filters")
col1, col2, col3 = st.columns(3)
with col1:
min_return = st.number_input("Minimum Return (%)", value=10.0) min_return = st.number_input("Minimum Return (%)", value=10.0)
with col2:
min_sharpe = st.number_input("Minimum Sharpe Ratio", value=1.0) min_sharpe = st.number_input("Minimum Sharpe Ratio", value=1.0)
with col3:
max_drawdown = st.number_input("Maximum Drawdown (%)", value=-20.0) max_drawdown = st.number_input("Maximum Drawdown (%)", value=-20.0)
# Add batch size control for processing
batch_size = st.number_input("Batch Size (tickers per batch)", value=50, min_value=1)
# Add progress tracking
progress_bar = st.progress(0)
status_text = st.empty()
# Date range selection # Date range selection
col1, col2 = st.columns(2) col1, col2 = st.columns(2)
with col1: with col1:
@ -329,12 +362,32 @@ def backtesting_page():
st.error(f"Error during backtest: {str(e)}") st.error(f"Error during backtest: {str(e)}")
else: else:
# Multiple ticker logic # Multiple ticker or All Available Tickers logic
try: try:
total_tickers = len(tickers)
processed_tickers = []
all_results = []
for i in range(0, total_tickers, batch_size):
batch = tickers[i:i+batch_size]
status_text.text(f"Processing batch {i//batch_size + 1} of {(total_tickers + batch_size - 1)//batch_size}")
results_df = run_multi_ticker_backtest( results_df = run_multi_ticker_backtest(
tickers, start_datetime, end_datetime, indicator_settings batch, start_datetime, end_datetime, indicator_settings
) )
if not results_df.empty:
all_results.append(results_df)
processed_tickers.extend(batch)
# Update progress
progress = min((i + batch_size) / total_tickers, 1.0)
progress_bar.progress(progress)
if all_results:
# Combine all results
results_df = pd.concat(all_results, ignore_index=True)
# Apply performance filters # Apply performance filters
filtered_df = results_df[ filtered_df = results_df[
(results_df['Return [%]'] >= min_return) & (results_df['Return [%]'] >= min_return) &
@ -348,8 +401,17 @@ def backtesting_page():
# Display summary statistics # Display summary statistics
st.write("### Summary Statistics") st.write("### Summary Statistics")
summary = pd.DataFrame({ summary = pd.DataFrame({
'Metric': ['Average Return', 'Average Sharpe', 'Average Drawdown', 'Success Rate'], 'Metric': [
'Total Tickers Tested',
'Successful Tests',
'Average Return',
'Average Sharpe',
'Average Drawdown',
'Success Rate'
],
'Value': [ 'Value': [
f"{len(processed_tickers)}",
f"{len(results_df)}",
f"{results_df['Return [%]'].mean():.2f}%", f"{results_df['Return [%]'].mean():.2f}%",
f"{results_df['Sharpe Ratio'].mean():.2f}", f"{results_df['Sharpe Ratio'].mean():.2f}",
f"{results_df['Max Drawdown [%]'].mean():.2f}%", f"{results_df['Max Drawdown [%]'].mean():.2f}%",
@ -375,9 +437,12 @@ def backtesting_page():
"text/csv", "text/csv",
key='download-csv' key='download-csv'
) )
else:
st.error("No valid results were generated from any ticker")
except Exception as e: except Exception as e:
st.error(f"Error during multi-ticker backtest: {str(e)}") st.error(f"Error during multi-ticker backtest: {str(e)}")
st.error("Full error details:", exc_info=True)
def run_optimization(df: pd.DataFrame, indicator_settings: Dict) -> List: def run_optimization(df: pd.DataFrame, indicator_settings: Dict) -> List:
"""Run optimization with different parameter combinations""" """Run optimization with different parameter combinations"""