资源加载中... loading...

Python商品期货量化入门教程

Author: 雨幕(youquant), Created: 2024-09-12 09:11:33, Updated: 2024-09-12 09:17:23

�平仓指令’) p.CoverAll()


状态栏定制首先我们需要一个用于描述表格的对象tbl,这涉及到了状态栏于展示交互的功能,我们设计的很简单:

* title: 表格的标题,这里是 "状态栏交互"。
* cols: 表格的列名数组,这里是 ["操作", "按钮"]。
* rows: 表格的行数据数组,每一行是一个数组,包含相应的单元格数据。

在这段代码中,tbl 定义了一个简单的表格,包括两列和两行。第一列名为 "操作",第二列名为"按钮"。第一行行名是开仓操作,包含一个按钮,点击按钮时触发的命令是 "open",按钮的名称为"开仓",这里设置了一个input,需要用户手动输入开仓数量。第二行行名是平仓操作,也包含一个按钮,点击按钮时触发的命令是 "coverAll",按钮的名称为 "全部平仓",这个按钮不需要提供其他的输入。接下来我们在LogStatus中,通过这行代码输出表格对象的结构和内容。交互控件的操作设置就是我们上述讲过的内容。

我们在实盘中看下,在状态栏里,可以看到点击`open`,填写数量,我们就可以开对应数量的多仓,然后点击`coverall`就可以全部平仓。这里只是一个粗糙的展示,通过自定义的状态栏,我们可以进行更多分页,比如化工类,黑色系和农产品,操作可以有开仓,平仓和止盈等。这些功能的设计我们都可以实现。总之,交互控件为交易提供了更多的灵活性和可操控性,使得交易变得更加智能化和个性化。通过合理地设计和使用交互控件,大家可以实现更灵活的量化交易,提高交易的便捷性和效率。

 ![image](https://www.youquant.com![img](/upload/asset/2b03e97c0b008336abb71.png))



## 8.3 策略进度恢复

在先前的课程中,我们讲解的策略都是运行在模拟回测系统中,策略直接出来回测结果,不受外部物理条件的干扰,是一种比较理想化的策略运行状态。然而,在实际实盘运行中,当我们部署托管者在自己的电脑上时,突发停电或者断网会造成托管者停止运行;而部署在云服务上的托管者,也有一定短暂宕机故障造成托管者程序停止。因此,怎样处理这种突发的情况,是我们在进行策略设计的时候需要关注的部分。量化交易中的策略进度恢复是指在遇到系统故障、网络中断或其他异常情况导致交易程序中断时,恢复并继续执行交易策略的过程。策略进度恢复是保证交易系统的稳定性和连续性的关键环节。


策略进度恢复的重要性不言而喻。在量化交易中,每一次交易决策都可能产生利润或者亏损,因此无论是策略还是交易状态的丢失都可能导致损失。策略进度恢复能够保证交易系统的连续性和稳定性,避免因系统故障而产生额外的风险和错误交易。同时,对于高频交易或需要实时响应市场变化的策略来说,策略进度恢复更加重要,它能够尽快将系统恢复到正常运行状态,减少交易信号的延迟和错过交易机会的风险。优宽量化作为专业的量化交易平台,非常重视策略进度恢复的设计和实现。而作为专业进阶的量化交易人,这些问题也需要未雨绸缪。相对于Pine语言和麦语言,这种高度封装的交易语言,可以自动实现策略的进度恢复,而JavaScript语言和Python语言作为从底层搭建的量化系统,需要我们手动实现策略的进度恢复。因此,本节课程我们将讲解Python语言的策略进度恢复的设计。

### 8.3.1 ```_G(K, V)```函数的使用


首先,来介绍我们的策略进度恢复的好帮手,```_G```函数。作为一个优宽量化平台的内置函数,它的数据结构为KV表,K必须为字符串,它不区分大小写,V可以为任何可以序列化的内容。它可以永久保存在本地文件,所以该函数实现了一个可保存的全局字典功能。它在回测和实盘中都是支持的。在模拟回测系统中,回测结束后,```_G```函数保存的数据会被清除。而在实盘系统中,每个实盘单独一个数据库,重启或者托管者退出后,```_G```函数保存的数据是一直存在的。所以它的使用很灵活,可以放入任何我们想要储存的数据,状态变量和持仓状态等。

我们举例示范一下。这里由于不使用接口获取数据的测试,就不需要使用```exchange.IO("status")```函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试```_G```函数。这里我们测试```_G```函数依次保存值为数字,字符串和列表,然后我们可以清空一下,再次读取会返回`None`值;在实盘运行中当调用```_G()```函数并且不传任何参数时,```_G()```函数返回当前实盘的ID。

```Python
def main():
    _G("num", 666) # 存储数字     
    _G("num", "ok") # 存储字符串    
    _G("num", [1,2,3]) # 存储列表
    Log(_G("num")) # 读取存储值
    _G(None) # 清空存储值
    Log(_G("num")) # 返回None
    Log(_G()) # 返回实盘ID
}

8.3.2 持仓状态的恢复

在了解完_G函数以后,我们来进行持仓状态恢复的尝试。持仓状态恢复的重要性在于确保交易系统的连续性和准确性。当交易程序中断时,在程序化策略中持仓信息可能会丢失,造成持仓信息和实际的仓位不一致的状况,这可能导致交易系统的错误决策和风险暴露。通过持仓状态恢复,我们可以将中断前的持仓信息重新加载到交易系统中,确保交易系统在恢复后能够基于正确的持仓信息进行进一步的决策和风险管理。此外,持仓状态恢复还对于交易流程的完整性和可追溯性至关重要。通过持仓状态的恢复,我们可以准确地记录交易系统的行为和决策过程,使得后续的回测、风险控制和复盘等工作能够进行。

下面我们我们举例示范一下,布林带策略的持仓状态恢复的设计,我们这里举例示范的交易策略都很简单,重点是让大家可以了解在实盘运作中出现问题,我们解决的思路应该怎样进行。相信各位聪明的小伙伴们也许还有更好的解决方法,如果有兴趣的话,大家可以分享到我们优宽量化平台的文库或者社区板块,供大家一起学习。

本策略当中,我们使用虚拟持仓变量代表持仓状态变量,在策略中断的时候,这个虚拟持仓变量会丢失,这样可能会造成实际仓位和虚拟持仓的不符,造成策略的运行冲突,出现错误。所以呢,我们针对上述的问题,可以提出解决方案。在策略初始阶段,使用_G函数获取持仓状态变量,这个方案可以在策略出现意外中断以后,恢复策略状态,继续策略的运行。

我们可以进行下面具体的设置。首先固定程序,连接交易所,获取k线数据,计算布林带指标。然后根据布林带指标和持有的仓位进行开平仓的操作。这里设置虚拟持仓变量mp,代表记录的持仓状态,在策略运行过程中,mp变量是不断更新的,我们需要及时的进行保存和获取。

在策略第一次运行的时候,我们直接获取持仓状态变量getPos,这个时候,_G函数的键值对还没有定义,因此返回的值会是一个空值,所以我们使用一个逻辑判断,当持仓状态变量存在,直接读取为保存值;否则当为空值的时候,保存getPos是0。这样就完成了保存变量的创建和获取。将它赋值为mp变量,接下来我们进行交易的操作。在每次操作完毕后,重新定义mp变量;最后不要忘了使用_G函数保存新的持仓状态变量。我们在实盘中测试下,可以看到策略开始的时候,getPos变量进行创建,而伴随交易的操作,getPos变量不断更新;接下来我们手动停掉实盘看下,然后重新打开,可以看到getPos的变量没有重置,依旧是实盘停掉之前的状态,策略可以继续运行。

def main():
    p = ext.NewPositionManager()
    symbol = 'FG401'

    
    if _G('getPos'):
        Log('已存在持仓变量:','#00FF00')
    else:
        _G('getPos', 0)
        Log('未存在持仓变量,创建持仓变量', '#FF0000')
        
    mp = _G('getPos')

    while True:
        if exchange.IO("status"):
            LogStatus(_D(), "已经连接到CTP!")
            exchange.SetContractType(symbol)
            records = exchange.GetRecords()

            if not records or len(records) < 20:
                Sleep(1000)  # 等待获取足够多的K线数据
                continue

            # 获取布林带指标
            boll = TA.BOLL(records, 20, 2)
            upLine = boll[0][-3]  # 上轨数值
            midLine = boll[1][-3]  # 中轨数值
            downLine = boll[2][-3]  # 下轨数值

            recclose = records[-2].Close  # 上根K线收盘价

            if mp == 0 and recclose > upLine:  # 如果无持仓,并且收盘价大于上轨,开多
                p.OpenLong(symbol, 1)
                mp = 1

            if mp == 1 and recclose < midLine:  # 如果持多,并且收盘价小于中轨,平多
                p.Cover(symbol)
                mp = 0

            if mp == 0 and recclose < downLine:  # 如果无持仓,并且收盘价小于下轨,开空
                p.OpenShort(symbol, 1)
                mp = -1

            if mp == -1 and recclose > midLine:  # 如果持空,并且收盘价大于中轨,平空
                p.Cover(symbol)
                mp = 0

            _G('getPos', mp)  # 更新持仓状态
            Log('更新持仓状态:', _G('getPos'))

            Sleep(5000)

        else:
            LogStatus(_D(), "未连接CTP!")
            Sleep(1000)

以上的就是_G进行持仓状态保存和恢复的样例展示,其实在实盘的代码当中,我们使用的都是GetPosition函数获取实际的仓位信息,然后和保存的持仓变量进行对比。大家可以尝试改写一下上面的代码。这就是我们策略进度恢复中,持仓状态恢复的一个简单的设计。关于其他状态变量,以及实盘数据的保存和恢复,相信大家可以在此基础上进行修改和完善,如果大家哪里遇到问题,大家可以及时反馈,我们会热心解答。

8.4 多品种合约回调策略设计

多品种(多个期货合约)量化策略是指同时在多个不同的期货合约上应用的量化交易策略。这种策略基于对多个期货品种的市场行情数据进行分析和建模,用来制定交易信号和执行交易。

多品种量化策略的优点如下:

  • 分散风险:通过在多个期货品种上分散投资,降低了单一品种风险对整体投资组合的影响。当一个品种的价格波动较大或遭受损失时,其他品种可能表现更好,从而减少整体风险。
  • 增加机会:不同的期货合约在市场中具有不同的特点和波动性。通过同时研究并交易多个期货合约,可以捕捉到更多的交易机会,并且在市场变动时能够快速做出反应。
  • 提高收益稳定性:由于不同期货品种之间的相关性通常较低,因此,在一个品种表现不佳的时候,其他品种可能仍然能够带来正向收益,从而提高整体收益的稳定性。
  • 顺势交易:多品种策略可以根据不同期货合约的趋势和走势进行交易。当一个品种的趋势明显时,可以选择在该品种上建立仓位,从而跟随市场的走势而获取收益。
  • 套利机会:多品种量化策略可以通过不同期货合约之间的套利机会来获得利润。例如,通过同时买入一个合约并卖出另一个合约,从价差中获取利润。

8.4.1 多品种策略架构介绍

多品种策略设计的优点在于使用方便,一个策略程序控制交易多个品种,可以统一信息状态显示。交易多个品种相对分散了风险,增加了交易机会。缺点在于设计比较复杂,各个品种之间不能相互影响,对程序执行效率要求比较高。所以设计难度远大于设计一个单品种策略。优宽量化交易平台上提供了大量策略范例,给我们提供了丰富的参考代码,设计思路。需要注意的是,多品种(多个期货合约)量化策略也面临一些挑战,如数据处理和模型构建复杂性增加、风险控制的难度提高等。因此,在设计和实施多品种量化策略时,需要充分考虑到市场特点、投资者风险承受能力和相关技术工具的支持。

多品种策略架构可以分为轮询设计和事件驱动的设计。轮询设计架构比较直观,它是基于不断循环合约列表,然后在检查到该品种最新的走势满足交易信号的时候,进行相应的交易操作,这样虽然易于设计,但是并不能实现多品种交易的并发执行。因为这是一个串联的模式,一次只能一个合约的信号判断和交易操作;如果在处理A合约的时候,B合约的信号现实触发的话,程序是无法探测并执行交易的。虽然这对于趋势的策略确实影响不大,但是对于高频策略,如果错过相应的信号触发,就不满足策略设计的初衷了。而基于事件驱动的设计,采样并发处理机制,能够显著提高多品种策略的执行效率和信号响应速度。事件驱动的设计模式非常适合处理高频交易策略,因为它可以确保在极短的时间内对多个品种进行快速反应。此外,它还可以有效地管理资源,因为每个合约的数据处理是独立的,不会相互阻塞。这种设计允许策略在保持高效率的同时,也能够灵活地处理各种市场情况。

因此本节课,我们从策略设计层面入手,剖析一个多品种合约回调策略设计,学习一些策略架构设计的经验。对于一个多品种合约的回调策略设计,我们首先需要了解行情的推送模式。在优宽量化平台,对于行情模式,可以使用mode参数进行切换:

  • exchange.IO("mode", 0):立即返回模式,如果当前还没有接收到交易所最新的行情数据推送,就立即返回旧的行情数据,如果有新的数据就返回新的数据。
  • exchange.IO("mode", 1):缓存模式(默认模式),如果当前还没有收到交易所最新的行情数据(同上一次接口获取的数据比较),就等待接收然后再返回,如果调用该函数之前收到了最新的行情数据,就立即返回最新的数据。
  • exchange.IO("mode", 2):强制更新模式,进入等待一直到接收到交易所下一次的最新推送数据后返回。

在同时,可以使用wait参数,设置阻塞:exchange.IO("wait", Timeout),通过这样的设置,在当前交易所有任何品种更新行情信息,或订单成交时才返回信息,可带第二个参数(代表毫秒数)指定超时,超时会返回空值,正常返回EventTick/OrderEvent结构。通过结合exchange.IO("mode", 0)函数使用,这样配合使用就可以使程序在有最新行情时进行响应,执行程序逻辑,这样的目的,是为了在程序中使用exchange.GetTicker()等函数调用时不阻塞)。如果Timeout参数设置为-1,该函数设置成为了立即返回模式,在没有新事件的时候,返回空值,如果Timeout参数设置为0,代表阻塞等待最新事件。需要注意的是在使用exchange.IO("wait")时,必须至少已经订阅了一个当前处于交易状态的合约。另外需要注意的是,该函数只支持商品期货实盘。

EventTick:{Event:“tick”, Index:交易所索引, Nano:事件纳秒级时间, Symbol:合约名称, Ticker:行情数据}。 OrderTick:{Event:“order”, Index:交易所索引, Nano:事件纳秒级时间, Order:订单信息}。

8.4.2 多品种行情回调示范

我们举例一个多品种行情回调的例子示范下:这里我们设置的多品种合约是黑色系类,包括热卷,螺纹钢,和铁矿石,然后设置wait函数,参数填写为-1,代表立即返回,因此当检查到最新的tick信息的时候,就是e.Event == "tick",该函数会执行on_tick函数,这里我们设置的on_tick函数是在我们初始设置的空图表中,不断的填充对应品种的最新10条的tick数据,然后使用LogStatus进行展示。

我们在实盘中运行下,可以看到由于我们设置了exchange.IO("wait", -1),是立即返回模式,如果有新的信息就立即更新,在没有新事件时返回空值。我们在实盘中可以看到,三个品种的信息不是伴随轮询,一条条逐渐更新的,而是实时更新的,实现了多品种行情的并联展示。这对于交易决策和监控多个品种的交易机会非常有益。

import json

def on_tick(symbol, ticker, rTbl):
    rTbl[symbol]["rows"].append([_D(ticker.Time), ticker.Time, ticker.High, ticker.Low, ticker.Sell, ticker.Buy, ticker.Last, ticker.Volume, ticker.OpenInterest, symbol])
    if len(rTbl[symbol]["rows"]) > 10:
        rTbl[symbol]["rows"].pop(0)

def main():
    symbols = ["hc2409", "rb2409", "i2409"]
    rTbl = {symbol: {
        "type": "table",
        "title": symbol,
        "cols": ["strTime", "Time", "High", "Low", "Sell", "Buy", "Last", "Volume", "OpenInterest", 'Symbol'],
        "rows": []
    } for symbol in symbols}

    while not exchange.IO("status"):
        Sleep(1000)

    for symbol in symbols:
        _C(exchange.SetContractType, symbol)
        
    exchange.IO("mode", 0)

    while True:
        e = exchange.IO("wait", -1)
        if e and e.Event == "tick":
            on_tick(e.Symbol, e.Ticker, rTbl)

        LogStatus(json.dumps(list(rTbl.values())))

8.4.3 多品种回调策略设计

下面我们就来举一个实例示范下,多品种合约的回调策略设计。上面的例子我们只使用到了EventTick数据的事件驱动的更新,这可以作为交易信号的判断,而交易操作,还需要另外一种状态变量的辨别,就是实时订单状态的获取,我们可以通过OrderEvent数据结构进行获取。

import json

global ticker_lists, symbol_pos, rTbl, orderTbl

symbols = ["hc2409", "rb2409", "i2410"]
cols = {
    "rTbl": ["strTime", "Time", "High", "Low", "Sell", "Buy", "Last", "Volume", "OpenInterest"],
    "orderTbl": ['ContractType', 'Id', "StatusMsg",  'Price', 'Status', 'Type', 'Offset', 'OrderSignal']
}

rTbl = {symbol: {"type": "table", "title": symbol, "cols": cols["rTbl"], "rows": []} for symbol in symbols}
orderTbl = {symbol: {"type": "table", "title": symbol, "cols": cols["orderTbl"], "rows": []} for symbol in symbols}

ticker_lists = {symbol: [] for symbol in symbols}
symbol_pos = {symbol: 0 for symbol in symbols}

def on_tick(ticksymbol, ticker):
    
    ticker_list = ticker_lists[ticksymbol]
    ticker_list.append(ticker.Last)

    tick_tbl = rTbl[ticksymbol]
    tick_tbl["rows"].append([_D(ticker.Time/1000), ticker.Time, ticker.High, ticker.Low, ticker.Sell, ticker.Buy, ticker.Last, ticker.Volume, ticker.OpenInterest])

    if len(ticker_list) < 100:
        return

    ma_long = TA.MA(ticker_list, 100)[-1]
    ma_short = TA.MA(ticker_list, 50)[-1]

    posInfo = exchange.GetPosition()

    if ticker.Last > ma_long and symbol_pos[ticksymbol] == 0:
        symbol_pos[ticksymbol] = 999
        q.pushTask(exchange, ticksymbol, "buy", 1, lambda task, ret: Log(task["desc"]))
        
    if ticker.Last < ma_long and symbol_pos[ticksymbol] == 0:
        symbol_pos[ticksymbol] = 999
        q.pushTask(exchange, ticksymbol, "sell", 1, lambda task, ret: Log(task["desc"]))
        
    if ticker.Last < ma_short and symbol_pos[ticksymbol] == 1:
        symbol_pos[ticksymbol] = 999
        q.pushTask(exchange, ticksymbol, "closebuy", 1, lambda task, ret: Log(task["desc"]))
        
    if ticker.Last > ma_short and symbol_pos[ticksymbol] == -1:
        symbol_pos[ticksymbol] = 999
        q.pushTask(exchange, ticksymbol, "closesell", 1, lambda task, ret: Log(task["desc"]))
        
def on_order(order):
    Log(order)
    ordersymbol = order.ContractType
    order_tbl = orderTbl[ordersymbol]
    
    if symbol_pos[ordersymbol] == 999 and order.Status == 1 and order.Type == 0 and order.Offset == 0 and order.AvgPrice > 0:  # 开多完成
        symbol_pos[ordersymbol] = 1

    if symbol_pos[ordersymbol] == 999 and order.Status == 1 and order.Type == 1 and order.Offset == 1 and order.AvgPrice > 0:  # 平多完成
        symbol_pos[ordersymbol] = 0

    if symbol_pos[ordersymbol] == 999 and order.Status == 1 and order.Type == 1 and order.Offset == 0 and order.AvgPrice > 0:  # 开空完成
        symbol_pos[ordersymbol] = -1

    if symbol_pos[ordersymbol] == 999 and order.Status == 1 and order.Type == 0 and order.Offset == 1 and order.AvgPrice > 0:  # 平空完成
        symbol_pos[ordersymbol] = 0

    order_tbl["rows"].append([order.ContractType, order.Id, order.Info["StatusMsg"], order.Price, 
    "成交" if order.Status == 1 else "未成交",  
    "买入" if order.Type == 0 else "卖出",  
    "开仓" if order.Offset == 0 else "平仓", 
    symbol_pos[ordersymbol]])

def main():
    global q

    while not exchange.IO("status"):
        Sleep(1000)

    for symbol in symbols:
        _C(exchange.SetContractType, symbol)

    q = ext.NewTaskQueue()

    while True:
        e = exchange.IO("wait", -1)
        if e:
            if e.Event == "order":
                on_order(e.Order)
            elif e.Event == "tick":
                on_tick(e.Symbol, e.Ticker)

                q.poll()

                rTbl_data = {symbol: {"type": "table", "title": symbol, "cols": cols["rTbl"], "rows": rTbl[symbol]["rows"][-10:-1]} for symbol in symbols}
                orderTbl_data = {symbol: {"type": "table", "title": symbol, "cols": cols["orderTbl"], "rows": orderTbl[symbol]["rows"][-4:-1]} for symbol in symbols}
                
                LogStatus("`" + json.dumps(rTbl_data['hc2409']) + "`\n" + 
                "`" + json.dumps(rTbl_data['rb2409']) + "`\n" + 
                "`" + json.dumps(rTbl_data['i2410']) + "`\n" +
                "`" + json.dumps(orderTbl['hc2409']) + "`\n" +
                "`" + json.dumps(orderTbl['rb2409']) + "`\n" +
                "`" + json.dumps(orderTbl['i2410']) + "`")

在先前的课程中,我们在回测系统中,order结构返回的信息比较少,其实在实际的交易场景中,作为一个交易的完整操作,从交易信号辨别,决定下单,向交易所发送请求,到最后完成下单,OrderEvent会返回一系列的事件:

1.第一阶段,订单提交,"StatusMsg"返回报单已提交,这里的重要属性Status代表(订单状态),Type表示(订单买卖类型),Offset表示(期货开平仓方向),Type和Offset决定操作的方向,而返回Status判断交易是否完成,这个时候Status是0,表示未完成状态; 2.在报单提交和报单完成之间,其实还有一个状态,“StatusMsg"会返回"未成交”,这个时候Status仍旧是0,表示未完成; 3.最后交易完成,Status会返回1,表示订单完成状态; 4.交易确认阶段。

注意:仿真交易所和实际交易所返回具体信息会有所区别,大家需要根据订单信息返回结构进行相应程序的修改。

  • Type:表示订单类型,Type为1,即表示该订单是一个卖单,如果是0,就是买单。
  • Offset:持仓方向。开仓(Open是0)或平仓(Close是1)。
  • Status:订单状态。Status为0,表示该订单的状态为“未成交”,如果是1,代表是成交,如果是2,代表被撤销的订单。

这就是一条完整的order返回数据,这里面有几个重要的属性可以帮助我们判断订单的方向和完成的阶段状态:这里的order事件返回也是事件驱动返回的,所以我们可以使用order事件进行持仓状态变量的实时判断;我们实现了信号判断和持仓状态判断的事件驱动,对于交易的操作,我们也可以实现非堵塞的模式,还记得前面我们讲过的交易类库中的pushtask函数吗?这里我们可以使用上。每当信号触发,使用pushtask接受交易任务,放进任务队列;然后继续进行其他品种的信号判断和交易任务的执行。通过这样的方式,我们就可以实现一个多品种合约的事件驱动架构的设计。

我们来看下这个策略的具体架构:首先我们来讲下这个策略的具体交易逻辑:作为一个事件驱动的策略,这个策略使用了ticker双均线作为基准。当持仓量为空,最新的ticker价格超过ticker慢线的时候,我们进行开多仓,跌破ticker慢线的时候,我们开空仓;当具有多仓的时候,最新的价格小于ticker快线,我们进行平多仓;具有空仓,价格大于ticker快线,我们平掉空仓。策略的交易确实很简单,下面我们来看怎样实现。

这里我们设置的多品种合约依旧是热卷,螺纹钢,和铁矿石,首先设置两个字典对象tickerListSymbolPos,用来保存这三个品种的ticker数据和持仓数据。这里的持仓数据,每个品种有三个索引,在下面的讲解中我会为大家进行介绍。接下来我们也要使用交易类库中的多任务对象NewTaskQueue

我们的交易操作是根据tick信息和order信息实时事件驱动的。这里的exchange.IO("wait", -1)设计表示是实时返回的机制,有信息立马进行返回。接下来,我们就要设置on_tickon_order函数了,当交易所返回tick或者order信息的时候,我们需要怎样的操作;

第一个on_tick函数,当不同品种tick数据更新的时候,用来搜集相应的ticker数据,并计算均线,决定交易信号的触发。这里我们使用对应的合约品种的键向tickerList中添加最新的ticker数据,我们设置的快线和慢线周期分别为50和100,然后等收集够足够的数量,我们计算相应的ticker均值。然后比较最新的价格ticker.Last和均值的突破作为交易的信号。交易信号的触发是频繁的,为避免频繁交易,所以我们需要设置交易锁,SymbolPos变量。

当交易信号触发,向交易所发送挂单请求,等待order信息返回的中间,如果最新的ticker信息返回,这个时候由于交易信号的再次触发,交易函数会进行二次下单,这个时候我们可以设置一个类似锁的功能,在对应交易信息触发以后,pushtask进行下单,然后就设置该信号对应的操作已经是完成状态,不需要再进行重复的下单。这里的开多,开空,平多和平空,我们都使用这样的交易锁的设置。使用目标品种的symbol_pos[ticksymbol]交易锁判断相应的状态,当满足交易信号,我们进行加锁的设置symbol_pos[ticksymbol] = 999

持仓状态 含义 可进行操作
symbol_pos[ticksymbol] = 0 无持仓 开多/开空
symbol_pos[ticksymbol] == 1 持有多仓 平多
symbol_pos[ticksymbol] == -1 持有空仓 平空

一个锁既然会锁上,必然也是需要打开的。下面在on_order函数中,我们进行持仓状态的判断和交易锁的解锁功能。我们前面讲过,一个交易操作的完成,会返回不同类型的操作信息,对于“上期模拟”仿真交易所,交易操作的属性和完成判断,需要order.Statusorder.Typeorder.Offsetorder.AvgPrice这四个属性,还有symbol_pos[ordersymbol] == 999在加锁状态下,一旦信号满足要求,我们修改相应的symbol_pos[ordersymbol]变量,进行下一次交易的操作。这样就可以实现了基于order事件驱动的持仓状态的及时修改和更新。

前提条件 Type Offset AvgPrice Status 代表状态 转换状态 可进行操作
symbol_pos[ticksymbol] = 999 0 0 >0 1 开多完成 symbol_pos[ticksymbol] = 1 平多
symbol_pos[ticksymbol] = 999 1 1 >0 1 平多完成(持仓为空) symbol_pos[ticksymbol] = 0 开多/开空
symbol_pos[ticksymbol] = 999 1 0 >0 1 开空完成 symbol_pos[ticksymbol] = -1 平空
symbol_pos[ticksymbol] = 999 0 1 >0 0 平空完成(持仓为空) symbol_pos[ticksymbol] = 0 开多/开空

注意:这是上期模拟仿真交易所判断条件,对于真实交易所,可以使用StatusMsg属性,返回“全部成交”代表操作完成。

当设计完成on_tickon_order函数,我们就可以带入我们的主循环,当事件更新时,驱动不同的函数进行运行,完成相应的交易操作;不要忘了这里的q.poll()函数,执行任务队列。这样就是一个区别于轮询架构的事件驱动架构的多品种合约的设计。为了展示不同的状态,我们这里输出了不同合约的ticker表和order事件表,可以伴随策略的更新,观察相应的状态变量的变化。

我们在实盘中看下,可以看到伴随不同的tick或者order事件,不同合约的事件信息状态栏不断进行更新,我们可以具体了解到基于事件的驱动架构是怎样完成的。本策略为了教学讲解,因此个别地方设计的比较冗余,大家可以根据自己的想法对这个策略进行更好的优化。作为一个区别于轮询架构的策略,该策略的细节确实比较多,我们在应用于这类策略的时候,可以在实盘中多次模拟检验,搭建自己的事件驱动架构的多品种策略。

8.5 多周期均线共振系统策略

商品期货的多周期均线共振系统策略是一种综合性的交易策略,它基于不同周期的均线共振现象来制定交易计划。这种策略的核心思想是利用不同周期的均线在同一时间点上形成的共振状态,来判断市场走势的强势或弱势,并以此为依据进行交易。期货交易界有句老话:大级别上涨,小级别不配合,涨不动;小级别上涨,大级别不配合,涨不多。只有多个周期的均线系统形成统一方向的趋势共振时,才可以形成持久的趋势行情,这也是多周期共振的理论认知。

8.5.1 策略讲解

我们可以从图像的角度了解多周期共振,具体包括均线的多头排列和空头排列。这里使用到了不同级别的k线,包括大周期,中周期,小周期,和不同滑动窗口的均线,包括快线,中线,和慢线。当不同级别的不同滑动周期的均线,均呈现金叉的信号向上扩散,则形成均线多头排列,均线多头排列代表上涨行情,可以视为多头开仓的信号;与之相反的,当不同级别的不同滑动周期的均线,均呈现死叉的信号,往下扩散,则形成均线空头排列,均线空头排列代表下跌行情,可以视为空头开仓信号。

当多个周期均线都形成多头排列或者空头排列时,就形成了均线多周期共振。均线多周期共振表示当前多个周期的均线走势是相同的。均线在某种意义上代表着资金的走向,同时均线对价格的支撑或阻力作用也较为明显。由此呢,我们可以制定多周期均值共振的交易策略。具体来说,该策略的应用包括以下步骤:

  • 选择合适的周期参数:首先,我们需要明确自己的交易习惯,选择适合自己的周期参数。长线交易可以选择月线、周线和日线三个周期共振;中线交易可以选择周线、日线和60分钟三个周期共振;短线交易可以选择日线、60分钟和15分钟三个周期共振。
  • 判断共振状态:当不同周期的均线在同一时间点附近出现交叉或聚集时,就形成了均线共振。例如,月线、周线和日线的均线方向一致向上,表明市场处于多头趋势。此时,我们可以制定相应的多头策略。
  • 制定交易策略:根据共振状态和市场走势,制定相应的交易策略。在多头趋势中,我们可以选择买入或持有期货合约;在空头趋势中,我们可以选择卖出或观望期货合约。同时,我们还需要注意不同周期的信号冲突和共振状态的变化,及时调整自己的交易策略。
  • 观察进场信号:在确定了交易策略后,需要密切关注市场走势,等待合适的进场信号。例如,在多头趋势中,我们可以等待价格回调到重要的支撑位附近时,发出买入信号时再进场。
  • 设定止损止盈:进场后,需要设定合理的止损止盈位。止损位可以设置在重要的支撑或压力位附近,止盈位则可以参考目标盈利位或移动平均线等指标来确定。
  • 跟踪持仓:进场后,需要密切关注市场走势和持仓情况。如果市场走势与预期不符,需要及时调整自己的交易策略或平仓离场。

8.5.2 策略设计

本节课呢,我们就来编写一个,更加灵活的多周期均线共振策略。这个策略不仅可以适用于多种的k线周期;另外,我们还可以使用量化的方式定义多头排列和空头排列的程度。下面我们来具体讲解一下。

'''backtest
start: 2024-01-02 00:00:00
end: 2024-04-11 23:00:00
period: 1m
basePeriod: 1m
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES","depthDeep":20}]
args: [["symbol","rb888"],["bigPeriod",480],["midPeriod",60],["stopProfit",20],["stopLoss",20]]
'''

import time
from datetime import datetime

def main():
    trend_big_signal = 0  # 大周期趋势信号
    trend_mid_signal = 0  # 中周期趋势信号
    trend_small_signal = 0  # 小周期趋势信号
    final_signal = 0  # 实时趋势信号
    p = ext.NewPositionManager()  # 交易类库函数

    while True:
        if exchange.IO('status'):

            # 订阅合约
            info = exchange.SetContractType(symbol)

            # 大周期趋势判断
            big_r = exchange.GetRecords(1 * 60 * bigPeriod)
            pop_big_r = big_r[:-1]

            if len(pop_big_r) < bigSlowMA:
                continue

            fast_ma_big = TA.MA(pop_big_r, bigFastMA)
            mid_ma_big = TA.MA(pop_big_r, bigMidMA)
            slow_ma_big = TA.MA(pop_big_r, bigSlowMA)

            if (fast_ma_big[-1] - mid_ma_big[-1] > diffBig) and (mid_ma_big[-1] - slow_ma_big[-1] > diffBig):
                trend_big_signal = 1
            elif (fast_ma_big[-1] - mid_ma_big[-1] < -diffBig) and (mid_ma_big[-1] - slow_ma_big[-1] < -diffBig):
                trend_big_signal = -1
            else:
                trend_big_signal = 0

            # 中周期趋势判断
            mid_r = exchange.GetRecords(1 * 60 * midPeriod)
            pop_mid_r = mid_r[:-1]
            fast_ma_mid = TA.MA(pop_mid_r, midFastMA)
            mid_ma_mid = TA.MA(pop_mid_r, midMidMA)
            slow_ma_mid = TA.MA(pop_mid_r, midSlowMA)

            if (fast_ma_mid[-1] - mid_ma_mid[-1] > diffMid) and (mid_ma_mid[-1] - slow_ma_mid[-1] > diffMid):
                trend_mid_signal = 1
            elif (fast_ma_mid[-1] - mid_ma_mid[-1] < -diffMid) and (mid_ma_mid[-1] - slow_ma_mid[-1] < -diffMid):
                trend_mid_signal = -1
            else:
                trend_mid_signal = 0

            # 小周期趋势判断
            small_r = exchange.GetRecords(1 * 60 * smallPeriod)
            pop_small_r = small_r[:-1]
            fast_ma_small = TA.MA(pop_small_r, smallFastMA)
            mid_ma_small = TA.MA(pop_small_r, smallMidMA)
            slow_ma_small = TA.MA(pop_small_r, midSlowMA)

            if (fast_ma_small[-1] - mid_ma_small[-1] > diffSmall) and (mid_ma_small[-1] - slow_ma_small[-1] > diffSmall):
                trend_small_signal = 1
            elif (fast_ma_small[-1] - mid_ma_small[-1] < -diffSmall) and (mid_ma_small[-1] - slow_ma_small[-1] < -diffSmall):
                trend_small_signal = -1
            else:
                trend_small_signal = 0

            final_signal = 1 if trend_big_signal == trend_mid_signal == trend_small_signal == 1 else -1 if trend_big_signal == trend_mid_signal == trend_small_signal == -1 else 0

            pos_info = exchange.GetPosition()

            # 下单
            if not pos_info:
                if final_signal == 1:
                    p.OpenLong(symbol, 1)
                    Log('开多仓')

                if final_signal == -1:
                    p.OpenShort(symbol, 1)
                    Log('开空仓')

            # 止盈止损平仓
            if pos_info:
                profit_level = info['VolumeMultiple'] * info['PriceTick'] * stopProfit
                loss_level = -info['VolumeMultiple'] * info['PriceTick'] * stopLoss
                    

                if (pos_info[0].Type % 2 == 0) and (pos_info[0].Profit > profit_level):
                    Log(pos_info[0].Profit)
                    p.Cover(symbol)
                    Log('平盈利多仓')

                if (pos_info[0].Type % 2 == 0) and (pos_info[0].Profit < loss_level):
                    Log(pos_info[0].Profit)
                    p.Cover(symbol)
                    Log('平亏损多仓')

                if (pos_info[0].Type % 2 == 1) and (pos_info[0].Profit > profit_level):
                    Log(pos_info[0].Profit)
                    p.Cover(symbol)
                    Log('平盈利空仓')

                if (pos_info[0].Type % 2 == 1) and (pos_info[0].Profit < loss_level):
                    Log(pos_info[0].Profit)
                    p.Cover(symbol)
                    Log('平亏损空仓')

                date_time_str = _D()
                hour, minute = datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S').hour, datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S').minute

                if (hour == 14 and minute == 55):
                    Log(pos_info[0].Profit)
                    p.Cover(symbol)
                    Log('日内平仓')

        else:
            LogStatus('未连接交易所')

        Sleep(1000)

在策略开始,我们定义策略的参数,这个策略的原理并不是十分的复杂,关键在于策略参数的制定。这里我们定义四组参数,第一组是期货合约,第二到第四组分别是大周期,中周期和小周期的参数设置,具体包括k线周期,代表使用的是月线,日线还是具体的小时分钟线,快线,中线和慢线周期是各个级别的均线周期,有效间隔用来确定各个均线的距离,因为很多时候,不同周期的均线很可能只有很少的差距,可能会形成不明确的假性信号,所以我们可以把均线间隔作为一个变量,这样呢,只有各个均线间隔之间具有显著的差异,我们确定下来具体的交易信号。最后一组参数是平仓的设置,包括止盈参数和止损参数,当持有的仓位达到固定点数的时候,我们直接平仓。

image

回到代码部分,在策略开头,我们定义几个初始化变量,包括大周期/中周期/小周期趋势信号。这里还定义单品种控制对象,使用交易类库可以更方便的进行交易的操作。

接着设置固定框架,while循环加上交易所连接判断。下面编写策略的主体逻辑,第一步订阅合约,然后就要进行不同周期的不同级别均线的计算了,这里首先获取不同级别的k线,还记得GetRecords()可以填写参数吗,根据这个参数可以获取相应周期的k线,参数是以秒为单位的,所以1*60就是一分钟,然后乘以策略参数bigPeriod,如果定义bigPeriod为60,就可以获取到小时级别的k线。但是最后一根k线是不固定的,所以我们可以舍弃最后一根,定义popbigR变量。然后去除popbigR最后一根k线。我们就可以利用这个完整的k线进行后续的移动均线的计算。

均线的计算是有一点数量要求的,所以如果k线数量不足够,我们跳过本次循环。接着来计算大周期快线,中线,和慢线的移动均值,使用参数bigFastMA,bigMidMA和bigSlowMA。最后来判断大周期的趋势,这里不单单是判断快线,中线和慢线不重叠,我们可以增加约束条件,如果两条均线之间的差值大于某个值的时候,我们定义是有效的信号。这里定义如果快线减中线,中线减慢线都大于阈值,定义大周期趋势为多,赋值为1;相反情况下快线小于中线,中线小于慢线,赋值为-1,是空头趋势;另外的其他情况,定义为0,代表不存在明显的趋势。对于中周期和小周期的趋势判断,也是同样的思路。

三个周期的趋势定义完成,这里我们就来定义共振了。使用if表达式,如果三个周期的趋势都为多,定义最终信号finalSignal也是1,如果三个周期趋势都为空,定义最终信号为-1,其他种情况,定义为0。这样多周期的均线共振就定义完成了。

接下来具体的开仓和平仓的思路大家可以自己发挥,可以使用顺势的信号,如果最终信号统一一致,我们确定入场信号,进行该方向的开仓;也可以增加额外条件,在最终信号判断一致的前提下,如果出现向上或者向下的回踩,我们再确定入场信号。这里呢,为了操作演示,采用第一种的思路,如果出现相应信号,直接入场进行开仓。

对于平仓的逻辑,我们可以使用固定止盈止损或者移动止盈止损,本策略当中我们使用固定止盈止损,当实时仓位的利润达到盈利线或者亏损线,我们直接选择平仓。这里的盈利线和亏损线需要注意下,因为不同合约的单位和跳数都是不同的,例如螺纹钢每次跳动PriceTick是1点,合约单位VolumeMultiple是10吨,所以每个价格变化是10;而纸浆的跳动单位是2,玻璃的合约单位是20,所以这里设置盈利线和亏损线使用止盈或者止损单位乘以合约单位,再乘以价格跳数。

另外一点,对于仓位类型type类型的判断,相对于以往我们手动的定义type等于PD_LONGPD_LONG_YDPD_SHORTPD_SHORT_YD,我们可以简单的表达,PD_LONGPD_LONG_YD的值是0和2,是偶数,另外两种仓位类型是奇数,所以可以除以2,然后取余数的方法,如果余数为0,代表判断是多头持仓,如果余数是1,代表空头持仓,更加简便一点。

这样,我们的策略的交易逻辑就编写完成,我们来使用回测系统测试一下,首先设置策略参数,这里我们大周期定义为一个小时,所以是60分钟,中周期是30分钟,小周期是15分钟,快线,中线和慢线分别定义为5,10,和15。止盈和止损点数设置为2和5。我们来运行一下,可以看到这里呈现了不同周期的空线以及相应的均线,并且在小周期的K线图上,标注了多头和空头信号开启和关闭的提醒,然后我们进行相应的入场和出场的操作。

总之,商品期货的多周期均线共振系统策略是一种综合性的交易策略,它需要我们综合考虑不同周期的市场走势和自己的交易习惯来制定交易计划。同时,我们还需要注意风险管理、进场信号和持仓跟踪等方面的问题,以确保自己的交易能够取得成功。本节课只是一个示范性的内容,大家有在量化交易中碰到的问题,都可以留言,我们在力所能及的范围内,都会耐心解答。

8.6 多品种海龟交易策略

8.6.1 海龟交易系统

在前面我们讲过的策略,为了讲解的方便,使用的大多数都是单品种的策略,并且每次交易的手数都是一手。然而,对于大资金的量化需求,我们需要的是一个多品种的,可以伴随盈利逐步加仓,达到盈利点位或者止损点位进行减仓,并且可以控制风险的量化模型。那么有没有一种策略可以满足我们的要求呢?多品种海龟交易策略不要错过。海龟交易系统是一种经典的趋势追踪交易策略,由美国期货交易员Richard Dennis和William Eckhardt在1983年推出。该系统基于价格的突破和趋势跟随原理,在市场中追踪并参与长期趋势,用来获取较大的收益。

首先,我们介绍下海龟交易系统的主要组成部分和特点:

  • 多个市场:海龟交易系统可以应用于多种交易市场,包括商品期货、股票和外汇等。
  • 入市规则:基于价格的突破是海龟交易系统的核心概念。通过观察市场价格是否突破一定周期内的高点或低点,决定是否入市建立头寸。
  • 逐步建仓:海龟交易系统采用逐步建仓的方式。根据市场的表现和波动情况,逐步扩大头寸规模,但也有严格的风控规则限制仓位大小。
  • 止损规则:海龟交易系统非常重视风险控制,当市场走势反向达到止损位时,及时平仓以限制损失。
  • 退出规则:海龟交易系统有多种退出规则,包括根据价格突破逆向信号、固定的离市周期或固定的利润目标等。这些规则用于判断何时平仓并退出头寸。
  • 波动性管理:海龟交易系统会根据市场的波动性进行头寸规模的调整,通常使用ATR(平均真实波幅)指标来计算波动性,并根据波动性来决定头寸的大小。

接下来我们来具体解释下海龟策略的交易逻辑。海龟交易系统是一个完整的交易系统,它有一个完整的交易系统应该有的所有成分,涵盖了期货交易中的每一个必要决策:

  • 入市----什么时候进行开仓?
  • 入市规模----具体开仓的手数;
  • 加仓----什么时候进行加仓;
  • 止损----什么时候达到止损点位,卖出亏损的品种;
  • 离市----什么时候获利了结,卖出赢利的品种。

首先,我们来看什么时候进行买卖?海龟用两个相关的系统选择品种,这两个系统都以通道突破系统为基础。系统一:以20日突破为基础的偏短线系统;系统二:以55日突破为基础的较简单的长线系统。这两种不同却有关系的突破系统法则,可以称为系统一和系统二。我们可以根据自己的交易理念,自行决定将资金配置在何种系统上。有些交易员选用单一的系统交易所有的资金,或者分别用资金的50%选择系统一,50%选择系统二,当然还有其他的不同的组合选择。海龟策略利用两个突破系统的触发情况确定最高价和最低价,并根据最新价格,与最高价和最低价的关系,选择对应的做多突破,或者做空突破,进而进行相应的做多或者做空操作。

第二点,我们来看买卖多少?买卖期货的数量,用下面的公式计算。这个公式可以帮助我们根据账户资金、风险承受能力和市场波动性来确定每个交易头寸的大小。

  • 交易头寸=帐户金额 *(1-保证金比率)* 风险系数/N值/合约乘数
  • 账户金额:指账户当前可用资金或账户净值。
  • 保证金比率:指期货合约所要求的初始保证金占总价值的比例。它表示每手合约所需的初始保证金占头寸价值的比例。
  • 风险系数:代表个体风险承受的程度,由交易员根据自身的风险偏好设定。
  • N值:是市场波动性的度量指标,例如平均真实波幅(ATR)。它用于衡量价格的波动情况。
  • 合约乘数:是期货合约中每手合约所代表的标的资产数量。

第三点,我们来看什么时候加仓?在建立头寸后,如果最新的价格突破成功,就是价差大于加仓系数(一般是1/2)乘以ATR的间隔,可以选择增加头寸,增加头寸的数量也是使用公式计算出来的。但是通常情况下,对于加仓次数会设置一个最大限制,如果超过,就不再加仓。这样的限制可以控制风险,避免过度加仓。

第四点,什么时候进行止损?在具有仓位的情况下,当判断最新一笔的盈亏大于设定的亏损限制(止损系数(一般是2)乘以N值的时候,表示触发止损,我们进行该品种的止损清仓操作。

第五点,什么时候进行平仓离场?在持有仓位的期间,我们会时刻记录一段周期内的最高价和最低价,定义为上线和下线。对于多头持仓,当前价格小于离场周期内的最低价,进行多头平仓。对于空头持仓,当前价格大于离场周期内的最高价,进行空头平仓。

以上呢,就是海龟策略的交易逻辑。当然,一个完整的量化交易系统,并不是只有交易策略的逻辑,我们还有下面的一系列需求:

  • 交易进度的保存和恢复:在海龟策略中,我们看到了大量的状态变量,比如各个品种的开仓价格,开仓次数,N值等等,而如果遇到实盘突然停止,这些状态变量的丢失,将会造成交易进度的丢失,影响策略的效果,因此我们需要及时的进行交易进度的保存和恢复工作。
  • 策略运行状态的界面展示:作为一个多品种的趋势策略,我们需要及时的了解各个品种的交易进度,包括各个品种的持仓数量,持仓方向,加仓次数,整体收益等等,因此我们需要设计完善的运行状态的表格和图像的展示。
  • 信息的及时推送:作为一个量化策略,省去的就是人工盯盘的烦恼。在每次交易开仓,加仓和平仓的时候,我们可以设置手机APP或者邮箱的自动提示,帮助我们及时关注策略的运行状况。

除了上述工作之外呢,我们还需要根据策略的运行情况,及时的进行不同品种和不同参数的设置,帮助优化策略表现,提升收益水平。

怎么样,在了解完海龟策略的基本概念和需求以后,有没有想尝试用代码搭建完成模型呢?来,让我们从打地基开始,着手开始吧。首先,提示一下,本节课代码内容确


更多内容

by2022 企业微信加不上啊啊啊啊啊啊

雨幕(youquant) 您好,企业微信满了,您加这个微信: https://www.youquant.com/upload/asset/1780ac4e8b9064c9d7d9a.png