双均线策略是各类股票软件及交易平台的标配功能,无论是同花顺还是券商客户端均广泛支持。本节将简要梳理该策略的逻辑渊源,并演示如何编写其交易信号。
1、双均线策略解析
该策略依托两条移动平均线(MA):一条短周期均线与一条长周期均线。二者均基于不同时间跨度的收盘价均值计算得出。
短周期均线:通常选取近期数据(如10日或20日),旨在捕捉股价的短期波动趋势。
长周期均线:基于较长时段数据(如60日或100日),用于揭示股票的长期运行方向。
策略核心法则:
金叉买入:当短周期均线自下而上穿越长周期均线时,视为买入信号。这暗示短期动能转强,预示价格可能上行。
死叉卖出:当短周期均线自上而下跌破长周期均线时,视为卖出信号。这意味着短期势头走弱,预示价格可能回调。
在A股市场,由于缺乏做空机制,操作简化为“金叉建仓、死叉平仓”。而在具备做空机制的其他市场,逻辑则更为复杂。
2、Python信号编写实战
明确规则后,我们利用Python代码实现该策略。以创业板龙头股特锐德2023年1月至12月的历史数据为例进行演示:
首先导入数据集,并编写基础代码如下:
import pandas as pdimport numpy as nppd.set_option('display.max_rows', 1000) #最大显示的行数pd.set_option('expand_frame_repr', False) # 当列太多时不换行#读取创业板特锐德数据file_path = '特锐德2023股票数据.csv'data = pd.read_csv(file_path)data = data[['股票名字','交易日期','开盘价','收盘价','最高价','最低价','成交量','成交额']]print(data.head())>> 股票名字 交易日期 开盘价 收盘价 最高价 最低价 成交量 成交额0 特锐德 2023-01-03 15.20 15.45 15.53 15.04 68790 105716309.01 特锐德 2023-01-04 15.44 15.35 15.55 15.25 55797 86230918.02 特锐德 2023-01-05 15.44 15.51 15.61 15.30 65886 102419685.03 特锐德 2023-01-06 15.45 15.57 15.77 15.45 67721 106339926.04 特锐德 2023-01-09 15.75 15.82 16.13 15.65 94456 150887826.0
计算5日与10日均线的值:
# 计算均线,我们以5日线和10日线演示paras = [5,10]data['ma_fast'] = data['收盘价'].rolling(window=paras[0], min_periods=1).mean()data['ma_slow'] = data['收盘价'].rolling(window=paras[1], min_periods=1).mean()
利用rolling函数直接计算均线数值,当然也可借助talib库实现(该部分将留待进阶课程详解)。
构建策略信号:
# 计算信号data['Signal'] = np.NAN#当前快线大于慢线,前一根的块线小于慢线 则开多cond1 = data['ma_fast'] > data['ma_slow']cond2 = data['ma_fast'].shift(1) <= data['ma_slow'].shift(1)open_pos_signal = cond1 & cond2data.loc[open_pos_signal, 'Signal'] = 1#当前快线小于慢线,前一根的快线大于慢线 则平仓cond3 = data['ma_fast'] < data['ma_slow']cond4 = data['ma_fast'].shift(1) >= data['ma_slow'].shift(1)close_pos_signal = cond3 & cond4data.loc[close_pos_signal, 'Signal'] = 0
首先将策略信号列Signal初始化为空值。
构建多头仓位需满足双重条件:
如图所示,红色代表5日快速均线,蓝色代表10日慢速均线。
当今日5日均线高于10日均线(预示股价上行),且昨日5日均线低于10日均线时,两项条件同时成立,确认为开仓信号。随后于次日开盘执行买入,或按前一日收盘价在夜间挂单。
在量化交易中,通常以long指代多头头寸,即买入操作。
# 计算信号data['Signal'] = np.NAN#当前快线大于慢线,前一根的块线小于慢线 则开多cond1 = data['ma_fast'] > data['ma_slow']cond2 = data['ma_fast'].shift(1) <= data['ma_slow'].shift(1)open_pos_signal = cond1 & cond2data.loc[open_pos_signal, 'Signal'] = 1#当前快线小于慢线,前一根的快线大于慢线 则平仓cond3 = data['ma_fast'] < data['ma_slow']cond4 = data['ma_fast'].shift(1) >= data['ma_slow'].shift(1)close_pos_signal = cond3 & cond4data.loc[close_pos_signal, 'Signal'] = 0print(data[['股票名字','交易日期','收盘价','ma_fast','ma_slow','Signal']].head(20))>>股票名字 交易日期 收盘价 ma_fast ma_slow Signal219 特锐德 2023-11-29 19.15 18.946000 18.982000 NaN220 特锐德 2023-11-30 19.17 18.990000 19.015000 NaN221 特锐德 2023-12-01 18.93 19.034000 18.990000 1.0222 特锐德 2023-12-04 18.84 19.034000 18.952000 NaN223 特锐德 2023-12-05 18.63 18.944000 18.903000 NaN224 特锐德 2023-12-06 18.73 18.860000 18.903000 0.0225 特锐德 2023-12-07 18.79 18.784000 18.887000 NaN226 特锐德 2023-12-08 19.03 18.804000 18.919000 NaN227 特锐德 2023-12-11 19.29 18.894000 18.964000 NaN
针对12月1日的交易信号进行解析:
当日快速均线(ma-fast)为19.034,高于慢速均线(ma-slow)的18.99;而前一日11月30日,快速均线18.99低于慢速均线19.01。
这种由下向上的穿越形态触发策略生成信号1,指示执行做多操作。
再看12月6日的平仓信号:当日快速均线18.73跌破慢速均线18.90;反观前一日12月5日,快速均线18.63尚高于慢速均线18.94(注:此处依原文数据逻辑陈述)。
这种由上向下的穿越导致策略输出信号0,意味着执行平仓指令。
3、信号数据清洗
观察数据表可知,存在大量NaN空值,严重干扰视觉判断。为了提升可读性,我们截取前20行数据进行详细审视:
股票名字 交易日期 收盘价 ma_fast ma_slow Signal0 特锐德 2023-01-03 15.45 15.450000 15.450000 NaN1 特锐德 2023-01-04 15.35 15.400000 15.400000 NaN2 特锐德 2023-01-05 15.51 15.436667 15.436667 NaN3 特锐德 2023-01-06 15.57 15.470000 15.470000 NaN4 特锐德 2023-01-09 15.82 15.540000 15.540000 NaN5 特锐德 2023-01-10 15.98 15.646000 15.613333 1.06 特锐德 2023-01-11 15.71 15.718000 15.627143 NaN7 特锐德 2023-01-12 15.82 15.780000 15.651250 NaN8 特锐德 2023-01-13 15.86 15.838000 15.674444 NaN9 特锐德 2023-01-16 15.96 15.866000 15.703000 NaN10 特锐德 2023-01-17 16.11 15.892000 15.769000 NaN11 特锐德 2023-01-18 16.20 15.990000 15.854000 NaN12 特锐德 2023-01-19 16.43 16.112000 15.946000 NaN13 特锐德 2023-01-20 16.50 16.240000 16.039000 NaN14 特锐德 2023-01-30 16.86 16.420000 16.143000 NaN15 特锐德 2023-01-31 16.95 16.588000 16.240000 NaN16 特锐德 2023-02-01 17.36 16.820000 16.405000 NaN17 特锐德 2023-02-02 17.14 16.962000 16.537000 NaN18 特锐德 2023-02-03 17.12 17.086000 16.663000 NaN19 特锐德 2023-02-06 17.33 17.180000 16.800000 NaN20 特锐德 2023-02-07 17.60 17.310000 16.949000 NaN21 特锐德 2023-02-08 17.33 17.304000 17.062000 NaN22 特锐德 2023-02-09 17.42 17.360000 17.161000 NaN23 特锐德 2023-02-10 17.52 17.440000 17.263000 NaN24 特锐德 2023-02-13 17.70 17.514000 17.347000 NaN25 特锐德 2023-02-14 18.45 17.684000 17.497000 NaN26 特锐德 2023-02-15 19.21 18.060000 17.682000 NaN27 特锐德 2023-02-16 18.23 18.222000 17.791000 NaN28 特锐德 2023-02-17 18.00 18.318000 17.879000 NaN29 特锐德 2023-02-20 17.90 18.358000 17.936000 NaN30 特锐德 2023-02-21 17.96 18.260000 17.972000 NaN31 特锐德 2023-02-22 17.98 18.014000 18.037000 0.032 特锐德 2023-02-23 17.60 17.888000 18.055000 NaN33 特锐德 2023-02-24 17.34 17.756000 18.037000 NaN34 特锐德 2023-02-27 17.40 17.656000 18.007000 NaN
观察发现,1月3日至9日信号均为NaN空值。
9日后本应确立多头仓位,却仍显示为空;直至2月22日触发平仓后,后续空仓期间也全为NaN。显然,这些缺失值必须经过处理才能使用。
# 对信号列进行处理# 对NaN信号,使用之前的值进行补全data['Signal'].fillna(method='ffill',inplace=True)# 对于初始的信号还是有一些空值,我们需要把这些signal值补全为0,表示没有交易信号data['Signal'].fillna(value=0, inplace=True)print(data[['股票名字','交易日期','收盘价','ma_fast','ma_slow','Signal']])
打印数据看一下结果:
股票名字 交易日期 收盘价 ma_fast ma_slow Signal0 特锐德 2023-01-03 15.45 15.450000 15.450000 0.01 特锐德 2023-01-04 15.35 15.400000 15.400000 0.02 特锐德 2023-01-05 15.51 15.436667 15.436667 0.03 特锐德 2023-01-06 15.57 15.470000 15.470000 0.04 特锐德 2023-01-09 15.82 15.540000 15.540000 0.05 特锐德 2023-01-10 15.98 15.646000 15.613333 1.06 特锐德 2023-01-11 15.71 15.718000 15.627143 1.07 特锐德 2023-01-12 15.82 15.780000 15.651250 1.08 特锐德 2023-01-13 15.86 15.838000 15.674444 1.09 特锐德 2023-01-16 15.96 15.866000 15.703000 1.010 特锐德 2023-01-17 16.11 15.892000 15.769000 1.011 特锐德 2023-01-18 16.20 15.990000 15.854000 1.012 特锐德 2023-01-19 16.43 16.112000 15.946000 1.013 特锐德 2023-01-20 16.50 16.240000 16.039000 1.014 特锐德 2023-01-30 16.86 16.420000 16.143000 1.015 特锐德 2023-01-31 16.95 16.588000 16.240000 1.016 特锐德 2023-02-01 17.36 16.820000 16.405000 1.017 特锐德 2023-02-02 17.14 16.962000 16.537000 1.018 特锐德 2023-02-03 17.12 17.086000 16.663000 1.019 特锐德 2023-02-06 17.33 17.180000 16.800000 1.020 特锐德 2023-02-07 17.60 17.310000 16.949000 1.021 特锐德 2023-02-08 17.33 17.304000 17.062000 1.022 特锐德 2023-02-09 17.42 17.360000 17.161000 1.023 特锐德 2023-02-10 17.52 17.440000 17.263000 1.024 特锐德 2023-02-13 17.70 17.514000 17.347000 1.025 特锐德 2023-02-14 18.45 17.684000 17.497000 1.026 特锐德 2023-02-15 19.21 18.060000 17.682000 1.027 特锐德 2023-02-16 18.23 18.222000 17.791000 1.028 特锐德 2023-02-17 18.00 18.318000 17.879000 1.029 特锐德 2023-02-20 17.90 18.358000 17.936000 1.030 特锐德 2023-02-21 17.96 18.260000 17.972000 1.031 特锐德 2023-02-22 17.98 18.014000 18.037000 0.032 特锐德 2023-02-23 17.60 17.888000 18.055000 0.033 特锐德 2023-02-24 17.34 17.756000 18.037000 0.034 特锐德 2023-02-27 17.40 17.656000 18.007000 0.0
对Signal列执行前向填充(fillna(method='ffill')),旨在保持持仓状态连贯。例如1月10日开仓后,若无新信号,每日信号均维持为1。
初始阶段(1月3日至9日)的空值则统一补0。
此为双均线策略基础,作为量化入门经典案例。