Home

Awesome

日本語はこちら

Build Status Coverage Status

R2 Bitcoin Arbitrager

R2 Bitcoin Arbitrager is an automatic arbitrage trading application targeting Bitcoin exchanges.

Web UI Mode

Web UI mode is for browser clients.

webui

Console Mode

Console mode is for CUI-only environment like Linux boxes with no GUI.

Screenshot

Getting Started

  1. Install Node.js 8.5 or newer.
  2. Clone this repository.
git clone https://github.com/bitrinjani/r2.git
  1. Run npm install. (or yarn)
cd r2
npm install
  1. Rename config_default.json in the folder to config.json
  2. Replace key and secret fields with your API keys (tokens) and secrets. Set enabled to false for exchanges you do not use.
  3. To run R2 in Web UI mode, set webGateway.enabled to true. By default, R2 starts in Console mode.
  4. Start the application by npm start or yarn start.
npm start
  1. Open http://127.0.0.1:8720 in Chrome.

or

  1. Install Docker
  2. Clone this repository.
git clone https://github.com/bitrinjani/r2.git
  1. Run docker build and docker run.
cd r2
docker build -t r2:latest .
docker run --rm -it r2:latest

Prerequisites

R2 works on any OS that supports Node.js, such as:

Web UI works on the latest version of Google Chrome.

Supported Exchanges

R2 supports the following exchanges.

ExchangeCashMargin
bitFlyer✔️✔️*
Quoine✔️✔️
Coincheck✔️✔️
bitbank.cc️️️✔️
BTCBox✔️

*bitFlyer margin trading (BTC-FX/JPY) is available as a broker plugin.

How it works

  1. Every 3 seconds, R2 downloads quotes from exchanges.
  2. Filters out quotes that are not usable for arbitrage. For example, if maxShortPosition config is 0 and the current position is 0 for a broker, the ask quotes for the broker will be filtered out.
  3. Calculates the best ask and the best bid from the filtered quotes and checks if the expected profit is larger than the configured minimum value, minTargetProfitPercent. If there is no arbitrage opportunity, R2 waits for the next iteration.
  4. R2 concurrently sends a buy leg and a sell leg to each broker that offered the best price.
  5. R2 checks whether the legs are filled or not for the configured period, say 30 seconds.
  6. If the both legs are filled, shows the profit. If one of the legs are not fully filled, R2 tries to send a cover order in order to balance the position. The covering behavior is configurable in onSingleLeg config.

After the spread has became smaller than the configured value, exitNetProfitRatio, R2 tries to close the pair.

Architecture Overview

diagram

Configuration

All configurations are stored in config.json.

Global Config

NameValuesDescription
language"ja" or "en"UI language. Japanese or English.
demoModetrue or falseIf it's true, the arbitrager analyzes spreads but doesn't send any real trade.
priceMergeSizenumberMerges small quotes into the specified price ladder before analyzing arbitrage opportunity.
maxSizenumberMaximum BTC size to be sent to brokers.
minSizenumberMinimum BTC size to be sent to brokers.
minTargetProfitPercentnumberMinimum target profit in percent against notional (= inverted spread %). Profit percentage against notional is calculated by 100 * expected profit / (MID price * volume).
maxTargetProfitnumber[Optional] Max target profit. This is a safe-guard for abnormal quotes. If the expected profit is larger than this, R2 won't attempt arbitrage.
maxTargetProfitPercentnumber[Optional] Max target profit in percent.
exitNetProfitRationumberR2 attempts to close open pairs when the spread has decreased by this percentage. For example, when the open profit of an open pair is 200 JPY and exitNetProfitRatio is 20(%), R2 closes the pair once the closing cost has became 160.
maxTargetVolumePercentnumber[Optional] In order to execute orders as fast as possible and avoid slippage, R2 checks the volume of the quotes from the exchanges and makes sure the target volume consumes less than this percentage of the volume of the target quote before executing the order
acceptablePriceRangenumber[Optional] Allows execution with price that is disadvantageous by the set value(%) from target price. (Like a market order.)
iterationIntervalMillisecondTime lapse in milliseconds of an iteration. When it's set to 3000, the quotes fetch and the spreads analysis for all the brokers are done every 3 seconds
positionRefreshIntervalMillisecondTime lapse in milliseconds of position data refresh. Position data is used to check max exposure and long/short availability for each broker.
sleepAfterSendMillisecondTime lapse in milliseconds after one arbitrage is done.
maxNetExposurenumberMaximum total net exposure. If net exposure qty is larger than this value, Arbitrager stops.
maxRetryCountnumberMaximum retry count to check if arbitrage orders are filled or not. If the orders are not filled after the retries, Arbitrager tries to cancel the orders and continues.
orderStatusCheckIntervalMillisecondTime lapse in milliseconds to check if arbitrage orders are filled or not.
stabilityTracker-See stabilityTracker config below
onSingleLeg-See onSingleLeg config below
analytics-See ANALYTICS_PLUGIN.md
webGateway-See webGateway config details below

webGateway config details

Default config:

  "webGateway": {
    "enabled": false,
    "host": "127.0.0.1",
    "openBrowser": true
  },

Web UI URL is http://127.0.0.1:8720 by default. TCP port 8720 and 8721 need to be opened.

stabilityTracker config details

R2 automatically disables trading activities on unstable brokers.

By default, a broker which has failed three API calls within 5 minutes would be disabled for trading for at most 5 minutes.

Default configuration:

...
  "stabilityTracker": {
    "threshold": 8,
    "recoveryInterval": 300000 
  },
...

onSingleLeg config details

The onSingleLeg config specifies what action should be taken when only one leg is filled.

...
  "onSingleLeg": {
    "action": "Reverse",
    "actionOnExit": "Proceed",
    "options": {
      "limitMovePercent": 5,
      "ttl": 3000
    }
  },
...

Broker config

NameValuesDescription
brokerBitflyer, Quoine or CoincheckBroker name
npmPathstringnpm package name for the broker plugin.
enabledtrue or falseEnable the broker for arbitrage
keystringBroker API Key
secretstringBrokerAPI Secret
maxLongPositionnumberMaximum long position allowed for the broker. R2 won't send orders to the broker if current long position is larger than this value.
maxShortPositionnumberMaximum short position allowed for the broker. R2 won't send orders to the broker if current short position is larger than this value.
cashMarginTypeCash, MarginOpen, NetOutArbitrage order type. Not all options are supported for each exchange. See the table below.
commissionPercentnumberComission percentage for each trade. Commission JPY amount is calculated by target price * target volume * (commissionPercent / 100). Arbitrager calculates expected profit by inversed spread * volume - commission JPY amount.
noTradePeriodslist of ["starttime", "endtime"]See noTradePeriods section below

Supported cashMarginType

ExchangeSupported option
BitflyerCash
QuoineCash, NetOut
CoincheckCash, MarginOpen, NetOut
BitbankccCash
BtcboxCash

Quoine's NetOut is natively handled by Exchange API. Quoine can close multiple positions by one order. Coincheck's NetOut is artificially handled by R2 because the exchange doesn't support netout operation. Coincheck's NetOut works as below.

  1. The arbitrager finds leverage positions with the following conditions.
  1. If the positions are found, the arbitrager closes the oldest one.
  2. If not found, the arbitrager opens a new position.

Please note this implementation doesn't close multiple positions by one order.

noTradePeriods config

The noTradePeriods config specifies the periods when the quotes from the exchange must be ignored. The config is useful for scheduled maintenance periods, e.g. 4:00-4:15 in bitFlyer.

    {
      "broker": "Bitflyer",
...
      "noTradePeriods": [["04:00", "04:15"]]
    },
    {
      "broker": "Bitflyer",
...
      "noTradePeriods": [["04:00", "04:15"], ["9:00", "9:30"]]
    },

Log notification config (Slack, LINE)

R2 can send notification messages to Slack and LINE when it detects the configured keywords in the output logs.

// config.json
{
...
  "logging": {
    "slack": {
      "enabled": false,
      "url": "https://hooks.slack.com/services/xxxxxx",
      "channel": "#ch1",
      "username": "abc",
      "keywords": ["error", "profit"]
    },
    "line": {
      "enabled": false,
      "token": "TOKEN",
      "keywords": ["error", "profit"]
    }
  }
}

Slack notification

NameValuesDescription
enabledtrue or falseEnable notification
urlstringSlack Incoming Webhook URL
channelstringSlack channel name
usernamestringSlack user name
keywordsstring[]Keyword list

LINE notification

NameValuesDescription
enabledtrue or falseEnable notification
tokenstringLINE Notify token
keywordsstring[]Keyword list

Log files

All log files are saved under logs directory.

File nameDescription
info.logStandard log file
debug.logVerbose logging, including all REST HTTP requests and responses in JSON format

Utility scripts

Several utility scripts are available to close positions, show balances and clear cache.

See TOOLS.md

Running the tests

test script runs ts-jest.

npm test

License

This project is licensed under the MIT License - see the LICENSE file for details

Disclaimer

USE THE SOFTWARE AT YOUR OWN RISK. YOU ARE RESPONSIBLE FOR YOUR OWN MONEY. THE AUTHOR HAS NO RESPONSIBILITY FOR YOUR TRADING RESULTS.

Inspirations

Blackbird, which targets US exchanges.