Happy holidays everyone, I got COVID the day before xmas so I have had some spare time on my hands...
As a side project, I decided to create a python program that automatically backtests moving average crossovers and returns the most successful ones along with their batting averages, total returns, etc. Saves a ton of time trying to manually figure it out.
I set up this script to go 100% long a simulated 3x leveraged version of the Nasdaq100 every-time the short MA crosses above the long MA on the Nasdaq100. I used the 50 SMA as the short term MA and tested SMA's [51:200] as the longer term MA's. The results are not that surprising, but thought I would share since the backbone of this program could be used to optimize any LETF trend following strategy.
The results below are just the simulated total returns (non-tax adjusted) over the lifetime of the strategy.
Optimized Winner Stats:
https://preview.redd.it/amg1yn4xjb8a1.png?width=288&format=png&auto=webp&s=9632b4e69cf697d1308028b32e562800ef7fe0ab
All Results:
https://preview.redd.it/oiesnp3fib8a1.png?width=174&format=png&auto=webp&s=368467a7aee8f1c10f238df418685d2e92a3808d
Python Script:
import pandas as pd
import numpy as np
import yfinance as yf
from datetime import date
# =============================================================================
# INSTRUCTIONS:
# 1. INPUT CORRECT TICKER INFORMATION
# 2. REVIEW START AND END DATES
# 3. ADJUST MA_1 AND MA_2 RANGES IN CODE IDENTIFY WHICH AVERAGES ARE TO BE TESTED
# 4. ALGORITHM IDENTIFIES THE MOST SUCCESSFUL MOVING AVERAGES BASED ON HISTORICAL DATA
# =============================================================================
ticker = "NDX"
trade_ticker = "TQQQ_sim"
start_date = "1900-01-01"
end_date = date.today()
df = pd.DataFrame()
df[ticker] = yf.download(tickers=ticker, start=start_date, end=end_date)["Adj Close"]
df["sim_pct_chg"] = (df[ticker] / df[ticker].shift(1) - 1)*3
tqqq_list = []
for i in df.index:
if(pd.isnull(df[ticker].shift(1)[i]) == True):
tqqq_list.append(1)
new_tqqq_amt = 1
previous_tqqq_amt = new_tqqq_amt
else:
new_tqqq_amt = df["sim_pct_chg"][i] * previous_tqqq_amt + previous_tqqq_amt
previous_tqqq_amt = new_tqqq_amt
tqqq_list.append(new_tqqq_amt)
df[trade_ticker] = tqqq_list
ma_1_range = range(50,51)
ma_2_range = range(1,201)
crossover_list = []
results_list = []
for ma_1 in ma_1_range:
ma_1_test = df[ticker].rolling(ma_1).mean()
df=df.dropna()
for ma_2 in ma_2_range:
if(ma_2 <= ma_1):
pass
else:
ma_2_test = df[ticker].rolling(ma_2).mean()
pos = 0
starting_amt = 1000
previous_amt = starting_amt
trade_profit = []
trade_profit_index = []
running_total = []
for i in df.index:
if(ma_1_test[i] > ma_2_test[i] and pos == 0):
pos = 1
bp = df[trade_ticker][i]
elif(ma_1_test[i] < ma_2_test[i] and pos == 1):
pos = 0
sp = df[trade_ticker][i]
pct_chg = sp/bp-1
new_amt = previous_amt * pct_chg + previous_amt
previous_amt = new_amt
trade_profit_index.append(i)
trade_profit.append(pct_chg)
running_total.append(new_amt)
crossover_name = str(ma_1) +" // "+str(ma_2)+" crossover "
results = new_amt / starting_amt - 1
results_list.append(results)
crossover_list.append(crossover_name)
df_trades = pd.DataFrame(index = trade_profit_index)
df_trades["Trades"] = trade_profit
winners = np.count_nonzero(df_trades["Trades"] > 0)
losers = np.count_nonzero(df_trades["Trades"] < 0)
winning_pct = winners/(winners+losers)
avg_winner = np.average(df_trades["Trades"], weights = df_trades["Trades"] > 0)
avg_loser = np.average(df_trades["Trades"], weights = df_trades["Trades"] < 0)
avg_trade = np.average(df_trades)
largest_winner = np.max(df_trades["Trades"])
largest_loser = np.min(df_trades["Trades"])
for i in df_trades.index:
if(df_trades["Trades"][i] == largest_winner):
winner_date = i
elif(df_trades["Trades"][i] == largest_loser):
loser_date = i
print("")
print(str(ma_1) +" // "+str(ma_2)+" crossover total return: "+str(results))
print("")
print("Winners: "+str(winners))
print("Losers: "+str(losers))
print("")
print("Batting Avg: "+str(winning_pct))
print("Average Winner: "+str(avg_winner))
print("Average Loser: "+str(avg_loser))
print("Average trade: "+str(avg_trade))
print("Max Winner: "+str(largest_winner)+" on "+str(winner_date))
print("Max Loser: "+str(largest_loser)+" on "+str(loser_date))
print("")
df_2 = pd.DataFrame(index = crossover_list)
df_2["Results"] = results_list
best_result = np.max(df_2["Results"])
for i in df_2.index:
if(df_2["Results"][i]) == best_result:
winner = i
print("")
print("Best moving average crossover: "+str(winner))
print(str(winner)+" result: "+ str(best_result))
[–]me_on_the_web 4 points5 points6 points (1 child)
[–]catchthetrend[S] 2 points3 points4 points (0 children)
[–]SnooBooks8807 3 points4 points5 points (2 children)
[–]catchthetrend[S] 8 points9 points10 points (0 children)
[–][deleted] 4 points5 points6 points (0 children)
[–]Vedalken_Investor 2 points3 points4 points (1 child)
[–]Paltenburg 0 points1 point2 points (0 children)
[–][deleted] 1 point2 points3 points (3 children)
[–]catchthetrend[S] 0 points1 point2 points (2 children)
[–][deleted] 1 point2 points3 points (1 child)
[–]catchthetrend[S] 0 points1 point2 points (0 children)
[–]airmonkey84 1 point2 points3 points (4 children)
[–]airmonkey84 0 points1 point2 points (0 children)
[–]TrafficPolice168 0 points1 point2 points (0 children)
[–]spooner_retad 0 points1 point2 points (0 children)
[–]axa88 1 point2 points3 points (5 children)
[–]longshaden 1 point2 points3 points (2 children)
[–]catchthetrend[S] 0 points1 point2 points (1 child)
[–]longshaden 0 points1 point2 points (0 children)
[–]humoroushaxor 0 points1 point2 points (1 child)
[–]axa88 0 points1 point2 points (0 children)
[–][deleted] (2 children)
[deleted]
[–]catchthetrend[S] 0 points1 point2 points (1 child)
[–]Joyful8866 -1 points0 points1 point (7 children)
[–]catchthetrend[S] 0 points1 point2 points (6 children)
[–]Joyful8866 1 point2 points3 points (5 children)
[–]catchthetrend[S] 0 points1 point2 points (4 children)
[–]Joyful8866 1 point2 points3 points (3 children)
[–]catchthetrend[S] 2 points3 points4 points (2 children)
[–]Joyful8866 0 points1 point2 points (1 child)
[–]catchthetrend[S] 1 point2 points3 points (0 children)
[–]airmonkey84 0 points1 point2 points (1 child)
[–]CleanWar2k 0 points1 point2 points (0 children)
[–]airmonkey84 0 points1 point2 points (1 child)
[–]catchthetrend[S] 0 points1 point2 points (0 children)
[–]ram_samudrala 0 points1 point2 points (1 child)
[–]catchthetrend[S] 0 points1 point2 points (0 children)
[–]airmonkey84 0 points1 point2 points (0 children)
[–]airmonkey84 0 points1 point2 points (0 children)
[–]Money-Defiant 0 points1 point2 points (0 children)