Skip to content

Market making demo

Xinxi Wang edited this page Apr 17, 2022 · 8 revisions

Market makers put buy/sell limit orders in the exchange at the same time to facilitate continuous trading. Because of them, when a user wants to buy Bitcoins, he can buy immediately from the market maker. Similarly, if he wants to sell, he can sell to the market maker.

Market makers do not provide this liquidity service for free. They usually make their sell price higher than their buy price. They make a profit from the price difference, i.e. the so-called bid / ask spread.

For example, if I am making the market for the BTC/USDT pair, and the current price is about 7500 USD per BTC, I can put a buy order at 7499 and a sell order at 7501. If a user buys a BTC from me, I get 7501 USD and my BTC balance is reduced by 1. Then if another user sells a BTC to me, I get 1 BTC and my USD balance reduces by 7499. In the end, my BTC amount does not change, and I get two dollars profit (7501 - 7499), i.e. the bid/ask spread.

In some cases, the market maker's inventory cannot be maintained timely. E.g. if a user sells a Bitcoin from the market maker, but no user is willing to buy from the market maker, then the market maker will be holding an extra Bitcoin. To hedge this risk, the market maker can sell the Bitcoin on another platform, e.g. binance.com. To make this process even more efficient, the market maker can put some Bitcoin on Binance.com beforehand, and thus if the selling of Bitcoin on Binance is necessary, he does not need to move the Bitcoin from Coinut.com to Binance.com on the spot.

The following is a simple market making bot based on our API. Hope you enjoy it.

import hmac
import hashlib
import json
import uuid
import random
import time
import requests
import logging

def get_logger():
    logger = logging.getLogger('logger')
    logger.setLevel(logging.DEBUG)
    ch = logging.StreamHandler()
    ch.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    ch.setFormatter(formatter)
    logger.addHandler(ch)
    return logger

logger = get_logger()

class CoinutAPI():
    def __init__(self, user = None, api_key = None):
        self.user = user
        self.api_key = api_key

    def request(self, api, content = {}):
        url = 'https://api.coinut.com'
        headers = {}
        content["request"] = api
        content["nonce"] = random.randint(1, 100000)
        content = json.dumps(content)
        logger.debug(content.strip())

        if self.api_key is not None and self.user is not None:
            sig = hmac.new(self.api_key, msg=content,
                           digestmod=hashlib.sha256).hexdigest()
            headers = {'X-USER': self.user, "X-SIGNATURE": sig}

        response = requests.post(url, headers=headers, data=content)
        logger.debug(response.text.strip())
        return response.json()

    def get_spot_instruments(self, pair = None):
        result = self.request("inst_list", {'sec_type': 'SPOT'})
        if pair != None:
            return result['SPOT'][pair][0]
        else:
            return result['SPOT']

    def get_existing_orders(self, inst_id):
        return self.request("user_open_orders", {"inst_id": inst_id})['orders']

    def cancel_orders(self, inst_id, ids):
        ords = [{'inst_id': inst_id, 'order_id': x} for x in ids]
        return self.request("cancel_orders", {'entries': ords})

    def submit_new_orders(self, ords):
        return self.request("new_orders", {"orders": ords})

    def submit_new_order(self, inst_id, side, qty, price = None):
        return self.request("new_order", self.new_order(inst_id, side, qty, price))

    def new_order(self, inst_id, side, qty, price = None):
        order = {
            'inst_id': inst_id,
            "side": side,
            'qty': "%.8f" % qty,
            'client_ord_id': random.randint(1, 4294967290)
        }
        if price is not None:
            order['price'] = "%.8f" % price

        return order

    def cancel_all_orders(self, inst_id):
        ords = self.get_existing_orders(inst_id)
        self.cancel_orders(inst_id, [x['order_id'] for x in ords])

    def balance(self):
        return self.request("user_balance")


def get_binance_ltcbtc():
    # use binance's API to get the LTC/BTC price on binance.com

def market_making():
    api = CoinutAPI("username", "REST API Key on https://coinut.com/account/settings page")
    bal = api.balance()
    logger.info("Balance: LTC=%s, BTC=%s" % (bal['LTC'], bal['BTC']))
    
    LTCBTC_ID = api.get_spot_instruments('LTCBTC')['inst_id']
    logger.info("LTCBTC instrument id: %d" % LTCBTC_ID)
    
    mid_price = 0.0
    while True:
        
        # We use the last price from binance.com as our estimation of the
        # fair market price. You can use other exchanges or come up
        # with your own one. It's good as long as you make
        # profit. 
        
        estimated_fair_price = get_binance_ltcbtc()
        if estimated_fair_price == 0.0:
            continue
        else:
            logger.info("estimated fair price %f", estimated_fair_price)
    
        # if the price on binance.com did not change much, we don't move either;
        # otherwise, we cancel existing orders and place new orders
        if abs(mid_price - estimated_fair_price) < 0.00001:
            continue
    
        # cancel all existing orders
        logger.info("cancel all orders")
        api.cancel_all_orders(LTCBTC_ID)
    
        mid_price = estimated_fair_price
        spread = mid_price * 0.02
    
        # generate a blanket of orders around the mid_price
        ords = []
        for i in range(1, 10):
            bid = mid_price - i * spread / 2
            ask = mid_price + i * spread / 2
            qty = 0.1
    
            logger.info("BUY  %f @ %s" % (qty, bid))
            buy_order = api.new_order(LTCBTC_ID, 'BUY', 0.1, bid)
            ords.append(buy_order)
    
            logger.info("SELL %f @ %s" % (qty, ask))
            sell_order = api.new_order(LTCBTC_ID, 'SELL', 0.1, ask)
            ords.append(sell_order)
    
        # submit the generated orders in batch mode.
        api.submit_new_orders(ords)
    
        time.sleep(0.1)
        bal = api.balance()
        logger.info("Balance: LTC=%s, BTC=%s" % (bal['LTC'], bal['BTC']))

if __name__ == "__main__":
    market_making()
Clone this wiki locally