上期文章我们讲到了程序化交易脚本。其实交易策略就是一个交易脚本程序,文章主要讲到了交易脚本程序需要有硬件载体(程序在哪里运行),这个脚本交易程序可以用那种计算机编程语言编写(列举了在发明者量化交易平台上使用的三种编程语言,当然本身做程序化交易可以使用任何编程语言实现策略)。本期文章我们继续讨论币圈量化、了解币圈量化的知识。
以上是从交易策略角度去划分的,在发明者量化交易平台上从策略设计角度去看,策略还可以分为:
API KEY
(忘记API KEY是什么的可以翻下上篇文章)。这类接口一般是行情接口,例如查询深度行情、查询K线数据、查询资金费率、查询交易品种相关信息、查询交易所服务器时间戳等。那么在发明者量化交易平台上这些接口是如何使用的呢?
发明者量化交易平台封装了交易所行为、定义一致的接口(例如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函数只是修改一些本地设置,例如设置当前交易对、设置合约代码、指标计算函数、获取交易所对象名称等。
基本上从函数的用途可以判断出来是否发生网络请求,只要是获取交易所数据、对交易所账户操作之类的都是产生网络请求的,这些接口都需要注意调用频率。
编写策略时对于接口返回的数据我们是都需要判断验证的,例如在发明者量化交易平台上获取行情这行代码(自己写程序直接访问交易所接口也是一样):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文档都会这么写。
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:]
由于下单函数就只有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也是搭配使用的,怎么就报错,不能平仓了?”。我会回答:“平错方向了!平的是多头仓位”
以上这个报错还可能出现的一种情况是:平仓方向设置正确、下单函数使用也正确、也持有这个方向的持仓,但是还是报这个错误。
原因是可能你的程序下了多次单,开始的订单并没成交,平仓单在盘口挂着等待成交,这个时候程序继续去平仓,就会提示超出平仓头寸的错误。
print
。console.log
。fmt.Println()
。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, \"...\")
}
JavaScript
语言策略打印算出的指标数据时就会显示null
。正好策略广场上有个教学例子:https://www.fmz.com/strategy/125770策略自定义画图,画出的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
买好币上币库:https://www.kucoin.com/r/1f7w3