Should You Buy the Dip or Just Hold?

Who would win?: Buy The Dip vs Dollar Cost Averaging vs Lumpsum Investing

Whether you are an experienced investor or just starting out,
you will see people talking about buy the dip vs dollar cost averaging.

“Investor” will sell you the idea about how time in the market beats timing the market.
“Speculator” will sell you the idea about how trend is your friend and why you should always follow the trend but not against it.

Both strategies sound reasonable, but here we only believe in cold hard data.

How do we know which strategy works the best?

Reading Data

I downloaded historical data of KLSE from Yahoo Finance in csv form.

We need to read it into memory to perform backtesting.

import pandas as pd
import datetime
import matplotlib.pyplot as plt
pd.options.mode.chained_assignment = None  # default='warn'
plt.style.use('seaborn')

period_type = 'M'

def read_and_process_data(name):
    df = pd.read_csv(name,parse_dates=['Date'])
    df = df.replace(',','', regex=True)
    df['Open'] = df['Open'].astype(float)
    df['Close'] = df['Close'].astype(float)

    data = df.resample(period_type, on = 'Date').last()
    data = data[data['Date'] > '01-01-2011']

    data['Open'] = data['Open'].resample(period_type).first()
    data['High'] = data['High'].resample(period_type).max()
    data['Low'] = data['Low'].resample(period_type).min()
    data['Close'] = data['Close'].resample(period_type).last()

    data['Change'] = ((data['Close'].shift(-1) - data['Close'])/data['Close']) + 1
    data['All_Time_High'] = data['Close'].cummax()
    data['Previous_Peak'] = data['Close'].rolling(24, min_periods = 1).max()
    data['Previous_Peak'] = data['Previous_Peak'].ffill()
    data['Changes_From_Previous_Peak'] = (data['Previous_Peak'] - data['Close'])/data['Previous_Peak']

    data.dropna(axis=1, how='all', inplace = True)
    data = data.fillna(1)    
    return data

data = read_and_process_data('MSGX.csv')
data.head()
DateOpenHighLowCloseAdj CloseChangeAll_Time_HighPrevious_PeakChanges_From_Previous_Peak
Date
2011-01-312011-01-313190.043280.773177.783179.723179.720.9467853179.723179.720.000000
2011-02-282011-02-283179.723232.992965.243010.513010.511.0316693179.723179.720.053215
2011-03-312011-03-313010.513115.472919.983105.853105.851.0215343179.723179.720.023232
2011-04-302011-04-303105.853208.343103.223172.733172.730.9959663179.723179.720.002198
2011-05-312011-05-313179.863182.463078.263159.933159.930.9875033179.723179.720.006224

Strategy

We are backtesting 4 different strategies here

  1. Buy fixed amount every month
  2. Save fixed amount every month, only buy when there is 20% dip from 24 months high
  3. Buy and hold from beginning
  4. Buy and hold from beginning, sell when price breaks all time high, buy when 20% dip

At the end, every strategy would have invested the same amount of money into the market.

initial_capital = 100
monthly_topup = 100
lumpsum = initial_capital + len(data)*monthly_topup

data['Capital_DCA'] = 0 # Buy fixed amount every month
data['Capital_buythedip'] = 0 # Save up fixed amount every month, only buy when there is 20% dip from previous peak
data['Capital_lumpsum'] = 0 # Buy and hold from beginning
data['Capital_lumpsum_tactical'] = 0 # Buy and hold from beginning, sell when price breaks all time high, buy when 20% dip
data['Capital'] = 0
data['Cash'] = 0

for i in range(0,len(data)):
    if i == 0:
        data['Capital_buythedip'][i] = data['Change'][i] * initial_capital
    else:
        if data['Changes_From_Previous_Peak'][i-1] > 0.2:
            data['Capital_buythedip'][i] = (data['Change'][i])*(data['Capital_buythedip'][i-1]+ 
                                                                monthly_topup + data['Cash'][i-1])
            data['Cash'][i] = 0
        else:
            data['Capital_buythedip'][i] = ((data['Change'][i])*data['Capital_buythedip'][i-1]) + monthly_topup/2
            data['Cash'][i] = data['Cash'][i-1] + monthly_topup/2
        
        if data['Close'][i] >= data['All_Time_High'][i]:
            temp = data['Cash'][i]
            data['Cash'][i] = data['Capital_buythedip'][i] + temp
            data['Capital_buythedip'][i] = 0

data['Capital_buythedip'] = data['Capital_buythedip'] + data['Cash']
            
for i in range(0,len(data)):
    if i == 0:
        data['Capital_DCA'][i] = data['Change'][i] * initial_capital
        data['Capital_lumpsum'][i] = data['Change'][i] * lumpsum
        data['Capital'][i] = data['Change'][i] * initial_capital
    else:
        data['Capital_DCA'][i] = ((data['Change'][i])*data['Capital_DCA'][i-1]) + monthly_topup
        data['Capital_lumpsum'][i] = data['Change'][i]*data['Capital_lumpsum'][i-1]
        data['Capital'][i] = data['Capital'][i-1] + monthly_topup

data['Cash'] = 0
for i in range(0,len(data)):
    if i == 0:
        data['Capital_lumpsum_tactical'][i] = data['Change'][i] * lumpsum
    else:
        if data['Changes_From_Previous_Peak'][i-1] > 0.2:
            data['Capital_lumpsum_tactical'][i] = (data['Change'][i])*(data['Capital_lumpsum_tactical'][i-1] \
                                                                       + data['Cash'][i-1])
            data['Cash'][i] = 0
        else:
            data['Capital_lumpsum_tactical'][i] = ((data['Change'][i])*data['Capital_lumpsum_tactical'][i-1])
            data['Cash'][i] = data['Cash'][i-1]
        
        if data['Close'][i] >= data['All_Time_High'][i]:
            temp = data['Cash'][i]
            data['Cash'][i] = data['Capital_lumpsum_tactical'][i] + temp
            data['Capital_lumpsum_tactical'][i] = 0

data['Capital_lumpsum_tactical'] = data['Capital_lumpsum_tactical'] + data['Cash']

ax = data['Capital_DCA'].plot(color = 'g')
data['Capital_buythedip'].plot(ax = ax, color = 'c')
data['Capital_lumpsum'].plot(ax = ax, color = 'k')
data['Capital_lumpsum_tactical'].plot(ax = ax, color = 'darkblue')

ax.set_ylabel('Capital')
plt.legend(['DCA', 'Buy the dip (20%)', 'Lumpsum', 'Lumpsum tactical'], bbox_to_anchor=(1, 1))
plt.show()

Great, Let’s Try That Again

The backtest looks great, but it will be more interesting if we test it with different markets.

def backtest_monthly_buythedip(data, initial_capital, monthly_topup):
    data['Capital'] = 0
    data['Cash'] = 0
    
    for i in range(0,len(data)):
        if i == 0:
            data['Capital'][i] = data['Change'][i] * initial_capital
        else:
            if data['Changes_From_Previous_Peak'][i-1] > 0.2:
                data['Capital'][i] = (data['Change'][i])*(data['Capital'][i-1]+ monthly_topup + data['Cash'][i-1])
                data['Cash'][i] = 0
            else:
                data['Capital'][i] = ((data['Change'][i])*data['Capital'][i-1])
                data['Cash'][i] = data['Cash'][i-1] + monthly_topup

            if data['Close'][i] >= data['Previous_Peak'][i]:
                temp = data['Cash'][i]
                data['Cash'][i] = data['Capital'][i] + temp
                data['Capital'][i] = 0
    #             sell if close >= previous_peak

    data['Capital'] = data['Capital'] + data['Cash']
    
    return data['Capital']

def backtest_lumpsum(data, lumpsum):
    data['Capital'] = 0
    data['Cash'] = 0

    for i in range(0,len(data)):
        if i == 0:
            data['Capital'][i] = data['Change'][i] * lumpsum
        else:
            data['Capital'][i] = data['Change'][i]*data['Capital'][i-1]
            
    return data['Capital']
            
def backtest_DCA(data, initial_capital, monthly_topup):
    data['Capital'] = 0
    data['Cash'] = 0

    for i in range(0,len(data)):
        if i == 0:
            data['Capital'][i] = data['Change'][i] * initial_capital
        else:
            data['Capital'][i] = ((data['Change'][i])*data['Capital'][i-1]) + monthly_topup
            
    return data['Capital']

def backtest_lumpsum_tactical(data, lumpsum):
    data['Capital'] = 0
    data['Cash'] = 0
    
    for i in range(0,len(data)):
        if i == 0:
            data['Capital'][i] = data['Change'][i] * lumpsum
        else:
            if data['Changes_From_Previous_Peak'][i-1] > 0.2:
                data['Capital'][i] = (data['Change'][i])*(data['Capital'][i-1] \
                                                                           + data['Cash'][i-1])
                data['Cash'][i] = 0
            else:
                data['Capital'][i] = ((data['Change'][i])*data['Capital'][i-1])
                data['Cash'][i] = data['Cash'][i-1]

            if data['Close'][i] >= data['Previous_Peak'][i]:
                temp = data['Cash'][i]
                data['Cash'][i] = data['Capital'][i] + temp
                data['Capital'][i] = 0

    data['Capital'] = data['Capital'] + data['Cash']
    
    return data['Capital']
KLSE = read_and_process_data('KLSE.csv')
SP500 = read_and_process_data('MSP500.csv')
CHN = read_and_process_data('MShangHai.csv')
SGX = read_and_process_data('MSGX.csv')

initial_capital = 100
monthly_topup = 100
lumpsum = initial_capital + (len(KLSE)*monthly_topup)
    
KLSE_DCA = backtest_DCA(KLSE.copy(), initial_capital, monthly_topup)
KLSE_20pct = backtest_monthly_buythedip(KLSE.copy(), initial_capital, monthly_topup)
KLSE_lumpsum = backtest_lumpsum(KLSE.copy(), lumpsum)
KLSE_lumpsum_tactical = backtest_lumpsum_tactical(KLSE.copy(), lumpsum)

SP500_DCA = backtest_DCA(SP500.copy(), initial_capital, monthly_topup)
SP500_20pct = backtest_monthly_buythedip(SP500.copy(), initial_capital, monthly_topup)
SP500_lumpsum = backtest_lumpsum(SP500.copy(), lumpsum)
SP500_lumpsum_tactical = backtest_lumpsum_tactical(SP500.copy(), lumpsum)

CHN_DCA = backtest_DCA(CHN.copy(), initial_capital, monthly_topup)
CHN_20pct = backtest_monthly_buythedip(CHN.copy(), initial_capital, monthly_topup)
CHN_lumpsum = backtest_lumpsum(CHN.copy(), lumpsum)
CHN_lumpsum_tactical = backtest_lumpsum_tactical(CHN.copy(), lumpsum)

SGX_DCA = backtest_DCA(SGX.copy(), initial_capital, monthly_topup)
SGX_20pct = backtest_monthly_buythedip(SGX.copy(), initial_capital, monthly_topup)
SGX_lumpsum = backtest_lumpsum(SGX.copy(), lumpsum)
SGX_lumpsum_tactical = backtest_lumpsum_tactical(SGX.copy(), lumpsum)

fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(15,10))

KLSE_DCA.plot(ax = axes[0,0], color = 'g')
KLSE_20pct.plot(ax = axes[0,0], color = 'c')
KLSE_lumpsum.plot(ax = axes[0,0], color = 'k')
KLSE_lumpsum_tactical.plot(ax = axes[0,0], color = 'darkblue')
axes[0,0].set_title("Kuala Lumpur Composite Index")

SP500_DCA.plot(ax = axes[0,1], color = 'g')
SP500_20pct.plot(ax = axes[0,1], color = 'c')
SP500_lumpsum.plot(ax = axes[0,1], color = 'k')
SP500_lumpsum_tactical.plot(ax = axes[0,1], color = 'darkblue')
axes[0,1].set_title("SP500")

CHN_DCA.plot(ax = axes[1,0], color = 'g')
CHN_20pct.plot(ax = axes[1,0], color = 'c')
CHN_lumpsum.plot(ax = axes[1,0], color = 'k')
CHN_lumpsum_tactical.plot(ax = axes[1,0], color = 'darkblue')
axes[1,0].set_title("Shang Hai Composite Index")

SGX_DCA.plot(ax = axes[1,1], color = 'g')
SGX_20pct.plot(ax = axes[1,1], color = 'c')
SGX_lumpsum.plot(ax = axes[1,1], color = 'k')
SGX_lumpsum_tactical.plot(ax = axes[1,1], color = 'darkblue')
axes[1,1].set_title("Straits Times Index (Singapore)")

plt.legend(['DCA', 'Buy the dip (20%)', 'Lumpsum', 'Lumpsum tactical'], bbox_to_anchor=(1, 1))
plt.show()
fig = plt.figure()
ax = fig.add_axes([0,0,1,1])

import numpy as np
x = np.arange(4)
DCA = [KLSE_DCA[-1], SP500_DCA[-1], CHN_DCA[-1], SGX_DCA[-1]]
PCT20 = [KLSE_20pct[-1], SP500_20pct[-1], CHN_20pct[-1], SGX_20pct[-1]]
lumpsum = [KLSE_lumpsum[-1], SP500_lumpsum[-1], CHN_lumpsum[-1], SGX_lumpsum[-1]]
lumpsum_tactical = [KLSE_lumpsum_tactical[-1], SP500_lumpsum_tactical[-1], 
                    CHN_lumpsum_tactical[-1], SGX_lumpsum_tactical[-1]]

width = 0.2
# plot data in grouped manner of bar type
plt.bar(x-0.2, DCA, width, color='cyan')
plt.bar(x, PCT20, width, color='orange')
plt.bar(x+0.2, lumpsum, width, color='green')
plt.bar(x+0.4, lumpsum_tactical, width, color='blue')

plt.xticks(x, ['KLSE', 'SP500', 'CHN', 'SGX'])
plt.xlabel("Assets")
plt.ylabel("Ending Wealth")
plt.legend(['DCA', '20%', 'lumpsum', 'lumpsum tactical'])

plt.show()

Thoughts

  • No single strategy can outperform others, performance of strategy depends on nature of the market
  • Lumpsum investment works the best in long bull market
  • Buy the dip or “market timing” works in volatile, sideway market
  • Market timing can be very punishing if you missed the bull market
  • Lumpsum works when u have large amount of money sitting idle, which many don’t

Leave a Reply

Your email address will not be published. Required fields are marked *