币圈量化交易萌新看过来--带你走近币圈量化(二)


上期文章我们讲到了程序化交易脚本。其实交易策略就是一个交易脚本程序,文章主要讲到了交易脚本程序需要有硬件载体(程序在哪里运行),这个脚本交易程序可以用那种计算机编程语言编写(列举了在发明者量化交易平台上使用的三种编程语言,当然本身做程序化交易可以使用任何编程语言实现策略)。本期文章我们继续讨论币圈量化、了解币圈量化的知识。

程序化交易脚本

交易策略的种类

初识程序化交易、量化交易的萌新们可能上来就被各种趋势策略、套利策略、高频策略、网格策略等名词整晕了,其实程序化交易、量化交易常见策略类型简单说就几个方向。

套利对冲策略
简单说,基本上一边持有看多头寸,一边持有看空头寸这样的策略可以归类为套利策略。具体的种类有很多,现货跨市场、期货跨期、期现套利、跨品种套利等。

趋势策略
简单说就是跟踪趋势下单持仓的策略,双均线、MACD等策略。

回归策略
例如,网格策略,赚取震荡行情中价格波动收益。

高频策略
简单说就是通过一些算法发掘市场微观结构、规律、机会等进行高频率交易的策略。

以上是从交易策略角度去划分的,在发明者量化交易平台上从策略设计角度去看,策略还可以分为:

单品种策略
就是说这个策略只操作一个品种,比如做BTC交易或者做ETH交易。

多品种策略
简单说就是按照一个策略逻辑去操作多个品种。

多账号策略
简单说就是一个实盘上配置多个交易所对象(上一篇文章中已经介绍交易所的概念,配置好API KEY的交易所对象就代表一个交易所账户)。比如一些跟单策略,多个账号一起跟随操作(可能是同一个交易所,也可能是不同交易所),总之一个实盘上管理多个交易所对象(账号)。

多逻辑策略
一个实盘上比如同时设计了MACD策略、均线策略、网格策略等(当然,是操作不同交易所对象,操作相同的交易所对象要看具体的策略是不是逻辑上有冲突)

交易所API接口

程序化交易脚本是如何操作交易所账户呢?答案就是通过交易所开放的API接口。那么交易所开放的接口都有哪些种类呢?上篇文章中我们在「交易所」部分讲了交易所一般有REST、Websocket接口。这里我们从策略程序层面补充一点概念。交易所接口按是否验证划分(REST、Websocket都是)有验证和非验证。

不需要验证的接口
一般称为“公共接口”,这类接口不需要验证API KEY(忘记API KEY是什么的可以翻下上篇文章)。这类接口一般是行情接口,例如查询深度行情、查询K线数据、查询资金费率、查询交易品种相关信息、查询交易所服务器时间戳等。简单说就是和你的账户基本毫无相关的接口可以大致确定就是一个公共接口(不需要验证)在发明者量化交易平台上,调用不验证的API函数时(封装交易所非验证接口,公共接口)即使API KEY配置错误,也可以正常获取到接口返回的数据。(因为不验证)

需要验证的接口
简单说就是需要验证的接口(通过API KEY验证),这类接口叫做私有接口。这类接口通常都和你的账户一些操作或者信息有关系,例如查询账户资产、查询账户持仓、查询挂单、查询转账、转币、调整杠杆、设置持仓模式等。这些操作都必须验证。在发明者量化交易平台上,调用需要验证的API函数时(封装的交易所需要验证的接口,私有接口),如果API KEY配置错误,调用接口时会报错,返回空值。

那么在发明者量化交易平台上这些接口是如何使用的呢?
发明者量化交易平台封装了交易所行为、定义一致的接口(例如K线接口、深度行情接口、查询当前资产接口、下单接口、撤单接口等),这些接口在发明者量化交易平台上叫做发明者量化交易平台API函数,可以通过查询API文档查阅( https://www.fmz.com/api )。

那么一些行为、定义并不统一的交易所接口在发明者量化交易平台上如何使用呢?
这些交易所接口例如:资产划转、条件委托、批量下单、批量撤单、修改订单等。这些接口有些交易所具备,有些交易所没有,并且功能、使用细节可能差别较大,所以这些接口在发明者量化交易平台上通过exchange.IO这个函数去访问(详情可以参看发明者量化交易平台API文档:https://www.fmz.com/api#exchange.io...)。在发明者量化交易平台策略广场上也有些实用IO的范例策略。

是否在发明者量化交易平台API文档上的所有API函数都会产生网络请求呢?
我们先说下交易所API接口是有访问频率限制的(例如一秒5次之类的),访问不能过于频繁否则就会报http 429错误,就拒绝访问了(大部分交易所都是报429)。那么在发明者量化交易平台上调用封装好的交易所接口也是同样有这个限制,对于发明者量化交易平台上不产生网络请求的API函数则没有这个限制。

并不是所有发明者量化交易平台的API函数都会产生网络请求,有些发明者的API函数只是修改一些本地设置,例如设置当前交易对、设置合约代码、指标计算函数、获取交易所对象名称等。基本上从函数的用途可以判断出来是否发生网络请求,只要是获取交易所数据、对交易所账户操作之类的都是产生网络请求的,这些接口都需要注意调用频率。

再来说几个在发明者量化交易平台使用API函数时常见的问题、经验

容错
这个是最常见的错误,困扰无数萌新,经常策略回测好好的一切正常,为何实盘跑了一段时间(随时可能触发)实盘就挂掉了~

编写策略时对于接口返回的数据我们是都需要判断验证的,例如在发明者量化交易平台上获取行情这行代码(自己写程序直接访问交易所接口也是一样):var ticker = exchange.GetTicker(),假如我们需要用这个ticker变量(参看GetTicker函数返回的结构)里面的Last(最近价格)这个数据,我们需要使用var newPrice = ticker.Last这样取得数据(newPrice是啥?new:最新,Price:价格,对!合起来!)

此时,如果GetTicker()函数返回了正常数据还好,但是如果发生了请求超时、网络错误、交易所拔网线、挖断电缆的、熊孩子拉电闸的等等..就会导致GetTicker()函数返回null。此时ticker的值就是null,我再去访问它的Last就会发生程序异常导致策略程序停止。

由此看来发生接口调用失败(GetTicker调用失败返回null)不是导致策略实盘停止的直接原因(直接原因是访问了一个null变量的属性),接口调用失败报错是不会导致实盘停止的(划重点)。

所以我们要怎么做才能避免实盘异常停止呢?答案就是对接口返回的数据做容错处理,很简单只用判断返回的数据是不是null(JavaScript语言举例,其它语言基本差不多)。写个小代码段说明(这只是个说明,直接运行是跑不起来的哦!)

var ticker = exchange.GetTicker()
if (ticker) {
    var newPrice = ticker.Last
    Log("打印最新价格:", newPrice)
} else {
    // 数据为null,不做操作就不会出问题
}

不光是GetTicker接口需要做容错,有网络请求的接口都需要对返回值做容错(如果你对函数的返回值使用的话)容错方式有很多种,可以使用_C()函数(参看FMZ API文档),自己写容错函数,自己设计容错机制、逻辑。关于_C()函数的使用,很多萌新同学也是大概率用错,注意_C()函数的参数是函数引用,不是函数调用。

通俗说:_C(funcName, param1, param2),调用正确,funcName 不带小括号,param1、param2是要给funcName这个函数传的参数。_C(funcName(param1, param2)),调用错误,通常萌新没认真看FMZ API文档都会这么写。

现货市价单买单的下单量
现货市价单买单的下单量也是很多萌新容易搞错的,上一篇中已经讲过现货市价单买单的下单量通常是金额(极个别的交易所可能是其它设定,一般FMZ上这些特殊的交易所设定都会在FMZ API文档上说明),例如我用OKEX V5 模拟盘测试:
交易对设置为:LTC_USDT

function main() {
    exchange.IO("simulate", true)   // 切换为OKEX交易所的模拟盘
    exchange.Buy(-1, 1)             // 价格是-1,表示下的订单为市价单,数量为1表示下单量是1USDT
}

由于交易所一般都有下单金额限制,小于限制的订单是不予报单的(例如币安现货要求每单大于5USDT才可以报单成功)。所以这样下单会报错:

错误	Buy(-1, 1): map[code:1 data:[map[clOrdId: ordId: sCode:51020 sMsg:Order amount should be greater than the min available amount. tag:]] msg:]

期货下单时的方向
在做期货策略时下单方向也是萌新们经常搞错导致出问题的,以在发明者量化交易平台上编写策略为例。我们先看下API文档上的描述:https://www.fmz.com/api#exchange.setdirection...

由于下单函数就只有Buy,Sell。然而期货(现货当然没问题了,现货只有买卖)有开多、平多、开空、平空这些方向,那么显然Buy/Sell表示不了这么多个方向的操作,这时就需要引入设置期货交易方向这个函数exchange.SetDirection()
在FMZ上

exchange.SetDirection("buy")(先设置方向)和exchange.Buy配合使用,就表示下的单子是开多仓的订单。
以此类推:
exchange.SetDirection("sell")exchange.Sell配合使用,就表示下的单子是开空仓的订单。
exchange.SetDirection("closebuy")exchange.Sell配合使用,就表示下的单子是平多仓的订单。
exchange.SetDirection("closesell")exchange.Buy配合使用,就表示下的单子是平空仓的订单。
通常萌新会exchange.SetDirection("sell")exchange.Buy配合使用,或者其它的错误组合。然后就报错了(回测可能不报错,但是这个明显是逻辑错误,强迫症不能忍....的)。

另一个萌新经常犯的错误

function main() {
    exchange.SetContractType("quarter")   // 设置当前合约为季度合约
    exchange.SetDirection("sell")
    var id = exchange.Sell(-1, 1)    
    Log("看我市价单下单了,成交了,就有持仓了", exchange.GetPosition())    
    exchange.SetDirection("closebuy")   // closebuy 和Sell 搭配使用,嗯没错~
    exchange.Sell(-1, 1)
}

看到这里会问:“为什么我有持仓并且closebuy和Sell也是搭配使用的,怎么就报错,不能平仓了?”。我会回答:“平错方向了!平的是多头仓位”以上这个报错还可能出现的一种情况是:平仓方向设置正确、下单函数使用也正确、也持有这个方向的持仓,但是还是报这个错误。原因是可能你的程序下了多次单,开始的订单并没成交,平仓单在盘口挂着等待成交,这个时候程序继续去平仓,就会提示超出平仓头寸的错误。

日志输出、交易信息展示
设计编写程序化、量化交易策略就离不开“数据显示”,“操作日志输出”等人机交互的设计。通常使用原生编程语言编写实盘脚本、策略程序。直接使用当前语言的输出函数。
例如:
python用print
javascript用console.log
Golang用fmt.Println()
C++用cout

再来说下FMZ平台上的信息显示,在发明者量化交易平台上,显示信息的地方有两个主要位置。

状态栏
在实盘运行起来之后,实盘页面如图

显示部分为状态栏信息,状态栏主要是为了显示一些实时变动的数据(因为实时变动需要实时观察,又不能每次都打印成日志,所以这类数据可以在状态栏显示,如果每条都打印日志会很多重复无意义的数据,影响查询)。状态栏上显示数据使用LogStatus函数,具体可以参看FMZ的API文档。

日志栏
同样在实盘页面,如图所示:

显示部分为日志栏,日志栏主要是为了永久记录某个时刻某些数据,或者记录某个时候策略的某项操作。日志分为多种类型:

1、普通日志,FMZ的策略中使用Log函数输出、打印在策略日志。

2、下单日志,FMZ的策略中使用exchange.Sell/exchange.Buy会自动在日志输出记录。

3、撤单日志,FMZ的策略中使用exchange.CancelOrder,会自动在日志输出撤单日志。

4、错误日志,FMZ的策略运行时,进行网络请求的接口发生调用错误时,抛出异常时(例如throw之类的语句),会自动在日志中输出错误日志。

FMZ的API函数,可以产生日志输出的函数比如Log(...),exchange.Buy(Price, Amount),exchange.CancelOrder(Id)等都可以在必要参数后跟一些附带输出参数,比如:exchange.CancelOrder(orders[j].Id, orders[j])这样就是在取消orders[j]这个订单时,附带输出这个订单信息。

function main() {
    Log("数据1", "数据2", "数据3", "...")
    var data2 = 200
    var id = exchange.Sell(100000, 0.1, "附带数据1", data2, "...")
    exchange.CancelOrder(id, "附带数据1", data2, "...")
    LogProfit(100, "附带数据1", data2, "...")
}

指标函数的使用
在说指标函数之前,我们先弄懂什么是指标,简单说就是均线、MACD、ATR之类的线。
问:这些指标怎么来的?
答:当然是计算出来的。

问:是依据什么计算的呢?
答:根据K线数据计算。

问:举个例子?
答:以最简单的指标均线指标举例,如果我们使用日K线(就是一根阳线或者阴线代表一天)数据作为指标计算的数据源。均线指标参数为10,那么计算出的均线指标就是10日均线。

问:如果K线BAR数量不够10根,可以算出均线指标么?
答:不仅是均线指标无法算出,任何指标在K线数据BAR数量不满足指标周期参数时都无法算出有效指标值,算出的数组对应位置上就会用空值填充,例如JavaScript语言策略打印算出的指标数据时就会显示null

正好策略广场上有个教学例子:https://www.fmz.com/strategy/125770
回测这个教学例子策略,可以看到回测系统生成的图表以及10周期的均线:

策略自定义画图,画出的K线以及均线图表:

问:如果我要10小时均线呢?
答:K线数据用小时周期的K线数据就可以了。

通俗说,我们看到的K线,我们把它数据化之后就是一个数组(数组概念不了解,可以百度下),其中每个元素就是一个K线柱,是按顺序排列的,数组第一个元素是距离当前时间最远的,数组最后一个元素是距离当前时间最近的。

通常K线数据最后一个线柱是当前周期的线柱,是实时变动的,是未完成的(登录一个交易所的页面观察他的K线就能观察出来变动)。计算出的指标也是和K线柱一一对应的,上面的例子可以看到一个指标数值对应一个K线柱。注意最后一个K线柱是实时变动的,算出的指标也是会跟随K线柱变化而变化的。

在发明者量化交易平台上,可以使用TA库(FMZ平台实现的库,集成在托管者,各种语言都可以直接使用)或者使用talib库(talib老牌指标库,JS、C++集成,Python需要自行安装)。

例如以上例子中计算计算均线:使用TA库:

function main() {
    var records = exchange.GetRecords()
    var ma = TA.MA(records, 10)
    Log(ma)       // 打印均线
}

使用talib库:

function main() {
    var records = exchange.GetRecords()
    var ma = talib.MA(records, 10)
    Log(ma)       // 打印均线
}      

算出的指标数据ma是一个数组,每个元素一一对应K线数组(records),即ma[ma.length -1]对应records[records.length - 1],以此类推。其它再复杂的指标也是同理,需要注意MACD这类指标。

var macd = TA.MACD(records)   // 这样只传入K线数据,不传入指标参数,指标参数采用的就是默认值,其它指标函数也是同理

此时macd这个变量是一个二维数组(不了解概念可以百度),二维数组简单说就是一个数组它的每个元素也是一个数组。

问:为什么macd指标数据是个二维数组呢?
答:因为macd指标是由两条线(dif线,dea线)和一组量柱组成(macd量柱,其实这个量柱数据也可以看成是一条线)。所以macd变量可以拆分为:

var dif = macd[0]
var dea = macd[1]
var macdColumn = macd[2]

这里也有一个现成的教学例子,有兴趣的研究:https://www.fmz.com/strategy/151972