Untitled
unknown
plain_text
a year ago
6.0 kB
23
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