双均线策略示例

策略简介

均线,即移动平均线(Moving Average),是指最近 n 天收市价格的算术平均线,在计算中,始终采用最近 n 天的价格数据,随着新的交易日的更迭,逐日向前推移。均线理论是当今应用最普遍的技术指标之一,它帮助交易者确认现有趋势、判断将出现的趋势、发现过度延生即将反转的趋势。我们这里探讨的双均线策略即是对均线理论的一项简单应用。

所谓双均线,是指考察短期、长期两条均线,例如 5 天和 10 天、或者 20 天和 40 天的移动平均线,当两条均线相交时,若短期均线向上穿过长期均线,即视为买入信号;反之若短期均线向下穿过长期均线,则视为卖出信号。其内在的经济学逻辑在于,当短期均线上穿并高过长期均线时,意味着该股票在近期表现好于之前,显示上涨趋势,因此选择买入。可以看出,我们这里使用的双均线策略是一个简单的趋势跟随型策略。

双均线策略逻辑清晰,易于实现,且在市场的检测中确有其效果,下面我们将使用云宽客平台实现该策略并对其结果略作检视。

收益曲线

收益归因

关于回测平台、历史数据,可以在这里下载:http://www.yunkuanke.com/#/introduce
这个平台的数据都是经过清洗的,而且策略不会外露,有保密系统,还是比较安全的。
想学习更多量化策略,也可以来这里看看:http://www.yunkuanke.com/#/lzClass
想学习更多关于量化投资策略相关内容的,可以关注微信:量化投资与金融科技(微信号:QuantumFintech)
微信二维码:

小结

从回测结果看,这一策略的表现还是不错的。在 2006.1.1-2017.1.1 这个时间段实现了 34.2% 的年化收益率(复合年化收益率 15.5%)。当然这里只是一个简单的单股票择时策略,波动率和最大回撤都不小,收益并不稳定。

双均线交叉只是双均线策略的其中一种应用,除此之外,我们还可以考虑更严格的双移动均线策略:把两条均线的中间看作某种中性区。那么,仅当收市价格同时向上越过了两条平均线之后,才构成买入信号。然后,如果价格再跌回中性区,则上述信号被取消。同样,仅当收市价格同时向下穿越了两条平均线之后,才构成卖出信号。然后,如果价格在涨回两条平均线之间的中性区,我们就平仓了结上述空头头寸。只要价格维持于中性区内,我们就袖手静观。依此方法设计的系统,也有一些其他系统所不及的长处。

既然两条移动平均线似乎比一条更好,那么,如果把三条平均线相组合,是否能胜过两条平均线的组合呢?基于这样的设想,就有了三重交叉方法。最常用的三重交叉法系统,要数 4-9-18 天移动平均线的组合。在商品行业,5 天、10 天和 20 天移动平均线是使用得最广泛的几种。4-9-18 天系统其实只是它的一种变化。在使用中,当短期的移动平均线从下往上穿过中期和长期移动平均线时,就构成了买入信号,而中期移动平均线也上穿长期移动平均线时买入信号得到确认;反之,当短期的移动平均线从上往下穿过中期和长期移动平均线时,就构成了卖出信号,而中期移动平均线也下穿长期移动平均线时卖出信号得到确认。

除了对双均线系统的分析研判,我们还可以对均线本身加以操作,例如我们可以选择使用更复杂的均线计算方式,如加权平均;或是结合其他选股类策略,对多股票进行操作,都有可能创造出更加稳健而有效的策略。

Code:

# -*- coding:utf-8 -*-

from CloudQuant import SDKCoreEngine  # 导入量子金服SDK
from CloudQuant import AssetType
from CloudQuant import QuoteCycle
from CloudQuant import OrderType
import numpy as np  # 使用numpy
np.seterr(invalid='ignore')

config = {
'username': 'username', #用户名
'password': 'password', #密码
'rootpath': 'c:/cStrategy', # 客户端所在路径
'assetType': AssetType.Stock, #交易资产类型
'initCapitalStock': 100000000, # 初始资金
'startDate': 20060101, # 交易开始日期
'endDate': 20170101, # 交易结束日期
'cycle': QuoteCycle.D,  # 回放粒度为日线
'feeRate': 0.001, #设定费率
'feeLimit': 5,  #设定最低费用为5
'strategyName': 'dualMA',  # 策略名
"logfile": "dualMA.log", #设定日志文件名
'dealByVolume': False  # 撮合是否考虑实际交易量
}

 TRADE_LIST=['000001'] #设定要交易的股票,可以结合选股策略进行选择,这里用平安银行做示范
LONG_PERIOD=40 #设定长期均线时长
SHORT_PERIOD=20 #设定短期均线时长

def initial(sdk): #整个回测前需要的操作
flag=[0]*len(TRADE_LIST) #标记现在均线的状态
sdk.setGlobal("flag",flag) #将flag设为全局变量

def initPerDay(sdk): #每天回测前需要的操作
pass

def strategy(sdk): #交易策略
priceList=sdk.getLatest(TRADE_LIST,count=LONG_PERIOD,timefreq="D") #获取股票池中股票历史价格
longMA=[]
shortMA=[]
for stock in TRADE_LIST: #计算长期均线
if len(priceList[stock]) >= LONG_PERIOD:
    temp=0
    for i in range(LONG_PERIOD):
        temp+=priceList[stock][-i-1].close
    temp=temp/LONG_PERIOD
    longMA.append(temp)
else:
    return

for stock in TRADE_LIST: #计算短期均线
if len(priceList[stock]) >= SHORT_PERIOD:
    temp=0
    for i in range(SHORT_PERIOD):
        temp+=priceList[stock][-i-1].close
    temp=temp/SHORT_PERIOD
    shortMA.append(temp)
else:
    return

 buyList=[]
 sellList=[]

flag=sdk.getGlobal("flag") #从全局变量获取现在均线状态
for i,stock in enumerate(TRADE_LIST): #判断是否买卖
if shortMA[i]>longMA[i] and flag==-1: #短期均线上穿长期均线,买入
    buyList.append(stock)
if shortMA[i]<longMA[i] and flag==1: #短期均线向下突破长期均线,卖出
    sellList.append(stock)
flag[i]=1 if shortMA[i]>longMA[i] else -1 #更新均线状态

 sdk.setGlobal("flag",flag)

if sellList: #卖出应售股票
for stock in sellList:
    pos=sdk.getPositions(code=stock) #检查股票持有情况
    if pos:
        quote=sdk.getQuote(stock)
        sdk.makeOrder(stock,quote.current,pos[0].optPosition,-1) #卖出所有可交易持仓
        sdk.sdklog([stock,quote.current,pos[0].optPosition,-1],'sell') #记入日志

if buyList: #买入应购股票
budget=sdk.getAccountInfo().availableCash/len(buyList) #将可用资金平均分配至每一支要买入的股票
for stock in buyList:
    quote = sdk.getQuote(stock)
    volume = (int(budget /(quote.current*(1+config['feeRate'])))//100)*100 #计算交易量,取100整数倍
    sdk.makeOrder(stock, quote.current, volume, 1) #买入
    sdk.sdklog([stock, quote.current, volume, 1], 'buy') #记入日志

def main():
# 将策略函数加入
config['initial'] = initial
config['strategy'] = strategy
config['preparePerDay'] = initPerDay
# 启动SDK
SDKCoreEngine(**config).run()

 if __name__ == "__main__":
main()