在期货市场,价格呈现一切。几乎所有的技术分析,如均线、布林线、MACD、KDJ等等,这些都是以价格为基础,通过特定的方法计算。包括基本面分析也是如此,通过分析近期和远期价差、期货和现货升贴水、上下游库存等等数据,计算当前价格是否合理,并预估未来的价格。既然如此,为什么不直接研究价格呢?今天我们讲的菲阿里四价策略就是完全根据价格来做出卖决定。
菲阿里是一位日本的交易者,主要偏向于商品期货日内主观交易。其大名远扬是在2001年的罗宾斯(ROBBINS-TAICOM)期货冠军大赛中,以1098%的成绩获得冠军。并且在之后的两年里再以709%、1131%的成绩夺冠。从成绩就知道,菲阿里是一个非常优秀的交易者。
幸运的是,菲阿里在《1000%的男人》这本书中详尽叙述了他的交易方法,菲阿里四价策略正是后人总结他的交易方法。虽然只是从外在形式加上自己的理解模仿了一部分,并不代表菲阿里全部交易精髓,但至少可以帮助我们在构建策略时拓展思路。
菲阿里四价策略是一种比较简单的趋势性日内交易策略,四价分别是指:昨天高点、昨天低点、昨日收盘价、今天开盘价。从书中的交易笔记来看,菲阿里不使用任何分析工具,而是大量应用阻溢线的概念,也就是通常我们所说的阻力线和支撑线。
对于阻力线和支撑线的定义,他使用的是昨日最高价和昨日最低价,可以视为昨天价格的波动范围,这也意味着多头或空头只有足够的力量时,才会有效突破阻力线和支撑线。并且一旦有突破这个波动范围,则说明价格背后的动能较大,后续走势可能会沿最小阻力线运动的概率较大。
如果开盘价处于阻力线和支撑线之间,当价格向上突破阻力线就建立多头,当价格向下突破支撑线就建立空头。如果一切顺利,则一直持仓到收盘。这样做的好处是,符合了充分非必要条件,即突破不一定上涨/下跌,但上涨/下跌一定会突破,也就是始终守在行情发生的必经之路伺机而动,因为较大行情的上涨和下跌一旦出现,势必要突破阻力线和支撑线的。
当然这也是出错率最高的方法,因为很多时候价格只是暂时性的突破了关键位置,如果贸然开仓可能会面临价格随时反向运动的风险。这时就需要设置一些过滤条件,限制假突破造成的来回开平仓问题。另外在交易周期上也尽量避免波动过于混乱的5分钟周期以下K线。
但是开仓后,盈利了还好,如果遇到亏损,总不能从小亏损一直积累到大亏损,才在收盘时平仓吧,这样显然不合理。所以我们对于平仓有两种处理方式:收盘平仓和止损平仓。如果K线上破高点或下破低点后又回到原来的区间内,就要考虑止损了。
实际上菲阿里在主观交易中,还有很多交易方法,包括:开盘后先涨后跌,跌破开盘价做空,止损设在之前上涨的最高点;开盘后先跌后涨,突破开盘价做多,止损设在之前下跌的最低点。动手能力强的朋友可以在自己的策略中增改。
到这里你会发现,对于一天的价格走势来说,收盘价相对于开盘价的涨跌,其概率接近于50%。菲阿里的交易方法在胜率上就利于不败之地,再加上行情顺利的时候一直持仓到收盘,在行情不符合自己的预期时及时止损。形成了截断亏损,让利润奔跑的正向交易方式,这也是长期交易下来积累利润的原因。
第1步:编写策略框架
# 策略主函数
def onTick():
pass
# 程序入口
def main():
while True: # 进入无限循环模式
onTick() # 执行策略主函数
Sleep(1000) # 休眠1秒
定义一个main函数和一个onTick函数,然后再main函数中,写入while循环,重复执行onTick函数。
第2步:导入库
import time # 用于转换时间格式
因为我们这个是日内策略,到时候需要判断K线时间戳,如果有持仓,并且临近收盘时平仓出局。那么我们就直接import time就可以了。
第3步:获取基础数据
bar_arr = _C(exchange.GetRecords, PERIOD_D1) # 获取日线数组
if len(bar_arr) < 2: # 如果小于2根K线
return # 返回继续等待数据
yesterday_high = bar_arr[-2]['High'] # 昨日最高价
yesterday_low = bar_arr[-2]['Low'] # 昨日最低价
yesterday_close = bar_arr[-2]['Close'] # 昨日收盘价
today_open = bar_arr[-1]['Open'] # 当日开盘价
菲阿里四价需要用到四个数据:昨日最高价、昨日最低价、昨日收盘价、当日开盘价。因为这些都是日线级别的数据,所以我们在使用GetRecords的时候,可以直接传入PERIOD_D1参数,表明我们要获取的是日K,这样无论你的策略加载的是哪个周期的数据,它始终获取的都是日线级别的数据。
另外,细心的朋友可能已经发现,为什么这一次在调用GetRecords的时候,代码的写法跟以前不一样?这次我们使用的是优宽量化软件内置的重试函数_C()。使用这个函数的好处是,该函数会一直调用指定函数到成功返回,这样可以避免直接使用GetRecords函数时,没有获取到数据导致报错的情况。
第4步:处理时间和获取最新价格
bar_arr = _C(exchange.GetRecords) # 获取当前设置周期K线数组
current_time = bar_arr[-1]['Time'] # 获取当前K线时间戳
time_local = time.localtime(current_time / 1000) # 处理时间戳
hour = int(time.strftime("%H", time_local)) # 格式化时间戳,并获取小时
minute = int(time.strftime("%M", time_local)) # 格式化时间戳,并获取分钟
current_close = bar_arr[-1]['Close'] # 获取最新价格
既然是获取当前的时间,那么肯定是使用获取当前设置周期的K线数据更为合适,所以需要重新使用一次GetRecords,这次我们同样也是使用_C()重试函数,在不传入参数的情况下,就是默认获取当前设置周期的K线数组。另外,获取最新价格的目的是,计算交易逻辑和下单。
第5步:处理时间
def trade_time(hour, minute):
hour = str(hour)
minute = str(minute)
if len(minute) == 1:
minute = "0" + minute
return int(hour + minute)
之所以创建这个函数,是因为我们在开仓之前,需要判断当前时间,是否在我们规定的交易时间之内,以及有持仓的时候,当前时间是否临近收盘。在第4步中,我们已经获取到了当前K线小时和分钟,为了方便比较,我们采用小时加分钟的方法,比如:如果K线时间是9:05,那么trade_time返回的结果就是905;如果K线时间是14:30,那么trade_time返回的结果就是1430等等。
第6步:获取持仓
real_position = _C(exchange.GetPosition) # 获取持仓数组
if len(real_position) > 0: # 如果持仓数组长度大于0
real_position = real_position[0]
if real_position['ContractType'] == 'rb888': # 如果持仓品种等于订阅品种
if real_position['Type'] == 0 or real_position['Type'] == 2: # 如果是多单
mp = real_position['Amount'] # 赋值持仓为正数
elif real_position['Type'] == 1 or real_position['Type'] == 3:
mp = -real_position['Amount'] # 赋值持仓为负数
else:
mp = 0 # 赋值持仓为0
在之前的章节中,我们一直使用的是虚拟持仓,虽然编写起来简单,但只能适用于理想环境下。为了更贴近实战,在之后的章节中,我们将使用真实的持仓数据。要获取真实的持仓数据,其实也没那么难,直接使用优宽量化软件的GetPosition方法,就可以获取到当前账户的所有持仓。
GetPosition返回的是一个包含字典的一维数组:
# 例子
[{'MarginLevel': 16, 'Amount': 1.0, 'FrozenAmount': 0.0, 'Price': 3540.0, 'Profit': -30.0, 'Margin': 2124.0, 'Type': 0, 'ContractType': 'rb888'}]
其中,Amount是持仓数量、Price是开仓价格、Profit是当前利润、Type是多空方向(根据优宽量化API的定义,Type的值是0或2是代表多头,1或3代表空头)、ContractType是合约代码。而我们把多头持仓规整为正数,把空头持仓规整为负数。
第7步:设置止损
# 设置多头止损
if today_open / yesterday_high > 1.005: # 如果当天开盘价大于昨天最高价
long_stop_loss = yesterday_high # 设置多头止损价为昨天最高价
elif today_open / yesterday_high < 0.995: # 如果当天开盘价小于昨天最高价
long_stop_loss = today_open # 设置多头止损价为当天开盘价
else: # 如果当天开盘价接近于昨天最高价
long_stop_loss = (yesterday_high + yesterday_low) / 2 # 设置多头止损为昨天中间价
# 设置空头止损
if today_open / yesterday_low < 0.995: # 如果当天开盘价小于昨天最低价
short_stop_loss = yesterday_low # 设置空头止损价为昨天最低价
elif today_open / yesterday_low > 1.005: # 如果当天开盘价大于昨天最低价
short_stop_loss = today_open # 设置空头止损价为当天开盘价
else: # 如果当天开盘价接近于昨天最低价
short_stop_loss = (yesterday_high + yesterday_low) / 2 # 设置多头止损为昨天中间价
在大多数情况下,价格突破阻力线和支撑线,就把止损设置到当前开盘价这个位置。但是这里面有个问题:如果当天开盘价大于阻力线,而价格往下走;或者当天开盘价小于支撑线,而价格往上走,会造成逻辑错误,导致频繁开平仓。
为了解决这个问题,我们需要根据当天开盘与阻力线和支撑线的位置关系,分别设置不同的止损价格。如果当天开盘价大于昨天最高价0.5%,那么就把多头的止损设置在昨天的最高价;如果当天开盘价在阻力线和支撑线之间,那么多头的止损价格还是当天的开盘价;如果当天开盘价接近于昨天最高价,那么就把多头的止损设置在昨天的中间价。设置空头的止损也是根据这个道理。
第8步:下单交易
if mp > 0: # 如果当前持有多单
if current_close < long_stop_loss or trade_time(hour, minute) > 1450: # 如果当前价格小于多头止损线,或者超过规定的交易时间
exchange.SetDirection("closebuy") # 设置交易方向和类型
exchange.Sell(current_close - 1, 1) # 平多单
if mp < 0: # 如果当前持有空单
if current_close > short_stop_loss or trade_time(hour, minute) > 1450: # 如果当前价格大于空头止损线,或者超过规定的交易时间
exchange.SetDirection("closesell") # 设置交易方向和类型
exchange.Buy(current_close + 1, 1) # 平空单
if mp == 0 and 930 < trade_time(hour, minute) < 1450: # 如果当前无持仓,并且在规定的交易时间内
if current_close > yesterday_high: # 如果当前价格大于昨天最高价
exchange.SetDirection("buy") # 设置交易方向和类型
exchange.Buy(current_close + 1, 1) # 开多单
elif current_close < yesterday_low: # 如果价格小于昨天最低价
exchange.SetDirection("sell") # 设置交易方向和类型
exchange.Sell(current_close - 1, 1) # 开空单
万事俱备就可以下单交易了,为了避免逻辑错误,最好是把平仓逻辑写到开仓逻辑的前面。
虽然距离比赛结束已经有近20年之久了,但以今天的眼光看那时的交易,毫无过时感。但需要注意的是,策略仅仅作为思路拓展,不能直接用于实盘。对于菲阿里策略来说,它提供了一个很好的入场参考工具,我们可以根据自己对市场的认知做更深的开发,市场是不断进化的,如果笃信一个交易方式,最终会被市场淘汰。
import time # 导入库,用于转换时间格式 mp = 0 # 虚拟持仓 def trade_time(hour, minute): minute = str(minute) if len(minute) == 1: minute = "0" + minute return int(str(hour) + minute) def onTick(): _C(exchange.SetContractType, "rb888") # 订阅期货品种 bar_arr = _C(exchange.GetRecords, PERIOD_D1) # 获取日线数组 if len(bar_arr) < 2: # 如果小于2根K线 return # 返回继续等待数据 yh = bar_arr[-2]['High'] # 昨日最高价 yl = bar_arr[-2]['Low'] # 昨日最低价 today_open = bar_arr[-1]['Open'] # 当日开盘价 bar_arr = _C(exchange.GetRecords) # 获取当前设置周期K线数组 current = bar_arr[-1]['Time'] # 获取当前K线时间戳 local = time.localtime(current / 1000) # 处理时间戳 hour = int(time.strftime("%H", local)) # 格式化时间戳,并获取小时 minute = int(time.strftime("%M", local)) # 格式化时间戳,并获取分钟 price = bar_arr[-1]['Close'] # 获取最新价格 global mp # 设置多头止损 if today_open / yh > 1.005: # 如果当天开盘价大于昨天最高价 long_stop = yh # 设置多头止损价为昨天最高价 elif today_open / yh < 0.995: # 如果当天开盘价小于昨天最高价 long_stop = today_open # 设置多头止损价为当天开盘价 else: # 如果当天开盘价接近昨天最高价 long_stop = (yh + yl) / 2 # 设置多头止损为昨天中间价 # 设置空头止损 if today_open / yl < 0.995: # 如果当天开盘价小于昨天最低价 short_stop = yl # 设置空头止损价为昨天最低价 elif today_open / yl > 1.005: # 如果当天开盘价大于昨天最低价 short_stop = today_open # 设置空头止损价为当天开盘价 else: # 如果当天开盘价接近昨天最低价 short_stop = (yh + yl) / 2 # 设置多头止损为昨天中间价 # 下单交易 trading = trade_time(hour, minute) if mp > 0: # 如果当前持有多单 # 如果当前价格小于多头止损线,或者超过规定的交易时间 if price < long_stop or trading > 1450: exchange.SetDirection("closebuy") # 设置交易方向和类型 exchange.Sell(price - 1, 1) # 平多单 mp = 0 # 重置虚拟持仓 if mp < 0: # 如果当前持有空单 # 如果当前价格大于空头止损线,或者超过规定的交易时间 if price > short_stop or trading > 1450: exchange.SetDirection("closesell") # 设置交易方向和类型 exchange.Buy(price, 1) # 平空单 mp = 0 # 重置虚拟持仓 # 如果当前无持仓,并且在规定的交易时间内 if mp == 0 and 930 < trading < 1450: if price > yh: # 如果当前价格大于昨天最高价 exchange.SetDirection("buy") # 设置交易方向和类型 exchange.Buy(price, 1) # 开多单 mp = 1 # 重置虚拟持仓 elif price < yl: # 如果价格小于昨天最低价 exchange.SetDirection("sell") # 设置交易方向和类型 exchange.Sell(price - 1, 1) # 开空单 mp = -1 # 重置虚拟持仓 def main(): while True: # 无限循环 onTick() # 执行策略主函数 Sleep(1000) # 休眠1秒