本文旨在描述一些策略开发的经验,以及一些技巧,让读者快速掌握交易策略开发的重点。
当您在一些策略设计中遇到类似的细节时,您可以立即想出一个合理的解决方案。
我们以 FMZ Quant 平台为例进行说明、测试和实践。
我们将使用 JavaScript 的策略编程语言
对于交易对象,我们以区块链资产市场(BTC、ETH等)为对象
通常,根据策略逻辑的不同,可能会使用以下不同的接口来获取行情数据,大部分策略逻辑都是由行情数据驱动的(当然,有些策略是不关心价格数据的,比如固定投资策略)。
在设计策略时,初学者通常会忽略各种错误,直观地认为策略中每个部分的结果都是成立的。但事实并非如此,在策略程序的运行中,在请求行情数据时,会遇到各种意想不到的情况。
例如,一些市场接口返回未执行的数据:
var depth = exchange.GetDepth()
// depth.Asks[0].Price < depth.Bids[0].Price \"Selling 1\" price is lower than \"buying 1\" price, this situation cannot exist on the market.
// Because the selling price is lower than the buying price, the order must have been executed.
// depth.Bids[n].Amount = 0 Order book buying list \"nth\" layer, order quantity is 0
// depth.Asks[m].Price = 0 Order book selling list \"mth\" layer, the order price is 0
或者直接 exchange.GetDepth() 返回一个空值。
这样奇怪的情况还有很多。因此,有必要处理这些可预见的问题。这样的处理方案称为容错处理。
处理故障的正常方法是丢弃数据并重新获取它。
例如:
function main () {
while (true) {
onTick()
Sleep(500)
}
}
function GetTicker () {
while (true) {
var ticker = exchange.GetTicker()
if (ticker.Sell > ticker.Buy) { // Take the example of fault-tolerant processing that detects whether the \"Selling 1\" price is less than the \"Buying 1\" price.
// Exclude this error, the current function returns \"ticker\".
Return ticker
}
Sleep(500)
}
}
function onTick () {
var ticker = GetTicker() // Make sure the \"ticker\" you get doesn\'t exist the situation that \"Selling 1\" price is less than the \"Buying 1\" price.
// ... specific strategy logic
}
类似的方法可以用于其他可预见的容错过程。
设计原则是永远不能使用错误的逻辑来驱动策略逻辑。
K线数据采集,调用:
var r = exchange.GetRecords()
得到的K线数据是一个数组,比如这样:
[
{\"Time\":1562068800000,\"Open\":10000.7,\"High\":10208.9,\"Low\":9942.4,\"Close\":10058.8,\"Volume\":6281.887000000001},
{\"Time\":1562072400000,\"Open\":10058.6,\"High\":10154.4,\"Low\":9914.5,\"Close\":9990.7,\"Volume\":4322.099},
...
{\"Time\":1562079600000,\"Open\":10535.1,\"High\":10654.6,\"Low\":10383.6,\"Close\":10630.7,\"Volume\":5163.484000000004}
]
可以看到每个大括号{}包含时间、开盘价、最高价、最低价、收盘价和成交量。
这是一个K线吧。一般K线数据用于计算移动平均线、MACD等指标。
将K线数据作为参数(原材料数据)传递,然后设置指标参数来计算指标数据的函数,我们称之为指标函数。
FMZ Quant量化交易平台上有很多指标功能。
例如,我们计算移动平均线指标。根据传递的K线数据的周期,我们计算相应周期的移动平均线。
比如传K线数据(一根K线柱代表一天),计算日均线,同理,如果传均指标函数的K线数据是1小时周期,那么计算指标为 1 小时移动平均线。
通常我们在计算指标时经常会忽略一个问题。如果我要计算5日均线指标,那么我们首先准备好日K线数据:
var r = exchange.GetRecords(PERIOD_D1) // Pass parameters to the \"GetRecords\" function \"PERIOD_D1\" specifies the day K line to be acquired.
// Specific function using method can be seen at: https://www.fmz.com/api#GetRecords
有了每日K线数据,我们就可以计算出均线指标。如果我们要计算 5 日均线,那么我们必须将指标函数的指标参数设置为 5。
var ma = TA.MA(r, 5) // \"TA.MA()\" is the indicator function used to calculate the moving average indicator. The first parameter sets the daily K-line data r just obtained.
// The second parameter is set to 5. The calculated 5-day moving average is the same as the other indicators.
我们忽略了一个潜在的问题。如果 K 线数据中的 K 线柱数少于 5 条,如何计算出有效的 5 日均线?
答案是你无能为力。
因为移动平均线指标是一定数量K线柱的收盘价的平均值。
因此,在使用K线数据和指标函数计算指标数据之前,需要确定K线数据中K线柱的数量是否满足指标计算的条件(指标参数)。
所以在计算 5 日移动平均线之前,你必须先检查一下。完整代码如下:
function CalcMA () {
var r = _C(exchange.GetRecords, PERIOD_D1) // _C() is a fault-tolerant function, the purpose is to avoid r being null, you can get more information at: https://www.fmz.com/api#_C
if (r.length > 5) {
Return TA.MA(r, 5) // Calculate the moving average data with the moving average indicator function \"TA.MA\", return it as a function return value.
}
Return false
}
function main () {
var ma = CalcMA()
Log(ma)
}
回测展示:
[null,null,null,null,4228.7,4402.9400000000005, ... ]
您可以看到计算出的 5 天移动平均线指标。前四个为空,因为K线条数少于5,无法计算平均值。当你到达第5个K线柱时,你可以计算它。
我们在写策略的时候,经常会有这样的场景,比如策略需要在每个K线周期完成的时候处理一些操作,或者打印一些日志。
我们如何实现这些功能?对于没有编程经验的初学者来说,可能是个比较麻烦的问题。在这里,我们为您提供解决方案。
如何判断一个K线柱周期就完成了。我们可以从K线数据中的时间属性入手。每次拿到K线数据,我们都会判断这个K线数据的最后一个K线柱的时间属性是否发生变化。如果变了,说明有新的K线柱生成(证明新生成的K线柱的上一个K线柱周期已经完成),如果没有变化,说明没有新的K线柱-line bar 生成(当前最后一个 K 线 bar 周期尚未完成)。
所以我们需要一个变量来记录K线数据的最后一个K线柱的时间。
var r = exchange.GetRecords()
var lastTime = r[r.length - 1].Time // \"lastTime\" used to record the last K-line bar time.
在实践中,通常是这样的:
function main () {
var lastTime = 0
while (true) {
var r = _C(exchange.GetRecords)
if (r[r.length - 1].Time != lastTime) {
Log (\"New K-line bar generated\")
lastTime = r[r.length - 1].Time // Be sure to update \"lastTime\", this is crucial.
// ... other processing logic
// ...
}
Sleep(500)
}
}
可以看到在回测中,K线周期设置为每天(exchange.GetRecords
函数调用时未指定参数,根据回测设置的K线周期为默认参数)。每当新的 K 线柱出现时,它都会打印一个日志。
如果您想对策略访问交易所界面所需的时间进行一定的显示或控制,您可以使用以下代码:
function main () {
while (true) {
var beginTime = new Date().getTime()
var ticker = exchange.GetTicker()
var endTime = new Date().getTime()
LogStatus(_D(), \"GetTicker() function time-consuming:\", endTime - beginTime, \"millisecond\")
Sleep(1000)
}
}
简单来说就是调用GetTicker
函数后记录的时间戳减去调用前的时间戳,计算出所经历的毫秒数,也就是GetTicker
函数从执行到返回所用的时间。
例如,在下达卖单的过程中,卖单的金额不得大于账户中的币数。
因为如果它大于账户中可用的硬币数量,订单将导致错误。
我们这样控制它:
例如,我们计划卖空 0.2 个硬币。
var planAmount = 0.2
var account = _C(exchange.GetAccount)
var amount = Math.min(account.Stocks, planAmount)
这样可以确保下达的订单数量不会超过账户中可用的硬币数量。
同理,Math.max
用于保证某个值的下限。
一般情况下,正常交易所对某些交易对有最低发送订单限制。如果低于最低金额,订单将被拒绝。这也会导致程序失败。
假设BTC通常最小下单数量为0.01。
交易策略有时会导致小于 0.01 的订单数量,因此我们可以使用Math.max
来确保最小订单数量。
可以使用_N()
函数或SetPrecision
函数来控制精度。
该SetPrecision()
功能只需设置一次,订单数量和价格值的小数位数在系统中自动截断。
功能是对_N()
某个值进行小数点截断(精度控制)。
例如:
var pi = _N(3.141592653, 2)
Log(pi)
pi的值被小数位截去,保留2位小数,即:3.14
有关详细信息,请参阅 API 文档。
可以利用这样的机制,利用时间戳检测的方法,确定当前时间戳减去上一次执行定时任务的时间戳,实时计算经过的时间。当经过的时间超过某个设置的时间长度时。之后,执行新的操作。
例如,用于固定投资策略。
var lastActTime = 0
var waitTime = 1000 * 60 * 60 * 12 // number of milliseconds a day
function main () {
while (true) {
var nowTime = new Date().getTime()
if (nowTime - lastActTime > waitTime) {
Log (\"Execution Fixed\")
// ... specific fixed investment operation, buying operation.
lastActTime = nowTime
}
Sleep(500)
}
}
这是一个简单的例子。
使用FMZ量化_G()
功能,退出保存功能,方便设计退出保存进度,重启自动恢复状态的策略。
var hold = {
Price : 0,
Amount : 0,
}
function main () {
if (_G(\"hold\")) {
var ret = _G(\"hold\")
hold.price = ret.price
hold.amount = ret.amount
Log(\"restore hold:\", hold)
}
var count = 1
while (true) {
// ... strategy logic
// ... In the strategy operation, it is possible that when opening a position, then assign the position price of the open position to \"hold.price\", and the amount of open positions is assigned to \"hold.amount\" to record the position information.
hold.price = count++ // simulate some values
hold.amount = count/10 // Simulate some values
Sleep(500)
}
}
function onexit () { // Click the stop button on the robot to trigger the execution of this function. After the execution, the robot stops.
_G(\"hold\", hold)
Log(\"save hold:\", JSON.stringify(hold))
}
可以看出,hold
每次机器人停止时都会保存对象中的数据。每次重新启动数据时,读取数据并将其值hold
恢复到停止前的状态。
当然,以上只是一个简单的例子。如果在实际交易策略中使用,应根据策略中需要恢复的关键数据(一般为账户信息、持仓、盈利值、交易方向等)进行设计。
此外,您还可以设置一些其他条件来恢复。
这些是制定交易策略的一些技巧,希望对初学者有所帮助!
动手实践训练是提高自己最快的方法!祝大家好运。
买好币上币库:https://www.kucoin.com/r/1f7w3