浅谈高频策略架构中的设计和优化

Author: ianzeng123, Created: 2024-03-08 15:46:39, Updated: 2024-05-08 09:34:05

img

阅前提醒:本文基于JavaScript语言,探讨了高频套利策略框架中多线程获取订单信息和下单的研究以及代码编写。具体的使用方法需要根据特定的使用场景进行优化。

在高频套利策略中,迅速获取各个合约的最新价格(ticker数据)以计算价差是至关重要的。当判断价差偏离正常范围时,我们需要迅速执行下单以抓住关键时刻。因此,这引入了两个关键问题。

首先,如果我们采用轮询的策略架构,即只有在一笔交易完成后才能进行下一笔交易,可能会错过一些极短暂的套利机会。为了有效应对这个问题,我们可以考虑采用并发的方式来处理价格数据,确保及时捕捉到合适的价差变化。其次,当出现价差偏离的时候,为了快速下单,通常我们会选择使用市价单进行不同合约的下单操作。然而,市价单的执行价格可能不容易掌握,这可能影响套利的盈利机会。解决这个问题的关键是确保以合适的限价单进行迅速的非阻塞下单,从而最大程度地保障套利的利润。

因此,我们需要一种高效的机制来抓住合适的价差,同时确保在下单时能够获得合适的限价单。采用合理高效的策略架构可以帮助我们及时获取价格数据,在结合适当的算法来判断套利机会的基础上,迅速的抓住关键点位下达订单,将有助于提高策略的灵活性和执行效率。在本篇内容中,我们将尝试探讨如何优化策略架构以解决这些挑战,确保在高频套利环境中实现更好的执行表现。

__Thread函数介绍

为满足并发操作的需求,FMZ平台升级了系统底层创建的函数,引入了__Thread函数,为JavaScript语言提供了真正的多线程支持。该函数的详细功能包括:

  1. 创建线程并发执行自定义的函数。
  2. 实现线程间通信。
  3. 支持共享线程间储存的变量。
  4. 等待线程执行结束,回收资源,并返回执行结果。
  5. 强制结束线程,并回收资源。
  6. 在并发的线程执行函数中获取当前线程ID。

关于这个函数的介绍,大家可以查看这篇文档《让策略程序真正并发执行,给JavaScript策略增加系统底层多线程支持》。本次呢,我们就来基于__Thread来尝试满足高频套利的一些基本需要。

并发获取合约ticker数据

如果我们想让策略主线程运行的同时,并发运行一个子线程来执行我们编写的自定义函数,就可以使用类似以下代码的设计。

在策略代码中自定义一个函数GetTickerAsync(),参数为合约的名称(contract)和定义数据的名称(dataKey)。这个函数首先设置目标合约,然后执行循环,在这个while循环中,不停的调用FMZ的API接口:GetTicker()来获取行情数据。然后使用__threadSetData函数向主线程(Id为0)写入一个数据,数据名称为dataKey,数据值为t即GetTicker()的返回值。这里为了方便演示,我们设置的数据返回值为合约名称(InstrumentID),最新价格(Last)和最新时间(Time)。

function GetTickerAsync(contract, dataKey) {
    _C(exchange.SetContractType, contract);
    while (true) { 
        var t = _C(exchange.GetTicker);
        __threadSetData(0, dataKey, [t.Info.InstrumentID, t.Last, _D(t.Time)]);
        Sleep(500); 
    }
}

function main() {

    while(!exchange.IO('status')){
        Sleep(1000)
    }
    
    __Thread(GetTickerAsync, 'rb2405', 'ticker');
    __Thread(GetTickerAsync, 'rb2410', 'ticker2');

    var beginTime = new Date().getTime();
    var endTime;

    while((endTime = new Date().getTime()) - beginTime < 1000 * 10) {
        var t = __threadGetData(0, "ticker")
        var t2 = __threadGetData(0, "ticker2")
        Log('t',t)
        Log('t2',t2)
        Sleep(500)
    }
}

main() 函数中,线程1和线程2是通过 __Thread 函数创建的两个独立的线程。它们分别运行 GetTickerAsync 函数,每个函数在一个无限循环中执行,独立地获取不同合约的数据。这些线程将并发运行,每个线程负责获取与特定合约相关的数据(线程1为 'rb2405',线程2为 'rb2410'),并写入到主线程当中。

然后具体的执行流程如下:

  1. 线程1(GetTickerAsync)持续获取 'rb2405' 合约的数据;最新获取的数据都写入到主线程中。
  2. 线程2(GetTickerAsync2)持续获取 'rb2410' 合约的数据;最新获取的数据都写入到主线程中。
  3. 在主循环中,使用 __threadGetData 从主线程中检索两个分线程传递过来的数据,这样可以访问两个合约('rb2405''rb2410')的最新数据。这样就是一个并发获取合约ticker数据的代码范例。

这里为了方便演示,我们在调试工具设置策略运行的时间为10秒钟,我们可以看到日志信息里实时返回了两个线程收集到的tick数据。

image

需要注意的是,不同处理器支持的线程数量不同,因此支持获取合约的数量也是不同的,大家可以根据自己处理器的配置,测试最多支持获取合约的数量。另外,这里测试尽量使用期货公司,因为仿真平台传递的数据具有一定的滞后性。

并发下单设计

第二部分我们来看下单的并发设计,高频套利的盈利机会是很短暂的几个tick之间的跳跃,因此对于开仓和平仓的关键点位我们需要及时抓住。虽然FMZ平台具有交易类库pushTask函数,可以帮助交易的非堵塞,但是这个函数是市价单交易的。如果我们想真正的实现并发的执行限价单的交易,我们同样可以使用__Thread函数。

function placeOrder(contract, type, price, amount) {
    var id = null
    exchange.SetContractType(contract) 
    if (type == "Buy") {
        exchange.SetDirection('buy')
        id = exchange.Buy(price, amount)
    } else if (type == "Sell") {
        exchange.SetDirection('sell')
        id = exchange.Sell(price, amount)
    } else if (type == "closebuy") {
        exchange.SetDirection('closebuy')
        id = exchange.Sell(price, amount)
    } else if (type == "closesell") {
        exchange.SetDirection('closesell')
        id = exchange.Buy(price, amount)
    } else if (type == "closebuy_today") {
        exchange.SetDirection('closebuy_today')
        id = exchange.Sell(price, amount)
    } else if (type == "closesell_today") {
        exchange.SetDirection('closesell_today')
        id = exchange.Buy(price, amount)
    } else {
        throw "type error! type:" + type
    }
}

function main(){
    
    // 假设交易信号确定以后
    var tids1 = placeOrder('rb2405', "限价单买入价格", 1)
    var tids2 = placeOrder('rb2410', "限价单卖出价格", 1)

    Sleep(1000)

    __threadTerminate(tids1)
    __threadTerminate(tids2)

    var orders = exchange.GetOrders()
    Log('订单', orders)
    for (var i = 0; i < orders.length; i++) {
        Log('订单', i, orders[i])
    }

    var posInfo = exchange.GetPosition()
    for (var i = 0; i < posInfo.length; i++) {
        Log('持仓', i, posInfo[i])
    }
}

首先,我们设置一个下单函数placeOrder,具体的参数包括合约名称,下单类型(包括开多,开空,平多/平今多,平空/平今空),价格还有数量。在函数内部,设置完成合约之后,根据下单类型type,我们随后设置对应的方向和交易操作。这就是线程当中需要执行的操作。

在主函数中,设置两个线程用来进行下单,这里选取的是两个螺纹钢品种的跨期合约进行正套交易(买入近月,卖出远月)。这里的“限价单买入价格”和“限价单卖出价格”大家可以根据算法计算得出(如果大家实时验证多线程下单的结果,我们使用市价单(-1)进行验证)。然后使用__threadTerminate回收线程资源。等待1秒钟过后,我们相应打印出来订单信息(未完成的交易)和持仓信息(已成交的交易)。这样我们就可以使用多线程进行下单的处理。

最后温馨提醒一下,本文涉及到的是在JavaScript高频套利策略中,尝试使用__Thread函数进行并发数据获取和下单。对于Python语言,我们更推荐使用更便捷的协程函数(asyncio)来实现并发操作。在实际应用中,请注意使用多线程进行交易的具体方法,以及确保在策略中的表现符合预期,需要根据具体场景进行详细分析。

最后一点要特别留意的是,高频策略的设计逻辑必须与商品期货CTP协议的规定一致,包括每秒请求次数、报撤单数量频率限制等。在执行策略时,请务必遵循相关规定,确保交易行为的合法性和合规性。希望大家可以顺利设计和执行高频交易策略!


更多内容