双均线策略构建及回测

# 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)]))])

猜你喜欢

转载自blog.csdn.net/xiaowu1997/article/details/124901503
今日推荐