目前不清退的交易所推荐:
1、全球第二大交易所OKX欧意
国区邀请链接: https://www.myts3cards.com/cn/join/1837888 币种多,交易量大!
国际邀请链接:https://www.okx.com/join/1837888 注册简单,交易不需要实名,新用户能开合约,币种多,交易量大!
2、老牌交易所比特儿现改名叫芝麻开门 :https://www.gate.win/signup/649183
全球最大交易所币安,国区邀请链接:https://accounts.binance.com/zh-CN/register?ref=16003031 币安注册不了IP地址用香港,居住地选香港,认证照旧,邮箱推荐如gmail、outlook。支持币种多,交易安全!
买好币上KuCoin:https://www.kucoin.com/r/af/1f7w3 CoinMarketCap前五的交易所,注册友好操简单快捷!
FMZ量化交易平台邀请链接:https://www.fmz.com/
数字货币期货类马丁策略设计
最近FMZ官方群里讨论马丁类型的策略比较多,平台上关于数字货币合约的马丁策略不多。所以,借此机会设计了一个简单的数字货币期货类马丁策略。为什么说是类马丁策略,因为马丁策略潜在风险确实不小,就没有完全按照马丁策略去设计。但是这类策略依然是有不小的风险的,并且马丁类型的策略参数设置和风险息息相关,对于风险是千万不能忽视的。
本篇文章主要从马丁类型策略的设计上讲解学习,策略思路本身已经很明了,作为FMZ的使用者我们更多考虑策略设计。
获取总权益
在设计数字货币期货策略时,经常要用到总权益这个数据。因为要计算收益,特别是需要计算浮动收益时。由于持仓占用保证金,挂单也占用。这个时候调用FMZ平台的API接口exchange.GetAccount()
获取的是可用资产和挂单冻结资产。其实大部分数字货币期货交易所都提供了总权益这个数据,只不过FMZ上没有统一封装这个属性。
所以我们根据不同的交易所分别设计函数获取这个数据:
// OKEX V5 获取总权益
function getTotalEquity_OKEX_V5() {
var totalEquity = null
var ret = exchange.IO(\"api\", \"GET\", \"/api/v5/account/balance\", \"ccy=USDT\")
if (ret) {
try {
totalEquity = parseFloat(ret.data[0].details[0].eq)
} catch(e) {
Log(\"获取账户总权益失败!\")
return null
}
}
return totalEquity
}
// 币安期货
function getTotalEquity_Binance() {
var totalEquity = null
var ret = exchange.GetAccount()
if (ret) {
try {
totalEquity = parseFloat(ret.Info.totalWalletBalance)
} catch(e) {
Log(\"获取账户总权益失败!\")
return null
}
}
return totalEquity
}
代码中totalEquity
就是我们需要的总权益。然后我们再写个函数作为调用入口,根据交易所名称去具体调用对应的函数。
function getTotalEquity() {
var exName = exchange.GetName()
if (exName == \"Futures_OKCoin\") {
return getTotalEquity_OKEX_V5()
} else if (exName == \"Futures_Binance\") {
return getTotalEquity_Binance()
} else {
throw \"不支持该交易所\"
}
}
设计一些辅助函数
在设计主函数、主要逻辑之前。我们还需要做一些准备,设计一些辅助功能的函数。
- 取消当前所有挂单
function cancelAll() {
while (1) {
var orders = _C(exchange.GetOrders)
if (orders.length == 0) {
break
}
for (var i = 0 ; i < orders.length ; i++) {
exchange.CancelOrder(orders[i].Id, orders[i])
Sleep(500)
}
Sleep(500)
}
}
这个函数相信经常看FMZ策略广场上策略范例代码的都很熟悉,很多策略都用过类似的设计。作用就是获取当前挂单列表,然后逐个撤销。
- 期货的下单操作
function trade(distance, price, amount) { var tradeFunc = null if (distance == \"buy\") { tradeFunc = exchange.Buy } else if (distance == \"sell\") { tradeFunc = exchange.Sell } else if (distance == \"closebuy\") { tradeFunc = exchange.Sell } else { tradeFunc = exchange.Buy } exchange.SetDirection(distance) return tradeFunc(price, amount)}function openLong(price, amount) { return trade(\"buy\", price, amount)}function openShort(price, amount) { return trade(\"sell\", price, amount)}function coverLong(price, amount) { return trade(\"closebuy\", price, amount)}function coverShort(price, amount) { return trade(\"closesell\", price, amount)}
期货交易有四个方向:开多仓(openLong)、开空仓(openShort)、平多仓(coverLong)、平空仓(coverShort)。所以我们设计了四个下单函数对应这些操作。如果只考虑下单,那么有这样几个必要的因素:方向、下单价格、下单量。
所以我们还设计了一个名为:trade
的函数来处理当方向(distance)
、下单价格(price)
、下单量(amount)
都明确时的操作。
开多仓(openLong)、开空仓(openShort)、平多仓(coverLong)、平空仓(coverShort)这些函数调用最终都由trade
函数完成实际功能,也就是根据既定的方向、价格、数量在期货交易所下单。
主函数
策略思路很简单,以当前价格为基线上下一定距离挂卖出(做空)、买入单(做多)。一边成交了就取消剩下的所有订单,然后根据持仓的价格在一定距离挂出新的平仓订单,在更新后的当前价格挂出加仓订单,但是加仓订单不加倍下单量。
- 初始工作
因为要挂单,所以我们需要两个全局变量记录订单ID。
var buyOrderId = null
var sellOrderId = null
然后策略界面参数里设计了使用OKEX_V5模拟盘的选项,所以代码里要做一些处理:
var exName = exchange.GetName()
// 切换OKEX V5模拟盘
if (isSimulate && exName == \"Futures_OKCoin\") {
exchange.IO(\"simulate\", true)
}
界面参数里还设计了重置所有信息的选项,所以代码里也要有对应的处理:
if (isReset) {
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log(\"重置所有数据\", \"#FF0000\")
}
我们只跑永续合约,所以这里写死了,只设置为永续合约。
exchange.SetContractType(\"swap\")
然后我们还要考虑到下单价格精度、下单量精度的问题,如果精度不设置好,策略计算过程中精度丢失,数据的小数位很多的话容易引起下单时被交易所接口拒绝。
exchange.SetPrecision(pricePrecision, amountPrecision)
Log(\"设置精度\", pricePrecision, amountPrecision)
设计上简单的数据恢复功能
if (totalEq == -1 && !IsVirtual()) {
var recoverTotalEq = _G(\"totalEq\")
if (!recoverTotalEq) {
var currTotalEq = getTotalEquity()
if (currTotalEq) {
totalEq = currTotalEq
_G(\"totalEq\", currTotalEq)
} else {
throw \"获取初始权益失败\"
}
} else {
totalEq = recoverTotalEq
}
}
如果想在策略运行时指定最初账户总权益,可以设置参数totalEq
,如果该参数设置为-1,策略会读取储存的总权益数据,如果没有储存的总权益数据,就是以当前读取的总权益作为策略运行进度的最初总权益,之后总权益增加就说明赚了,总权益少了就说明亏了。如果读取到总权益数据,则使用这个数据继续运行。
- 主要逻辑
初始工作做完之后,终于来到了策略主要逻辑的部分了,为了方便讲解,我直接把说明写在代码注释上了。
while (1) { // 策略主要逻辑设计为一个死循环
var ticker = _C(exchange.GetTicker) // 首先读取当前行情信息,主要用到最新成交价
var pos = _C(exchange.GetPosition) // 读取当前持仓数据
if (pos.length > 1) { // 判断持仓数据,由于这个策略的逻辑,是不太可能同时出现多空持仓的,所以发现同时出现多空持仓就抛出错误
Log(pos)
throw \"同时有多空持仓\" // 抛出错误,让策略停止
}
// 根据状态而定
if (pos.length == 0) { // 根据持仓状态做出不同操作,pos.length == 0是当没有持仓时
// 未持仓了,统计一次收益
if (!IsVirtual()) {
var currTotalEq = getTotalEquity()
if (currTotalEq) {
LogProfit(currTotalEq - totalEq, \"当前总权益:\", currTotalEq)
}
}
buyOrderId = openLong(ticker.Last - targetProfit, amount) // 挂开多仓的买单
sellOrderId = openShort(ticker.Last + targetProfit, amount) // 挂开空仓的卖单
} else if (pos[0].Type == PD_LONG) { // 有多头持仓,挂单位置、数量有所不同
var n = 1
var price = ticker.Last
buyOrderId = openLong(price - targetProfit * n, amount)
sellOrderId = coverLong(pos[0].Price + targetProfit, pos[0].Amount)
} else if (pos[0].Type == PD_SHORT) { // 有空头持仓,挂单位置、数量有所不同
var n = 1
var price = ticker.Last
buyOrderId = coverShort(pos[0].Price - targetProfit, pos[0].Amount)
sellOrderId = openShort(price + targetProfit * n, amount)
}
if (!sellOrderId || !buyOrderId) { // 如果有一边挂单失败就取消所有挂单,重来
cancelAll()
buyOrderId = null
sellOrderId = null
continue
}
while (1) { // 挂单完成,开始监控订单
var isFindBuyId = false
var isFindSellId = false
var orders = _C(exchange.GetOrders)
for (var i = 0 ; i < orders.length ; i++) {
if (buyOrderId == orders[i].Id) {
isFindBuyId = true
}
if (sellOrderId == orders[i].Id) {
isFindSellId = true
}
}
if (!isFindSellId && !isFindBuyId) { // 检测到买卖单都成交了
cancelAll()
break
} else if (!isFindBuyId) { // 检测到买单成交
Log(\"买单成交\")
cancelAll()
break
} else if (!isFindSellId) { // 检测到卖单成交
Log(\"卖单成交\")
cancelAll()
break
}
LogStatus(_D())
Sleep(3000)
}
Sleep(500)
}
整个逻辑和设计就讲解完了。
回测
让策略经历一次5月19日的行情。
可以看到,类马丁策略依然是有一定风险的。
实盘可以用OKEX V5模拟盘跑跑玩一下
策略地址:https://www.fmz.com/strategy/294957
策略主要用于学习,真金白银慎用 ~!
FMZ量化交易平台邀请链接:https://www.fmz.com/
全球最大交易所币安,国区邀请链接:https://accounts.binance.com/zh-CN/register?ref=16003031 币安注册不了IP地址用香港,居住地选香港,认证照旧,邮箱推荐如gmail、outlook。支持币种多,交易安全!
买好币上KuCoin:https://www.kucoin.com/r/af/1f7w3 CoinMarketCap前五的交易所,注册友好操简单快捷!
目前不清退的交易所推荐:
1、全球第二大交易所OKX欧意,邀请链接:https://www.myts3cards.com/cn/join/1837888 注册简单,交易不需要实名,新用户能开合约,币种多,交易量大!。
2、老牌交易所比特儿现改名叫芝麻开门 :https://www.gate.win/signup/649183
买好币上币库:https://www.kucoin.com/r/1f7w3
火必所有用户现在可用了,但是要重新注册账号火币:https://www.huobi.com
全球最大交易所币安,
国区邀请链接:https://accounts.suitechsui.mobi/zh-CN/register?ref=16003031 支持86手机号码,网页直接注册。
exchange.GetAccount()
获取的是可用资产和挂单冻结资产。其实大部分数字货币期货交易所都提供了总权益这个数据,只不过FMZ上没有统一封装这个属性。// OKEX V5 获取总权益
function getTotalEquity_OKEX_V5() {
var totalEquity = null
var ret = exchange.IO(\"api\", \"GET\", \"/api/v5/account/balance\", \"ccy=USDT\")
if (ret) {
try {
totalEquity = parseFloat(ret.data[0].details[0].eq)
} catch(e) {
Log(\"获取账户总权益失败!\")
return null
}
}
return totalEquity
}
// 币安期货
function getTotalEquity_Binance() {
var totalEquity = null
var ret = exchange.GetAccount()
if (ret) {
try {
totalEquity = parseFloat(ret.Info.totalWalletBalance)
} catch(e) {
Log(\"获取账户总权益失败!\")
return null
}
}
return totalEquity
}
totalEquity
就是我们需要的总权益。然后我们再写个函数作为调用入口,根据交易所名称去具体调用对应的函数。function getTotalEquity() {
var exName = exchange.GetName()
if (exName == \"Futures_OKCoin\") {
return getTotalEquity_OKEX_V5()
} else if (exName == \"Futures_Binance\") {
return getTotalEquity_Binance()
} else {
throw \"不支持该交易所\"
}
}
function cancelAll() {
while (1) {
var orders = _C(exchange.GetOrders)
if (orders.length == 0) {
break
}
for (var i = 0 ; i < orders.length ; i++) {
exchange.CancelOrder(orders[i].Id, orders[i])
Sleep(500)
}
Sleep(500)
}
}
这个函数相信经常看FMZ策略广场上策略范例代码的都很熟悉,很多策略都用过类似的设计。作用就是获取当前挂单列表,然后逐个撤销。
function trade(distance, price, amount) { var tradeFunc = null if (distance == \"buy\") { tradeFunc = exchange.Buy } else if (distance == \"sell\") { tradeFunc = exchange.Sell } else if (distance == \"closebuy\") { tradeFunc = exchange.Sell } else { tradeFunc = exchange.Buy } exchange.SetDirection(distance) return tradeFunc(price, amount)}function openLong(price, amount) { return trade(\"buy\", price, amount)}function openShort(price, amount) { return trade(\"sell\", price, amount)}function coverLong(price, amount) { return trade(\"closebuy\", price, amount)}function coverShort(price, amount) { return trade(\"closesell\", price, amount)}
期货交易有四个方向:开多仓(openLong)、开空仓(openShort)、平多仓(coverLong)、平空仓(coverShort)。所以我们设计了四个下单函数对应这些操作。如果只考虑下单,那么有这样几个必要的因素:方向、下单价格、下单量。
所以我们还设计了一个名为:trade
的函数来处理当方向(distance)
、下单价格(price)
、下单量(amount)
都明确时的操作。
开多仓(openLong)、开空仓(openShort)、平多仓(coverLong)、平空仓(coverShort)这些函数调用最终都由trade
函数完成实际功能,也就是根据既定的方向、价格、数量在期货交易所下单。
因为要挂单,所以我们需要两个全局变量记录订单ID。
var buyOrderId = null
var sellOrderId = null
然后策略界面参数里设计了使用OKEX_V5模拟盘的选项,所以代码里要做一些处理:
var exName = exchange.GetName()
// 切换OKEX V5模拟盘
if (isSimulate && exName == \"Futures_OKCoin\") {
exchange.IO(\"simulate\", true)
}
界面参数里还设计了重置所有信息的选项,所以代码里也要有对应的处理:
if (isReset) {
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log(\"重置所有数据\", \"#FF0000\")
}
我们只跑永续合约,所以这里写死了,只设置为永续合约。
exchange.SetContractType(\"swap\")
然后我们还要考虑到下单价格精度、下单量精度的问题,如果精度不设置好,策略计算过程中精度丢失,数据的小数位很多的话容易引起下单时被交易所接口拒绝。
exchange.SetPrecision(pricePrecision, amountPrecision)
Log(\"设置精度\", pricePrecision, amountPrecision)
设计上简单的数据恢复功能
if (totalEq == -1 && !IsVirtual()) {
var recoverTotalEq = _G(\"totalEq\")
if (!recoverTotalEq) {
var currTotalEq = getTotalEquity()
if (currTotalEq) {
totalEq = currTotalEq
_G(\"totalEq\", currTotalEq)
} else {
throw \"获取初始权益失败\"
}
} else {
totalEq = recoverTotalEq
}
}
如果想在策略运行时指定最初账户总权益,可以设置参数totalEq
,如果该参数设置为-1,策略会读取储存的总权益数据,如果没有储存的总权益数据,就是以当前读取的总权益作为策略运行进度的最初总权益,之后总权益增加就说明赚了,总权益少了就说明亏了。如果读取到总权益数据,则使用这个数据继续运行。
初始工作做完之后,终于来到了策略主要逻辑的部分了,为了方便讲解,我直接把说明写在代码注释上了。
while (1) { // 策略主要逻辑设计为一个死循环
var ticker = _C(exchange.GetTicker) // 首先读取当前行情信息,主要用到最新成交价
var pos = _C(exchange.GetPosition) // 读取当前持仓数据
if (pos.length > 1) { // 判断持仓数据,由于这个策略的逻辑,是不太可能同时出现多空持仓的,所以发现同时出现多空持仓就抛出错误
Log(pos)
throw \"同时有多空持仓\" // 抛出错误,让策略停止
}
// 根据状态而定
if (pos.length == 0) { // 根据持仓状态做出不同操作,pos.length == 0是当没有持仓时
// 未持仓了,统计一次收益
if (!IsVirtual()) {
var currTotalEq = getTotalEquity()
if (currTotalEq) {
LogProfit(currTotalEq - totalEq, \"当前总权益:\", currTotalEq)
}
}
buyOrderId = openLong(ticker.Last - targetProfit, amount) // 挂开多仓的买单
sellOrderId = openShort(ticker.Last + targetProfit, amount) // 挂开空仓的卖单
} else if (pos[0].Type == PD_LONG) { // 有多头持仓,挂单位置、数量有所不同
var n = 1
var price = ticker.Last
buyOrderId = openLong(price - targetProfit * n, amount)
sellOrderId = coverLong(pos[0].Price + targetProfit, pos[0].Amount)
} else if (pos[0].Type == PD_SHORT) { // 有空头持仓,挂单位置、数量有所不同
var n = 1
var price = ticker.Last
buyOrderId = coverShort(pos[0].Price - targetProfit, pos[0].Amount)
sellOrderId = openShort(price + targetProfit * n, amount)
}
if (!sellOrderId || !buyOrderId) { // 如果有一边挂单失败就取消所有挂单,重来
cancelAll()
buyOrderId = null
sellOrderId = null
continue
}
while (1) { // 挂单完成,开始监控订单
var isFindBuyId = false
var isFindSellId = false
var orders = _C(exchange.GetOrders)
for (var i = 0 ; i < orders.length ; i++) {
if (buyOrderId == orders[i].Id) {
isFindBuyId = true
}
if (sellOrderId == orders[i].Id) {
isFindSellId = true
}
}
if (!isFindSellId && !isFindBuyId) { // 检测到买卖单都成交了
cancelAll()
break
} else if (!isFindBuyId) { // 检测到买单成交
Log(\"买单成交\")
cancelAll()
break
} else if (!isFindSellId) { // 检测到卖单成交
Log(\"卖单成交\")
cancelAll()
break
}
LogStatus(_D())
Sleep(3000)
}
Sleep(500)
}
买好币上KuCoin:https://www.kucoin.com/r/af/1f7w3 CoinMarketCap前五的交易所,注册友好操简单快捷!
目前不清退的交易所推荐:
1、全球第二大交易所OKX欧意,邀请链接:https://www.myts3cards.com/cn/join/1837888 注册简单,交易不需要实名,新用户能开合约,币种多,交易量大!。
2、老牌交易所比特儿现改名叫芝麻开门 :https://www.gate.win/signup/649183
买好币上币库:https://www.kucoin.com/r/1f7w3