Source code for cointrader.bot

# -*- coding: utf-8 -*-
import datetime
import time
import logging
import sqlalchemy as sa
from cointrader import Base, engine, db
from cointrader.strategy import BUY, SELL, QUIT

log = logging.getLogger(__name__)


[docs]def replay_tradelog(trades): btc = 0 amount = 0 for t in trades: if t.order_type == "INIT": btc = t.btc amount = t.amount elif t.order_type == "BUY": btc -= t.btc amount += t.amount else: btc += t.btc amount -= t.amount return btc, amount
[docs]def init_db(): Base.metadata.create_all(engine)
[docs]def get_bot(market, strategy, resolution, start, end, btc, amount): try: bot = db.query(Cointrader).filter(Cointrader.market == market._name).one() log.info("Loading bot {} {}".format(bot.market, bot.id)) bot._market = market bot.strategy = str(strategy) bot._strategy = strategy bot._resolution = resolution bot._start = start bot._end = end db.commit() btc, amount = replay_tradelog(bot.trades) log.info("Loaded state from trade log: {} BTC {} COINS".format(btc, amount)) bot.btc = btc bot.amount = amount except sa.orm.exc.NoResultFound: bot = Cointrader(market, strategy, resolution, start, end) log.info("Creating new bot {}".format(bot.market)) if btc is None: balances = market._exchange.get_balance() btc = balances["BTC"]["quantity"] if amount is None: balances = market._exchange.get_balance() amount = balances[market.currency]["quantity"] bot.btc = btc bot.amount = amount chart = market.get_chart(resolution, start, end) rate = chart.get_first_point()["close"] date = datetime.datetime.utcfromtimestamp(chart.get_first_point()["date"]) trade = Trade(date, "INIT", 0, 0, market._name, rate, amount, 0, btc, 0) bot.trades.append(trade) db.add(bot) db.commit() strategy.set_bot(bot) return bot
[docs]class Trade(Base): """All trades of cointrader are saved in the database. A trade can either be a BUY or SELL.""" __tablename__ = "trades" id = sa.Column(sa.Integer, primary_key=True) bot_id = sa.Column(sa.Integer, sa.ForeignKey('bots.id')) date = sa.Column(sa.DateTime, nullable=False, default=datetime.datetime.utcnow) order_type = sa.Column(sa.String, nullable=False) order_id = sa.Column(sa.Integer, nullable=False) trade_id = sa.Column(sa.Integer, nullable=False) market = sa.Column(sa.String, nullable=False) rate = sa.Column(sa.Float, nullable=False) amount = sa.Column(sa.Float, nullable=False) amount_taxed = sa.Column(sa.Float, nullable=False) btc = sa.Column(sa.Float, nullable=False) btc_taxed = sa.Column(sa.Float, nullable=False) def __init__(self, date, order_type, order_id, trade_id, market, rate, amount, amount_taxed, btc, btc_taxed): """Initialize a new trade log entry. :bot_id: ID of the bot which initiated the trade :date: Date of the order :order_id: ID of the order :order_type: Type of order. Can be either "BUY, SELL" :trade_id: ID of a single trade within the order :market: Currency_pair linke BTC_DASH :rate: Rate for the order :amount: How many coins sold :amount_taxed: How many coins bought (including fee) order :btc: How many payed on buy :btc_taxed: How many BTC get (including fee) from sell """ if not isinstance(date, datetime.datetime): self.date = datetime.datetime.strptime(date, "%Y-%m-%d %H:%M:%S") else: self.date = date self.order_type = order_type self.order_id = order_id self.trade_id = trade_id self.market = market self.rate = rate self.amount = amount self.amount_taxed = amount_taxed self.btc = btc self.btc_taxed = btc_taxed if self.order_type == "BUY": log.info("{}: BUY {} @ {} paid -> {} BTC".format(self.date, self.amount_taxed, self.rate, self.btc)) elif self.order_type == "SELL": log.info("{}: SELL {} @ {} earned -> {} BTC".format(self.date, self.amount, self.rate, self.btc_taxed)) else: log.info("{}: INIT {} BTC {} COINS".format(self.date, self.btc, self.amount))
[docs]class Cointrader(Base): """Cointrader""" __tablename__ = "bots" id = sa.Column(sa.Integer, primary_key=True) created = sa.Column(sa.DateTime, nullable=False, default=datetime.datetime.utcnow) active = sa.Column(sa.Boolean, nullable=False, default=True) market = sa.Column(sa.String, nullable=False) strategy = sa.Column(sa.String, nullable=False) trades = sa.orm.relationship("Trade") def __init__(self, market, strategy, resolution="30m", start=None, end=None): self.market = market._name self.strategy = str(strategy) self._market = market self._strategy = strategy self._resolution = resolution self._start = start self._end = end self.amount = 0 self.btc = 0 # The bot has either btc to buy or amount of coins to sell. def _buy(self): result = self._market.buy(self.btc) order_id = result["orderNumber"] order_type = "BUY" total_amount = 0 for t in result["resultingTrades"]: trade_id = t["tradeID"] date = t["date"] amount = t["amount"] total_amount += float(amount) rate = t["rate"] btc = t["total"] trade = Trade(date, order_type, order_id, trade_id, self._market._name, rate, 0, amount, self.btc, btc) self.trades.append(trade) # Finally set the internal state of the bot. BTC will be 0 after # buying but we now have some amount of coins. self.amount = total_amount self.btc = 0 self.state = 1 db.commit() def _sell(self): result = self._market.sell(self.amount) order_id = result["orderNumber"] order_type = "SELL" total_btc = 0 for t in result["resultingTrades"]: trade_id = t["tradeID"] date = t["date"] amount = t["amount"] rate = t["rate"] btc = t["total"] total_btc += float(btc) trade = Trade(date, order_type, order_id, trade_id, self._market._name, rate, self.amount, amount, 0, btc) self.trades.append(trade) # Finally set the internal state of the bot. Amount will be 0 after # selling but we now have some BTC. self.state = 0 self.amount = 0 self.btc = total_btc db.commit()
[docs] def stat(self, delete_trades=False): """Returns a dictionary with some statistic of the performance of the bot. Performance means how good cointrader performs in comparison to the market movement. Market movement is measured by looking at the start- and end rate of the chart. The performance of cointrader is measured by looking at the start and end value of the trade. These values are also multiplied with the start and end rate. So if cointrader does some good decisions and increases eater btc or amount of coins of the bot the performance should be better.""" chart = self._market.get_chart(self._resolution, self._start, self._end) first = chart.get_first_point() market_start_rate = first["close"] start_date = datetime.datetime.utcfromtimestamp(first["date"]) last = chart.get_last_point() market_end_rate = last["close"] end_date = datetime.datetime.utcfromtimestamp(last["date"]) # Set start value for trade in self.trades: if trade.order_type == "INIT": trader_start_btc = trade.btc trader_start_amount = trade.amount market_start_btc = trade.btc market_start_amount = trade.amount trader_end_btc = trader_start_btc trader_end_amount = trader_start_amount for trade in self.trades: if trade.order_type == "BUY": trader_end_amount += trade.amount_taxed trader_end_btc -= trade.btc elif trade.order_type == "SELL": trader_end_btc += trade.btc_taxed trader_end_amount -= trade.amount trader_start_value = trader_start_btc + trader_start_amount * market_start_rate market_start_value = trader_start_value trader_end_value = trader_end_btc + trader_end_amount * market_end_rate market_end_value = market_start_btc + market_start_amount * market_end_rate trader_profit = trader_end_value - trader_start_value market_profit = market_end_value - market_start_value stat = { "start": start_date, "end": end_date, "market_start_value": market_start_value, "market_end_value": market_end_value, "profit_chart": market_profit / market_end_value * 100, "trader_start_value": trader_start_value, "trader_end_value": trader_end_value, "profit_cointrader": trader_profit / trader_end_value * 100, } if delete_trades: for trade in self.trades: db.delete(trade) db.commit() return stat
def _in_time(self, date): return (self._start is None or self._start <= date) and (self._end is None or date <= self._end)
[docs] def start(self, interval=None, backtest=False): """Start the bot and begin trading with given amount of BTC. The bot will trigger a analysis of the chart every N seconds. The default number of seconds is set on initialisation using the `resolution` option. You can overwrite this setting by using the `interval` option. By setting the `backtest` option the trade will be simulated on real chart data. This is useful for testing to see how good your strategy performs. :btc: Amount of BTC to start trading with :interval: Number of seconds to wait until the bot again call for a trading signal. This defaults to the resolution of the bot which is provided on initialisation. :backtest: Simulate trading on historic chart data on the given market. :returns: None """ self._start_btc = self.btc if interval is None: interval = self._market._exchange.resolution2seconds(self._resolution) while 1: signal = self._strategy.signal(self._market, self._resolution, self._start, self._end) if signal.value == BUY and self.btc and self._in_time(signal.date): self._buy() elif signal.value == SELL and self.amount and self._in_time(signal.date): self._sell() elif signal.value == QUIT: log.info("Bot stopped") break if backtest: if not self._market.continue_backtest(): log.info("Backtest finished") break time.sleep(interval)