shazow / liquid

Arbitrage bot to provide liquidity between two Bitcoin exchanges.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

liquid

Arbitrage bot to provide liquidity across two exchanges.

The bot will synchronize the Bitme order book with a subset of remote exchange orders, with an added premium. The bot will poll the Bitme order book to track completed orders. Once a Bitme completed order is noticed, then it will place a corresponding (sans-premium) order on the remote exchange.

Status

ARCHIVED: This project is deprecated. The code is published for posterity.

Strongly advised against using this bot as a whole, but there are stand-alone well-tested components that can be re-used in other applications.

  • Bitme (the Bitcoin exchange used as a destination in arbitrage) no longer exists, so the bot won't work even if you try to run it.
  • Valuable stand-alone components that can be re-used:
    • lib/order.js: Order operations abstraction, including calculating spread, producing order set diffs and patches, fitting to budget constraints.

Original README:

Warning

If you plan on using this bot, be prepared to lose money.

While the code is reasonably well-tested, there are still many edge cases that may not be covered such as unlikely crashes or extreme market volatility.

Keep a balance only of what you're willing to lose, and monitor the bot closely.

Quickstart

Run the tests:

$ npm test
...

42 passing (35ms)

Use the tool:

$ ./liquid --help

  Usage: liquid [options] <origin:remote>

  Options:

    -h, --help              output usage information
    -V, --version           output the version number
    ...

  API keys loaded from environment variables:

    ✖ BITSTAMP_CLIENT_ID
    ✖ BITSTAMP_KEY
    ✖ BITSTAMP_SECRET
    ✖ BITME_KEY
    ✖ BITME_SECRET

Start in DummyExchange mode, aggregating orders to value of $500 at a premium of 150%:

$ ./liquid dummy:dummy -v --minValue 500 --premium 1.5
debug:   Set debug level: "debug"
info:    Bot created in dummy mode. All trades will be fake using a DummyExchange.
debug:   [bot] init values: origin=DummyOrigin, remote=DummyRemote, premium=undefined, resetOnly=undefined, minValue=undefined, maxOrders=undefined
info:    Resetting exchanges into a safe state.
debug:   [bot] Binding to exchange events.
info:    Bot started.
...

Start in LIVE mode, but only pretend to make trades. For this, we'll need to export the environment variables mentioned in --help first.

$ export BITSTAMP_CLIENT_ID="XXX"
$ export BITSTAMP_KEY="XXX"
$ export BITSTAMP_SECRET="XXX"
$ export BITME_KEY="XXX"
$ export BITME_SECRET="XXX"

Once the environment variables have been set:

$ ./liquid bitme:bitstamp -v --pretend --minValue 500 --premium 1.5
debug:   Set debug level: "debug"
info:    Bot created in PRETEND mode. Orderbook will be watched but no real trades will be placed.
debug:   [bot] init values: origin=bitme, remote=bitstamp, premium=1.5, resetOnly=undefined, minValue=500, maxOrders=undefined
debug:   [exchange:bitme] Preparing.
debug:   [exchange:bitme] Verifying credentials.
debug:   [exchange:bitme] Loaded placed orders: 0
debug:   [exchange:bitme] Loaded balance: currency_cd=BTC, currency_name=Bitcoin, balance=1.00000000000000000000, available=1.00000000000000000000, cleared=1.00000000000000000000, currency_cd=USD, currency_name=US Dollar, balance=1000.00000000000000000000, available=1000.00000000000000000000, cleared=1000.00000000000000000000
debug:   [exchange:bitme] Starting tick loop.
debug:   [exchange:bitstamp] Preparing.
debug:   [exchange:bitstamp] Loaded placed orders: 0
debug:   [exchange:bitstamp] Loaded balance: btc_reserved=0, fee=0.5000, btc_available=1.00000000, usd_reserved=0, btc_balance=1.00000000, usd_balance=0.00, usd_available=0.00
debug:   [exchange:bitstamp] Subscribing to orderbook stream.
info:    Resetting exchanges into a safe state.
debug:   [bot] Binding to exchange events.
info:    Bot started.
...

Running in production

For best results, assume the bot will crash or shutdown at some point, then run it in reset-only mode and send yourself an alert. Once the situation has been investigated, restart the bot. It's not recommended to let it auto-restart on its own by using a process manager.

A script like this might do the trick:

#!/bin/bash
liquid bitme:bitstamp \
  --stopAfter 1 \
  --logfile log.json \
  --minValue 70 \
  --email "$EMAIL";

# Bot went down, make sure we're in a safe state.
liquid bitme:bitstamp \
  --resetOnly;

echo "Bot stopped. Take a look at it." | mail "$EMAIL"

You can also add notifications to the Slack channel of your choice, you just need to add a Incoming WebHooks API Integration for the channel of your choice and export the key into a SLACK_SECRET environment variable.

export SLACK_SECRET="XXX"

liquid ... \
  --slack "#trading";

...

Components

Bot

The bot is the glue that controls trading between the exchanges and maintains state.

var Bot = require('./lib/bot.js').Bot;

var bot = new Bot(bitmeExchange, bitstampExchange);

It's instantiated with an originExchange, a remoteExchange, and some other options as configured by the command-line argument parser. The origin exchange is our exchange that we're mirroring into. The remote exchange is the exchange we're mirroring from.

The bot will pull orderbook updates from the remote exchange and keep the origin exchange in sync with every tick. If it notices that one of the origin orders disappears, it will treat that as a completed trade and will create a reciprocal trade on the remote exchange.

bot.start(callback);

When the bot is started, it will prepare each of the exchanges by calling exchange.ready(callback) on them, and then it will implicitly call bot.reset(callback) to reset the trading into a safe state--in case it wasn't shut down properly. This mostly means clearing all of the active origin trades so that we can start synchronizing the origin trades ourselves.

The bot is notified of trades from exchanges by subscribing to events on them.

bot.stop(callback);

When stopping conditions are reached (such as stopAfter count) or an error occurs, the bot will attempt to shut down gracefully by calling exchange.cleanup(callback) on each exchange, removing the event subscroptions.

Finally, the bot will again implicitly call bot.reset(callback) and attempt to reach a safe shutdown state.

Exchange

We abstract exchange APIs by extending the BaseExchange which provides a common interface for our Bot to work with. An exchange implementation is an event emitter which emits trade (our placed order is executed) and orderbook (exchange orderbook has been updated) events.

var DummyExchange = require('./lib/exchanges/dummy.js');

var exchange = new DummyExchange('FakeExchange');

A DummyExchange is provided which fakes trades by maintaining its own state in-memory.

exchange.ready(callback);

The exchange is responsible for loading its state and setting up any authentication or websockets during the ready phase.

exchange.placeOrders(orders, callback);

var newOrders = exchange.getOrders();
exchange.cancelOrders(newOrders, callback);

It provides functions for placing and deleting orders in bulk, and it keeps track of placed orders and the exchange balance.

exchange.cleanup(callback);

To achieve a graceful shutdown, the exchange must clean up after itself. This means unsubscribing from any websockets and clearing any interval timers. It may also attempt to push through any pending orders before shutting down.

var BitmeExchange = require('./lib/exchanges/bitme.js');

var exchange = BitmeExchange.fromConfig({
    apiKeys: {
        BITME_KEY: '...',
        BITME_SECRET: '...',
    },
    tickDelay: 1000,
    pretend: true
});

When exchanges are loaded by the command-line argument parser, they're instantiated using a static helper which knows how to parse the various command-line options.

To use real exchanges but avoid making real trades, we can start them with pretend: true which will skip making API calls whenever placeOrders and cancelOrders is called, and instead print an INFO log and pretend that orders were successful.

Order module

Along with the main class representation of an Order, the core logic of dealing with orders lives inside of the order module.

var order = require('./lib/order.js');

var bid = new order.Order(null, 'BID', 1, 500);

The Order class should be treated as immutable, and any mutations can be done by cloning it.

var ask = bid.clone({}, /* premium */ 1.05, /* invertType */ true);

Clone will override any fields we pass, but it can also apply a premium and invert the type at the same time--a very common operation when trading back and forth.

There are several important utilities used primarily by the bot whenever the orderbook needs to be updated:

  • order.diffOrders(oldOrders, newOrders, ...) is used for detecting when the orderbook has changed.
  • order.aggregateOrders(orders, minValue, ...) is used to combine smaller orders into fewer larger orders.
  • order.budgetOrders(orders, budget, ...) is used to prune aggregated orders into a subset which will fit in our budget. It will even replace the final oversized order with a smaller equivalent order that fits within the budget.
  • order.patchOrders(placedOrders, newOrders) is used to get instructions to update our placed orders with an updated set of orders using the fewest possible operations.

Further, there several more helpers used to get the spread from a list of orders, sort orders by spread, compute the total value of a list of orders, and get the budget to be used with aggregateOrders.

All of these pieces are generally well-tested, so don't forget to check the tests for examples on how they can be used independently or together.

Sponsors

This project was sponsored by Mirror.

License

MIT.

About

Arbitrage bot to provide liquidity between two Bitcoin exchanges.

License:MIT License


Languages

Language:JavaScript 99.9%Language:Makefile 0.1%