How I Got the AI to Create, Code, and Backtest a Quant Portfolio in Python Without Writing a Single Line of Code!
I wanted to explore the new capabilities of the Advanced Data Analysis module, available in the GPT-4 options. I provided ChatGPT with historical daily prices for SPY, TLT, and GLD, and asked it to devise a trading strategy with a strong Sharpe ratio. Simple enough, right?
Here’s the prompt I gave to GPT, enabled with Beta Advanced Data Analysis:
I allowed it the freedom to choose one or all of the instruments and asked what the next steps would be. It responded with a list of Preliminary Steps.
It proposed starting by loading the data, to which I agreed.
A small window then appeared, stating “Working.” What was happening in the background? The AI was writing and executing Python code!
ChatGPT read the CSV data into a Pandas DataFrame. What’s a Pandas DataFrame? Well, thanks to AI, you don’t need to know (although you really should)!
Once it finished, it informed me about the data format, dates, etc., and suggested next steps:
Data Alignment and Feature Engineering! “Sure, go for it,” I said.
It proceeded to run a script that determined when each price history started, aligned the data, and calculated two simple moving averages.
Next steps included Correlation Analysis, Hypothesis Generation, and Initial Backtesting. “Not .bad..”, I thought to myself.
“Go ahead.”
It tried to write code to generate the correlation but encountered an error while creating the heat-map chart. It apologised and asked if displaying the correlation table alone would suffice. I agreed. So it gave me a table of correlations, and asked whether to move to the next steps.
Upon inspecting the table, I realized that the correlation numbers didn’t make sense. For instance, SPY and TLT had a correlation of 0.791? Clearly, it had run a correlation analysis on all the data. What was needed was a 1-month average correlation. Coding that in Python? No need!
I pointed out the issue, and it responded, “Good catch!” It then suggested using a rolling correlation analysis.
“Yes, I would prefer to see the average 1-month correlation,” I replied.
It showed me the updated correlation numbers: negative for SPY/TLT and neutral for SPY/GLD, which sounded about right. The Python code it used for this was surprisingly simple:
# Calculate the 1-month (approx. 21 trading days) rolling correlation
rolling_correlation = merged_data[['Adj Close_SPY', 'Adj Close_TLT', 'AdjClose_GLD']].rolling(window=21).corr(pairwise=True)
# Take the mean of the rolling correlations to get the average 1-month correlation average_rolling_correlation = rolling_correlation.groupby(level=1).mean()
It then asked if I wanted to proceed to formulate trading rules. “Yes!” I exclaimed.
It proposed three simplistic strategies:
- A moving average 50/200 SMA crossover for SPY.
- A TLT ‘hedge’ strategy based on the TLT/SPY correlation.
- A GLD ‘diversification’ strategy based on buying GLD when SPY and TLT underperformed.
I agreed to proceed, one strategy at a time.
It clearly stated the rules for the 500/200 SMA crossover strategy for SPY and executed the Python code.
It returned the final portfolio value and return. Below is the python code it wrote and executed:
# Initialize variables for backtesting the SMA crossover strategy for SPY
initial_investment = 10000 # USD
cash = initial_investment
stock_quantity = 0
buy_price = 0 # Price at which the stock was bought
portfolio_value = [] # To store the value of the portfolio at each time step
# Flags to indicate the state of the portfolio
in_position = False # True if currently holding the stock
# Loop through the SPY data to simulate the trading
for i in range(len(spy_data_aligned)):
row = spy_data_aligned.iloc[i]
date = row['Date']
price = row['Adj Close']
sma_50 = row['SMA_50']
sma_200 = row['SMA_200']
# Skip rows where the SMA values are not available
if pd.isna(sma_50) or pd.isna(sma_200):
portfolio_value.append(cash)
continue
# Check for Buy signal
if sma_50 > sma_200 and not in_position:
# Buy the stock
stock_quantity = cash // price # Floor division to get the integer number of stocks that can be bought
buy_price = price
cash -= stock_quantity * buy_price # Deduct the investment from cash
in_position = True
# Check for Sell signal
if sma_50 < sma_200 and in_position:
# Sell the stock
cash += stock_quantity * price
stock_quantity = 0
in_position = False
# Calculate the current value of the portfolio
current_portfolio_value = cash + stock_quantity * price
portfolio_value.append(current_portfolio_value)
# Calculate the final returns
final_portfolio_value = portfolio_value[-1]
total_return = (final_portfolio_value - initial_investment) / initial_investment * 100 # in percentage
total_return, final_portfolio_value
I asked it to plot the equity curve and compare it against SPY as a benchmark. All good.
I continued in the same manner with the TLT and GLD strategies.
After finalising the three strategies, I asked ChatGPT to allocate a piece of the portfolio to each strategy, based on it’s Sharpe ratio. Despite some hiccups, it constructed a portfolio with a combination of the three rule-based strategies, allocating more to the best ones.. Overall, an impressive feat for a text-based chatbot!
You can see the conversation here.
I did not record this conversation but you can see a similar one here.
Conclusion
To be honest it surprised me! Using ChatGPT equipped with the Advanced Data Analysis tool is akin to having a junior coder sitting right beside you. It’s incredibly useful for tackling trading questions that might be tedious to code but are easy to understand conceptually.
For instance: “Find out what would happen if you bought SPY shares each Monday and sold them on a Tuesday for the past 10 years”. It is also very useful for running sophisticated analysis using Python libraries that are time-consuming to learn. Like SciPy to cluster ETFs based on their correlation.
For more advanced analysis it needs an experienced operator/coder. It makes mistakes that are difficult to spot and hard to debug as they are hidden away in code that is written as ‘language’ not as ‘logic’. When it gets too messy, writing the code from scratch can sometimes be easier.
This tool was not meant for backtesting trading strategies. Amazingly it does a great job with the basics.