数字货币期货类马丁策略设计

Author: 小小梦, Created: 2021-07-02 14:35:34, Updated: 2023-09-21 21:09:56

img

数字货币期货类马丁策略设计

最近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日的行情。

img

img

可以看到,类马丁策略依然是有一定风险的。

实盘可以用OKEX V5模拟盘跑跑玩一下

img

策略地址:https://www.fmz.com/strategy/294957

策略主要用于学习,真金白银慎用 ~!


Related

More

lisa20231 梦大,想要請問這句 if (!isFindSellId && !isFindBuyId) { // 检测到买卖单都成交了 偵測訂單時,若快速上下插針同時成交了買賣單,那是否會拋出錯誤?

Neo1898 另外就是合约的模式是全仓还是逐仓怎么设定?目前是啥模式呢

Neo1898 既然开的是合约,为啥没有合约倍数的设定呢?买卖都是多少倍的呢

轻轻的云 谢谢梦大,我终于能从头到尾看懂了, 然后学会了挂单监控了,然后写了一个双边的马丁。两天时间,写了580行。。。。。 谢谢梦大[抱拳]

hk量 /upload/asset/1a9ebf427c4e2cbf1c327.png false true 交换下?

梦想身价八位数 如果

梦想身价八位数 所有者权益合计

null 需要止损吗

btcrobot 马丁,回测下了,归0

wqy 开单数量那里没看懂呢= =,那个n永远都等于1

lvdalei 策略是多空双开吗?还是单开的

小小梦 不会抛出错误。依然会取消所有挂单,跳出当前循环,然后继续挂单逻辑。瞬间都成交了就相当于吃到价差利润了。

小小梦 一般要用全仓吧。

小小梦 杠杆可以在交易所具体设置,根据自身风险偏好。

小小梦 云总 666!

小小梦 那这个变量名叫 isFindBuyId 就不合适了吧。 应该叫isNotFindBuyId了。

小小梦 这策略没设计止损。。所以跑出来的曲线可以看到是一路往上的。

小小梦 哈哈,马丁的归宿。 本篇主要是教学策略设计,不用太在意收益。

小小梦 那个N是为了做之后的改动用的,比如想n倍距离加仓,暂时可以定1。

小小梦 单开的。