导语:商品期货分钟交易上线啦!听闻这个消息的小编当然坐不住了,决定立刻商品期货走一波! 本文选择实现的是经典的卡夫曼自适应移动均线交易系统,让我们来看看在商品期货中的应用吧!
技术分析往往离不开均线系统,它是我们观察价格走势的基础。短期均线贴近价格走势,灵敏度高,但会有很多噪声, 产生虚假信号;长期均线在判断趋势上一般比较准确,但是长期均线有着严重滞后的问题。我们想得到这样的均线, 当价格沿一个方向快速移动时,短期的移动平均线是最合适的;当价格在横盘的过程中,长期移动平均线是合适的。
为了达到上述目的,我们选择了卡夫曼自适应移动均线。
价格方向被表示为整个时间段中的净价格变化。比如,使用n天的间隔(或n小时):
$$d_t = p_t – p_{t-n}$$其中,$d_t$是当前价格差或方向数值,$p_t$是时间t的价格。
波动性是市场噪音的总数量,它可以用许多不同的方法定义,但是这个计算使用了所有“日到日”或“小时到小时”的价格变化的总和 (每一个都作为一个正数),在同样的n个周期上。如下定义: $$vol = \sum_{t \in \mathcal{W}} |p_t – p_{t-1}|$$ 其中,vol是指波动性数值,$|\cdot|$表示绝对值,$\sum$后面的表示滑动窗口求和。
以上两个成分被组合起来,以表达方向移动对噪音之比,称之为效率系数,ER: $$ER = \frac{d}{vol}$$ 用“方向性”除以“噪音”,该系数的值就从0到1 变化。当市场在全部n日以同一方向移动时,则方向=波动性,效率系数=1。如果波动对于同样的价格移动是增加了,“波动性”就变得较大并且ER往小于1的方向移动。如果价格不变化,则方向=0,ER=0。 这个结果作为一个指数式平滑系数是方便的,它每天改变趋势线的一个百分比,ER=1就等效于100%,对应最快的移动平均线,它应当能有效工作,因为价格在一个方向上移动而没有回撤。当ER=0时,一个非常慢的移动平均值是最好的,可以在市场趋势不明时避免贸然止损离场。
为了应用于一个指数式移动平均值,比率将被变换为一个平滑系数c,依靠下面的公式,每天的均线速度可以简单地用改变平滑系数来改变,成为自适应性的。该公式如下: $$EXPMA_t = EXPMA_{t-1} + c*(p – EXPMA_{t-1})$$ 测试表明,平方平滑系数的数值大大地改进了结果,这是依靠在一个横盘的市场中阻止了趋势线的移动。 在横盘的市场中这个过程选择了非常慢的趋势,而在高度趋势化的周期中加速至非常快的趋势(但不是100%)。这个平滑系数是: \begin{align*} fastest &= 2/(N_f+1) = 2/(2+1) = 0.6667\\ slowest &= 2/(N_s+1) = 2/(30+1) = 0.0645\\ smooth &= ER \cdot (fastest - slowest) + slowest\\ c &= smooth^2 \end{align*} 平方平滑系数迫使c的数值趋向于零。这意味着较慢的移动平均线将比快速移动平均值用得更多。这和在出现不确定状况时你就更加保守是一样的道理。 于是,我们最终的卡夫曼自适应移动均线AMA如下定义:
$$AMA_t = AMA_{t-1} + c \cdot (p – AMA_{t-1})$$为了与系统的自适应特性相一致,当价格波动变得更多或更少时,过滤器也要相应取较大或较小值。为了完成这点,过滤器被定义为AMA变化的一个小的百分数:
$$filter = percentage \cdot std(AMA_t-AMA_{t-1}, n)$$其中,std(series, n)是滑动标准差函数。
最小的过滤器百分数0.1可被用于较快的交易,而较大的百分数1.0将可以选择出更有意义的价格移动的交易。 典型例证是:外汇和期货市场交易较快,股票和利率市场交易较慢。通常,过滤器大小是依据20天周期的数据来计算。
当AMA - min(AMA, n) > filter,买入;
当max(AMA, n) – AMA > filter,卖出。
import talib as ta
# 1. 策略基本参数
start_date = '2018-04-20 09:01:00'
end_date = '2018-05-30 15:15:00'
benchmark = 'RB1901.SHF'
instruments = 'RB1901.SHF'
# 起始资金
capital_base = 50000
# 回测频率 daily or minute
data_frequency = 'minute'
# 2. 策略主体函数
# 初始化虚拟账户状态,只在第一个交易日运行
def initialize(context):
# 设置手续费,买入时万3,卖出是千分之1.3,不足5元以5元计
context.set_commission(futures_commission=PerContract(cost={'RB':(0.000045, 0.000045, 0.000045)}))
context.set_margin('RB', 0.07)
context.index = 1 #日期记录
context.set_need_settle(False) #逐日结算开关设置
#设置参数
context.window = 60
# 策略交易逻辑,handle_data函数会每个周期(日/分)运行一次,可以把行情数据理解成K线,
# 然后handle_data函数会在每个K线上依次运行
def handle_data(context, data):
today = data.current_dt#.strftime('%Y-%m-%d-%H-%M')
if context.index < context.window: # 在形成均线后才开始交易
context.index+=1
return
if today.hour > 15:
return
instrument = context.future_symbol(context.instruments[0]) # 交易标的
curr_position = context.portfolio.positions[instrument].amount # 持仓数量
price = data.history(instrument, 'close', context.window, '1m') # 历史价格
if np.any(np.isnan(price)):
return
n=40
kama = ta.KAMA(price, timeperiod=n)
ft = 0.8*np.std((kama-kama.shift(1))[-n:])
if kama[-1] - np.min(kama[-n:]) > ft:
context.order_target(instrument, 3)
if np.max(kama[-n:]) - kama[-1] > ft:
context.order_target(instrument, -3)
# 3. 启动回测
m = M.trade.v4(
instruments=instruments,
start_date=start_date,
end_date=end_date,
initialize=initialize,
handle_data=handle_data,
# 股票买入的时候,假设以次日开盘价成交
order_price_field_buy='open',
# 股票卖出的时候,假设以次日开盘价成交
order_price_field_sell='open',
capital_base=capital_base,
benchmark=benchmark,
volume_limit=0,
data_frequency=data_frequency,
product_type='future',
m_deps=np.random.rand()
)