Untitled
unknown
plain_text
5 months ago
6.0 kB
4
Indexable
import yfinance as yf import numpy as np import pandas as pd import matplotlib.pyplot as plt # ======================================================== # PARAMETERS # ======================================================== # Define backtest period and initial investment settings start_date = "2022-12-08" end_date = "2023-12-09" initial_capital = 100000.0 # Starting portfolio value # Constant daily return for "box" strategy daily_box_return = 0.45 / 100 / 21 # About 0.00214% daily return # Technical indicator configurations rsi_period = 6 # Relative Strength Index (RSI) calculation period ma_period = 5 # Moving average (MA) window size (in days) # Categorize tickers by strategy low_ma_tickers = ["TQQQ", "LABU", "SPXL"] # High-risk, high-reward assets high_ma_tickers = ["WMT", "MSTR", "AMZN", "KO", "BRK-B", "AAPL", "TSLA", "NVDA"] # Stable or growth-focused all_tickers = ["SPY"] + low_ma_tickers + high_ma_tickers # Combine all tickers # ======================================================== # FUNCTIONS # ======================================================== def download_data(tickers, start, end): """ Fetch historical price data for a list of tickers from Yahoo Finance. """ data = yf.download(tickers, start=start, end=end, timeout=90) prices = data['Adj Close'] return prices def compute_RSI(prices, period): """ Calculate the Relative Strength Index (RSI) for a price series. """ delta = prices.diff() up = delta.clip(lower=0) down = -1 * delta.clip(upper=0) ema_up = up.ewm(com=period-1, adjust=False).mean() ema_down = down.ewm(com=period-1, adjust=False).mean() rs = ema_up / ema_down rsi = 100 - (100 / (1 + rs)) return rsi # ======================================================== # MAIN BACKTEST LOGIC # ======================================================== # Load price data for all selected tickers prices = download_data(all_tickers, start_date, end_date) # Ensure consistency in column naming for BRK-B if 'BRK-B' not in prices.columns: for col in prices.columns: if 'BRK' in col and '.' in col: prices.rename(columns={col: 'BRK-B'}, inplace=True) break # Clean the price data prices.dropna(how='all', inplace=True) prices.fillna(method='ffill', inplace=True) # Compute technical indicators spy_rsi = compute_RSI(prices["SPY"], period=rsi_period) # RSI for SPY ma_low = prices[low_ma_tickers].rolling(ma_period).mean() # Moving averages for "low MA" group ma_high = prices[high_ma_tickers].rolling(ma_period).mean() # Moving averages for "high MA" group # Initialize portfolio and track performance dates = prices.index capital = initial_capital portfolio_values = pd.Series(index=dates, dtype=float) portfolio_values.iloc[0] = capital chosen_ticker = None # Ticker chosen at the end of each day # Simulate daily portfolio performance for i in range(len(dates)): today = dates[i] # Realize returns for the previous day's choice if i > 0: yesterday = dates[i - 1] if chosen_ticker == "BOX": # Apply box strategy daily return capital *= (1 + daily_box_return) else: # Calculate actual daily return for the chosen ticker if chosen_ticker in prices.columns: prev_price = prices.loc[yesterday, chosen_ticker] curr_price = prices.loc[today, chosen_ticker] daily_ret = (curr_price / prev_price) - 1 capital *= (1 + daily_ret) else: # Fallback to box return if unexpected issue capital *= (1 + daily_box_return) # Record portfolio value for today portfolio_values.loc[today] = capital # Decide what to hold for tomorrow if i < len(dates) - 1: # Skip decision for the last day current_rsi = spy_rsi.loc[today] if today in spy_rsi.index else np.nan if pd.isna(current_rsi): # Default to box strategy if RSI data is unavailable chosen_ticker = "BOX" else: if current_rsi >= 90: # Overbought - default to box chosen_ticker = "BOX" elif current_rsi <= 28: # Oversold - pick ticker with lowest MA in low MA group current_mas = ma_low.loc[today] chosen_ticker = current_mas.idxmin() if not current_mas.isna().any() else "BOX" else: # Neutral RSI - pick ticker with highest MA in high MA group current_mas = ma_high.loc[today] chosen_ticker = current_mas.idxmax() if not current_mas.isna().any() else "BOX" # ======================================================== # RESULTS # ======================================================== # Calculate final metrics final_value = capital total_return = (final_value / initial_capital - 1) * 100 num_days = max(len(dates) - 1, 1) cagr = ((final_value / initial_capital)**(252 / num_days) - 1) * 100 if num_days > 1 else 0.0 # Print results print("========================================================") print(f"Final Capital: ${final_value:,.2f}") print(f"Total Return: {total_return:.2f}%") print(f"CAGR: {cagr:.2f}%") print("========================================================") # Plot equity curve plt.figure(figsize=(12, 6)) plt.plot(portfolio_values, label="Equity Curve", color="blue") plt.title("Equity Curve") plt.xlabel("Date") plt.ylabel("Portfolio Value") plt.legend() plt.grid() plt.show() # Analyze drawdown peak = portfolio_values.cummax() drawdown = (portfolio_values - peak) / peak max_drawdown = drawdown.min() print("========================================================") print(f"Maximum Drawdown: {max_drawdown * 100:.2f}%") print("========================================================")
Editor is loading...
Leave a Comment