Loading ...

[TOC]

基础说明

入门

FMZ量化交易平台能够做什么?

FMZ(发明者)量化交易平台是量化交易领域最专业的量化社区,在这里你可以学习、编写、分享、买卖量化策略;在线回测和使用模拟盘进行模拟交易;运行、公开、围观实盘。支持商品期货CTP/易盛API/中泰证券XTP/腾讯富途证券(美股港股)。

系列教程

电子书PDF:

书名 发明者量化交易入门 商品期货量化交易实战
封面 img img
链接 https://q.fmz.cn/chart/doc/fmzquantbook.pdf https://q.fmz.cn/chart/doc/fmzcfquant.pdf

视频教程:

如果遇到问题可以随时到论坛发帖提问、讨论,在平台提出工单。问题一般都会很快解答。

可用哪些编程语言实现我的策略呢?

FMZ量化交易平台支持使用JavaScriptPythonC++麦语言Blockly可视化编写设计策略。

这些策略设计语言中掌握其中一种就足够了。除了支持编写代码的方式设计策略,还可以使用可视化模块创建策略(Blockly)。可视化模块拼接搭建策略采用了更加直观的方式设计策略,无需编码。非常适合培养策略设计兴趣,以便快速入门程序化、量化交易。

设置Python策略程序使用的Python解释器

使用Python编写的策略,回测或实盘时如果托管者所在系统环境同时安装了Python2Python3,可以在策略开始第一行设置策略运行时启动的Python版本。例如:#!python3#!python2,这样系统就会自动查找解释器。也可以指定绝对路径,例如:#!/usr/bin/python3

什么是托管者?

托管者可以理解为您的交易策略的执行者,负责复杂的数据请求、数据接收、网络链接、日志回传等等工作。托管者运行在您的服务器上,即使FMZ量化交易平台网站出现网络故障也不影响您的托管者运行。托管者可运行在LinuxWindowsMac OSandroid树莓派 ARM Linux等系统上。托管者页面Linux托管者安装步骤及托管者更新步骤。托管者管理的实盘日志均保存在托管者程序所在目录./logs/storage内,文件为db3Sqlite数据库文件中。可以用Sqlite管理软件直接编辑,对于这些扩展名为db3的实盘数据库文件来说文件名即为实盘的ID

支持的协议

策略安全性

在FMZ量化交易平台上开发策略,策略仅FMZ量化账户持有者可见。并且在FMZ量化交易平台上可以实现策略代码完全本地化,例如把策略封装成一个Python包在策略代码中加载,这样就实现了策略本地化。

Python代码的安全性: 因为Python是开源且极易被反编译的语言,如果策略非自用而是出租,如果担心策略泄漏可让策略运行于自己布署的托管者上并以子账号或全托管管理这种形式出租。

Python策略代码加密: 默认情况下,Python策略代码作者自用时不加密,租出给他人使用时加密。在Python策略开头编辑如下代码,可以指定自用或者租出Python策略运行时是否加密策略代码。支持策略代码加密的Python版本为:Python 2.7版本、Python 3.5版本、Python 3.6版本。

  • 策略作者自己运行、通过注册码给他人使用均加密策略代码: #!python为指定Python解释器版本,之后使用逗号,间隔,输入加密指令encrypt。如果不指定Python版本直接添加#!,encrypt

    #!python,encrypt
    

    或者

    #!,encrypt
    
  • 策略作者自己运行、通过注册码给他人使用均不加密策略代码:

    #!python,unencrypt
    

    或者

    #,unencrypt
    

判断Python策略代码加密是否生效使用代码os.getenv('__FMZ_ENV__'),返回字符串"encrypt"说明已经生效。仅实盘有效,回测不会加密Python策略代码。

#!,encrypt
def main():
    ret = os.getenv('__FMZ_ENV__')
    # 打印变量ret为字符串encrypt或者ret == "encrypt"为真,即代表加密生效
    Log(ret, ret == "encrypt")

密钥安全性

在FMZ量化交易平台上配置的账户信息、策略参数中的加密字符串等敏感数据均在浏览器端加密。这些在FMZ量化交易平台上储存的信息均为加密信息(非明文数据)。只有用户的私有设备可以解密使用,从而极大提高了敏感数据的安全性。

回测系统

什么是回测系统,有什么用?

当您完成了一个量化交易策略的设计工作后,怎么才能知道您这个策略的逻辑、策略收益方向等基本情况?当然我们不能直接拿真金白银去交易市场上跑策略,我们可以用历史数据来测试您的策略。看看您的策略在历史数据中盈利如何。

回测系统的数据准确么,回测结果的准确度如何?

FMZ量化交易平台将回测模式分为实盘级回测模拟级回测。实盘级回测完全按照完整的历史数据回测;模拟级回测则根据真实K线数据生成tick数据来进行回测。两者都是根据真实历史数据回测的,但实盘级回测的数据更精准,结果更加可信。FMZ回测机制说明。但是回测仅仅是策略在历史数据下的表现,历史数据并不能完全代表将来的行情。历史行情可能重演,也可能飞出黑天鹅。所以对待回测结果要理性、客观看待。

不同语言策略回测时应注意的问题:

JavaScriptC++策略回测是在浏览器端进行。期货公司实盘账户、simnow仿真账户配置的实盘,运行不用安装任何其它软件、库或模块。 Python回测是在托管者上进行,可以在FMZ量化的公共服务器上回测,也可以在用户自己的托管者上回测。实盘和回测都依赖托管者所在系统上安装的Python,如果需要使用一些库,需要自行安装(公共服务器上只支持常用的库)。

回测系统中的数据

FMZ量化交易平台回测分模拟级回测和实盘级回测两种,模拟级回测根据底层K线周期生成模拟的tick,每个底层K线周期上将生成14个回测时间点,而实盘级则是真实收集的tick,大约几秒就有一次,数据量很大,回测速度慢,因此不能回测特别长的时间。FMZ的回测机制可以使策略在一根K线上交易多次,避免了只能收盘价成交的情况,更加精准又兼顾了回测速度。具体的说明可参考,链接

回测系统中支持的交易所

  • 期货证券 商品期货:支持大商所、郑商所、上期所、中金所期货数据,暂时不支持商品期权数据。 股票证券:用于测试的有限数据。

模拟级别

模拟级别回测是根据回测系统的底层K线数据,按照一定算法在给定的底层K线Bar的最高价、最低价、开盘价、收盘价的数值构成的框架内模拟出tick数据,作为实时tick数据在请求接口时返回。具体可以参考:发明者量化模拟级别回测机制说明

实盘级别

实盘级别回测是真实的tick级别数据在Bar的时间序列中。对于基于tick级别数据的策略来说,使用实盘级别回测更贴近真实。实盘级别回测tick是真实记录的数据,并非模拟生成。支持深度数据、市场成交记录数据回放,支持自定义深度,支持分笔数据。实盘级别回测数据最大支持50MB,在数据上限内不限制回测时间范围,如果需要尽可能增大回测时间范围,可以降低深度档位数值设置,不使用分笔数据以增加回测时间范围。调用GetDepthGetTrades函数获取回放行情数据。在时间轴上某个行情数据时刻,调用GetTickerGetTradesGetDepthGetRecords,不会多次推动时间在回测时间轴上移动(不会触发跳到下一个行情数据时刻)。对于以上某个函数重复调用,将推动回测时间在回测时间轴上移动(跳到下一个行情数据时刻)。回测时使用实盘级别回测不宜选择过早时间,可能过早时间段没有实盘级别数据。

实盘级别回测目前支持

  • 商品期货

回测系统参数调优

FMZ量化交易平台回测系统参数调优功能是在回测时根据各个参数的调优选项设置调优,如下:

  • 最小值:限定参数的起始值。

  • 最大值:限定参数递增变动后的最大值。

  • 步长:参数递增变动量。

生成参数组合,遍历这些参数组合进行回测(即每种参数组合都回测一遍)。策略参数只有类型为 数字型(number) 的参数才可以在回测系统中进行参数调优。

例如,在回测页面设置参数调优选项:

img

参数调优模式回测:

img

保存回测设置

在策略编辑页面,「模拟回测」分页中(即:回测系统)可以设置回测配置、策略参数等选项进行策略回测。回测配置是用来设置回测时间范围、回测的交易所、回测时滑点、手续费等条件;策略参数则是设置策略的参数选项。当设置好这些参数配置时便可按照设定回测策略,那么如何保存这些设置好的配置信息呢?方便下一次回测时使用(页面刷新回测时设置的选项会重置)。可以使用策略编辑页面的「保存回测设置到源文件」按钮将所有回测配置信息(包含回测设置、策略参数设置)以代码形式记录在策略源码中。再次打开策略编辑页面切换到回测系统时策略代码中记录的回测配置信息会自动配置在回测页面。

img

JavaScript策略为例,点击「保存回测设置到源文件」:

img

JavaScript/Python/C++/麦语言保存回测设置到源文件格式略有差别:

/*backtest
start: 2021-09-25 09:00:00
end: 2021-10-24 15:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
*/
'''backtest
start: 2021-09-25 09:00:00
end: 2021-10-24 15:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
'''
/*backtest
start: 2021-09-25 09:00:00
end: 2021-10-24 15:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
*/

麦语言:

(*backtest
start: 2021-09-25 09:00:00
end: 2021-10-24 15:00:00
period: 1d
basePeriod: 1h
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
*)

自定义数据源

系统用GET方法请求自定义的URL(可公开可访问的网址)来获取外部数据源进行回测,附加的请求参数如下:

参数 意义 说明
symbol 品种名 如: futures.MA888
eid 交易所 如: Futures_CTP
round 价格精度 如3 那么返回的数据里的价格都要乘于1000取整
vround 数量精度 如2 那么返回的数据里的数量都要乘于100取整
period bar周期(毫秒) 比如60000为请求分钟bar
depth 深度档数 1-20
trades 是否需要分笔数据 true/false
from 开始时间 unix时间戳
to 结束时间 unix时间戳

注意:

round与vround是为了避免网络传输过程中浮点数的精度丢失设计的两个参数,价格数据和成交量、订单量数据都采用整型传输。

一个拼接后的数据的例子:

http://customserver:80/data?symbol=futures.MA888&eid=Futures_CTP&round=3&vround=3&period=900000&from=1564315200&to=1567267200

返回的格式必须为以下两种格式(可返回任意两种格式,系统自动识别):

普通的Bar级别回测

{
    "schema":["time","open","high","low","close","vol"],
    "data":[[1564315200000,9531300,9531300,9497060,9497060,787],[1564316100000,9495160,9495160,9474260,9489460,338]]
}

Tick级回测的数据(包含盘口深度信息, 深度格式为[价格,量]的数组, 可有多级深度, asks为价格升序, bids为价格倒序)

{
    "schema":["time","asks", "bids","trades","close","vol"],
    "data":[[1564315200000,[[9531300,10]], [[9531300,10]],[[1564315200000,0,9531300,10]],9497060,787]]
}

说明

字段 说明
schema 指定data数组里列的属性,区分大小写, 仅限于 time, open, high, low, close, vol, asks, bids
data 一个按schema指一列保存数据的数组
detail 商品期货的品种需要提供的属性

detail字段

字段 说明 例子
PriceTick 一跳的值 0.1
VolumeMultiple 一手多少个单位 100
ExchangeID 交易所ID CZCE
LongMarginRatio 做多保证金比率 0.2
ShortMarginRatio 做空保证金比率 0.2
InstrumentID 合约真实代码 rb1906

数据格式

字段 说明
asks/bids [[价格,数量],…]
trades [[时间,方向(0:买,1:卖),价格,数量],…]

自定义数据源范例:

指定数据源,网址:http://xxx.xx.x.xx:9090/data 自定义数据服务端,使用golang编写:

package main 
import (
    "fmt"
    "net/http"
    "encoding/json"
)

func Handle (w http.ResponseWriter, r *http.Request) {
    // e.g. set on backtest DataSourse: http://xxx.xx.x.xx:9090/data
    // r.URL: /data?depth=20&detail=true&eid=Futures_CTP&from=1566820800&period=900000&round=3&symbol=futures.MA888&to=1569686400&trades=1&vround=5
    // response
    defer func() {
        // response data
        /* e.g. data
        {
            "schema":["time","open","high","low","close","vol"],
            "data":[
                [1564315200000,9531300,9531300,9497060,9497060,787],
                [1564316100000,9495160,9495160,9474260,9489460,338]
            ]
        }
        */
        ret := map[string]interface{}{
            "schema" : []string{"time","open","high","low","close","vol"},
            "data" : []interface{}{
                []int64{1564315200000,9531300,9531300,9497060,9497060,787},
                []int64{1564316100000,9495160,9495160,9474260,9489460,338},
            },
        }
        b, _ := json.Marshal(ret)
        w.Write(b)
    }()
}

func main () {
    fmt.Println("listen http://localhost:9090")
    http.HandleFunc("/data", Handle)
    http.ListenAndServe(":9090", nil)
}

测试策略,JavaScript范例:

/*backtest
start: 2019-07-28 00:00:00
end: 2019-07-29 00:00:00
period: 1m
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES","feeder":"http://120.24.2.20:9090/data"}]
*/

function main() {
    // 对于测试代码,我们通过判断exchange.IO("status")函数,连接成功之后执行测试代码,区别于商品期货策略一般架构
    while(!exchange.IO("status")) {
        Sleep(1000)
    }
    // 设置合约为MA888
    exchange.SetContractType("MA888")
    // 获取ticker数据
    var ticker = exchange.GetTicker()
    // 获取records数据,即K线数据
    var records = exchange.GetRecords()
    // 打印数据
    Log(ticker)
    Log(records)
}

回测系统中自定义的数据画出的图表:

策略打印信息:

本地回测引擎

FMZ量化交易平台开源了JavaScript语言和Python语言的本地回测引擎,支持回测时设置底层K线周期

回测页面快捷键

  • 策略编辑页面和策略回测页面切换的快捷键

    使用Ctrl + ,键,切换回测页面和策略编辑页面,按住Ctrl键后,单按,键。

  • 策略保存的快捷键

    使用Ctrl + s键,保存策略。

  • 启动回测的快捷键

    使用Ctrl + b键,启动回测。

代码说明

入口函数

函数名 说明
main() 为入口函数。
onexit() 为正常退出时的扫尾函数,最长执行时间为5分钟,可以不声明,如果超时会报错interrupt错误。
onerror() 为异常退出触发执行的函数,最长执行时间为5分钟,可以不声明,Python语言、C++语言编写的策略不支持该函数。
init() 为初始化函数,策略程序会在开始运行时首先自动调用,可不声明。
  • 说明:
    • 回测系统不支持onerror()函数。
    • 在实盘时先触发了onerror()函数,就不会再触发onexit()函数。

onexit()

onexit(),处理扫尾工作,最长执行5分钟,由用户实现。

function main(){
    Log("开始运行, 5秒后停止,并执行扫尾函数!")
    Sleep(1000 * 5)
}

// 扫尾函数实现
function onexit(){
    // 不使用接口获取数据的测试,就无需使用exchange.IO("status")函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试
    var beginTime = new Date().getTime()
    while(true){
        var nowTime = new Date().getTime()
        Log("程序停止倒计时..扫尾开始,已经过去:", (nowTime - beginTime) / 1000, "秒!")
        Sleep(1000)
    }
}
import time 
def main():
    Log("开始运行, 5秒后停止,并执行扫尾函数!")
    Sleep(1000 * 5)

def onexit():
    beginTime = time.time() * 1000
    while True:
        ts = time.time() * 1000
        Log("程序停止倒计时..扫尾开始,已经过去:", (ts - beginTime) / 1000, "秒!")
        Sleep(1000)
void main() {
    Log("开始运行, 5秒后停止,并执行扫尾函数!");
    Sleep(1000 * 5);
}

void onexit() {
    auto beginTime = Unix() * 1000;
    while(true) {
        auto ts = Unix() * 1000;
        Log("程序停止倒计时..扫尾开始,已经过去:", (ts - beginTime) / 1000, "秒!");
        Sleep(1000);
    }
}

init()

init(),用户实现初始化函数init(),策略开始运行时首先自动执行init()函数,完成策略中设计的初始化任务。

function main(){
    // 不使用接口获取数据的测试,就无需使用exchange.IO("status")函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试
    Log("程序第一行代码执行!", "#FF0000")
    Log("退出!")
}

// 初始化函数
function init(){     
    Log("初始化!")
}
def main():
    Log("程序第一行代码执行!", "#FF0000")
    Log("退出!")

def init():
    Log("初始化!")
void main() {
    Log("程序第一行代码执行!", "#FF0000");
    Log("退出!");
}

void init() {
    Log("初始化!");
}

onerror()

onerror(),发生异常时会触发onerror()函数执行,该函数不支持PythonC++语言的策略。

function main() {
    // 不使用接口获取数据的测试,就无需使用exchange.IO("status")函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试
    var arr = []
    Log(arr[6].Close)
}

function onerror() {
    Log("错误")
}
# python不支持
// C++不支持

经典策略框架

JavaScriptPythonC++语言编写的策略中需要在策略主循环中调用Sleep()函数。回测时用于控制回溯的速度,实盘时用于控制策略轮询的时间间隔,从而控制访问交易所API接口的请求频率。

  • 经典的商品期货策略架构:

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

模板类库

模板类库是FMZ量化交易平台中可复用的代码模块,是策略代码的一种类别。创建策略时如果类别设置为模板类库,则创建一个模板类库在发明者量化交易平台当前登录的账号策略库中,创建后无法再修改类别为普通策略。

JavaScript语言模板类库:

img

Python语言模板类库:

img

C++语言模板类库:

img

  • 模板类库的导出函数

    导出函数为模板类库的接口函数,可以被引用该模板类库的策略调用。导出函数在模板类库中声明以及实现的例子代码如下:

    /*
    -- 策略引用该模板以后直接用 $.Test() 调用此方法
    -- main 函数在策略中不会触发, 只做为模板调试的入口
    */
    $.Test = function() {
        Log('Test')
    }
    
    function main() {
        $.Test()
    }
    
    def Test():
        Log("template call")
    
    # 导出Test函数, 主策略可以通过ext.Test()调用
    ext.Test = Test 
    
    // 策略引用该模板以后直接用 ext::Test() 调用此方法
    void Test() {
        Log("template call");
    }
    
  • 模板类库的参数 模板类库也可以设置自己的界面参数,模板类库的参数在模板类库代码中是以全局变量的形式使用的。

    模板类库设置参数:

    img

    模板类库代码:

    $.SetParam1 = function(p1) {
        param1 = p1
    }
    
    $.GetParam1 = function() {
        Log("param1:", param1)
        return param1
    }
    
    def SetParam1(p1):
        global param1
        param1 = p1
    
    def GetParam1():
        Log("param1:", param1)
        return param1
    
    ext.SetParam1 = SetParam1
    ext.GetParam1 = GetParam1
    
    void SetParam1(float p1) {
        param1 = p1;
    }
    
    float GetParam1() {
        Log("param1:", param1);
        return param1;
    }
    

    引用以上模板类库例子的策略代码:

    function main () {
        Log("调用$.GetParam1:", $.GetParam1())
        Log("调用$.SetParam1:", "#FF0000")
        $.SetParam1(20)
        Log("调用$.GetParam1:", $.GetParam1())
    }
    
    def main():
        Log("调用ext.GetParam1:", ext.GetParam1())
        Log("调用ext.SetParam1:", "#FF0000")
        ext.SetParam1(20)
        Log("调用ext.GetParam1:", ext.GetParam1())
    
    void main() {
        Log("调用ext::GetParam1:", ext::GetParam1());
        Log("调用ext::SetParam1:", "#FF0000");
        ext::SetParam1(20);
        Log("调用ext::GetParam1:", ext::GetParam1());
    }
    

    img

  • 引用模板类库

    在策略编辑页面模板栏中勾选引用后,保存策略即可。

    img

内置结构

全局变量

exchange

exchange可视为一个交易所对象,默认为策略参数中添加的第一个交易所对象。所有与交易所的交互都通过这个对象里面的函数实现。一个交易所对象绑定一个期货账户。

  • 回测添加交易所对象

  • 实盘页面添加交易所对象

添加的交易所对象就对应代码中的exchange对象:

function main() {
    // 不使用接口获取数据的测试,就无需使用exchange.IO("status")函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试
    Log("实盘页面或者回测页面上,添加的第一个交易所对象名字:", exchange.GetName(), ",标签:", exchange.GetLabel())
}
def main():
    Log("实盘页面或者回测页面上,添加的第一个交易所对象名字:", exchange.GetName(), ",标签:", exchange.GetLabel())
void main() {
    Log("实盘页面或者回测页面上,添加的第一个交易所对象名字:", exchange.GetName(), ",标签:", exchange.GetLabel());
}
exchanges

可以理解为储存如同exchange交易所对象的所有交易所对象的数组,可能包含多个交易所对象,exchanges[0]即是exchange

添加的交易所对象对应策略代码中的exchanges[0]exchanges[1]exchanges[2]、… ,以此类推。

function main() {
    // 不使用接口获取数据的测试,就无需使用exchange.IO("status")函数判断连接状态,也不用设置合约代码,因为这里仅仅是测试
    for(var i = 0; i < exchanges.length; i++) {
        Log("添加的交易所对象索引(第一个为0以此类推):", i, "名称:", exchanges[i].GetName(), "标签:", exchanges[i].GetLabel())
    }
}
def main():
    for i in range(len(exchanges)):
        Log("添加的交易所对象索引(第一个为0以此类推):", i, "名称:", exchanges[i].GetName(), "标签:", exchanges[i].GetLabel())
void main() {
    for(int i = 0; i < exchanges.size(); i++) {
        Log("添加的交易所对象索引(第一个为0以此类推):", i, "名称:", exchanges[i].GetName(), "标签:", exchanges[i].GetLabel());
    }
}
订单状态

Order结构中的Status属性。

常量名 定义
ORDER_STATE_PENDING 未完成 0
ORDER_STATE_CLOSED 已经完成 1
ORDER_STATE_CANCELED 已经取消 2
ORDER_STATE_UNKNOWN 未知状态(其它状态) 3

ORDER_STATE_UNKNOWN状态,可以调用exchange.GetRawJSON()获取原始订单状态信息,查询交易所文档,查看具体描述。 表格中的常量名可以直接在策略代码中用于和Order结构的Status属性比较、判断是否相等从而确定订单状态。打印这些常量名会显示这些常量名对应的值,以下其它常量名同理不再赘述。

订单买卖类型

Order结构中的Type属性。

常量名 定义
ORDER_TYPE_BUY 买单 0
ORDER_TYPE_SELL 卖单 1
仓位类型

Position结构中的Type属性。

常量名 定义 说明 适用
PD_LONG 多头仓位 CTP用exchange.SetDirection(“closebuy_today”)设置平仓方向 商品期货 0
PD_SHORT 空头仓位 CTP用exchange.SetDirection(“closesell_today”)设置平仓方向 商品期货 1
PD_LONG_YD 昨日多头仓位 CTP用exchange.SetDirection(“closebuy”)设置平仓方向 商品期货 2
PD_SHORT_YD 昨日空头仓位 CTP用exchange.SetDirection(“closesell”)设置平仓方向 商品期货 3
期货开平仓方向

Order结构中的Offset属性。

常量名 定义
ORDER_OFFSET_OPEN 开仓的订单 0
ORDER_OFFSET_CLOSE 平仓的订单 1
策略参数

在策略代码中策略界面上设置的策略参数,是以全局变量形式体现的。JavaScript语言中可以直接访问策略界面上设置的参数数值或者修改,Python策略的函数中修改全局变量时需要使用global关键字。

参数种类:

img

变量 描述 备注 类型 默认值 说明
number 数值类型 备注 数字型(number) 1 C++策略为浮点型。
string 字符串 备注 字符串(string) Hello FMZ 默认值输入时不需要加引号,输入均作为字符串处理。
combox 下拉框 备注 下拉框(selected) 1|2|3 combox变量本身是数值,代表下拉框控件选择的栏目的索引,第一个下拉框栏目内容是1,其索引值是0,依次类推。
bool 勾选项 备注 布尔型(true/false) true 勾选上,变量bool为true,不勾选,变量bool为false。
secretString 加密字符串 备注 加密串(string) passWord 使用和字符串相同,加密字符串会被加密发送,不会明文传输。
  • 界面参数,在策略编辑页面代码编辑区下方策略参数区设置。
  • 界面参数在策略代码中是以全局变量形式存在的,也就是说可以在代码中修改界面参数。
  • 界面参数在策略代码中的变量名:即上图中的numberstringcomboxboolsecretString
  • 描述选项:界面参数在策略界面上的名字。
  • 备注选项:界面参数的详细描述,该描述会在鼠标停留在界面参数上时相应的显示出。
  • 类型选项:该界面参数的类型。
  • 默认值选项:该界面参数的默认值。

参数依赖设置:

可以设置一个参数,让另一个参数基于该参数的选择实现显示与隐藏。比如我们设置参数numberA,是一个数值类型。我们让numberA基于一个参数:isShowA(布尔类型)的真假决定numberA显示与隐藏。需要把numberA变量在界面参数上设置为:numberA@isShowA

img

这样不勾选isShowA参数,numberA参数就隐藏了。对于下拉框控件类型的参数,参数依赖部分为判断是否等于下拉框某个选项的索引值。同样以isShowA参数为例,在参数设置变量时写为:numberA@combox==2numberA参数就基于combox参数是否选择为第三个选项进行显示或隐藏(索引0对应第一个选项,索引1对应第二个选项,索引2对应第三个选项)。

策略界面参数、交互控件、模板上的参数分组功能: 只用在开始分组的参数的描述开头加上(?第一组)即可,例如下图。

img

在策略使用时会分组显示参数:

img

参数默认值保存: 策略参数如图,在回测时如果希望将策略参数默认值保存,可以在策略参数修改后点击保存回测设置按钮。

img

img

即可将设置后的策略参数以代码形式保存在策略中:

/*backtest
start: 2020-02-29 00:00:00
end: 2020-03-29 00:00:00
period: 1d
args: [["number",10],["string","Hello 发明者"],["combox",1],["bool",false]]
*/
'''backtest
start: 2020-02-29 00:00:00
end: 2020-03-29 00:00:00
period: 1d
args: [["number",10],["string","Hello 发明者"],["combox",1],["bool",false]]
'''
/*backtest
start: 2020-02-29 00:00:00
end: 2020-03-29 00:00:00
period: 1d
args: [["number",10],["string","Hello 发明者"],["combox",1],["bool",false]]
*/

数据结构

部分函数会附带在调用时请求返回的原始JSON数据,该原始JSON数据储存在返回对象的Info属性中。回测时由于并不是访问某个交易所的接口,所以回测时返回的数据中无Info属性,以下是各个数据结构的主要属性描述。

Trade

获取所有交易历史(非自己),由exchange.GetTrades()函数返回。商品期货、股票证券不支持该函数。

{
    Id      : 9585306,          // 交易记录ID
    Time    : 1567736576000,    // 时间(Unix timestamp 毫秒)
    Price   : 1000,             // 价格
    Amount  : 1,                // 数量
    Type    : 0                 // 订单类型,参考常量里的订单类型,0即为ORDER_TYPE_BUY,ORDER_TYPE_BUY的值为0
}
Ticker

市场行情由exchange.GetTicker()函数返回。

{
    Info    : {...},             // 请求交易所接口后,交易所接口应答的原始数据,回测时无此属性
    High    : 1000,              // 最高价
    Low     : 500,               // 最低价
    Sell    : 900,               // 卖一价
    Buy     : 899,               // 买一价
    Last    : 900,               // 最后成交价
    Volume  : 10000000,          // 最近成交量
    Time    : 1567736576000      // 毫秒级别时间戳
}
Record

标准的OHLC结构,用来画K线和指标计算分析。由exchange.GetRecords()函数返回此结构的数组。每一个Record结构代表一个K线柱,即一根K线BAR。Record其中的Time为这根K线柱周期的起始时间。

{
    Time    : 1567736576000,     // 一个时间戳,精确到毫秒,与Javascript的new Date().getTime()得到的结果格式一样
    Open    : 1000,              // 开盘价
    High    : 1500,              // 最高价
    Low     : 900,               // 最低价
    Close   : 1200,              // 收盘价
    Volume  : 1000000            // 交易量
}
Order

订单结构,可由exchange.GetOrder()exchange.GetOrders()函数返回。exchange.GetOrders()返回的是该结构的数组或者空数组(如果没有当前未完成的订单,返回[],即空数组)。

{
    Info        : {...},         // 请求交易所接口后,交易所接口应答的原始数据,回测时无此属性
    Id          : 123456,        // 交易单唯一标识
    Price       : 1000,          // 下单价格
    Amount      : 10,            // 下单数量
    DealAmount  : 10,            // 成交数量
    AvgPrice    : 1000,          // 成交均价
    Status      : 1,             // 订单状态,参考常量里的订单状态ORDER_STATE_CLOSED
    Type        : 0,             // 订单类型,参考常量里的订单类型ORDER_TYPE_BUY
    Offset      : 0              // 商品期货的订单数据中,订单的开平仓方向,ORDER_OFFSET_OPEN为开仓,ORDER_OFFSET_CLOSE为平仓方向
    ContractType : ""            // 该属性为具体的合约代码
}
MarketOrder

市场深度单,即exchange.GetDepth()函数返回数据中BidsAsks数组中的元素的数据结构。

{
    Price   : 1000,              // 价格
    Amount  : 1                  // 数量
}
Depth

市场深度,由exchange.GetDepth()函数返回。

{
    Asks    : [...],             // 卖单数组,MarketOrder数组,按价格从低向高排序
    Bids    : [...],             // 买单数组,MarketOrder数组,按价格从高向低排序
    Time    : 1567736576000      // 毫秒级别时间戳
}
Account

账户信息,由exchange.GetAccount()函数返回。

{
    Info            : {...},     // 请求交易所接口后,交易所接口应答的原始数据,回测时无此属性
    Balance         : 1000,      // 可用余额
    FrozenBalance   : 0,         // 挂单冻结的余额
    Stocks          : 1,         // 传统期货、股票证券此属性固定为0
    FrozenStocks    : 0          // 传统期货、股票证券此属性固定为0
}
Position

期货交易中持有的仓位信息,由exchange.GetPosition()函数返回此Position结构的数组。

{
    Info            : {...},     // 请求交易所接口后,交易所接口应答的原始数据,回测时无此属性
    MarginLevel     : 10,        // 杆杠大小,商品期货无法修改杠杆值
    Amount          : 100,       // 持仓量
    FrozenAmount    : 0,         // 挂单平仓时,仓位冻结量
    Price           : 10000,     // 持仓均价
    Profit          : 0,         // 股票不支持此字段,商品期货为盯市盈亏
    // CTP中用closebuy_today平今多头仓位,对应的Type属性为PD_LONG;用closesell_today平今空头仓位,对应的Type属性为PD_SHORT;用closebuy平昨多头仓位,对应的Type属性为PD_LONG_YD;用closesell平昨空头仓位,对应的Type属性为PD_SHORT_YD
    Type            : 0,
    ContractType    : "rb2201",  // 合约代码、股票代码
    Margin          : 1          // 仓位占用的保证金
}

全局函数

Version()

Version(),返回系统当前版本号。返回值:字符串类型。

Sleep(Millisecond)

Sleep(Millisecond),休眠函数,使程序暂停一段时间。参数值:Millisecond为数值类型。参数为毫秒数,例如:Sleep(1000)为休眠1秒。

注意: 在使用Python语言编写策略时,对于轮询间隔、时间等待的操作应当使用Sleep(Millisecond)函数。不建议使用Pythontime库的time.sleep(second)函数。因为策略中使用time.sleep(second)函数在回测时会让策略程序实际等待一定秒数(second参数为设置暂停的秒数),导致策略回测非常慢。

IsVirtual()

IsVirtual(),判断当前策略运行是否为模拟回测。返回值:布尔类型。 模拟回测状态返回true,实盘返回false

Mail(…)

Mail(smtpServer, smtpUsername, smtpPassword, mailTo, title, body),发送邮件函数。参数值:参数全部为字符串类型。返回值:布尔类型,发送成功返回truesmtpServer为发送邮箱smtp服务,smtpUsername为邮箱账号,smtpPassword为邮箱的SMTP密码(不是邮箱登录密码),mailTo为接受邮件的邮箱账号,title为发送的邮件标题,body为发送的邮件内容,例如:

function main(){
    Mail("smtp.163.com", "asdf@163.com", "password", "111@163.com", "title", "body")
}
def main():
    Mail("smtp.163.com", "asdf@163.com", "password", "111@163.com", "title", "body")
void main() {
    Mail("smtp.163.com", "asdf@163.com", "password", "111@163.com", "title", "body");
}
  • Mail函数的异步版本Mail_Go函数: 使用方式和exchange.Go函数类似。

    function main() {
        var r1 = Mail_Go("smtp.163.com", "asdf@163.com", "password", "111@163.com", "title", "body")
        var r2 = Mail_Go("smtp.163.com", "asdf@163.com", "password", "111@163.com", "title", "body")
        
        var ret1 = r1.wait()
        var ret2 = r2.wait()
        
        Log("ret1:", ret1)
        Log("ret2:", ret2)
    }
    
    # 不支持
    
    // 不支持
    

注意: 阿里云服务器可能会封一些端口,导致邮件无法发出。如需更改端口,可以直接在第一个参数中加入端口号,例如:QQ邮箱的smtp.qq.com:587,该端口测试可用。 如果出现报错:unencryped connection,需要修改Mail函数的smtpServer参数的格式为:ssl://xxx.com:xxx,举例QQ邮箱的SMTP的ssl方式:ssl://smtp.qq.com:465或者smtp://xxx.com:xxx

SetErrorFilter(…)

SetErrorFilter(RegEx),过滤错误日志。参数值:字符串类型。 被此正则表达式匹配的错误日志将不上传到日志系统,可多次调用设置多个过滤条件(被过滤的日志不写入托管者目录下对应实盘ID的数据库文件,防止频繁报错导致数据库文件膨胀)。

function main() {
    SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused")
}
def main():
    SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused")
void main() {
    SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused");
}

过滤某个接口错误信息:

function main() {
    // 鉴于测试代码,不使用商品期货策略一般架构,这里仅仅判断exchange.IO("status")函数,判断连接期货公司前置机成功后立即执行测试代码。股票证券无需使用exchange.IO("status")判断连接状态
    while(!exchange.IO("status")) {
        Sleep(1000)
    }
    // 设置合约代码
    exchange.SetContractType("rb888")

    // 随便查询一个不存在的订单,id为123,故意让接口报错
    var order = exchange.GetOrder("123")
    Log(order)
    // 过滤http502错误、GetOrder接口错误,设置错误过滤之后,第二次调用GetOrder不再报错
    SetErrorFilter("502:|GetOrder")
    order = exchange.GetOrder("123")
    Log(order)
}
def main():
    while not exchange.IO("status"):
        Sleep(1000)

    exchange.SetContractType("rb888")

    order = exchange.GetOrder("123")
    Log(order)
    SetErrorFilter("502:|GetOrder")
    order = exchange.GetOrder("123")
    Log(order)
void main() {
    while(exchange.IO("status") == 0) {
        Sleep(1000);
    }

    exchange.SetContractType("rb888");

    TId orderId;
    Order order = exchange.GetOrder(orderId);
    Log(order);
    SetErrorFilter("502:|GetOrder");
    order = exchange.GetOrder(orderId);
    Log(order);
}

GetPid()

GetPid(),返回实盘进程ID。返回值:字符串类型。

function main(){
    var id = GetPid()
    Log(id)
}
def main():
    id = GetPid()
    Log(id)
void main() {
    auto id = GetPid();
    Log(id);
}

GetLastError()

GetLastError(),获取最近一次出错信息。一般无需使用,因为程序会把出错信息自动上传到日志系统。返回值:字符串类型。调用GetLastError()函数后会清除这个错误缓存,再次调用时不会再返回上次记录的错误信息。

function main(){
    // 鉴于测试代码,不使用商品期货策略一般架构,这里仅仅判断exchange.IO("status")函数,判断连接期货公司前置机成功后立即执行测试代码。股票证券无需使用exchange.IO("status")判断连接状态
    while(!exchange.IO("status")) {
        Sleep(1000)
    }

    exchange.SetContractType("rb888")

    // 因为不存在编号为123的订单,所以会出错
    exchange.GetOrder("123")
    var error = GetLastError()
    Log(error)
}
def main():
    while not exchange.IO("status"):
        Sleep(1000)

    exchange.SetContractType("rb888")

    exchange.GetOrder("123")
    error = GetLastError()
    Log(error)
void main() {
    while(exchange.IO("status") == 0) {
        Sleep(1000);
    }

    exchange.SetContractType("rb888");

    // 订单ID类型:TId,所以不能传入字符串,我们下一个不符合交易所规范的订单来触发
    exchange.GetOrder(exchange.Buy(1, 1));
    auto error = GetLastError();
    Log(error);
}

GetCommand()

GetCommand(),获取交互命令字符串(utf-8)。获取策略交互界面发来的命令并清空缓存,没有命令则返回空字符串。返回的命令格式为按钮名称:参数,如果交互控件没有参数(例如不带输入框的按钮控件)则命令就是按钮名称。

function main(){
    while(true) { 
        var cmd = GetCommand()
        if (cmd) { 
            Log(cmd)
        }
        Sleep(1000) 
    }
}
def main():
    while True:
        cmd = GetCommand()
        if cmd:
            Log(cmd)
        Sleep(1000)
void main() {
    while(true) {
        auto cmd = GetCommand();
        if(cmd != "") {
            Log(cmd);
        }
        Sleep(1000);
    }
}

底层系统有一个队列结构记录交互命令,当GetCommand()函数被调用时,会取出队列中最先进入的交互命令(如果没有交互命令时返回空字符串)。

交互控件的使用例子,策略编辑界面设置交互控件。

img

策略中设计交互代码:

function main() {
    while (true) {
        LogStatus(_D())
        var cmd = GetCommand()
        if (cmd) {
            Log("cmd:", cmd)    
            var arr = cmd.split(":")
            if (arr[0] == "buy") {
                Log("买入,该控件不带数量")
            } else if (arr[0] == "sell") {
                Log("卖出,该控件带数量:", arr[1])
            } else {
                Log("其它控件触发:", arr)
            }
        }
        Sleep(1000)
    } 
}
def main():
    while True:
        LogStatus(_D())
        cmd = GetCommand()
        if cmd:
            Log("cmd:", cmd)
            arr = cmd.split(":")
            if arr[0] == "buy":
                Log("买入,该控件不带数量")
            elif arr[0] == "sell":
                Log("卖出,该控件带数量:", arr[1])
            else:
                Log("其它控件触发:", arr)
        Sleep(1000)
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
void split(const string& s,vector<string>& sv,const char flag = ' ') {
    sv.clear();
    istringstream iss(s);
    string temp;

    while (getline(iss, temp, flag)) {
        sv.push_back(temp);
    }
    return;
}

void main() {
    while(true) {
        LogStatus(_D());
        auto cmd = GetCommand();
        if (cmd != "") {
            vector<string> arr;
            split(cmd, arr, ':');
            if(arr[0] == "buy") {
                Log("买入,该控件不带数量");
            } else if (arr[0] == "sell") {
                Log("卖出,该控件带数量:", arr[1]);
            } else {
                Log("其它控件触发:", arr);
            }
        }
        Sleep(1000);
    }
}

GetMeta()

GetMeta()函数返回生成策略注册码时写入的Meta的值,该函数返回值为字符串类型。 应用场景,例如策略需要对不同租户做资金限制。注意:生成注册码时Meta的长度不能超过190字符,该函数仅适用于实盘,需要使用最新的托管者。如果生成策略注册码时没有设置元数据GetMeta()返回空值。

Dial(…)

Dial(Address, Timeout),原始的Socket访问,支持tcpudptlsunix协议。参数值:Address为字符串类型,TimeOut为数值类型,数值单位为秒,如果超时Dial(...)函数返回空值。

Address参数详细说明:

参数说明
设置Dial功能的参数 在正常的地址:wss://xxx.xxx.xxx:10441/websocket?compress后以"|“符号分隔,如果参数字符串中有|字符,则以”||“作为分隔符号。各个参数之间用”&"连接,例如ss5代理和压缩参数一起设置:Dial(“wss://baidu.com/stream|proxy=socks5://xxx:9999&compress=gzip_raw&mode=recv”)
用于ws协议时,数据压缩相关的参数:compress=参数值 compress为压缩方式,compress参数,可选gzip_raw,gzip等。非标准gzip。需要使用扩展的方式:gzip_raw,即在分隔符"|“后添加设置compress=gzip_raw,用”&"符号和下一个mode参数分隔。
用于ws协议时,数据压缩相关的参数:mode=参数值 mode为模式,可选dual,send,recv三种。dual为双向,发送压缩数据,接收压缩数据。send为发送压缩数据。recv为接收压缩数据,本地解压缩。
用于设置socks5代理的相关参数:proxy=参数值 proxy为ss5代理设置,参数值格式:socks5://name:pwd@192.168.0.1:1080,name为ss5服务端用户名,pwd为ss5服务端登录密码,1080为ss5服务的端口
用于ws协议时,设置底层自动重连相关的参数:reconnect=参数值 reconnect为是否设置重连,reconnect=true为启用重连,不设置默认不重连
用于ws协议时,设置底层自动重连相关的参数:interval=参数值 interval为重试时间间隔,单位毫秒,interval=10000为重试间隔10秒,不设置默认1秒,即interval=1000
用于ws协议时,设置底层自动重连相关的参数:payload=参数值 payload为ws重连时需要发送的订阅消息,例如:payload=okok
function main(){
    // Dial支持tcp://,udp://,tls://,unix://协议,可加一个