无论你是投资老手还是刚开始学习投资的小白
你都会留意到网路上人们都在讨论投资策略,捞底,还是不管市场高低定期定额投资等
“投资者” 会教你要长期持有,如果资金不大就定期定额投资
“投机者” 会教你要根据市场的趋势,情绪来交易,以”趋势是你的朋友”的理念来投资
其实双方的论点都听起来行得通,这里我们只相信数据
数据怎么说呢?到底哪个策略最赚钱?
数据读取
我从Yahoo Finance上下载了各大指数的历史数据,
要分析数据得先读取这个csv文件。
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()
Date | Open | High | Low | Close | Adj Close | Change | All_Time_High | Previous_Peak | Changes_From_Previous_Peak | |
---|---|---|---|---|---|---|---|---|---|---|
Date | ||||||||||
2011-01-31 | 2011-01-31 | 3190.04 | 3280.77 | 3177.78 | 3179.72 | 3179.72 | 0.946785 | 3179.72 | 3179.72 | 0.000000 |
2011-02-28 | 2011-02-28 | 3179.72 | 3232.99 | 2965.24 | 3010.51 | 3010.51 | 1.031669 | 3179.72 | 3179.72 | 0.053215 |
2011-03-31 | 2011-03-31 | 3010.51 | 3115.47 | 2919.98 | 3105.85 | 3105.85 | 1.021534 | 3179.72 | 3179.72 | 0.023232 |
2011-04-30 | 2011-04-30 | 3105.85 | 3208.34 | 3103.22 | 3172.73 | 3172.73 | 0.995966 | 3179.72 | 3179.72 | 0.002198 |
2011-05-31 | 2011-05-31 | 3179.86 | 3182.46 | 3078.26 | 3159.93 | 3159.93 | 0.987503 | 3179.72 | 3179.72 | 0.006224 |
策略
这次我们回测的有4个策略
- 每月买入相同的数额
- 每个存下相同的数额,在市场从24个月高点跌了20%才买入
- 一次性投入,买入并持有
- 一次性投入,买入并持有,市场破历史新高时卖出,在市场从24个月高点跌了20%才买入
每个策略投入的总金额都是一样的
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()
再回测
回测结果看起来不错,先试试别的市场数据
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()
结论
- 没有一个策略可以总是跑赢其他策略,策略的有效性得看市场的”性格”
- 一次性投入, 在超长牛市是最好的策略
- 投机或捞底, 在高波动的市场的最好的策略
- 要是你总是在等捞底,你可能错过牛市
- 一次性投入需要大量资金,每月定期定额的策略对普通人比较适合