Multi-Exchange Spot Spread Arbitrage Strategy Logic Sharing
Tags: arbitrage, strategy, trading, fmzquant
Published: 2024-04-28 09:10:37

##### Principles of strategy
Due to liquidity reasons, when there is a large amount of smashing and pulling in the market, there will inevitably be large price fluctuations, and an instantaneous price difference will be formed between exchanges, and the strategy is to capture these moments in which quick trades are executed to complete the process of buying low and selling high.
Some clients asked me why I have to get so many exchanges. This is inevitable. What we earn is the instantaneous price difference between exchanges. The more exchanges, the more opportunities for the price difference formed after the crossover.

##### Core logic of strategy
1. To obtain multi-exchange market information concurrently, it must be obtained concurrently to reduce the delay of the obtained market. For concurrent acquisition, please refer to the tool plug-in I shared [Multi-Exchange Concurrent Plug-in](https://www.fmz.com/strategy/365412)
2. Combine the ask and bid of all exchange orders to get a combined order information, where RealPrice is the price after deducting the handling fee,

```
function createOrders(depths, askOrders, bidOrders) {
    let asksIndex = 0;
    let bidIndex = 0;
    for (let i = 0; i < depths.length; i++) {
        let exchangeTariff = getExchangeTariff(i);
        let asks = depths[i].Asks;
        let bids = depths[i].Bids;
        for (let j = 0; j < Math.min(asks.length, bids.length, 20); j++) {
            if (asks[j].Amount >= minTakerAmount) {
                askOrders[asksIndex] = {
                    "Price": asks[j].Price,
                    "Amount": asks[j].Amount,
                    "Fee": asks[j].Price * exchangeTariff,
                    "RealPrice": asks[j].Price * (1 + exchangeTariff),
                    "Index": i,
                };
                asksIndex++;
            }
            if (bids[j].Amount >= minTakerAmount) {
                bidOrders[bidIndex] = {
                    "Price": bids[j].Price,
                    "Amount": bids[j].Amount,
                    "Fee": bids[j].Price * exchangeTariff,
                    "RealPrice": bids[j].Price * (1 - exchangeTariff),
                    "Index": i,
                };
                bidIndex++;
            }
        }
    }
    askOrders.sort(function (a, b) {
        return a.RealPrice - b.RealPrice;
    });
    bidOrders.sort(function (a, b) {
        return b.RealPrice - a.RealPrice;
    });
}
```

3. Calculate the best arbitrage spread from the combined market information. Since we are taking the order, that is, buying from the lowest price ask and selling from the highest price bid, as long as bid.RealPrice > ask.RealPrice, there is room for profit

```
function getArbitrageOrders(askOrders, bidOrders) {
    let ret = [];
    for (let i = 0; i < askOrders.length; i++) {
        for (let j = 0; j < bidOrders.length; j++) {
            let bidOrder = bidOrders[j];
            let askOrder = askOrders[i];
            if (bidOrder.Index === askOrder.Index) {
                continue
            }
            let minMigrateDiffPrice = ((askOrder.Price + bidOrder.Price) / 2 * minMigrateDiffPricePercent / 100);
            if (bidOrder.RealPrice - askOrder.RealPrice > minMigrateDiffPrice) {
                ret.push({
                    "Ask": askOrder,
                    "Bid": bidOrder,
                })
            }
        }
    }
    if (ret.length === 0) {
        ret.push({
            "Ask": askOrders[0],
            "Bid": bidOrders[0],
        });
    }
    //Sort by best spread
    ret.sort((a, b) => {
        return (b.Bid.RealPrice - b.Ask.RealPrice) - (a.Bid.RealPrice - a.Ask.RealPrice);
    });
    return ret;
}
```

4. At this point, we have obtained the information on the arbitrage spread in the market, so there are several points of judgment on how to choose whether to execute a trade and how much to trade:
  - Current remaining assets
  - The size of spread (if the spread is too small, only the amount of currency will be balanced, and if the spread is large enough, the number of trade will be maximized)
  - The number of pending orders

```
    var askOrder = arbitrageOrder.Ask;
    var bidOrder = arbitrageOrder.Bid;
    var perAmountFee = arbitrageOrder.Ask.Fee + arbitrageOrder.Bid.Fee;
    var minRealDiffPrice = (askOrder.Price + bidOrder.Price) / 2 * minDiffPricePercent / 100;
    var minMigrateDiffPrice = ((askOrder.Price + bidOrder.Price) / 2 * minMigrateDiffPricePercent / 100);
    var curRealDiffPrice = arbitrageOrder.Bid.RealPrice - arbitrageOrder.Ask.RealPrice;
    var buyExchange = exchanges[arbitrageOrder.Ask.Index];
    var sellExchange = exchanges[arbitrageOrder.Bid.Index];
    var buySellAmount = 0;
    if (curRealDiffPrice > minRealDiffPrice) {
        buySellAmount = math.min(
            bidOrder.Amount,
            askOrder.Amount,
            maxTakerAmount,
            runningInfo.Accounts[bidOrder.Index].CurStocks,
            runningInfo.Accounts[askOrder.Index].CurBalance / askOrder.Price
        );
    } else if (bidOrder.Index !== askOrder.Index) {
        if (migrateCoinEx == -1) {
            if (curRealDiffPrice > minMigrateDiffPrice && runningInfo.Accounts[bidOrder.Index].CurStocks - runningInfo.Accounts[askOrder.Index].CurStocks > maxAmountDeviation) {
                buySellAmount = math.min(
                    bidOrder.Amount,
                    askOrder.Amount,
                    maxTakerAmount,
                    runningInfo.Accounts[bidOrder.Index].CurStocks,
                    runningInfo.Accounts[askOrder.Index].CurBalance / askOrder.Price,
                    runningInfo.Accounts[bidOrder.Index].CurStocks - ((runningInfo.Accounts[bidOrder.Index].CurStocks + runningInfo.Accounts[askOrder.Index].CurStocks) / 2)
                );
                if (buySellAmount >= minTakerAmount) {
                    Log("Start exchange balancing!");
                }
            }
        } else if (migrateCoinEx == askOrder.Index) {
            if (curRealDiffPrice > minMigrateDiffPrice && runningInfo.Accounts[bidOrder.Index].CurStocks > 0) {
                buySellAmount = math.min(
                    bidOrder.Amount,
                    askOrder.Amount,
                    maxTakerAmount,
                    runningInfo.Accounts[bidOrder.Index].CurStocks,
                    runningInfo.Accounts[askOrder.Index].CurBalance / askOrder.Price
                );
                if (buySellAmount >= minTakerAmount) {
                    Log("Initiate currency migration:", exchanges[bidOrder.Index].GetName(), "-->", exchanges[askOrder.Index].GetName());
                }
            }
        }
    }
```

5. After calculating the number of orders to be placed, the transaction can be executed. The strategy adopts the method of directly adding slippage and taking orders at the same time

```
            var buyWait = buyExchange.Go("Buy", _N(askOrder.Price * (1.01), pricePrecision), buySellAmount);
            var sellWait = sellExchange.Go("Sell", _N(bidOrder.Price * (0.99), pricePrecision), buySellAmount);
            var startWaitTime = new Date().getTime()
            Sleep(3000);
            var buyOrder = buyWait.wait()
            var sellOrder = sellWait.wait()
```

6. All that remains is the logic of calculating returns, handling stop losses on failed orders, and so on.

##### The actual profits of the strategy

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cpfegdsdteb73az2ah9r.png)
 
#### Current real bot display, core logic remains unchanged, optimized to support multiple currencies
[https://www.fmz.com/robot/464965](https://www.fmz.com/robot/464965)

Finally, welcome to join the Laoqiu quantitative exchange communication: https://t.me/laoqiu_arbitrage

From: https://blog.mathquant.com/2022/07/11/multi-exchange-spot-spread-arbitrage-strategy-logic-sharing.html