Skip to content

Simple Arbitrage

In this tutorial, we will build a simple arbitrage strategy. We will assume that you have downloaded and set up Algo, following the QuickStart to set all of the environment variables.

Configuration

In the following configuration, we set up the following:

  1. We are doing spot-swap arbitraging.
  2. Our trading pair is BTCUSDT.
  3. Our exchange is OKEX.

arbitrage.json

{
    "instance": {
        "log_path": "/data/cc/logs",
        "name": "arbitrage",
        "license_id":"TRAIL001",
        "license_key":"apifiny123456"
    },
    "sim": {
        "ioc_only": false,
        "use_tbbo": true,
        "delay_o2a_us": 0,
        "delay_a2m_us": 0
    },
    "fees": {
        "OKEX_SWAP": {
            "make": 0.0002,
            "take": 0.0004
        },
        "OKEX": {
            "make": 0.0000,
            "take": 0.0006
        }
    },
    "players": [
        ["BTCUSDT.OKEX_Player", ["TardisPlayer", {"port": ["BTCUSDT", "OKEX"], "path": "/data/cc/tardis_data"}]], 
        ["BTCUSDTSWAP.OKEXSWAP_Player", ["TardisPlayer", {"port": ["BTCUSDTSWAP", "OKEX_SWAP"], "path": "/data/cc/tardis_data"}]]
    ],
    "risk_formulas": [
        ["Standard_Risk", ["RiskFormula", {"components": [[["BTCUSDT", "OKEX"], 1.0], [["BTCUSDTSWAP", "OKEX_SWAP"], 1.0]]}]]
    ],
    "accounts": [
        [10001, ["Account", {"risk_formulas": ["Standard_Risk"], "id": 10001}]]
    ],
    "symbols": [
        {"port": ["BTCUSDT", "OKEX"], "cid": 10001}, 
        {"port": ["BTCUSDTSWAP", "OKEX_SWAP"], "cid": 10002}
    ],
    "pricing_models": [
        ["BTCUSDT.OKEX_askpx", ["AskPx", {"port": ["BTCUSDT", "OKEX"]}]], 
        ["BTCUSDT.OKEX_bidpx", ["BidPx", {"port": ["BTCUSDT", "OKEX"]}]],
        ["dummy", ["AskPx", {"port": ["BTCUSDTSWAP", "OKEX_SWAP"], "comment": "This is just to notify on book change"}]]
    ],
    "samplers": [
    ],
    "variables": [
        ["VAR_A_askpx", ["PriceVar", {"pm": "BTCUSDT.OKEX_askpx"}]], 
        ["VAR_A_bidpx", ["PriceVar", {"pm": "BTCUSDT.OKEX_bidpx"}]],
        ["ask_rid_price", ["LevelPriceQty", {"sizeCap": 10000, "side": false, "ref_pm": "dummy", "port": ["BTCUSDTSWAP", "OKEX_SWAP"]}]],
        ["bid_rid_price", ["LevelPriceQty", {"sizeCap": 10000, "side": true, "ref_pm": "dummy", "port": ["BTCUSDTSWAP", "OKEX_SWAP"]}]],
        ["bid_spread", ["Sub", {"v1": "VAR_A_bidpx", "v2": "bid_rid_price"}]],
        ["ask_spread", ["Sub", {"v1": "VAR_A_askpx", "v2": "ask_rid_price"}]],
        ["ask_total", ["Add", {"v1": "VAR_A_askpx", "v2": "ask_rid_price"}]],
        ["bid_total", ["Add", {"v1": "VAR_A_bidpx", "v2": "bid_rid_price"}]],
        ["bid_fee_neg", ["Scale", { "coef": 0.0004, "variable": "bid_total"}]],
        ["bid_fee", ["Neg", {"variable": "bid_fee_neg"}]],
        ["ask_fee", ["Scale", {"coef": 0.0004, "variable": "ask_total"}]],
        ["bid_signal", ["GreaterThan", {"v1": "bid_fee", "v2": "bid_spread"}]],
        ["ask_signal", ["GreaterThan", {"v1": "ask_spread", "v2": "ask_fee"}]]
    ],
    "models": [
        ["dummy", ["LinearModel", {"variable": "bid_spread", "comment": "This is just a pass-through as well"}]]
    ],
    "strategies": [
        ["Maker", ["ArbSimple1", {"symbol": "BTCUSDT", "trade_market": "OKEX", "use_margin": true, "pos_expanding_cooloff": 1000, "cooloff": 1000, "account": 10001, "use_separate_logs": true, "model": "dummy", "ask_signal": "ask_signal", "bid_signal": "bid_signal", "order_notional": 4000, "max_notional": 40000, "max_risk": 8000, "start_time": "00:30:00", "end_time": "23:59:59"}]],
        ["Hedger", ["ArbSimple2", {"symbol": "BTCUSDTSWAP", "trade_market": "OKEX_SWAP", "pos_expanding_cooloff": 1000, "cooloff": 1000, "use_margin": true, "account": 10001, "use_separate_logs": true, "model": "dummy", "ioc_notional": 4000, "max_notional": 45000, "bid_rid_price": "bid_rid_price", "ask_rid_price":"ask_rid_price", "start_time": "00:30:00", "end_time": "23:59:59"}]]
    ]
}

If you want to make this strategy your own, the variables to change are as follows:

  1. All of the paths beginning with "/data/cc" should be updated to the respective paths on your machine
  2. If you want to simulate latency, change "sim"->"delay_o2a_us" and "sim"->"delay_a2m_us"; the times are in microseconds.
  3. If you want to change the trading pair, change all "port" keys to the new symbol and exchange, respectively. Also, in "strategies", change both "symbol" and "trade_market" variables.
  4. If you want to change your threshold, change "bid_fee_neg"->"coef" and "ask_fee"->"coef". This should be your average fee over both legs, added to profit.
  5. in "ask_rid_price" and "bid_rid_price", sizeCap is the amount of notional that the strategy must be able to hedge before it sends a make order. This can be set close to the ioc_notional for a more risky yet more profitable strategy, or higher for more confidence that all risk can be hedged.
  6. In "strategies":
    1. max_notional is the maximum total notional of all assets. This should be set high, as by the nature of arbitrage a large notional can be accumulated without associated risk.
    2. ioc_notional and order_notional are the notional caps for the two legs for each individual trade. Setting these well above the minimum order size for the hedging leg is advised.
    3. max_risk is the maximum risk allowed based on the Standard_Risk formula. This should be set to a small multiple of order_notional, equal to the number of simultaneous unhedged trades are allowed. Unlike max_notional, the hedging step reduces this close to zero.

Simulating

Now that we have the strategy, we want to test it against past market data. To do so, we first need to add the scripts directory to our PATH:

export PATH=${ALGO_HOME}/scripts:$PATH

Then, we can run

gen_dates.py -sd 20220101 -ed 20220319 | parallel -j 10 ccc_sim_trader arbitrage.json

To simulate the strategy on past data from January 1st to March 19th.

Now, we can use

sim_ana.py -p /data/cc/logs -sd 20220101 -ed 20220319

to generate statistics about the simulation, and if we do so we get the following results:

which shows that at least in simulation, our strategy makes a profit. Here, the pnlUSD is the profit before fees, and netUSD is the profit after fees. Either way, we can make a profit.

As we have all of our trades stored in /data/cc/logs, we can also graph the daily profit, and we get the following graph: