使用JavaScript入门商品期货量化交易

Author: 雨幕(youquant), Created: 2023-08-15 17:04:31, Updated: 2024-04-12 11:16:19

时模拟是和实盘一样的连接机制。在先前的课程中,我们了解、学习的行情接口因为篇幅有限,为了容易理解,并没有检测与前置机连接状态代码,其实一个完整的商品期货策略框架应该首先检查与前置机的连接状态:

function main(){
    while(true){
        // 需要在判断exchange.IO("status")函数返回true,即为真值时才可调用行情、交易等函数
        if(exchange.IO("status")){
            exchange.SetContractType("MA888")
            var ticker = exchange.GetTicker()
            Log("MA888 tickerBuy:", ticker.Buy)
            Log(_D(), "已经连接CTP !")
        } else {
            Log(_D(), "未连接CTP !")
        }
    }
}

这段代码中exchange.IO函数用来调用协议、交易所等其它功能接口。exchange.IO("status")函数可以判断当前是否和期货公司前置机连接,如果连接成功返回 1,如果是非连接状态就返回0。在连接成功的状况下,Log函数在日志信息显示时间和“已经连接CTP”,这里的_D()函数会返回当前时间的字符串,连接失败会显示未连接。

执行交易操作

买单函数

交易函数都可以通过exchange对象调用。首先我们来看下买单函数exchange.Buy。该函数返回一个订单ID。参数值:Price为订单价格。Amount为订单数量,两个参数都是数值类型。exchange.Buy(Price, Amount)函数的返回值是订单编号,是字符串类型,可用于查询订单信息和取消订单。

exchange.Buy(Price, Amount)

卖单函数

同样的,卖单函数为exchange.Sell,需要的参数和返回的结果和买单函数一样。

exchange.Sell(Price, Amount)

设置交易方向

期货下单时必须注意交易方向。exchange.SetDirection(Direction)用来设置exchange.Buy或者exchange.Sell函数进行期货下单的方向。这里面有特殊的 closebuy_todayclosesell_today,这是平今仓的指令,只有上海期货交易所的品种有平今仓指令。

下单函数 SetDirection函数的参数设置的方向 备注
exchange.Buy “buy” 买入开多仓
exchange.Buy “closesell” 买入平空仓
exchange.Buy “closesell_today” 买入平空仓(今仓)
exchange.Sell “sell” 卖出开空仓
exchange.Sell “closebuy” 卖出平多仓
exchange.Sell “closebuy_today” 卖出平多仓(今仓)

在设置好交易方向后,我们就可以进行开平仓的操作,我们使用代码示范下。

function main() {
    while (!exchange.IO("status")) {
        Sleep(1000)
    }
    // 设置合约代码
    exchange.SetContractType("rb888")
    
    // 设置下单方向
    exchange.SetDirection("buy")
    var id = exchange.Buy(100, 1)
    Log("id:", id)
    
    // 设置下单方向
    exchange.SetDirection("closebuy")
    // 平掉仓位
    var id2 = exchange.Sell(100, 1)
    Log("id:2", id2)
    
}

在设置好交易方向后,我们就可以进行开平仓的操作,我们使用代码示范下。我们来看下交易逻辑。

设置合约代码为"rb888",即螺纹钢主力期货合约。接着设置交易方向为"buy",开多仓。使用exchange.Buy函数下单,买入1手期货合约,价格为ticker的卖单,所以会立即成交。将返回的订单编号存储在id变量中。

然后设置交易方向为"closebuy",平多仓。使用exchange.Sell函数下单,卖出1手期货合约。价格是ticker的买单,所以也会立即成交,将返回的订单编号存储在id2变量中。

使用Log函数输出订单编号id和id2,以便后续查看订单状态等信息。可以看到结果里,买入开多仓的id为1,卖出多仓的id为2。

这里只是给大家展示了一个开平仓的操作。需要注意的是,此代码中的交易策略比较简单,没有考虑风险控制等因素,只是演示了如何使用API函数进行期货交易的基本操作。在实际交易中,需要根据自己的交易策略和风险偏好进行相应的修改和补充。

exchange.GetOrder(Id)

如果要查询订单的状态,可以根据订单号获取订单详情。参数值:Id为需要获取的订单号,参数Id为字符串类型。它的返回值是Order结构体。

function main(){
    while (!exchange.IO("status")) {
        Sleep(1000)
    }
    // 设置合约代码
    exchange.SetContractType("rb888")
    // 设置下单方向
    exchange.SetDirection("sell")
    var id = exchange.Sell(99999, 1)
    var order = exchange.GetOrder(id)      
    Log("Id:", order.Id, "Price:", order.Price, "Amount:", order.Amount, "DealAmount:",
        order.DealAmount, "Status:", order.Status, "Type:", order.Type)
}

我们来看下代码,方向和下单都是sell,所以是一个开空仓的操作。这里的下单价格只是举例,较大的价格不会成交,订单会处于待成交状态,在实盘中我们可以自行调整价格。订单建立后,参数id为订单号码,在exchange.GetOrder函数中填入我们想要查询的订单的号码。

重点看一下返回值结果:

  • Id:订单号。
  • Price:下单价格。
  • Amount:该订单中下单数量。
  • DealAmount:该订单中实际成交的合约张数。在订单成交之前,该值通常为 0。
  • AvgPrice:该订单的成交平均价格。成交之前,该值也是 0。
  • Type:表示订单类型,Type为1,即表示该订单是一个卖单,如果是0,就是买单。
  • Offset:持仓方向。开仓(Open是0)或平仓(Close是1)。
  • Status:订单状态。Status为0,表示该订单的状态为“未成交”,如果是1,代表是成交,如果是2,代表被撤销的订单。
  • ContractType:期货合约类型,一般包含品种代码和到期日期等信息。

exchange.GetOrders()

exchange.GetOrders(),获取所有未完成的订单新。返回值:Order结构体数组。当交易所对象exchange代表的账户当前交易对没有挂单(就是没有未完成的订单时)时,调用该函数将返回空数组([])。

同样的,这里设置了两个不能成交的单子,然后利用GetOrders获取到了所有未完成的订单的信息。

function main(){
    while (!exchange.IO("status")) {
        Sleep(1000)
    }
    // 设置合约代码
    exchange.SetContractType("rb888")
    // 设置下单方向
    exchange.SetDirection("sell")

    exchange.Sell(99999, 1)
    exchange.Sell(88888, 1)
    var orders = exchange.GetOrders()
    Log("未完成订单一的信息,ID:", orders[0].Id, "Price:", orders[0].Price, "Amount:", orders[0].Amount,
        "DealAmount:", orders[0].DealAmount, "type:", orders[0].Type)
    Log("未完成订单二的信息,ID:", orders[1].Id, "Price:", orders[1].Price, "Amount:", orders[1].Amount,
        "DealAmount:", orders[1].DealAmount, "type:", orders[1].Type)
}

同样的,这里设置了两个不能成交的单子,然后利用GetOrders获取到了所有未完成的订单的信息。

实盘下单小tips

在实盘中,为了确保交易成功,价格参数可以传-1。但是在回测系统中不支持。商品期货除了使用市价单还可以用限价单方式下单,可以使用一个较大的滑价确保和对手盘成交。

function main() {
    while(true) {
        if (exchange.IO("status")) {
            exchange.SetContractType("rb888")               
            exchange.SetDirection("buy")
            // 获取当前行情
            var ticker = exchange.GetTicker()                
            // 拿到当前卖一价格
            var currSell1Price = ticker.Sell                 
            // 加50元滑价,即为比出价卖出的挂单高50,要求买入1手
            var id = exchange.Buy(currSell1Price + 50, 1)    
            Log(exchange.GetOrder(id))
        } else {
            Log("未连接")
        }
    }
}

exchange.CancelOrder(orderId)

exchange.CancelOrder(orderId)函数可以根据订单 ID 取消订单。 比如下面的代码: 我们使用 Sell 函数下单了一个价格为99999不能成交的单子。 然后使用 CancelOrder 函数传入 id 参数,来取消了这个订单。最后使用 GetOrder 函数获取这个 id 的当前订单状态,可以看到打印出的订单信息,其中 Status属性为 2,代表被取消的订单。

function main(){
    while (!exchange.IO("status")) {
        Sleep(1000)
    }
    // 设置合约代码
    exchange.SetContractType("rb888")
    // 设置下单方向
    exchange.SetDirection("sell")
    // 下单价格只是举例,较大的价格不会成交,订单会处于订单薄中待成交状态,具体测试可以自行调整价格
    var id = exchange.Sell(99999, 1)
    exchange.CancelOrder(id)
    Log(exchange.GetOrder(id))
}

监控交易状态

在订单完成以后,我们来看下有关于查看交易状态的函数。

exchange.GetAccount()

第一个exchange.GetAccount(),它会返回交易所账户信息。这是一个账户资金余额信息的示例,包含以下几个字段:

  • Balance:账户可用余额,当前账户中可以用于交易的可用资金数量。在这个例子中,可用余额为模拟金额 100 万元。
  • FrozenBalance:账户冻结余额,是已经被冻结,无法用于交易的资金数量。
  • Stocks:账户持仓数量,表示已经持有的合约数量。
  • FrozenStocks:账户已冻结的持仓数量,指哪些被冻结的、无法进行交易的合约数量。

需要注意的是,在实际交易中,账户资金和持仓情况会随着交易的进行而发生变化,因此需要及时查询和更新账户信息,以便进行下一步的交易操作。

function main(){
    
    while (!exchange.IO("status")) {
        Sleep(1000)
    }
    // 获取账户资产信息,可以不用设置合约
    var account = exchange.GetAccount()
    Log("账户信息,Balance:", account.Balance, "FrozenBalance:", account.FrozenBalance, "Stocks:",
        account.Stocks, "FrozenStocks:", account.FrozenStocks)
}

exchange.GetPosition()

exchange.GetPosition(),获取当前持仓信息。 这里我们买了一手螺纹钢期货,然后返回持仓信息,包含以下几个字段:

  • Price:持仓合约的成本价格,就是当时该合约的买入或卖出价格。
  • Amount:持仓合约的数量,就是当前账户中所持有的该合约的数量。买了一手合约,所以amount是1.
  • FrozenAmount:被冻结的持仓数量,指被冻结而无法进行交易的合约数量。
  • Profit:当前合约的浮动盈亏,该合约相对于成本价格的盈亏情况。这里可以看到浮动盈亏为 -10,表示当前持有的该合约相对于成本价格亏损了 10 元。
  • Margin:当前持仓所占用的初始保证金数量。
  • MarginLevel:当前账户的保证金水平。
  • Type:合约类型,一般分为买单(Buy)和卖单(Sell)。在这个例子中,Type 为 0,即表示当前持仓是一份买单合约。

需要注意的是,GetPosition函数获取的是所有持仓品种的持仓信息,如果没有持仓则返回空数组,所以使用该接口返回的数据前要先判断返回的数据是否为空数组。

function main(){
    while(!exchange.IO("status")) {
        Sleep(1000)
    }
    exchange.SetContractType("rb888")
    var ticker = exchange.GetTicker()
    exchange.SetDirection("buy")
    exchange.Buy(ticker.Last + 50, 1)
    
    var position = exchange.GetPosition()
    Log(position)
    
}

好了,量化策略中交易函数的内容就讲到这里,在实际的工作中,使用交易函数需要综合考虑市场和策略的各种因素,注重实践和经验总结,才能够获得良好的交易效果和表现。

7:布林带通道策略

在前面一个阶段的课程中,我们从 JavaScript 语言的简介、 基础语法、 金融数据的获取和计算等方面为大家讲解实现交易策略的前提部分,本篇我们将继续前面的内容,从常用的策略模块、技术指标,一步一步帮助大家实现一个可行的日内量化交易策略。

策略简介

布林带也称为布林通道,英文简称BOLL。它是最常用的技术指标之一, 由约翰·包宁杰(John Bollinger)在1980年代发明。理论上,价格总是围绕着价值在一定范围内上下波动,布林带正是根据这个理论基础, 引入了“价格通道” 的概念。

布林带的计算方式是利用统计学原理,先计算一段时间价格的“标准差” , 再由均线加/减2倍的标准差。给大家稍微解释一下,这里假设价格的波动符合正态分布,2倍的标准差就在95%的置信区间,所以属于正常范围的波动,这样就可以求出价格的“信赖区间” ,如果波动超过2倍的标准差,可以认为是均值,也就是价格发生了实质性的改变。其基本的型态是由三条轨道线组成的带状通道(中轨、上轨、下轨)。中轨为价格的平均成本, 上轨和下轨分别代表价格的压力线和支撑线。

由于采用了标准差的概念,使得布林通道的宽度会根据近期价格的波动而做出动态调整。波动小,布林通道会变窄;波动大, 布林通道会变宽。当BOLL通道由宽变窄,说明价格逐渐向均值回归。当BOLL通道由窄变宽,意味着行情开始发生变化,如果价格上穿上轨, 表明买力增强, 如果价格下穿下轨, 表明卖力增强。

image

布林带指标计算方法

在所有的技术指标中,布林带的计算方法是比较复杂的一种,其中引进了统计学中的标准差概念,涉及到中轨线(MB)、上轨线(UP)和下轨线(DN)的计算。

参数有两个,N是时间周期,K是标准层宽度系数。三条轨道具体的计算方法如下:

  • 中轨 = N 时间段的简单移动平均线
  • 上轨 = 中轨 + K × N 时间段的标准差
  • 下轨 = 中轨 − K × N 时间段的标准差

但是使用JavaScript的ta库,布林带的计算可以使用成熟的函数获取布林带的上轨,中轨和下轨。这里我们使用的是默认的参数,20个周期,标准差为2。然后使用log函数将三个轨道的数组打印出来。

在发明者量化工具中,获取布林带数组很简单,直接调用布林带的 API 就可以了,因为布林带数组是一个二维数组。二维数组其实很好理解,它就是数组中的数组,那么获取的顺序就是: 先获取数组中指定的数组,然后在从指定的数组中获取指定的元素。索引为0,1,2的元素就是布林带的上轨,中轨和下轨数组。

这里为了以防最新的k线没有走完,所以我们获取的是倒数第二根布林带轨道的值[r.length-2]。当然这只是技术指标的计算,技术指标是为了判断策略逻辑。布林线的使用方法有很多,可以单独使用,也可以和其他指标结合在一起使用。

/*backtest
start: 2023-01-29 09:00:00
end: 2023-06-04 15:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES","depthDeep":20}]
*/


function main(){
    while(true){
        exchange.SetContractType('rb888')
        var r = exchange.GetRecords(); //获取K线数组
     
        var boll = TA.BOLL(r, 20, 2); //计算布林带指标
        var upLine = boll[0];//获取上轨数组
        var midLine = boll[1];//卖取中轨数组
        var downLine = boll[2]; //获取下轨数组
        Log('上轨:',upLine[r.length-2])
        Log('中轨:',midLine[r.length-2])
        Log('下轨:',downLine[r.length-2])
    }
}

本节教程我们将采用布林线一种最简单的使用方法。 就是: 当价格自下而上突破上轨,即突破上方压力线时,我们认为多方力量正在走强,一波上涨行情已经形成,买入开仓信号产生; 当价格自上而下跌破下轨,就是跌破支撑线时,我们认为空方力量正在走强,一波下跌趋势已经形成,卖出开仓信号产生。

有开仓必然也有平仓,我们来看下平仓的逻辑。如果买入开仓后, 价格又重新跌回到了布林线中轨,我们认为多方力量正在走弱,或者空方力量正在加强,卖出平仓信号产生;如果卖出开仓后,价格又重新涨回到布林线中轨,我们认为空方力量正在走弱, 或者多方力量正在加强,买入平仓信号产生。因此可以总结市场的操作信号:

  • 多头开仓: 如果无持仓, 并且收盘价大于上轨
  • 空头开仓: 如果无持仓, 并且收盘价小于下轨
  • 多头平仓: 如果持多单, 并且收盘价小于中轨
  • 空头平仓: 如果持空单, 并且收盘价大于中轨

策略代码实现

/*backtest
start: 2023-01-29 09:00:00
end: 2023-06-04 15:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES","depthDeep":20}]
*/

function main(){
    var mp = 0 //设置持仓信息
    while(true){
        exchange.SetContractType('rb888')
        var r = exchange.GetRecords(); //获取K线数组

        if (r.length < 20){//需要超过K线的长度20
            return;} 
     
        var boll = TA.BOLL(r, period, width); //计算布林带指标

        var upLine = boll[0];//获取上轨数组
        var midLine = boll[1];//卖取中轨数组
        var downLine = boll[2]; //获取下轨数组
        var upPrice = upLine[upLine.length - 3];
            //获取上上根K线上轨数值
        var midPrice = midLine[midLine.length - 3];
            //获取上上根K线中轨数值
        var downPrice = downLine[downLine.length - 3]; 
            //获取上上根K线下轨数值

        recclose = r[r.length - 2].Close; //获取上根K线收盘价

        if(mp == 0 && recclose > upPrice){//如果无持仓,并且收盘价大于上轨,开多
                // 设置下单方向
            exchange.SetDirection("buy");
            exchange.Buy(recclose,1)    
            mp = 1;} 

        if(mp == 0  && recclose < downPrice){ //如果无持仓,并且收盘价小于下轨,开空
                // 设置下单方向
            exchange.SetDirection("sell");
            exchange.Sell(recclose,1)    
            mp = -1;} 

        if(mp == 1 && (recclose < midPrice)){ //如果持多,并且收盘价小于中轨,平多
                // 设置下单方向
            exchange.SetDirection("closebuy");
            exchange.Sell(recclose-5,1);//这里为了保证交易,所以设置限价单为最新的价格减5
            mp = 0;} 

        if(mp == -1 && (recclose > midPrice )){ //如果持空,并且收盘价大于中轨,平空
                // 设置下单方向
            exchange.SetDirection("closesell");
            exchange.Buy(recclose+5,1);//同样的,为保证交易,这里设置限价单
            mp = 0;}  
                   
        Sleep(1000)
    }
}

这是一段使用JavaScript语言编写的期货交易策略代码。该代码实现的交易策略基于布林带指标,用于自动化期货交易。 具体说明如下:

首先开始一个无限循环,是为了获取最新K线数据。检查K线的长度是否达到20根或以上,如果没有就结束程序。

使用TA.BOLL 计算20根K线周期内的布林带指标,其中2表示标准差宽度系数。

获取上轨、中轨、下轨的价格。为了防止最新的k线没有走完,所以我们使用倒数第二根k线[r.length-3],然后判断倒数第一根k线和布林带的关系,去形成买卖的信号。

获取前一根K线的收盘价 recclose,作为当前价格的参考。 如果持仓 mp = 0(无持仓) 并且 recclose > upPrice,则开多仓。设置交易方向为 “buy”,然后使用exchange.Buy函数下单,买入1手期货合约。最后将mp值设为 1,表示现在持有多仓。

如果持仓 mp = 0(无持仓)并且 recclose < downPrice,则开空仓。设置交易方向为 “sell”,然后使用exchange.Sell函数下单,卖出1手期货合约。最后将mp值设为 -1,表示现在持有空仓。

如果持仓 mp = 1(持有多仓)并且 recclose < midPrice,则平仓。设置交易方向为 “closebuy”,然后使用exchange.Sell函数下单,卖出1手期货合约。这里为了保证交易,所以设置限价单为最新价格减1。最后将mp值设为 0,表示现在无持仓。

如果持仓 mp = -1(持有空仓)并且 recclose > midPrice,则平仓。设置交易方向为 “closesell”,然后使用exchange.Buy函数下单,买入1手期货合约。这里同样的,为了保证交易,设置限价单为最新价格加1。最后将mp值设为 0,表示现在无持仓。

使用Sleep(1000)函数延迟1秒后再重新获取最新K线数据,继续执行上述的交易策略。

上面这些就是我们学习开发一个完整的日内量化交易策略的每个步骤, 包括:策略简介、布林带指标计算方法、策略逻辑、 买卖条件、 策略代码实现等。点击开始回测,我们看到我们的策略,在半年的时间,获取了2000多元的收益。

通过这个策略案例, 不仅熟悉发明者量化工具的编程方法, 还可以根据这个模板改编成不同的策略。策略能否改进的更好呢,上节课我们提到参数的调整,这里我们设置布林带周期和标准差宽度为外部参数,进行参数调优。在参数编辑页面设置好参数以后,在回测页面这里我们设置参数区间,然后进行调参,看一下优化的结果。可以看到,当设置周期为18和标准差宽度为1的时候,取得了最高的胜率和收益,但是使用固定品种和固定周期可能陷入过拟合的风险,大家也需要考虑下。

量化交易策略无非是主观交易经验或系统的总结,如果我们在写策略之前, 把主观交易中用到的经验或系统,分别写出来,然后再一条一条翻译成代码,你会发现写策略就会容易很多。大家可以尝试一下!

8:JavaScript画图函数:Chart函数(上)

在量化交易中,画图具有非常重要的作用。画图可以帮助我们更好地理解和分析市场情况,提高交易准确性,同时也可以验证和改进量化交易策略。首先,画图可以帮助我们进行行情分析。通过绘制K线图、均线、波动指标等图形,我们可以更直观地了解市场趋势、价格波动等情况,从而更好地预测市场走势和发现交易机会。其次,画图也可以帮助我们发现交易信号。例如,我们可以通过绘制支撑位、压力位、趋势线等图形来识别市场的趋势和反转点,从而产生相应的交易信号,提高交易准确性。此外,画图还可以帮助我们进行风险管理。例如,我们可以通过绘制止损线、趋势线、波动范围等图形来识别市场风险,并制定相应的止损策略,从而有效控制风险和保护资金。最后,画图还可以帮助我们验证和优化量化交易策略。通过回测和画图,我们可以确认某种策略在特定市场环境下的表现,并针对不同的市场情况进行优化和改进,提高交易效果和收益率。总之,画图在量化交易中具有极其重要的作用,可以帮助我们更好地理解市场行情、发现交易信号、进行风险管理和验证和优化量化交易策略等。因此,在进行量化交易时,合理运用画图工具可以大大提高交易准确性和效果。

在发明者平台,JavaScript语言有多种的画图方法,今天我们介绍Chart画图函数。Chart自定义图表画图函数,专门用于绘制各种类型的交互式图表。它支持折线图、区域图、柱状图、饼图等多种类型的图表,并提供了丰富的工具和选项,可以满足各种需求。

首先我们讲下Chart函数的使用方法,在使用Chart画图函数时,我们需要使用数据和选项的配置对象来控制图表的显示和交互效果。

选项配置对象

选项配置对象用于控制图表的显示效果、交互效果等。以下是选项配置对象的常见属性:

  • chart: 表示图表的整体配置选项,包括类型(如折线图、柱状图、饼图等)、背景色、边框等。
  • title: 表示图表的标题配置选项,包括文本、样式等。
  • subtitle: 表示图表的副标题配置选项,包括文本、样式等。
  • legend: 表示图例(Legend)的配置选项,包括位置、样式等。
  • tooltip: 表示提示框(Tooltip)的配置选项,包括触发方式、内容格式等。
  • plotOptions: 表示系列(Series)的配置选项,包括类型、颜色、标签等。

示例

下面我们示范一下,如果我们想画两条双均线(五日均线和十日均线),首先我们可以定义选项配置对象:

var chart = {                                           
    // 标记是否为一般图表,有兴趣的可以改成false运行看看
    __isStock: true,                                    
    // 缩放工具
    tooltip: {xDateFormat: '%Y-%m-%d %H:%M:%S, %A'},    
    // 标题
    title : { text : '均线'},                       
    // 坐标轴横轴即:x轴,当前设置的类型是:时间
    xAxis: { type: 'datetime'},                         
    // 坐标轴纵轴即:y轴,默认数值随数据大小调整
    yAxis : {                                           
        // 标题
        title: {text: '均线'},                           
        // 是否启用右边纵轴
        opposite: false                                 
    },
}

该选项配置对象包含了对图表的完整配置。其中__isStock属性表示是否为一般图表,选择true为Highstocks,Highstocks是一个专门用来创建交互式股票图表和金融图表的JavaScript库。它是Highcharts图表库的一部分,并提供了更多的功能,包括支持股票指标、数据区域缩小、鼠标拖拽和滚轮缩放等。tooltip属性定义了缩放工具的格式,title属性定义了图表的标题,xAxis属性定义了X轴的配置,yAxis属性定义了Y轴的配置。

数据配置对象

数据配置对象用于控制图表的数据源。其中,最重要的属性是series,它是一个数组,每个元素表示一个系列(Series)的数据。

数据配置对象:

// 数据系列,该属性保存的是各个数据系列(线,K线图,标签等...)
series : [                                          
    // 索引为0,data数组内存放的是该索引系列的数据
    {name : "line1", id : "线1,五日均线", data : []},                          
    // 索引为1,设置了dashStyle:'shortdash'即:设置虚线
    {name : "line2", id : "线2,十日均线", dashStyle : 'shortdash', data : []}  
]

该数据配置对象包含了两条均线数据,分别被定义在一个数组上。其中每个数据系列都有name、id、data属性。name属性用于图例显示的名称,id属性为数据系列的唯一标识符,data属性则为数据系列的数据。

双均线例子

我们将数据配置对象和选项配置对象的属性合并到一个名为chart的对象中。这个对象包含了所有的配置选项,从而实现了用一个对象控制整个图表的效果。

var chart = {                                           
    // 标记是否为一般图表,有兴趣的可以改成false运行看看
    __isStock: true,                                    
    // 缩放工具
    tooltip: {xDateFormat: '%Y-%m-%d %H:%M:%S, %A'},    
    // 标题
    title : { text : '均线'},                       
    // 坐标轴横轴即:x轴,当前设置的类型是:时间
    xAxis: { type: 'datetime'},                         
    // 坐标轴纵轴即:y轴,默认数值随数据大小调整
    yAxis : {                                           
        // 标题
        title: {text: '均线'},                           
        // 是否启用右边纵轴
        opposite: false                                 
    },
    // 数据系列,该属性保存的是各个数据系列(线,K线图,标签等...)
    series : [                                          
        // 索引为0,data数组内存放的是该索引系列的数据
        {name : "line1", id : "线1,五日均线", data : []},                          
        // 索引为1,设置了dashStyle:'shortdash'即:设置虚线
        {name : "line2", id : "线2,十日均线", dashStyle : 'shortdash', data : []}  
    ]
}

在chart对象设置完成以后,接着我们需要往里面添加数据。

function main(){
    // 调用Chart函数,初始化图表
    var ObjChart = Chart(chart)         
    // 清空
    ObjChart.reset()                      
    while(true){
        exchange.SetContractType("rb888")
        // 获取本次轮询的时间戳,即一个毫秒的时间戳。用来确定写入到图表的X轴的位置
        var nowTime = new Date().getTime()
        // 获取行情数据
        var r = exchange.GetRecords()
        // 五日均线
        var ave_5 = TA.MA(r, 5)    
        // 十日均线
        var ave_10 = TA.MA(r, 10)  
        // 用时间戳作为X值,均价作为Y值传入索引0的数据序列
        ObjChart.add(0, [nowTime, ave_5[r.length-1]])
        // 同上
        ObjChart.add(1, [nowTime, ave_10[r.length-1]])
        Log(ave_5)
        Log(ave_10)
    }
}

首先,通过Chart(chart)函数初始化了一个图表对象ObjChart,其中chart是一个选项配置对象,用于配置图表的各项属性。然后,通过ObjChart.reset()函数清空了图表中已经存在的数据。

接下来,通过一个无限循环while(true)来实现不断获取并更新行情数据。在每次循环中,调用exchange.GetRecords()函数获取当前品种的K线行情数据,并用TA.MA()计算出五日和十日均线的值。

然后,通过new Date().getTime()获取当前时间戳作为X值,将五日和十日均线的值作为Y值,调用ObjChart.add()函数把这些数据加入到图表中。其中,第一个参数0和1分别对应了选项配置对象中的两个数据序列,即五日均线和十日均线。第二个参数是一个包含X值(也就是时间)和Y值(就是最新时刻的均线值)的数组。

总之,这段代码通过数据配置对象和选项配置对象来定义图表,然后不断获取K线数据,计算均线,将计算结果添加到图表上,最终实现了两条均线的效果。

image

这里呢,我们只涉及到均线的计算和绘图,下节课我们将研究下k线图的画法。

Chart画图函数确实比较复杂,参数也比较多。不过,对于刚接触量化交易的人来说,掌握这些画图函数十分重要,因为它可以帮助我们更好地理解市场行情和策略效果。但是,对于刚入门量化学习的我们可能会被这些代码吓到,不知道从何下手。这时候,兴趣是最好的老师。只要抱着兴趣和学习的心态,慢慢琢磨、尝试,相信大家一定可以掌握这些画图函数的使用方法。另外,FMZ平台提供了许多丰富的讲解材料和策略案例,这些都可以帮助刚接触量化交易的人更好地入门和掌握相关知识。我们可以通过阅读官方文档、教程视频、参与社区讨论等方式获取更多的相关知识和经验。

9:JavaScript画图函数:Chart函数(下)

因为交易员需要关注多个市场、多种资产的行情数据,以及交易策略的实时表现,经常可以看到职业的交易员有多个屏幕显示不同维度的数据。观察不同维度的数据可以帮助交易员更好地跟踪多个市场、多种资产的行情情况,分析市场趋势和交易信号,以及实时评估交易策略的效果。本节课程呢,我们就是用JavaScript语言实现一些复杂的画图展示。

复杂图表的例子

在量化交易中,复杂图表是指包含多个技术指标的图表或者同时展示多个品种的监控图表等复杂的图表类型。这些图表可能会同时包含多条曲线、多个子图以及各种颜色和标记等元素,从而使其更加详细和全面地呈现市场行情和交易策略效果。复杂图表可以帮助我们更好地了解市场走势和交易机会,同时可以辅助我们进行交易决策,并提供更为细致的风险管理和位置管理。但是,这些图表可能也会比较难懂和使用,需要对技术指标的原理和用法有一定理解、熟练使用相应的量化工具和软件等。

黑色系铁矿石、螺纹钢和热卷是钢铁工业中的三个主要品种,它们的相关性比较强。一般来说,铁矿石作为钢铁生产的原材料,直接影响生产成本,价格上涨或下跌都会对螺纹钢产生一定的影响。而螺纹钢和热卷都是钢铁制品的重要品种,两者的价格都受到市场供需关系、宏观经济环境等多个因素的影响,所以它们之间的价格走势也具有很强的相关性。这段代码是一个基于量化交易框架的示例程序,主要用于实时监控这三个相关品种数据并绘制相应的图表。

该段代码实现了对三个不同品种的均线和K线数据的获取和显示。具体来说,该段代码创建了三个图表配置对象 cfgA、cfgB 和 cfgC,分别代表要展示的三个品种 i888、rb888 和 hc888 的图表。每个图表配置对象中包含了图表的标题、x轴的类型等参数,其中每个对象中的数据系列用于展示蜡烛图。之后,使用 Chart 函数将这三个图表配置对象包装成一个 chart 对象。

var cfgA = {
    __isStock: true,
    title: {
        text: 'A'
    },
    series: [{
        type: 'candlestick',
        name: 'A',
        id: 'A',
        data: []
    }, {
        type: 'line',
        yAxis: 0,
        name: "A_MA",
        data: [],
    }]
}
var cfgB = {
    __isStock: true,
    title: {
        text: 'B'
    },
    series: [{
        type: 'candlestick',
        name: 'B',
        id: 'B',
        data: []
    }, {
        type: 'line',
        yAxis: 0,
        name: "B_MA",
        data: [],
    }]
}
var cfgC = {
    __isStock: true,
    title: {
        text: 'C'
    },
    series: [{
        type: 'candlestick',
        name: 'C',
        id: 'C',
        data: []
    }, {
        type: 'line',
        yAxis: 0,
        name: "C_MA",
        data: [],
    }]
}
function main() {
    var symbols = ["rb888", "MA888", "i888"]
    var chart = Chart([cfgA, cfgB, cfgC])
    chart.reset()
    var arrLastTime = [0, 0, 0]
    while (true) {
        if (exchange.IO("status")) {
            LogStatus("时间:", _D(), ",已经连接")
            for (var i = 0; i < symbols.length; i++) {
                exchange.SetContractType(symbols[i])
                var r = exchange.GetRecords()
                var ma = TA.MA(r, 10)
                for (var j = 0; j < r.length; j++) {
                    if (r[j].Time > arrLastTime[i]) {
                        // 增加
                        chart.add(i * 2, [r[j].Time, r[j].Open, r[j].High, r[j].Low, r[j].Close])
                        chart.add(i * 2 + 1, [r[j].Time, ma[j]])
                        arrLastTime[i] = r[j].Time
                    } 
                    
                    else if (r[j].Time == arrLastTime[i]) {  
                        // 更新                                          
                        chart.add(i * 2, [r[j].Time, r[j].Open, r[j].High, r[j].Low, r[j].Close], -1)
                        chart.add(i * 2 + 1, [r[j].Time, ma[j]], -1)
                    }
                    
                }
                
            }
    
            chart.update([cfgA, cfgB, cfgC])
        }
        Sleep(5000)
    }
}

在进入 while 循环之后,通过 exchange.SetContractType 函数循环遍历每个品种,调用 exchange.GetRecords() 获取该品种最新一根 K 线数据并存储到变量中,并使用 TA.MA(r, 10) 函数计算10日移动平均线。然后遍历获取到的 K 线数据,根据时间戳判断是需要新增还是更新,根据判断结果使用 chart.add()方法将K线数据和对应的MA值添加或更新到对应的图表系列中去。其中,i * 2i * 2 +1表示三个合约的K线数据和对应的均线数据分别在系列数组中的索引位置,该索引位置与初始化的 cfgA、cfgB、cfgC 对象中的系列顺序是相对应的。

下面我们来研究下这段代码的细节:extension是一个在图表开发中常用的属性,用于对图表进行进一步的自定义和细粒度控制。它可以包含一些子属性,比如在该示例中使用的layout、height、col等。

  • layout可以指定图表的布局方式,支持多种取值,例如示例代码中的single表示单独显示,不参与分组,正如我们图形中展示的一样,呈现纵向排列的状态。默认取值为group,表示与其他图表一起分组显示。这里我们设置为group看一下,可以看到,是分组折叠展示的。
  • height是一个数值型属性,指定了图表的高度。该属性只在layout为single时才有效,因为分组时采用的是自适应的方式。
  • col是一个数值型属性,指定了图表的宽度占据几个单元格。这个属性只在分组布局,也就是group中有意义,因为布局时每行通常包含12个单元格,可以将多个图表放在同一行实现紧凑排列的效果(比如有两个图一个宽度为8,一个为4,所有紧密的并排在一起,如果调大其中一个,那么另一个图像就会换行展示)。

cfgA、cfgB 和 cfgC 分别代表了三个不同品种(i888、rb888 和 hc888)的图表配置对象,三个配置是一样的。对于三个品种,配置对象都用来展示品种的均线走势图和蜡烛图。其中,title 表示图表标题,xAxis 表示 x 轴的类型是类别型轴,series时数据的配置对象,包含了两个系列,分别是均线线形图和蜡烛图。具体解释如下:name 是系列的名称。type 是系列的类型,这里定义了两个不同类型的系列,一个是线形图,另一个是蜡烛图,data 是系列的数据,是一个数组,用来存储该系列要展示的数据。series数据是有单独索引的,第一第二个数据索引属于铁矿石,第三第四属于螺纹钢,最后两个是热卷的。所以可以看到我们的数据添加过程是这样的。

这里我们解释下三个品种数据的添加过程:

这段代码中使用了两个 for 循环。

第一个循环语句 for (var i = 0; i < symbols.length; i++)遍历了 symbols 数组中的所有元素,该数组包含了要显示在图表上的三个品种的合约代码。通过调用 exchange.SetContractType(symbols[i]) 方法设置当前合约代码为数组 symbols 中的第 i 个元素。

第二个循环语句 for (var j = 0; j < r.length; j++) 遍历了 r 数组中的所有元素,该数组包含了当前品种最新的 K 线数据。通过遍历 r 数组并将每个元素添加到当前品种对应的系列中,实现了将最新的 K 线数据添加到图表中并刷新的效果。在每次添加完数据之后,通过更新 arrLastTime 数组来记录最新的时间戳,并用于判断下一次是否需要添加/更新数据。

这里的数据添加和更新也很有意思,如果上一根k线已经走完,就是新的时间戳大于上一个周期的时间戳,表示新的k线已经产生,这时候就要增加最新的数据。

而如果此时的k线周期还没有完成,最新的k线数据还没有固定,这时候就要不断的更新最新的k线数据,add函数里最后使用-1进行k线数据的更新。

综上,这两个 for 循环共同实现了将最新的 K 线数据添加到对应的系列中,用于刷新图表并展示最新的数据。

image

混合图表的例子

在量化交易中,混合图表是指同时展示多种不同类型或不同时间尺度的K线图、技术指标图、成交量图等多种图表,以便更全面地呈现市场行情和交易策略效果。混合图表可以帮助我们更好地了解市场走势和交易机会,同时可以帮助我们对策略的执行效果进行监控和评估。特别是对于一些复杂的交易策略,通过混合图表可以更好地展示策略在不同时间尺度上的表现和回测结果。

在FMZ平台中,我们提供了丰富的图表类型和工具,包括K线图、技术指标图、成交量图,同时也可以自由组合这些图表,并支持简单的操作交互,使用户可以方便而又详尽地观察市场情况和策略效果。我们来看一个混合图表的例子。MACD 是一种技术分析指标,全称为“Moving Average Convergence Divergence”,中文翻译为“移动平均线收敛/发散指标”。它由两条曲线和一个柱形图组成,可以帮助分析价格趋势的变化情况,以及判断价格是处于超买还是超卖状态,从而提供交易信号。

在期货中,MACD 也是相当重要的分析工具之一。期货中的价格波动非常剧烈,因此需要使用技术分析工具来帮助交易者理性判断市场走势和价位变动方向,并制定相应的交易策略。MACD 在期货交易中被广泛应用,可作为趋势跟随和逆势交易的参考依据。

在期货软件上,MACD 指标通常以两条线和一个柱形图的形式展示,其中包括了 DIF、DEA 和 MACD 三个指标数据。DIF 代表短期(12周期)EMA 值减去长期(26周期)EMA 值的差值。DEA 则是 DIF 的9周期 EMA 平均值,称为离差平均值。MACD 则是 DIF 与 DEA 差值的2倍,表示市场短期动量的差异程度。这些指标通常与 K 线图一起显示,交易者可以通过观察 MACD 指标的变化情况来判断市场走势,制定相应的交易策略。使用JavaScript语言我们可以呈现和期货软件几乎一样的可视化结果。

下面这段代码是展示铁矿石 MACD 指标的变化情况。整个代码分为两个部分:首先定义了一个 chartCfg 变量,其中包括了图表的标题、纵轴、数据序列等配置信息,用于初始化图表;然后在 main 函数中不断循环获取 i888 合约的 K 线数据,并计算出其对应的指标数据,然后将这些指标数据实时添加到已经初始化的图表上。

var chartCfg = {
    subtitle: {
        text: "铁矿石MACD指标",
    },
    yAxis: [{
        height: "60%",
        lineWidth: 2,
        title: {
            text: 'i888',
            color: '#333',
        },
        opposite: true,
        labels: {
            align: "right",
            x: -3,
            color: '#333',
        }
    }, 
    {
        title: {
            text: '',
            color: '#333',
        },
        top: '62%',
        height: '40%',
        offset: 0
    },
    {
        title: {
            text: '',
            color: '#333',
        },
        top: '62%',
        height: '40%',
        offset: 0
    },
    {
        title: {
            text: '',
            color: '#333',
        },
        top: "62%",
        height: "40%",
        offset: 0
    }, ],
    series: [{
        type: 'candlestick',
        name: 'i888',
        data: [],
        tooltip: {
            xDateFormat: '%Y-%m-%d %H:%M:%S'
        },
        yAxis: 0,
        color: b.Open > b.Close ? '#00ff00' : '#ff0000',
        dataLabels: {
            shadow: true // 添加阴影
        }
    }, {
        type: 'line',
        name: 'DIF',
        data: [],
        yAxis: 1,
        lineWidth: 1
    },
    {
        type: 'line',
        name: 'DEA',
        data: [],
        yAxis: 1,
        lineWidth: 1
    },
    {
        type: 'column',
        lineWidth: 2,
        name: 'MACD',
        data: [],
        yAxis: 1,
        zones: [{
            value: 0,
            color: "#00ff00"
        }, {
            color: "#ff0000"
        }]
    }]
};

function main() {
    let c = Chart(chartCfg);
    while (true) {
        Sleep(1000)
        
        exchange.SetContractType('i888')
        var records = exchange.GetRecords(); // 将K线数据存储到对应的变量中
        var DIF = TA.MACD(records, 12, 26, 9)[0][records.length-1]
        var DEA = TA.MACD(records, 12, 26, 9)[1][records.length-1]
        var MACD = TA.MACD(records, 12, 26, 9)[2][records.length-1]
        var b = records[records.length - 1]
        c.add(0, [b.Time, b.Open, b.High, b.Low, b.Close])
        //c.add([0, [new Date().getTime(), records[records.length-1].Close]]);
        c.add([1, [new Date().getTime(), DIF]]);
        c.add([2, [new Date().getTime(), DEA]]);
        c.add([3, [new Date().getTime(), MACD]]);
    }
}

首先,定义了一个包含图表配置信息的对象 chartCfg,其中包括图表标题 subtitle、Y 轴 yAxis 和数据系列 series 等属性。其中,Y 轴主要由四个属性构成,分别对应蜡烛图、DIF 线、DEA 线和 MACD 柱形图等数据所在的 Y 轴上下文。每个 Y 轴的属性包括高度 height、轴线宽度 lineWidth、标题 title、opposite 属性等,用于控制 Y 轴的显示效果和位置等特性。

定义 main 函数,用于初始化图表,并不断添加新的数据到图表中。该函数使用 while 循环实现,每隔 1 秒钟获取一次最新的铁矿石价格数据,并将其存储在 records 变量中。然后,使用技术指标库 TA 中的 MACD 函数计算 DIF、DEA 和 MACD 三个指标的值,并将最新的指标值和价格数据添加到相应的数据系列中。

使用 Chart 对象的 add 方法将新的数据添加到图表中。其中,add 方法的参数包括数据系列的索引和数据点的数组,第一个元素为时间戳,第二个元素为具体的数值,分别将k线数据(包括最高,最低,开盘,收盘),dea,dif和macd的值输出到图表上。最后,通过 Sleep 方法使程序暂停 1 秒钟,然后再次执行循环,实现数据的实时更新和图表的动态展示。

我们需要看下这里的主图和副图的设计。chartCfg 的对象包括主图和副图配置信息。主图中包含一个名为 “i888”的蜡烛图系列,用于显示铁矿石的价格信息。该数据系列的类型为 candlestick,表示使用蜡烛图的方式展示数据。数据系列的各种属性包括数据 data、Y 轴 yAxis、颜色 color、数据标签 dataLabels 等。

副图中包含三个数据系列,分别是 DIF 线、DEA 线和 MACD 柱形图。它们都是使用 line (就是DIF 线、DEA 线)或 column 类型(MACD)来展示数据,三个指标的 Y 轴坐标都是1,所以他们会在幅图中重叠呈现,和上面的标准的结果显示的一致。height: “60%” 是设置 Y 轴高度的属性。在主图中,Y 轴高度为图表总高度的 60%,副图中各个 Y 轴的高度为图表总高度的 40%。这样可以在一个图表中同时展示多个数据系列,更方便对比不同的数据。

image

以上的就是使用chart画图的例子,我们的课程,不能面面俱到,还有很多的细节在highstock介绍,大家都可以尝试探索下,构造出符合你的交易习惯的可视化面板。

10:JavaScript画图:KLineChart函数

在使用 JavaScript 或 Python 编写策略时,设计策略图表的显示是非常重要的。对编程不熟练或者对FMZ平台使用的图表库不熟悉的小伙伴,经常会苦恼于自定义图表上画图的代码设计。我们在上个阶段,使用Chart函数进行图表设置的时候,在图表参数设置,数据导入等阶段,都需要编写复杂的代码。那么有没有一种画图方法,结构更加清晰,而且只用编写少量的代码,又可以画出丰富内容的策略图表呢?

Pine语言相信大家都听说过,它是一门高度封装的专门为交易而生的语言。Pine语言有丰富画图功能,它的画图方式非常的简单,并且功能也十分的强大。如果可以把Pine语言的画图接口接入到JavaScript语言的策略中进行使用,那么就极大得方便了我们设计策略的画图功能。于是FMZ平台基于这种需求,升级了自定义画图功能,扩展了使用KLineChart函数进行自定义图表画图的方式。大家可以对比一下chart函数,KLineChart对于编程基础薄弱的我们,确实是一个更为友好地选择。

简单例子

function main() {
    var c = KLineChart()
    while (true) {
        if (exchange.IO("status")) {
            exchange.SetContractType("rb888")
            var bars = exchange.GetRecords()
            if (!bars) {
                Sleep(1000)
                continue
            }
            
            for (var i = 0 ; i < bars.length ; i++) {
                var bar = bars[i]
                c.begin(bar)
                c.plot(bar.Volume, "volume")
                c.close()
            }
        }
        Sleep(1000)
    }
}

话不多说,让我们来尝试一下。可以看到下面的代码,相对于chart函数需要先设置一系列选项和参数的配置选项的图表对象,KLineChart非常简单和直观。你只需要在主函数中调用KLineChart()函数创建一个图表对象c就可以了。接着,你可以利用while循环不断地获取交易数据并绘制图表。

画图操作要从begin()函数开始,close()函数结束。begin、close函数都是图表对象的方法。我们还使用plot()方法将成交量指标画在图表上。其中,第一个参数bar.Volume表示成交量数据,第二个参数 volume 表示成交量指标的名称。在这个例子中,我们只绘制了一个成交量指标,但是你可以通过添加更多的plot方法来绘制其他的指标线。

最后,通过Sleep函数设置程序每隔1秒钟执行一次,以保证实时更新数据。我们看一下回测结果,可以看到,交易量图表呈现


更多内容