Recently, there are many Martingale strategies discussed in the FMZ official group, and there are not many Martingale strategies of cryptocurrency contracts on the platform. Therefore, I took this opportunity to design a simple Martingale strategy for cryptocurrency futures. Why is it called a Martingale strategy? Because the potential risks of the Martin strategy are indeed not small, it is not designed exactly according to the Martin strategy. However, this type of strategy still has a lot of risks, and the parameter settings of the Martin-type strategy are closely related to the risk, and the risk should not be ignored.
This article mainly explains and learns from the design of Martin-type strategies. Since the strategy idea is very clear, as a user of FMZ, we consider strategy design more.
Obtain total equity
Total equity is often used when designing cryptocurrency futures strategies. This is because returns have to be calculated, especially when you need to calculate floating returns. Since the position is occupied with margin, the pending order is also occupied. At this time, the API interface exchange.GetAccount()
of the FMZ platform is called to obtain the available assets and pending order frozen assets. In fact, most cryptocurrency futures exchanges provide the data of total equity, but this attribute is not uniformly packaged on FMZ.
So we design functions to obtain this data according to different exchanges:
// OKEX V5 obtain total equity 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("failed to obtain the total equity of the account!") return null } } return totalEquity } // Binance futures function getTotalEquity_Binance() { var totalEquity = null var ret = exchange.GetAccount() if (ret) { try { totalEquity = parseFloat(ret.Info.totalWalletBalance) } catch(e) { Log("failed to obtain the total equity of the account!") return null } } return totalEquity }
The totalEquity
in the code is the total equity we need. Then we write a function as the call entry, and call the corresponding function according to the name of the exchange.
function getTotalEquity() { var exName = exchange.GetName() if (exName == "Futures_OKCoin") { return getTotalEquity_OKEX_V5() } else if (exName == "Futures_Binance") { return getTotalEquity_Binance() } else { throw "This exchange is not supported" } }
Design some auxiliary functions
Before designing the main function and main logic, we need to do some preparations and design some auxiliary functions.
- Cancel all current pending orders
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) } }
This function is familiar to those who often read the strategy example code on the FMZ strategy square, and many strategies have used similar designs. The function is to get the current pending order list, and then cancel them one by one.
- Placement operations for futures
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) }
There are four directions for futures trading: openLong, openShort, coverLong andcoverShort. So we designed four order functions corresponding to these operations. If you consider only the order, then there are several necessary factors: direction, order price and order volume.
So we also designed a function named: trade
to handle the operation when distance
, price
, amount
are specified.
The function calls to openLong, openShort, coverLong and coverShort are ultimately completed by the trade
function, that is, placing an order on a futures exchange based on the established distance, price, and quantity.
Main function
The strategy idea is very simple, take the current price as the baseline, and place sell (short) and buy orders (long) at a certain distance up or down. Once the transaction is completed, all remaining orders will be cancelled, and then a new closing order will be placed at a certain distance according to the price of the position, and an increase order will be placed at the updated current price, but the order volume will not be doubled for additional positions.
- Initial work
Because of the pending order, we need two global variables to record the order ID.
var buyOrderId = null var sellOrderId = null
Then the strategy interface parameters are designed to use the OKEX_V5 simulated bot option, so some processing needs to be done in the code:
var exName = exchange.GetName() // Switch OKEX V5 simulated bot if (isSimulate && exName == "Futures_OKCoin") { exchange.IO("simulate", true) }
There is also an option to reset all information in the interface parameters, so there should be corresponding processing in the code:
if (isReset) { _G(null) LogReset(1) LogProfitReset() LogVacuum() Log("reset all data", "#FF0000") }
We only run perpetual contracts, so the writing is fixed here and set to perpetual only.
exchange.SetContractType("swap")
Then we also need to consider the accuracy of the order price and the order amount. If the accuracy is not set properly, the accuracy will be lost during the strategy calculation process. If the data has a large number of decimal places, it is easy to cause the order to be rejected by the exchange interface.
exchange.SetPrecision(pricePrecision, amountPrecision) Log("set precision", pricePrecision, amountPrecision)
Simple data recovery by design
if (totalEq == -1 && !IsVirtual()) { var recoverTotalEq = _G("totalEq") if (!recoverTotalEq) { var currTotalEq = getTotalEquity() if (currTotalEq) { totalEq = currTotalEq _G("totalEq", currTotalEq) } else { throw "failed to obtain initial equity" } } else { totalEq = recoverTotalEq } }
If you want to specify the initial total equity of the account when the strategy is running, you can set the parameter totalEq
. If this parameter is set to -1, the strategy will read the stored total equity data. If there is no stored total equity data, the current read total equity is used as the initial total equity of the strategy running progress. After that, an increase in total equity indicates a profit, and a decrease in total equity indicates a loss. If the total equity data is read, the strategy will continue to run with this data.
- main logic
After the initial work is done, finally we came to the main logic part of the strategy. For the convenience of explanation, I wrote the instructions directly on the code comments.
while (1) { // The main logic of the strategy is designed as an infinite loop var ticker = _C(exchange.GetTicker) // Read the current market information first, mainly using the latest transaction price var pos = _C(exchange.GetPosition) // Read current position data if (pos.length > 1) { // Judging the position data, because of the logic of this strategy, it is unlikely that long and short positions will appear at the same time, so if there are long and short positions at the same time, an error will be thrown Log(pos) throw "Simultaneous long and short positions" // Throw an error to stop the strategy } //Depends on status if (pos.length == 0) { // Make different operations according to the position status, when there is no position, pos.length == 0 // If you have not held a position, count the profit once if (!IsVirtual()) { var currTotalEq = getTotalEquity() if (currTotalEq) { LogProfit(currTotalEq - totalEq, "current total equity:", currTotalEq) } } buyOrderId = openLong(ticker.Last - targetProfit, amount) // Open a buy order for a long position sellOrderId = openShort(ticker.Last + targetProfit, amount) // Open a short sell order } else if (pos[0].Type == PD_LONG) { // For long positions, the position and quantity of pending orders are different 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) { // For short positions, the position and quantity of pending orders are different 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) { // If one side of the pending order fails, cancel all pending orders and start over cancelAll() buyOrderId = null sellOrderId = null continue } while (1) { // The pending order is completed, start monitoring the order 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) { // Detected that both buy and sell orders have been filled cancelAll() break } else if (!isFindBuyId) { // Detected buy order closing Log("buy order closing") cancelAll() break } else if (!isFindSellId) { // Detected sell order closing Log("sell order closing") cancelAll() break } LogStatus(_D()) Sleep(3000) } Sleep(500) }
The whole logic and design are explained.
Backtesting
Let the strategy go through a May 19 market.
It can be seen that the Martingale strategy still has certain risks.
The real bot can be run with the OKEX V5 simulation bot
Strategy address: https://www.fmz.com/strategy/294957
Strategies are mainly used for learning, and real money should be used with caution~!