# coding=utf-8
from __future__ import print_function, absolute_import
from gm.api import *
import pandas as pd
import talib as tb
import numpy as np
import math as mh
'''
本策略以BULL中轨线5min(即300s频度)bar数据建立双均线模型,
短周期为short,长周期为60,当短期均线由上向下穿越长期均线时做空,
当短期均线由下向上穿越长期均线时做多,每次开仓前先平掉所持仓位,再开仓。
注:为了适用于仿真和实盘,在策略中增加了一个“先判断是否平仓成功再开仓”的判断逻辑,以避免出现未平仓成功,可用资金不足的情况。
回测数据为:SHFE.rb2101的60s频度bar数据
回测时间为:2020-04-01 09:00:00到2020-05-31 15:00:00
'''
def init(context): # 定义参数
context.shorttime = '300s' # 短周期,5min
context.longtime= '1500s' # 长周期,15min
context.long1 = 12 # 短周期取样频率
context.period1 = context.long1 + 1 # 订阅数据滑窗长度
context.long2 = 4 # 长周期取样频率
context.period2 = context.long2 + 1 # 订阅数据滑窗长度
context.BandWidth = 2 # 带宽率
context.Angle1 = 2 # 上涨均线角度
context.Angle2 = -2 # 下跌均线角度
context.maPeriod1 = 12 # 计算BOLL布林线中轨的参数
context.stdPeriod1 = 26 # 计算BOLL 标准差的参数
context.stdRange1 = 2.6 # 计算BOLL 上下轨和中轨距离的参数
context.maPeriod2 = 4 # 计算BOLL布林线中轨的参数
context.stdPeriod2 = 8 # 计算BOLL 标准差的参数
context.stdRange2 = 2.6 # 计算BOLL 上下轨和中轨距离的参数
context.symbol = 'DCE.i2109' # 订阅交易标的
context.open_long = False # 开多单标记
context.open_short = False # 开空单标记
subscribe(context.symbol, frequency = context.shorttime, count = context.period1) # 订阅短周期行情
subscribe(context.symbol, frequency = context.longtime, count = context.period2) # 订阅长周期行情
'''
AS:=(C*3+O+H+L)/6;
AS1:=0.618*REF(AS,1);
AS2:=0.382*REF(AS,2);
AS3:=0.236*REF(AS,3);
AS4:=0.146*REF(AS,4);
NEWPRICE1:=(AS+AS1+AS2+AS3+AS4)/2.382;
'''
'''
在通达信中有一个REF(N)函数,用来表示滞后N期的数据,REF(CLOSE,1)就表示滞后1期的收盘价。转化到python中,需要用索引的方式直接取出前一期数据。
比如:REF(CLOSE,1)在python中表示为CLOSE.iloc[-2],意思为CLOSE的倒数第二个元素,即CLOSE的前一期元素。
(因为CLOSE的最后一个元素是当前日期的数据,倒数第二个元素才是前一日的数据,所以选择倒数第二个元素)
Tips : iloc的用法
iloc表示索引引用方法,使用方式为:df.iloc[m,n],表示df第m行n列的数据。如果df是一维数据,则df.iloc[n]表示df第n个数据。
'''
def newprice1(price_frequency, price_count):
prices = context.data(context.symbol, price_frequency, price_count, fields='open,close,high,low')
High = context.data['high'] # 最高价
Low = context.data['low'] # 最低价
Close = context.data['close'] # 收盘价
Open = context.data['open'] # 开盘价
AS = (Close*3 + Open + High + Low) / 6 # 拟合成本价格
AS1 = 0.618 * AS.iloc[-2]
AS2 = 0.382 * AS.iloc[-3]
AS3 = 0.382 * AS.iloc[-4]
AS4 = 0.382 * AS.iloc[-5]
result1 = (AS+AS1+AS2+AS3+AS4)/2.382
return result1
'''
NEWPRICE2:=IF(C>REF(C,1),0.618*C+0.382*H,0.618*C+0.382*L);
'''
def newprice2(price_frequency,price_count):
prices = context.data(context.symbol, price_frequency, price_count, fields='open,close,high,low')
High = context.data['high'] # 最高价
Low = context.data['low'] # 最低价
Close = context.data['close'] # 收盘价
Open = context.data['open'] # 开盘价
if (Close.iloc[-1] > Close.iloc[-2]) :
result2 = 0.618* Close + 0.382 * High
else :
result2 = 0.618* Close + 0.382 * Low
return result2
'''
MA2:=MA(NEWPRICE2,21);
'''
# 利用talib库计算周期均线
def newprice2_avg(price_frequency,price_count,N):
price = newprice2(price_frequency,price_count,N)
avg = price.rolling(N).mean()
return avg
def on_bar(context, bars): # 获取数据滑窗,只要在init里面有订阅,在这里就可以取的到,返回值是pandas.DataFrame
# 获取通过subscribe订阅的数据,取出是dataframe格式
data1 = context.data(context.symbol, context.shorttime, context.period1, fields='open,close,high,low')
data2 = context.data(context.symbol, context.longtime, context.period2, fields='open,close,high,low')
'''
# BOLL:BOLL带指标计算
# 中轨线 = N日收盘价平均值
# 上轨线 = 中轨线 + N日收盘价标准差
# 下轨线 = 中轨线 - N日收盘价标准差
{中轨}
MID:=EMA(NEWPRICE1,NSHORT);
中轨:MID,COLORWHITE,LINETHICK2;
STD_WIDTH:=2.6*STD(MID,MLONG);
{上轨}
UPPER:=MID+STD_WIDTH;
上轨:UPPER,COLORYELLOW,LINETHICK2;
{下轨}
LOWER:=MID-STD_WIDTH;
下轨:LOWER,COLORGREEN,LINETHICK2;
{轨道收缩率}
BAND_RATE:=STD_WIDTH/MID*1000;
带宽比:BAND_RATE,NODRAW;
MIDANGLE:=ATAN((MID/REF(MID,1)-1)*100)*180/3.1416;
MID角度:MIDANGLE,COLORWHITE,NODRAW;
'''
# 计算boll中轨价格
N1 = newprice1(context.shorttime, context.period1)
BollMid = talib.EMA(N1,context.period1)
BollStd = context.stdRange1 * N1.rolling(context.stdPeriod1).std()
# 计算boll的上下界
BollUpper = BollMid + BollStd
BollBottom = BollMid - BollStd
# 计算带宽比
BandRate = BollStd / BollMid * 100
# 计算中轨均线角度
MidAngle = math.atan(BollMid/BollMid[-1]) # atan()是不能直接访问的,需要导入 math 模块,然后通过 math 静态对象调用该方法。
'''
{布林带下降趋势,图标15-小人逃跑,平仓或做空}
S1:=MID<REF(MID,1) AND NEWPRICE2<MA2 ;
S2:=MIDANGLE<ANGLE2;
DRAWICON(S1 AND S2 ,MID*0.999,15);
{布林带上降趋势,图标9-金钱袋,开仓仓或做多}
B1:MID>REF(MID,1) AND NEWPRICE2>MA2 AND NEWPRICE1>MID AND C>MID,NODRAW;
B2:MIDANGLE>ANGLE1,NODRAW;
BUYCONDITION:=B1 AND B2 ;
DRAWICON(BUYCONDITION,MID*1.002,9);
'''
# SellCondition 卖出条件
S1 = BollMid < BollMid[-1] and newprice2 < newprice2_avg(contex.shorttime,context.period1,21)
S2 = MidAngle < context.Angle2
SellCondition = S1 and S2
# BuyCondition 买入条件
B1 = BollMid > BollMid[-1] and newprice2 > newprice2_avg(contex.shorttime,context.period1,21) and newprice1>BollMid and data['close'] > BollMid
B2 = MidAngle > context.Angle1
BuyCondition = B1 and B2
# 查询持仓
position_long = context.account().position(symbol=context.symbol, side=1) # PositionSide_Long == 1,多单
position_short = context.account().position(symbol=context.symbol, side=2) # PositionSide_Short == 1,空单
# 做空
if SellCondition.iloc[-2] < SellCondition.iloc[-1] and SellCondition.iloc[-2] == 0 :
# 无多仓情况下,直接开空
if not position_long:
order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell, position_effect=PositionEffect_Open, order_type=OrderType_Market)
print(context.symbol, '以市价单调空仓到仓位')
# 有多仓情况下,先平多,再开空(开空命令放在on_order_status里面)
else:
context.open_short = True
# 以市价平多仓
order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell, position_effect=PositionEffect_Close, order_type=OrderType_Market)
print(context.symbol, '以市价单平多仓')
# 做多
if BuyCondition.iloc[-2] < BuyCondition.iloc[-1] and BuyCondition.iloc[-2] == 0:
# 无空仓情况下,直接开多
if not position_short:
order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy, position_effect=PositionEffect_Open, order_type=OrderType_Market)
print(context.symbol, '以市价单调多仓到仓位')
# 有空仓的情况下,先平空,再开多(开多命令放在on_order_status里面)
else:
context.open_long = True
# 以市价平空仓
order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy, position_effect=PositionEffect_Close, order_type=OrderType_Market)
print(context.symbol, '以市价单平空仓')
def on_order_status(context, order):
# 查看下单后的委托状态
status = order['status']
# 成交命令的方向
side = order['side']
# 交易类型
effect = order['position_effect']
# 当平仓委托全成后,再开仓
if status == 3:
# 以市价开空仓,需等到平仓成功无仓位后再开仓
# 如果无多仓且side=2(说明平多仓成功),开空仓
if effect == 2 and side == 2 and context.open_short:
context.open_short = False
order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell, position_effect=PositionEffect_Open, order_type=OrderType_Market)
print(context.symbol, '以市价单调空仓到仓位')
# 以市价开多仓,需等到平仓成功无仓位后再开仓
# 如果无空仓且side=1(说明平空仓成功),开多仓
if effect == 2 and side == 1 and context.open_long:
context.open_long = False
order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy, position_effect=PositionEffect_Open, order_type=OrderType_Market)
print(context.symbol, '以市价单调多仓到仓位')
if __name__ == '__main__':
'''
strategy_id策略ID,由系统生成
filename文件名,请与本文件名保持一致
mode实时模式:MODE_LIVE
mode回测模式:MODE_BACKTEST
token绑定计算机的ID,可在系统设置-密钥管理中生成
backtest_start_time回测开始时间
backtest_end_time回测结束时间
backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
backtest_initial_cash回测初始资金
backtest_commission_ratio回测佣金比例
backtest_slippage_ratio回测滑点比例
'''
run(strategy_id='1c566dba-c345-11eb-9923-b07b25324c88',
filename='main.py',
mode=MODE_BACKTEST,
token='{{token}}',
backtest_start_time='2021-01-01 09:00:00',
backtest_end_time='2021-05-31 15:00:00',
backtest_adjust=ADJUST_NONE,
backtest_initial_cash=10000,
backtest_commission_ratio=0.0001,
backtest_slippage_ratio=0.0001)