Example of dYdX strategy design
Tags: dydx, strategy, fmzquant, trading
Published: 2024-04-29 05:22:46

In response to the demand of many users, the FMZ platform has accessed dYdX recently, a decentralized exchange. Someone who have strategies can enjoy the process of acquiring digital currency dYdX. I just wanted to write a stochastic trading strategy for a long time ago, it doesn't matter whether it make profits. So next we come together to design a stochastic exchange strategy, no matter the strategy performs good or not, we just learn the strategy design.

### Stochastic trading strategy design

Let's have a brainstorm! It is planned to design a strategy of placing orders randomly with random indicators and prices. Placing orders is nothing more than going long or going short, just betting on the probability. Then we will use the random number 1~100 to determine whether to go long or go short.

Condition for going long : random number 1~50.
Condition for going short : random number 51~100.

Thus both going long and going short are 50 numbers. Next, let's think about how to close the position, since it is a bet, then there must be a criteria for win or lose. We set a criteria for fixed stop profit and loss in the transaction. Stop profit for the winning, stop loss for the losing. As for the amount of stop profit and loss, it is actually the impact of the profit and loss ratio, oh yes! It also affect the win rate! (Is this strategy design effective? Can it be guaranteed as a positive mathematical expectations? Do it first! (After, it is just for learning, research!)

Trading is not cost-free, there is enough slippage, fees, etc. to pull our stochastic trading win rate towards the side of less than 50%. So how to design it continuely?
How about designing a multiplier to increase the position? Since it is a bet, then the probability of losing for 8~10 times in a row of random trades should be low. So the first transaction was designed to place a small amount of orders, as small as possible. Then if I lose, I will increase the amount of orders and continue to place orders randomly.

OK, the strategy is designed simply.

Source code designed:

```
var openPrice = 0 
var ratio = 1
var totalEq = null 
var nowEq = null 

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)
    }
}

function main() {
    if (isReset) {
        _G(null)
        LogReset(1)
        LogProfitReset()
        LogVacuum()
        Log("reset all data", "#FF0000")
    }

    exchange.SetContractType(ct)

    var initPos = _C(exchange.GetPosition)
    if (initPos.length != 0) {
        throw "Strategy starts with a position!"
    }
    
    exchange.SetPrecision(pricePrecision, amountPrecision)
    Log("set the pricePrecision", pricePrecision, amountPrecision)
    
    if (!IsVirtual()) {
        var recoverTotalEq = _G("totalEq")
        if (!recoverTotalEq) {
            var currTotalEq = _C(exchange.GetAccount).Balance   // equity
            if (currTotalEq) {
                totalEq = currTotalEq
                _G("totalEq", currTotalEq)
            } else {
                throw "failed to obtain initial interest"
            }
        } else {
            totalEq = recoverTotalEq
        }
    } else {
        totalEq = _C(exchange.GetAccount).Balance
    }
    
    while (1) {
        if (openPrice == 0) {
            // Update account information and calculate profits
            var nowAcc = _C(exchange.GetAccount)
            nowEq = IsVirtual() ? nowAcc.Balance : nowAcc.Balance  // equity
            LogProfit(nowEq - totalEq, nowAcc)
            
            var direction = Math.floor((Math.random()*100)+1)   // 1~50 , 51~100
            var depth = _C(exchange.GetDepth)
            if (depth.Asks.length <= 2 || depth.Bids.length <= 2) {
                Sleep(1000)
                continue 
            }
            if (direction > 50) {
                // long
                openPrice = depth.Bids[1].Price
                exchange.SetDirection("buy")
                exchange.Buy(Math.abs(openPrice) + slidePrice, amount * ratio)
            } else {
                // short
                openPrice = -depth.Asks[1].Price
                exchange.SetDirection("sell")
                exchange.Sell(Math.abs(openPrice) - slidePrice, amount * ratio)
            }       
            Log("place", direction > 50 ? "buying order" : "selling order", ", price:", Math.abs(openPrice))
            continue
        }

        var orders = _C(exchange.GetOrders)
        if (orders.length == 0) {
            var pos = _C(exchange.GetPosition)
            if (pos.length == 0) {
                openPrice = 0
                continue
            }
            
            // Test for closing the position
            while (1) {
                var depth = _C(exchange.GetDepth)
                if (depth.Asks.length <= 2 || depth.Bids.length <= 2) {
                    Sleep(1000)
                    continue 
                }
                var stopLossPrice = openPrice > 0 ? Math.abs(openPrice) - stopLoss : Math.abs(openPrice) + stopLoss 
                var stopProfitPrice = openPrice > 0 ? Math.abs(openPrice) + stopProfit : Math.abs(openPrice) - stopProfit
                var winOrLoss = 0 // 1 win , -1 loss 
                
                // drawing the line
                $.PlotLine("bid", depth.Bids[0].Price)
                $.PlotLine("ask", depth.Asks[0].Price)
                
                // stop loss
                if (openPrice > 0 && depth.Bids[0].Price < stopLossPrice) {
                    exchange.SetDirection("closebuy")
                    exchange.Sell(depth.Bids[0].Price - slidePrice, pos[0].Amount)
                    winOrLoss = -1
                } else if (openPrice < 0 && depth.Asks[0].Price > stopLossPrice) {
                    exchange.SetDirection("closesell")
                    exchange.Buy(depth.Asks[0].Price + slidePrice, pos[0].Amount)
                    winOrLoss = -1
                }
                
                // stop profit
                if (openPrice > 0 && depth.Bids[0].Price > stopProfitPrice) {
                    exchange.SetDirection("closebuy")
                    exchange.Sell(depth.Bids[0].Price - slidePrice, pos[0].Amount)  
                    winOrLoss = 1
                } else if (openPrice < 0 && depth.Asks[0].Price < stopProfitPrice) {
                    exchange.SetDirection("closesell")
                    exchange.Buy(depth.Asks[0].Price + slidePrice, pos[0].Amount)
                    winOrLoss = 1
                }
                
                // Test the pending orders
                Sleep(2000)
                var orders = _C(exchange.GetOrders)                
                if (orders.length == 0) {
                    pos = _C(exchange.GetPosition)
                    if (pos.length == 0) {
                        if (winOrLoss == -1) {
                            ratio++
                        } else if (winOrLoss == 1) {
                            ratio = 1
                        }
                        break
                    }                    
                } else {
                    // cancel pending orders
                    cancelAll()
                    Sleep(2000)
                    pos = _C(exchange.GetPosition)
                    // update the position after cancellation, and check it again
                    if (pos.length == 0) {
                        if (winOrLoss == -1) {
                            ratio++
                        } else if (winOrLoss == 1) {
                            ratio = 1
                        }
                        break
                    }    
                }
                
                var tbl = {
                    "type" : "table", 
                    "title" : "info", 
                    "cols" : ["totalEq", "nowEq", "openPrice", "bid1Price", "ask1Price", "ratio", "pos.length"], 
                    "rows" : [], 
                }
                tbl.rows.push([totalEq, nowEq, Math.abs(openPrice), depth.Bids[0].Price, depth.Asks[0].Price, ratio, pos.length])
                tbl.rows.push(["pos", "type", "amount", "price", "--", "--", "--"])
                for (var j = 0 ; j < pos.length ; j++) {
                    tbl.rows.push([j, pos[j].Type, pos[j].Amount, pos[j].Price, "--", "--", "--"])
                }
                LogStatus(_D(), "\n", "`" + JSON.stringify(tbl) + "`")
            }
        } else {
            // cancel the pending orders
            // reset openPrice
            cancelAll()
            openPrice = 0
        }
        Sleep(1000)
    }
}
```

Strategy parameters:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4kjgj4ogx690zso7o9bd.png)

Oh yes! The strategy needs a name, let's call it "guess the size (dYdX version)".

### Backtest

Backtesting is for reference only, >_<!
The main purpose is to check whether there is any bug in the strategy, backtesting by Binance Futures.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6cufzp3o6ojauw4ekxpd.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q3tj30xvpk2oddb0wmvo.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uskvr74tgetv4i2c0c4u.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vm4qyi1rpvj60m0xld8k.png)

The backtest has finished, there is no bug.

This strategy is used for learning and reference only, do not use it in the real bot!

From: https://blog.mathquant.com/2022/10/24/example-of-dydx-strategy-design.html