Source code for cointrader.exchange

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import datetime
import collections
import time
from cointrader.exchanges.poloniex import Poloniex as PoloniexApi
from cointrader.chart import Chart
from cointrader.indicators import MIN_POINTS


[docs]def get_market_name(market): return market[0]
[docs]def add_fee(btc, fee=0.025): return btc - (btc / 100 * fee)
[docs]class ExchangeException(Exception): pass
[docs]class Coin(object): """Docstring for Coin.""" def __init__(self, name, quantity, btc_value=None): self.name = name self.quantity = quantity self.btc_value = btc_value @property def value(self): return self.btc_value
[docs]class Market(object): """Docstring for Market. """ def __init__(self, exchange, name, dry_run=False): """TODO: to be defined1. :name: TODO """ self._exchange = exchange self._name = name self._dry_run = dry_run @property def currency(self): pair = self._name.split("_") return pair[1] @property def url(self): return "{}{}".format(self._exchange.url, self._name) def _get_chart_data(self, resolution, start, end): """Will return the data for the chart.""" # To ensure that the data cointains enough data to calculate SMA # or EMA right from the start we need to calculate internal # start date of the chart which lies before the given start # date. On default we excpect at least 120 data points in the # chart to be present. period = self._exchange.resolution2seconds(resolution) internal_start = start - datetime.timedelta(seconds=period * MIN_POINTS) return self._exchange._api.chart(self._name, internal_start, end, period)
[docs] def get_chart(self, resolution="30m", start=None, end=None): """Will return a chart of the market. You can provide a `resolution` of the chart. On default the chart will have a resolution of 30m. You can define a different timeframe by providing a `start` and `end` point. On default the the chart will include the last recent data. :resolution: Resolution of the chart (Default 30m) :start: Start of the chart data (Default Now) :end: End of the chart data (Default Now) :returns: Chart instance. """ if end is None: end = datetime.datetime.utcnow() if start is None: start = datetime.datetime.utcnow() data = self._get_chart_data(resolution, start, end) return Chart(data, start, end)
[docs] def buy(self, btc, price=None, option=None): """Will buy coins on the market for the given amount of BTC. On default we will make a market order which means we will try to buy for the best price available. If price is given the order will be placed for at the given price. You can optionally provide some options. See :class:`cointrader.exchanges.poloniex.api` for more details. :btc: Amount of BTC :price: Optionally price for which you want to buy :option: Optionally some buy options :returns: Dict witch details on the order. """ if price is None: # Get best price on market. orderbook = self._exchange._api.book(self._name) asks = orderbook["asks"] # Asks in the meaning of "I wand X for Y" best_offer = asks[-1] price = float(best_offer[0]) amount = btc / price if self._dry_run: amount = add_fee(amount) date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") return {u'orderNumber': u'{}'.format(int(time.time() * 1000)), u'resultingTrades': [ {u'tradeID': u'{}'.format(int(time.time() * 1000)), u'rate': u'{}'.format(price), u'amount': u'{}'.format(amount), u'date': u'{}'.format(date), u'total': u'{}'.format(btc), u'type': u'buy'}]} else: return self._exchange._api.buy(self._name, amount, price, option)
[docs] def sell(self, amount, price=None, option=None): if price is None: # Get best price on market. orderbook = self._exchange._api.book(self._name) bids = orderbook["bids"] # Bids in the meaning of "I give you X for Y" best_offer = bids[-1] price = float(best_offer[0]) btc = amount * price if self._dry_run: btc = add_fee(btc) date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") return {u'orderNumber': u'{}'.format(int(time.time() * 1000)), u'resultingTrades': [ {u'tradeID': u'{}'.format(int(time.time() * 1000)), u'rate': u'{}'.format(price), u'amount': u'{}'.format(amount), u'date': u'{}'.format(date), u'total': u'{}'.format(btc), u'type': u'sell'}]} else: return self._exchange._api.sell(self._name, amount, price, option)
[docs]class BacktestMarket(Market): """Market to enable backtesting a strategy on the market.""" def __init__(self, exchange, name): """TODO: to be defined1. :exchange: TODO :name: TODO """ Market.__init__(self, exchange, name) self._chart_data = None self._backtest_tick = 1
[docs] def continue_backtest(self): self._backtest_tick += 1 if self._chart_data and len(self._chart_data) >= self._backtest_tick: return True return False
[docs] def get_chart(self, resolution="30m", start=None, end=None): if self._chart_data is None: self._chart_data = self._get_chart_data(resolution, start, end) self._backtest_tick += MIN_POINTS return Chart(self._chart_data[0:self._backtest_tick], start, end)
[docs] def buy(self, btc, price=None): price = float(self._chart_data[0:self._backtest_tick][-1]['close']) date = datetime.datetime.utcfromtimestamp(self._chart_data[0:self._backtest_tick][-1]['date']) btc = add_fee(btc) amount = btc / price return {u'orderNumber': u'{}'.format(int(time.time() * 1000)), u'resultingTrades': [ {u'tradeID': u'{}'.format(int(time.time() * 1000)), u'rate': u'{}'.format(price), u'amount': u'{}'.format(amount), u'date': u'{}'.format(date), u'total': u'{}'.format(btc), u'type': u'buy'}]}
[docs] def sell(self, amount, price=None): price = float(self._chart_data[0:self._backtest_tick][-1]['close']) date = datetime.datetime.utcfromtimestamp(self._chart_data[0:self._backtest_tick][-1]['date']) btc = add_fee(amount * price) return {u'orderNumber': u'{}'.format(int(time.time() * 1000)), u'resultingTrades': [ {u'tradeID': u'{}'.format(int(time.time() * 1000)), u'rate': u'{}'.format(price), u'amount': u'{}'.format(amount), u'date': u'{}'.format(date), u'total': u'{}'.format(btc), u'type': u'sell'}]}
[docs]class Exchange(object): """Baseclass for all exchanges""" # According to Poloniex support the following candlestick period in # seconds; valid values are 300, 900, 1800, 7200, 14400, and 86400. resolutions = {"5m": 300, "15m": 900, "30m": 1800, "2h": 7200, "4h": 14400, "24h": 86400} def __init__(self, config, api=None): """TODO: to be defined1. """ self._api = api self.coins = collections.OrderedDict() # Setup coins balance = self._api.balance() for currency in sorted(balance): if balance[currency]["btc_value"] > 0: self.coins[currency] = Coin(currency, balance[currency]["quantity"], balance[currency]["btc_value"]) @property def url(self): raise NotImplementedError @property def total_btc_value(self): return sum([self.coins[c].value for c in self.coins]) @property def total_euro_value(self, limit=10): ticker = self._api.ticker() return float(ticker["USDT_BTC"]["last"]) * self.total_btc_value @property def markets(self): ticker = self._api.ticker() tmp = {} for currency in ticker: if currency.startswith("BTC_"): change = round(float(ticker[currency]["percentChange"]) * 100, 2) volume = round(float(ticker[currency]["baseVolume"]), 1) tmp[currency] = {"volume": volume, "change": change} return tmp
[docs] def get_top_markets(self, markets, limit=10): if not markets: markets = self.markets top_profit = self.get_top_profit_markets(markets, limit) top_volume = self.get_top_volume_markets(markets, limit) top_profit_markets = set(map(get_market_name, top_profit)) top_volume_markets = set(map(get_market_name, top_volume)) top_markets = {} for market in top_profit_markets.intersection(top_volume_markets): top_markets[market] = markets[market] return sorted(top_markets.items(), key=lambda x: x[1]["change"], reverse=True)[0:limit]
[docs] def get_top_profit_markets(self, markets=None, limit=10): if not markets: markets = self.markets return sorted(markets.items(), key=lambda x: (float(x[1]["change"]), float(x[1]["volume"])), reverse=True)[0:limit]
[docs] def get_top_volume_markets(self, markets=None, limit=10): if not markets: markets = self.markets return sorted(markets.items(), key=lambda x: (float(x[1]["volume"]), float(x[1]["change"])), reverse=True)[0:limit]
[docs] def is_valid_market(self, market): return market in self.markets
[docs] def is_valid_resolution(self, resolution): return resolution in self.resolutions
[docs] def resolution2seconds(self, resolution): try: return self.resolutions[resolution] except KeyError: raise ExchangeException("Resolution {} is not supported.\n" "Please choose one of the following: {}".format(resolution, ", ".join(self.resolutions.keys())))
[docs]class Poloniex(Exchange): def __init__(self, config): api = PoloniexApi(config) Exchange.__init__(self, config, api) @property def url(self): return "https://poloniex.com/exchange#"
[docs] def btc2dollar(self, amount): ticker = self._api.ticker("USDT_BTC") rate = float(ticker["last"]) return round(amount * rate, 2)
[docs] def dollar2btc(self, amount): ticker = self._api.ticker("USDT_BTC") rate = float(ticker["last"]) return round(amount / rate, 8)
[docs] def get_balance(self, currency=None): if currency is None: return self._api.balance() else: return self._api.balance()[currency]