This project is not maintained anymore.

After many years of working on Gekko, I’ve decided to stop my involvement in maintaining this project. You can read more about this decision on medium.

I’m now putting all my focus on my new prop trading firm Folkvang. You can find an article about that here on Coindesk.

If you’re interested in following this new journey, feel free to add me on Twitter.

Best of luck to everyone in their trading. So long, and thanks for all the fish!

Fork me on GitHub

Exchanges #

This is a technical document about adding a new exchange to Gekko and Gekko Broker.

Gekko arranges all communication about when assets need to be bought or sold between the strategy and Gekko Broker. All differences between the different API's are abstracted away just below Gekko Broker inside an "exchange wrapper". This document describes all requirements for adding a new exchange wrapper (adding exchange support to Gekko).

When you add a new exchange to Gekko you need to expose an object that has methods to query the exchange. This exchange file needs to reside in gekko/exchange/wrappers and the filename is the slug of the exchange name + .js. So for example the exchange integration with Binance is explained in gekko/exchange/wrappers/binance.js.

Please use a npm module to query an exchange. This will separate the abstract API calls from the Gekko specific stuff (In the case of Bitstamp there was no module yet, so I created one).

Finally Gekko needs to know how it can interact with the exchange, so add a static method getCapabilities() that returns it's properties. They are all described here.

Gekko's expectations #

Gekko Broker implements an exchange like so:

const Exchange = require('./wrapper' + [exchange slug]);
this.exchange = new Exchange({key: '', secret: '', username: '', currency: 'USD', asset: 'BTC'});

It will run the following methods on the exchange object:

getTrades #

With this single method implemented Gekko won't be able to do live trading, but it will be able to do paper trading and storing market data for backtesting.

this.watcher.getTrades(since, callback, descending);

If since is truthy, Gekko requests as much trades as the exchange can give (up to ~10,000 trades, if the exchange supports more you can create an importer).

The callback expects an error and a trades object. Trades is an array of trade objects in chronological order (0 is older trade, 1 is newer trade). Each trade object needs to have:

Gekko Broker's expectations #

The methods below are needed for Gekko to automatically trade on the exchange.

getTicker #

this.exchange.getTicker(callback)

The callback needs to have the parameters of err and ticker. Ticker needs to be an object with at least the bid and ask property in float.

getFee #

this.exchange.getFee(callback);

The callback needs to have the parameters of err and fee. Fee is a float that represents the amount the exchange takes out of the orders Gekko places. If an exchange has a fee of 0.2% this should be 0.002.

getPortfolio #

this.exchange.getPortfolio(callback);

The callback needs to have the parameters of err and portfolio. Portfolio needs to be an array of all currencies and assets combined in the form of objects, an example object looks like {name: 'BTC', amount: 1.42131} (name needs to be an uppercase string, amount needs to be a float).

getLotSize #

this.exchange.getLotSize(tradeType, amount, size, callback);

The callback needs to have the parameters of err and lot. Lot needs to be an object with amount and purchase size appropriately for the exchange. In the event that the lot is too small, return 0 to both fields and this will generate a lot size warning in the portfolioManager.

Note: This function is currently optional. If not implemented Gekko Broker will fallback to basic lot sizing mechanism it uses internally. However exchanges are not all the same in how rounding and lot sizing work, it is recommend to implement this function.

buy #

this.exchange.buy(amount, price, callback);

sell #

this.exchange.sell(amount, price, callback);

This should create a buy / sell order at the exchange for [amount] of [asset] at [price] per 1 asset. The callback needs to have the parameters err and order. The order needs to be some id that can be passed back to checkOrder, getOrder and cancelOrder.

getOrder #

this.exchange.getOrder(order, callback);

Will only be called on orders that have been completed. The order will be something that the manager previously received via the sell or buy methods. The callback should have the parameters err and order. Order is an object with properties price, amount and date. Price is the (volume weighted) average price of all trades necessary to execute the order. Amount is the amount of currency traded and Date is a moment object of the last trade part of this order.

checkOrder #

this.exchange.checkOrder(order, callback);

The order will be something that the manager previously received via the sell or buy methods. The callback should have the parameters err and a result object. The result object will have two or three properties:

cancelOrder #

this.exchange.cancelOrder(order, callback);

The order will be something that the manager previously received via the sell or buy methods. The callback should have the parameters err, filled and optionally a fill object, filled should be true if the order was fully filled before it could be cancelled. If the exchange provided how much was filled before the cancel this should be passed in the fill object as a amountFilled property. If the exchange provided how much of the original amount is still remaining, pass this as the remaining property instead.

roundPrice #

this.exchange.roundPrice(rawPrice);

Should return a price. Rounds the price into a valid price Gekko Broker can later feed into a buy or sell order. Note: if representation of the number is important for the exchange (for example if the exchange will not accept numbers expressed in scientific notation) you can return a string from this method (Gekko Broker will perform calculations so the string should be castable to a number.)

roundAmount #

this.exchange.roundAmount(rawAmount);

Should return an amount. Rounds the amount into a valid amount Gekko Broker can later feed into a buy or sell order. Note: if representation of the number is important for the exchange (for example if the exchange will not accept numbers expressed in scientific notation) you can return a string from this method (Gekko Broker will perform calculations so the string should be castable to a number.)

Gekko Broker's optional methods #

isValidPrice #

this.exchange.isValidPrice(price);

Should return true or false. If the exchange has restrictions on the price you can submit limit orders at. If there are no such restrictions you should not implement this method.

isValidLot #

this.exchange.isValidLot(price, amount);

Should return true or false. If the exchange has restrictions on the lot size (order size expressed in "currency" amount) you can check for that here. If there are no such restrictions you should not implement this method.

Error handling #

It is the responsibility of the wrapper to handle errors and retry the call in case of a temporary error. Gekko exposes a retry helper you can use to implement an exponential backoff retry strategy. Your wrapper does need pass a proper error object explaining whether the call can be retried or not. If the error is fatal (for example private API calls with invalid API keys) the wrapper is expected to upstream this error. If the error is retryable (exchange is overloaded or a network error) an error should be passed with the property notFatal set to true. If the exchange replied with another error that might be temporary (for example an Insufficient funds error right after Gekko canceled an order, which might be caused by the exchange not having updated the balance yet) the error object can be extended with an retry property indicating that the call can be retried for n times but after that the error should be considered fatal.

For implementation refer to the bitfinex implementation, in a gist this is what a common flow looks like:

Capabilities #

Each exchange must provide a getCapabilities() static method that returns an object with these parameters:

Below is a real-case example how bistamp exchange provides its getCapabilities() method:

Trader.getCapabilities = function () {
  return {
    name: 'Bitstamp',
    slug: 'bitstamp',
    currencies: ['USD', 'EUR'],
    assets: ['BTC', 'EUR'],
    maxTradesAge: 60,
    maxHistoryFetch: null,
    markets: [
      { pair: ['USD', 'BTC'], minimalOrder: { amount: 1, unit: 'currency' } },
      { pair: ['EUR', 'BTC'], minimalOrder: { amount: 1, unit: 'currency' } },
      { pair: ['USD', 'EUR'], minimalOrder: { amount: 1, unit: 'currency' } }
    ],
    requires: ['key', 'secret', 'username'],
    fetchTimespan: 60,
    tid: 'tid',
    gekkoBroker: '0.6.2',
    limitedCancelConfirmation: false
  };
}