How to code your own stock backtester [PART 3: Backtesting your strategy]

How much money does this strategy make based on historical data?

Lucas Moyer
The Koi Life
5 min readDec 20, 2023

--

Alright, here comes the sandwich.

We got our ingredients (got stock data using yahoo finance)

We know the recipe (we created a trading indicator using the MACD strategy)

Now we need to figure out if our ingredients and recipe make a good sandwich, in other words…

DO WE MAKE MONEY WHEN WE DO THIS?

Let’s get started.

If you have been following along with my previous articles, we have data that is in a pandas dataframe that looks something like this:

T stock data with some technical indicators we made in previous articles

We are going to use this “macd_norm” column to determine how much stock to buy. The thinking is that when the short moving average crosses the long moving average it indicates the stock is increasing in value and we should buy. When the macd_norm is negative, we can short or sell the stock.

Backtesting code

Let’s start with $1000.

To backtest our code, we will create a new column in our dataframe called “position” which is the exact same as our indicator, “macd_norm”. This indicates how many shares we will buy or sell. To visualize our stock trades better, I’ll increase the weight of our strategy by 100x which means multiplying our position by 100. Using the our position and the open price of the stock, we can determine if we can afford the stock trade we will do.

Check each line’s comments for how we set this up.

initial_capital = 1000 # our $1000
df['position'] = 100 * df['macd_norm'] # renaming macd_norm to position for readability and multiplying by 100 to increase the weight of the strategy
df['position'].at[0] = 0 # we set it to zero since we start with no stock position
df['position_diff'] = df['position'].diff() # gets the difference in positions so we know how to adjust our stock trades
df['position_diff'].at[0] = 0 # set to zero since it's NaN since there is no difference on the first day
df['holdings'] = 0 # represents how much our stock position is worth, starts at 0 since we don't have any stocks yet
df['cash'] = initial_capital # tracks how much cash we have
df.head()
Result of the above code

Next we are going to loop through our positions.

  1. First we check if we have enough cash for our trade. If so, we don’t need to adjust our position, only deduct how much cash was needed from our amount.
  2. If we don’t have enough cash, check if we are shorting the stock. If so, we update our cash and check if it brings us out of bankruptcy.
  3. If the above two conditions arn’t satisfied, it means we don’t have enough money and we are not shorting the stock. There are a number of things we can do if we don’t have enough money. Some people quit trading, and some take a second loan on their house and double down. In our case, if it’s the initial trade, we calculate the max amount of stock we could buy instead. If it’s not the initial trade, then we can just keep the previous day’s position.

Here is what I described above in code:

outOfCash = False
for index, row in df.iterrows():
if index==0:
continue
cash = df.loc[index -1]['cash']
position = df.loc[index]['position']
price = row['Open']
pos_diff = df.loc[index]['position'] - df.loc[index -1]['position']
cash_needed = pos_diff * price
df.at[index, 'cash_needed'] = cash_needed


if (cash > cash_needed) and (not outOfCash):
cash = cash - cash_needed
elif (cash_needed < 0):
cash = cash-cash_needed
if (cash > (position * price)):
outOfCash = False
else:
# ran out of money
if (index == 1):
df[index, position] = cash / price # when we don't have enough money for the initial trade, then we buy the most stock that we can afford
cash = 0
else:
df.at[index,'position'] = df.at[index -1, 'position'] # don't do anything since we are don't have enough money to trade on our startegy

outOfCash = True

df.at[index,'cash'] = cash

df['holdings'] = df['position'] * df['Open']
df['total'] = df['holdings'] + df['cash']
df.plot(x=f'Date', y='total')

Take your time to comb through the code above.
Here is how we did:

You can see that we end up with just under $1100. That’s about a 10% return on the year. That’s pretty good considering the stock price went down during that time.

Here is another visualization. Here you can see that when our position increases, we end up emptying our cash reserves (blue) to buy lots of stock (holdings are in orange). When the position (red) decreases, we end up selling some that stock.

Some notes on what we did and what’s next

We did it! We coded our own backtester, that tells us how our strategy performed. However, there is a lot more to how a portfolio performs than this code can capture. For example, fees, taxes, and slippage can hurt your returns.

Now that we can implement strategies, we are going to clean up and organize our code into a “Stock” python class. This will enable us to start implementing portfolios of stocks with more complicated strategies. For example, we can use modern portfolio theory to optimize our returns per our level or risk.

Thanks for reading!

We made it to the 4th Quarter! Finish strong!

--

--

Lucas Moyer
The Koi Life

I strive to wake up everyday and pursue what I find most interesting. Writer for The Startup. Owner of The Koi Life medium.com/lucas-moyer