• 【量化交易笔记】11.移动平均交易策略


    概述

    上一节我们建立了最最简单的交易策略,尽管有了盈利,但实际操作上是不可行的。本节将运用移动平均指标,包括单一移动平均策略和双移动平均策略,来建立经典的移动平均策略。

    数据采集处理

    本文采用上一节的相同数据,为了方便,可以将数据保证在本地。为了完整性本次仍将提供完整代码,便于小伙伴们复现。

    # 加载相应的库
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import baostock as bs
    import warnings
    warnings.filterwarnings("ignore")
    plt.rcParams['font.family'] = ['sans-serif']
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['axes.unicode_minus']=False
    
    lg = bs.login()
    #指定一下获取股票数据的起始日期和截止日期
    #这里就用2023年1月1日至今日的数据
    start_date = '2023-01-01'
    end_date = '2023-10-19'
    #创建数据表,这里选择下载的股票代码为600000
    
    rs=bs.query_history_k_data_plus('600000.sh', 
     "date,open,high,low,close,volume",
        start_date=start_date, end_date=end_date,
    frequency="d", adjustflag="3")
    # .get_data()
    #下面来检查一下数据表的前5行
    data=rs.get_data()
    
    cols=["open","high","low","close","volume"]
    data[cols]=data[cols].astype('float')
    data['date']=pd.to_datetime(data['date'])
    data.set_index('date',inplace=True)
    data.to_csv("./data/sh600000.csv")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    单一移动平均策略

    这个策略的核心思想:利用趋势,低买高卖。一般选择一个适当的平均指标来衡量市场的趋势。比如,可以使用简单移动平均线 (SMA) 或指数移动平均线 (EMA) 等,这些指标在之前的文章已作详细说明。
    策略: 当股价向上且向上穿过N日均,说明股价向上突破,此时买入;当股价下降且向下穿过N日均线时,说明股价整体出现下跌趋势,此时卖出!
    接下来,我将以10日均线(均价)为例进行讲解。

    获取均值

    如果直接用rolling 函数或 talib.MA获取移动平均值,会出现前面的数值为空情况。

    data.close.rolling(window=10).mean()
    
    • 1

    date
    2023-01-03 NaN
    2023-01-04 NaN
    2023-01-05 NaN
    2023-01-06 NaN
    2023-01-09 NaN

    2023-10-13 7.102
    2023-10-16 7.091
    2023-10-17 7.084
    2023-10-18 7.074
    2023-10-19 7.045
    Name: close, Length: 191, dtype: float64

    为了更好的平滑这个曲线,我们做了一些简单的处理,还不到这些10天的空值,均由前面的平均给定。
    最后获得均值合并到data数据中。

    #这里使用10日均线
    period = 10
    #设置一个空列表,用来存储每10天的价格
    avg_10 = []
    #再设置一个空列表,用来存储每10天价格的均值
    avg_value = []
    #设置一个循环
    for price in data['close']:
        #把每天的价格传入到avg_10列表
        avg_10.append(price)
        #当列表中存储的数值多于10个时
        if len(avg_10) > period:
            #就把前面传入的价格数据删掉,确保列表中只有10天的数据
            del avg_10[0]
        #将10天数据的均值传入到avg_value列表中
        avg_value.append(np.mean(avg_10))
    #把计算好的10日均价写到股票价格数据表中    
    data['MA10']=avg_value
    data
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    如下表,MA10 ,第一个数据,即为close 本身,第二个数据为7.23和7.31 的平均值,以此累推。

    dateopenhighlowclosevolumeMA10
    2023-01-037.277.287.177.2325892521.07.230000
    2023-01-047.277.357.237.3130947081.07.270000
    2023-01-057.377.387.307.3530162154.07.296667
    2023-01-067.357.387.317.3420312881.07.307500
    2023-01-097.387.387.307.3419612260.07.314000
    2023-10-137.117.157.087.1019650410.07.102000
    2023-10-167.127.137.047.0724907733.07.091000
    2023-10-177.097.107.057.0919029143.07.084000
    2023-10-187.077.117.057.0521485721.07.074000
    2023-10-197.047.056.836.8461679771.07.045000

    191 rows × 6 columns

    设置交易信号

    当股价向上且向上穿过10日均线,说明股价向上突破,此时买入;当股价下降且向下穿过10日均线时,说明股价整体出现下跌趋势,此时卖出!

    # 定义买入和卖出的条件  
    # data['buy_signal'] = np.where(data['close'] > data['MA10'], 1, 0)  
    # data['sell_signal'] = np.where(data['close'] < data['MA10'], -1, 0)
    data['signal']=np.where(data['close'] > data['MA10'], 1, 0) 
    data['order']=data['signal'].diff()*100
    
    • 1
    • 2
    • 3
    • 4
    • 5

    原本分别设置买入和卖出,导致算法复杂,统一修改成上节,便于操作,结果也不受影响,最后根据交易信号确定买卖数量。
    这里有一个注意事项,在操作中,可能会出现,先卖后买的情况,即在data中先有-100的值,遇到这种情况,需要作一些调整,或直接修改为0,即可。

    图形查看

    利用图形,查看具体操作信号的位置。

    #设置图像尺寸为12*8
    plt.figure(figsize=(15,10))
    #绘制股价的变化
    plt.plot(data['close'],lw=2, c='k',label='股价')
    #绘制10日均线
    plt.plot(data['MA10'], '--',lw=2, c='b',label='10日均线')
    
    #如果当天股价下跌给出买入信号,用正三角表示
    plt.scatter(data['close'].loc[data.order>0].index,
            data['close'][data.order>0],
            marker = '^', s=80, c='r')
    
    #如果当天股价上涨,标出卖出信号,用倒三角表示
    plt.scatter(data['close'].loc[data.order<0].index,
            data['close'][data.order<0],
            marker = 'v', s=80, c='g')
    
    #添加图注和网格
    plt.legend()
    plt.grid()
    #将图像进行显示
    plt.show();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    交易

    处理数据
    df=data.copy()
    df['price']=df['close']
    df.fillna(0.0,inplace=True)
    df=df.fillna(0)
    #一般情况下,在A股市场,买入或卖出至少为100股,即1手
    df['order'] = df['signal'].diff()*100
    df.fillna(0.0,inplace=True)
    df['cash']=np.NaN
    df['total']=np.NaN
    df['position']=df['order'].cumsum()
    df.cash.iloc[0]=1000
    
    for i in range(1,len(df)):
        if (df.order.iloc[i]==0):
            df.cash.iloc[i] =df.cash.iloc[i-1]
        else:
            df.cash.iloc[i] =df.cash.iloc[i-1]-df.price.iloc[i]*df.order.iloc[i]
    df['total'] =df['cash']+df['position']*df['price']
    # 有一些警告错误 可以关闭
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    #为了让直观看到自己的总资产变化
    #我们用图形来进行展示
    #设置图形的尺寸是10*6
    plt.figure(figsize=(10,6))
    #分别绘制总资产和持仓股票市值的变化
    plt.plot(df['total'],label='总市值')
    plt.plot(df['order'].cumsum()*df['price'],'--',
            label='股票市值')
    #增加网格,调整一下图注的位置,就可以显示图像了
    plt.grid()
    plt.legend(loc='center right')
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    查看收益
    dateopenhighlowclosevolumeMA10signalorderpricecashtotalposition
    2023-01-037.277.287.177.2325892521.07.23000000.07.231000.01000.00.0
    2023-01-047.277.357.237.3130947081.07.2700001100.07.31269.01000.0100.0
    2023-01-057.377.387.307.3530162154.07.29666710.07.35269.01004.0100.0
    2023-01-067.357.387.317.3420312881.07.30750010.07.34269.01003.0100.0
    2023-01-097.387.387.307.3419612260.07.31400010.07.34269.01003.0100.0
    2023-10-137.117.157.087.1019650410.07.1020000-100.07.10946.0946.00.0
    2023-10-167.127.137.047.0724907733.07.09100000.07.07946.0946.00.0
    2023-10-177.097.107.057.0919029143.07.0840001100.07.09237.0946.0100.0
    2023-10-187.077.117.057.0521485721.07.0740000-100.07.05942.0942.00.0
    2023-10-197.047.056.836.8461679771.07.04500000.06.84942.0942.00.0
    小结

    本次的单一移动均值策略,居然亏损了,但我们分析一下整理效果。

    双移动平均策略

    顾名思义,双移动平均策略,就是用两条不同的均线来判定股票走势。一般情况,在两条均线中,一条是短期均线(如5日均线),另一条是长期均线(如20日均线)【5日均线又称周均线,20日均线又称月均线,因为一周有5个交易日,一月有21个交易】。

    仍以上述数据,只增加5日均值线和20日均值线合并到data。

    为了方便定义了一个函数。 在设置交易信号不同,其余均与上述方法相同。

    def getSMA(data,period=10):
        # period = 10
        #设置一个空列表,用来存储每10天的价格
        avg_10 = []
        #再设置一个空列表,用来存储每10天价格的均值
        avg_value = []
        #设置一个循环
        for price in data['close']:
            #把每天的价格传入到avg_10列表
            avg_10.append(price)
            #当列表中存储的数值多于10个时
            if len(avg_10) > period:
                #就把前面传入的价格数据删掉,确保列表中只有10天的数据
                del avg_10[0]
            #将10天数据的均值传入到avg_value列表中
            avg_value.append(np.mean(avg_10))
        return avg_value
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    获得5日和20均线
    df=data.copy()
    #把分别计算好5日和20日均线    
    df['MA5']=getSMA(df,5)
    df['MA20']=getSMA(df,20)
    # 删除多余的列
    df.drop(['MA10','signal','order'],axis=1,inplace=True)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    设置交易信号

    当5日均线上穿20日均线就买入,当5日均线下穿20日均线就卖出。

    # 定义买入和卖出的条件  
    df['signal']=np.where(df['MA5'] > df['MA20'], 1, 0) 
    df['order']=df['signal'].diff()*100
    
    • 1
    • 2
    • 3
    画交易点图形
    #设置图像尺寸为12*8
    plt.figure(figsize=(12,8))
    #绘制股价的变化
    plt.plot(df['close'],lw=2, c='k',label='股价')
    #绘制10日均线
    plt.plot(df['MA5'], '--',lw=2, c='b',label='5日均线')
    plt.plot(df['MA20'], '*',lw=2, c='g',label='20日均线')
    
    #如果当天股价下跌给出买入信号,用正三角表示
    plt.scatter(df['close'].loc[df.order>0].index,
            df['close'][df.order>0],
            marker = '^', s=80, c='r')
    
    #如果当天股价上涨,标出卖出信号,用倒三角表示
    plt.scatter(df['close'].loc[df.order<0].index,
            df['close'][df.order<0],
            marker = 'v', s=80, c='g')
    
    #添加图注和网格
    plt.legend()
    plt.grid()
    #将图像进行显示
    plt.show();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    处理数据
    # df=data.copy()
    df['price']=df['close']
    df.fillna(0.0,inplace=True)
    df=df.fillna(0)
    #一般情况下,在A股市场,买入或卖出至少为100股,即1手
    df['order'] = df['signal'].diff()*100
    df.fillna(0.0,inplace=True)
    df['cash']=np.NaN
    df['total']=np.NaN
    df['position']=df['order'].cumsum()
    df.cash.iloc[0]=1000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    for i in range(1,len(df)):
        if (df.order.iloc[i]==0):
            df.cash.iloc[i] =df.cash.iloc[i-1]
        else:
            df.cash.iloc[i] =df.cash.iloc[i-1]-df.price.iloc[i]*df.order.iloc[i]
    df['total'] =df['cash']+df['position']*df['price']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    plt.figure(figsize=(10,6))
    #分别绘制总资产和持仓股票市值的变化
    plt.plot(df['total'],label='总市值')
    plt.plot(df['order'].cumsum()*df['price'],'--',
            label='股票市值')
    #增加网格,调整一下图注的位置,就可以显示图像了
    plt.grid()
    plt.legend(loc='center right')
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    查看收益
    dateopenhighlowclosevolumeMA5MA20signalorderpricecashtotalposition
    2023-01-037.277.287.177.2325892521.07.2300007.23000000.07.231000.01000.00.0
    2023-01-047.277.357.237.3130947081.07.2700007.27000000.07.311000.01000.00.0
    2023-01-057.377.387.307.3530162154.07.2966677.29666700.07.351000.01000.00.0
    2023-01-067.357.387.317.3420312881.07.3075007.30750000.07.341000.01000.00.0
    2023-01-097.387.387.307.3419612260.07.3140007.31400000.07.341000.01000.00.0
    2023-10-137.117.157.087.1019650410.07.0600007.09150000.07.10911.0911.00.0
    2023-10-167.127.137.047.0724907733.07.0680007.09500000.07.07911.0911.00.0
    2023-10-177.097.107.057.0919029143.07.0840007.09750000.07.09911.0911.00.0
    2023-10-187.077.117.057.0521485721.07.0900007.09700000.07.05911.0911.00.0
    2023-10-197.047.056.836.8461679771.07.0300007.08650000.06.84911.0911.00.0

    191 rows × 13 columns

    总结

    1. 这几个策略都没有取得良好的效果,这是因为移动平均策略是适合趋势市场。这个震荡的市场效果不理想。
    2. 以上回测,并没有加入交易费用,是不全面的。
    3. 回测的图形不是很直观,没有看到收益情况,需要查表格最后才能看明白。
    4. 回测应有收益曲线,基准曲线等
    5. 回测一些相关参数,如 α 和 β \alpha 和 \beta αβ,最大回测,年化收益等
    6. 完整的策略,包括 指标、标的、择时以及风控等组成。
      在以后文章中,将逐步增加以上相关知识点。

    在此警告:文章中的所有内容,不能给你构成投资的理由。

  • 相关阅读:
    LeetCode(力扣)55. 跳跃游戏Python
    铁路设备屡遭破坏!RFID电子锁实现铁路防护网破坏实时报警管理
    大数据-玩转数据-Flink状态编程(上)
    Cpp/Qt-day050921Qt
    世界经济论坛:ChatGPT等生成式AI,对全球23%岗位产生巨大影响
    鸿鹄工程项目管理系统 Spring Cloud+Spring Boot+Mybatis+Vue+ElementUI+前后端分离构建工程项目管理系统项目背景
    数组转树形结构
    【动态规划】爬楼梯爬的不仅仅是楼梯
    HTTP协议:最常用的应用层
    【特征选择】基于二元多邻域人工蜂群 (BMNABC) 特征选择问题(Matlab代码实现)
  • 原文地址:https://blog.csdn.net/cndrip/article/details/133955018