# python构建双均线策略
# 均线策略中最常见的一种方法是根据长期均线和短期均线的交叉情况来确定交易信号,即:当短期均线从下往上穿越长期均线时,形成金叉,做多;
# 反之,当长期均线从上往下穿越短期均线时,形成死叉,做空或平仓。下面是常见的双均线策略如下:
# 均线:以 5 日均线为短期均线、以 20 日均线为长期均线;
# 买入开仓:当前无持仓,当日 5 日均线上穿 20 日均线,第二天以市价单买入,开仓;
# 卖出平仓:当前持有多单,当日 5 日均线下穿 20 日均线,第二天以市价单卖出,平仓。
# 三均线策略与双均线策略类似,只不过交易信号是由短期均线、中期均线、长期均线这 3 条均线共同确定的。如果只考虑做多的情况,一般是短期均线>中期均线>长期均线,
# 呈多头排列时,买入开仓;出现短期均线下穿中期均线时,卖出平仓。下面是案例具体的策略逻辑:
# 均线:5 日均线为短期均线、20 日均线为中期均线、60 日均线为长期均线;
# 买入开仓:当前无持仓,当开始出现 5 日均线>20 日均线>60 日均线多头排列时,第二天以市价单买入,开仓;
# 卖出平仓:当前持有多单,当日 5 日均线下穿 20 日均线,第二天以市价单卖出,平仓。
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
def get_data(code,start_time,end_time):
df = get_price(security=code, start_date=start_time, end_date=end_time, frequency='daily')
df.index=pd.to_datetime(df.index)
df['openinterest']=0
df=df[['open','high','low','close','volume','openinterest']] #按照back trader数据格式进行整合
return df
code='600519.XSHG'
start_time='2016-9-7'
end_time='2022-5-20'
stock_df=get_data(code,start_time,end_time)
stock_df
open | high | low | close | volume | openinterest | |
---|---|---|---|---|---|---|
2016-09-07 | 290.26 | 290.26 | 286.34 | 287.37 | 3162902.0 | 0 |
2016-09-08 | 286.07 | 287.28 | 285.32 | 286.64 | 1782374.0 | 0 |
2016-09-09 | 287.42 | 287.61 | 284.93 | 285.52 | 1730813.0 | 0 |
2016-09-12 | 283.06 | 284.93 | 279.09 | 279.36 | 4079778.0 | 0 |
2016-09-13 | 281.14 | 282.09 | 280.53 | 281.59 | 1734373.0 | 0 |
... | ... | ... | ... | ... | ... | ... |
2022-05-16 | 1797.00 | 1797.00 | 1742.67 | 1758.00 | 2776531.0 | 0 |
2022-05-17 | 1769.88 | 1775.00 | 1755.00 | 1768.00 | 2014474.0 | 0 |
2022-05-18 | 1773.66 | 1773.92 | 1743.02 | 1761.00 | 2485044.0 | 0 |
2022-05-19 | 1745.00 | 1757.00 | 1739.00 | 1756.00 | 1977626.0 | 0 |
2022-05-20 | 1759.99 | 1801.15 | 1752.00 | 1800.01 | 4194960.0 | 0 |
1382 rows × 6 columns
# 计算均线
def sma_cal(df,window):
sma=df['close'].rolling(window=window).mean()
return sma
sma_df={
}
window=[5,10,20,60]
for i in range(len(window)):
sma_name=str(window[i])
value=sma_cal(stock_df,window[i])
sma_df['sma'+sma_name]=value
sma_df=pd.DataFrame(sma_df)
sma_df['close']=stock_df['close']
sma_df['ret']=sma_df['close'].pct_change()
sma_df=sma_df.dropna()
sma_df
sma5 | sma10 | sma20 | sma60 | close | ret | |
---|---|---|---|---|---|---|
2016-12-08 | 305.418 | 302.715 | 297.6910 | 290.251167 | 316.53 | 0.031345 |
2016-12-09 | 308.414 | 304.671 | 298.7535 | 290.737833 | 316.57 | 0.000126 |
2016-12-12 | 310.464 | 305.361 | 299.4975 | 291.066500 | 306.36 | -0.032252 |
2016-12-13 | 311.698 | 306.209 | 300.3880 | 291.509833 | 312.12 | 0.018801 |
2016-12-14 | 312.602 | 307.445 | 301.2840 | 292.044333 | 311.43 | -0.002211 |
... | ... | ... | ... | ... | ... | ... |
2022-05-16 | 1766.620 | 1787.848 | 1782.9985 | 1771.657833 | 1758.00 | -0.011382 |
2022-05-17 | 1766.982 | 1781.148 | 1780.9485 | 1769.614167 | 1768.00 | 0.005688 |
2022-05-18 | 1765.582 | 1774.410 | 1778.6990 | 1767.421667 | 1761.00 | -0.003959 |
2022-05-19 | 1764.248 | 1766.310 | 1776.9580 | 1765.238500 | 1756.00 | -0.002839 |
2022-05-20 | 1768.602 | 1767.011 | 1777.4435 | 1763.455333 | 1800.01 | 0.025063 |
1323 rows × 6 columns
import matplotlib.pyplot as plt
sma_df.iloc[:,0:5].plot(figsize=(12,7))
# 双均线
# 采用5日均线与20日均线
signal=pd.Series(0,index=sma_df.index)
for i in range(1,len(signal)):
if all([sma_df['sma5'][i]>sma_df['sma20'][i],sma_df['sma5'][i-1]<sma_df['sma20'][i-1]]):
signal[i]=1
elif all([sma_df['sma5'][i]<sma_df['sma20'][i],sma_df['sma5'][i-1]>sma_df['sma20'][i-1]]):
signal[i]=-1
signal
2016-12-08 0
2016-12-09 0
2016-12-12 0
2016-12-13 0
2016-12-14 0
..
2022-05-16 0
2022-05-17 0
2022-05-18 0
2022-05-19 0
2022-05-20 0
Length: 1323, dtype: int64
trad_signal=signal.shift(1)
trad_signal
2016-12-08 NaN
2016-12-09 0.0
2016-12-12 0.0
2016-12-13 0.0
2016-12-14 0.0
...
2022-05-16 0.0
2022-05-17 0.0
2022-05-18 0.0
2022-05-19 0.0
2022-05-20 0.0
Length: 1323, dtype: float64
long=pd.Series(0,index=sma_df.index)
long[trad_signal==1]=1
longret=long*sma_df['ret']
long_winret=len(longret[longret>0])/len(longret[longret!=0])
long_winret
0.43243243243243246
short=pd.Series(0,index=sma_df.index)
short[trad_signal==-1]=-1
shortret=short*sma_df['ret']
short_winret=len(shortret[shortret>0])/len(shortret[shortret!=0])
short_winret
0.5128205128205128
tradret=trad_signal*sma_df['ret'].dropna()
winret=len(tradret[tradret>0])/len(tradret[tradret!=0])
winret
0.4675324675324675
cumlong=np.cumprod(1+longret)-1
cumshort=np.cumprod(1+shortret)-1
cumret=np.cumprod(1+tradret)-1
plt.figure(figsize=(12,8))
plt.plot(cumlong,label='long ret')
plt.plot(cumshort,label='short ret')
plt.plot(cumret,label='long_short ret')
plt.legend()
plt.show()
# 三均线策略
# 采用5日均线、20日均线与60日均线
signal1=pd.Series(0,index=sma_df.index)
for i in range(1,len(signal1)):
if all([sma_df['sma5'][i]>sma_df['sma20'][i]>sma_df['sma60'][i]]):
signal1[i]=1
elif all([sma_df['sma5'][i]<sma_df['sma20'][i],sma_df['sma5'][i-1]>sma_df['sma20'][i-1]]):
signal1[i]=-1
signal1
2016-12-08 0
2016-12-09 1
2016-12-12 1
2016-12-13 1
2016-12-14 1
..
2022-05-16 0
2022-05-17 0
2022-05-18 0
2022-05-19 0
2022-05-20 0
Length: 1323, dtype: int64
trad_signal1=signal1.shift(1)
trad_signal1
2016-12-08 NaN
2016-12-09 0.0
2016-12-12 1.0
2016-12-13 1.0
2016-12-14 1.0
...
2022-05-16 0.0
2022-05-17 0.0
2022-05-18 0.0
2022-05-19 0.0
2022-05-20 0.0
Length: 1323, dtype: float64
long1=pd.Series(0,index=sma_df.index)
long1[trad_signal1==1]=1
longret1=long1*sma_df['ret']
long_winret1=len(longret1[longret1>0])/len(longret1[longret1!=0])
long_winret1
0.5363214837712519
short1=pd.Series(0,index=sma_df.index)
short1[trad_signal1==-1]=-1
shortret1=short1*sma_df['ret']
short_winret1=len(shortret1[shortret1>0])/len(shortret1[shortret1!=0])
short_winret1
0.5128205128205128
tradret1=trad_signal1*sma_df['ret'].dropna()
winret1=len(tradret1[tradret1>0])/len(tradret1[tradret1!=0])
winret1
0.5342066957787481
cumlong1=np.cumprod(1+longret1)-1
cumshort1=np.cumprod(1+shortret1)-1
cumret1=np.cumprod(1+tradret1)-1
plt.figure(figsize=(12,8))
plt.plot(cumlong1,label='long ret1')
plt.plot(cumshort1,label='short ret1')
plt.plot(cumret1,label='long_short ret1')
plt.legend()
plt.show()
import backtrader as bt
import pandas as pd
# 自定义信号指标
class MySignal(bt.Indicator):
lines = ('signal',) # 声明 signal 线,交易信号放在 signal line 上
params = dict(
short_period=5,
long_period=20)
def __init__(self):
self.dataclose=self.datas[0].close
self.s_ma = bt.ind.SMA(period=self.p.short_period)
self.l_ma = bt.ind.SMA(period=self.p.long_period)
# 短期均线上穿长期均线,取值为1;反之,短期均线下穿长期均线,取值为-1
self.lines.signal = bt.ind.CrossOver(self.s_ma, self.l_ma)
# 实例化大脑
cerebro = bt.Cerebro()
# 加载数据
datafeed = bt.feeds.PandasData(dataname=stock_df,
fromdate=pd.to_datetime('2019-01-02'),
todate=pd.to_datetime('2021-01-28'))
cerebro.adddata(datafeed)
# 初始资金 1,000,000
cerebro.broker.setcash(1000000.0)
# 佣金,双边各 0.0003
cerebro.broker.setcommission(commission=0.0003)
# 滑点:双边各 0.0001
cerebro.broker.set_slippage_perc(perc=0.0001)
#每次固定交易100股
cerebro.addsizer(bt.sizers.FixedSize, stake=100)
# 添加交易信号
cerebro.add_signal(bt.SIGNAL_LONG, MySignal)
# 添加分析器
cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='pnl') # 返回收益率时序数据
cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='_AnnualReturn')
cerebro.addanalyzer(bt.analyzers.SharpeRatio, riskfreerate=0.003, annualize=True, _name='_SharpeRatio')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='_DrawDown')
# 添加观测器
cerebro.addobserver(bt.observers.Value) # 查看账户资产变动
print(f'组合初始价值:%.2f'%cerebro.broker.getvalue())
result = cerebro.run()
print(f'组合期末价值:%.2f'%cerebro.broker.getvalue())
cerebro.plot(iplot=False)
组合初始价值:1000000.00
组合期末价值:1055108.41
strat = result[0]
print("--------------- AnnualReturn -----------------")
print(strat.analyzers._AnnualReturn.get_analysis())
print("--------------- SharpeRatio -----------------")
print(strat.analyzers._SharpeRatio.get_analysis())
print("--------------- DrawDown -----------------")
print(strat.analyzers._DrawDown.get_analysis())
--------------- AnnualReturn -----------------
OrderedDict([(2019, 0.004241777782910017), (2020, 0.04177344221019741), (2021, 0.008522332874576044)])
--------------- SharpeRatio -----------------
OrderedDict([('sharperatio', 0.9048730445652012)])
--------------- DrawDown -----------------
AutoOrderedDict([('len', 3), ('drawdown', 0.8102639726476181), ('moneydown', 8619.0), ('max', AutoOrderedDict([('len', 172), ('drawdown', 3.3309121179482157), ('moneydown', 33756.307311659795)]))])
# 自定义信号指标
class MySignal(bt.Indicator):
lines = ('signal',) # 声明 signal 线,交易信号放在 signal line 上
params = dict(
short_period=5,
median_period=20,
long_period=60)
def __init__(self):
self.s_ma = bt.ind.SMA(period=self.p.short_period)
self.m_ma = bt.ind.SMA(period=self.p.median_period)
self.l_ma = bt.ind.SMA(period=self.p.long_period)
# 短期均线在中期均线上方,且中期均取也在长期均线上方,三线多头排列,取值为1;反之,取值为0
self.signal1 = bt.And(self.m_ma>self.l_ma, self.s_ma>self.m_ma)
# 求上面 self.signal1 的环比增量,可以判断得到第一次同时满足上述条件的时间,第一次满足条件为1,其余条件为0
self.buy_signal = bt.If((self.signal1-self.signal1(-1))>0, 1, 0)
# 短期均线下穿长期均线时,取值为1;反之取值为0
self.sell_signal = bt.ind.CrossDown(self.s_ma, self.m_ma)
# 将买卖信号合并成一个信号
self.lines.signal = bt.Sum(self.buy_signal, self.sell_signal*(-1))
# 实例化大脑
cerebro = bt.Cerebro()
# 加载数据
# 读取行情数据
datafeed = bt.feeds.PandasData(dataname=stock_df,
fromdate=pd.to_datetime('2019-01-02'),
todate=pd.to_datetime('2021-01-28'))
cerebro.adddata(datafeed)
# 初始资金 1,000,000
cerebro.broker.setcash(1000000.0)
# 佣金,双边各 0.0003
cerebro.broker.setcommission(commission=0.0003)
# 滑点:双边各 0.0001
cerebro.broker.set_slippage_perc(perc=0.0001)
#每次固定交易100股
cerebro.addsizer(bt.sizers.FixedSize, stake=100)
# 添加交易信号
cerebro.add_signal(bt.SIGNAL_LONG, MySignal)
# 添加交易信号
cerebro.add_signal(bt.SIGNAL_LONG, MySignal)
# 添加分析器
cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='pnl') # 返回收益率时序数据
cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='_AnnualReturn')
cerebro.addanalyzer(bt.analyzers.SharpeRatio, riskfreerate=0.003, annualize=True, _name='_SharpeRatio')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='_DrawDown')
# 回测时需要添加 PyFolio 分析器
cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
cerebro.addobserver(bt.observers.Value) # 查看账户资产变动
print(f'组合初始价值:%.2f'%cerebro.broker.getvalue())
result = cerebro.run()
print(f'组合期末价值:%.2f'%cerebro.broker.getvalue())
cerebro.plot(iplot=False)
组合初始价值:1000000.00
组合期末价值:1071222.93
strat = result[0]
print("--------------- AnnualReturn -----------------")
print(strat.analyzers._AnnualReturn.get_analysis())
print("--------------- SharpeRatio -----------------")
print(strat.analyzers._SharpeRatio.get_analysis())
print("--------------- DrawDown -----------------")
print(strat.analyzers._DrawDown.get_analysis())
--------------- AnnualReturn -----------------
OrderedDict([(2019, 0.007321597032730054), (2020, 0.0545856770351798), (2021, 0.008393054551040002)])
--------------- SharpeRatio -----------------
OrderedDict([('sharperatio', 0.9274312773078117)])
--------------- DrawDown -----------------
AutoOrderedDict([('len', 3), ('drawdown', 0.7981723781349532), ('moneydown', 8619.0), ('max', AutoOrderedDict([('len', 137), ('drawdown', 2.7729689789715986), ('moneydown', 29437.66091440979)]))])