#!/usr/bin/env python import logging from os import getenv from dotenv import load_dotenv from argparse import ArgumentParser from datetime import datetime from decimal import Decimal from random import uniform from time import sleep from db import * from tradeogre import TradeOgre logging.basicConfig( level=getenv('LOGLEVEL', 'INFO'), format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) class Trader(TradeOgre): load_dotenv('.env') satoshi = .00000001 satoshi_multiplier = getenv('SATOSHI_MULTIPLIER', 10) base_currency = getenv('BASE_CURRENCY', 'BTC') trade_currency = getenv('TRADE_CURRENCY', 'XMR') trade_pair = f'{base_currency}-{trade_currency}' trade_amount = float(getenv('TRADE_AMOUNT', .5)) spread_target = float(getenv('SPREAD_TARGET', 10)) amount_multiplier = float(getenv('AMOUNT_MULTIPLIER', 1.2)) active_order_limit = float(getenv('ACTIVE_ORDER_LIMIT', 10)) def get_market_data(self): logging.info(f'Getting market data for trade pair {self.trade_pair}') res = self.get_trade_pair(self.trade_pair) spread_btc = Decimal(res['ask']) - Decimal(res['bid']) spread_sats = float(spread_btc / Decimal(self.satoshi)) spread_perc = (spread_btc / Decimal(res['ask'])) * 100 res['spread_btc'] = spread_btc res['spread_sats'] = spread_sats res['spread_perc'] = spread_perc logging.debug(res) return res def store_market_data(self): res = self.get_market_data() t = Ticker( trade_pair=self.trade_pair, initial_price=res['initialprice'], current_price=res['price'], high_price=res['high'], low_price=res['low'], volume=res['volume'], bid=res['bid'], ask=res['ask'], spread_btc=res['spread_btc'], spread_sats=res['spread_sats'], spread_perc=res['spread_perc'] ) t.save() logging.info(f'Stored market data as ID {t.id}') return t def store_balance(self, currency, dst): logging.info(f'Storing balance for currency {currency}') res = self.get_balance(currency) logging.debug(res) b = Balance( currency=currency, total=res['balance'], available=res['available'], date=dst ) b.save() logging.info(f'Stored market data as ID {b.id}') return b def store_balances(self): now = datetime.utcnow() for cur in self.base_currency, self.trade_currency: self.store_balance(cur, now) sleep(3) def get_active_orders(self): logging.info('Getting active orders in local database') orders = Order.select().where(Order.active == True, Order.trade_pair == self.trade_pair) logging.debug(f'Found {len(orders)} in database') return orders def reconcile_orders(self): logging.info('Reconciling orders on TradeOgre with local database') to_orders = self.get_orders(self.trade_pair) for order in to_orders: if not Order.filter(Order.uuid == order['uuid']): o = Order( trade_pair=order['market'], trade_type='manual', buy=order['type'] == 'buy', quantity=float(order['quantity']), price=float(order['price']), uuid=order['uuid'], date=datetime.utcfromtimestamp(order['date']) ) o.save() logging.info(f'Saved order {order["uuid"]} to the database') def update_orders(self): logging.info('Updating orders in local database against TradeOgre') for order in self.get_active_orders(): logging.info(f'Checking order {order.uuid}') o = self.get_order(order.uuid) logging.info(f'Found order: {o}') if o['success'] is False: order.active = False order.save() logging.info(f'Order {order.uuid} no longer active on TradeOgre. Setting inactive.') sleep(3) def calculate_earnings(self): orders = {'buy': [], 'sell': []} completed_orders = Order.select().where(Order.cancelled == False, Order.active == False) for order in completed_orders: if order.buy: type = 'buy' else: type = 'sell' orders[type].append(Decimal(round(order.price * order.quantity, 12))) orders['total_sell'] = sum(orders['sell']) orders['total_buy'] = sum(orders['buy']) orders['total_earnings'] = orders['total_sell'] - orders['total_buy'] e = Earning( trade_pair=self.trade_pair, quantity=orders['total_earnings'] ) e.save() def update_portfolio(self): logging.info('Updating portfolio') base_balance = Balance.select().where(Balance.currency == self.base_currency).order_by(Balance.date.desc()).first() trade_balance = Balance.select().where(Balance.currency == self.trade_currency).order_by(Balance.date.desc()).first() trade_ticker = Ticker.select().where(Ticker.trade_pair == self.trade_pair).order_by(Ticker.date.desc()).first() btc_price = BitcoinPrice.select().order_by(BitcoinPrice.date.desc()).first() portfolio_btc = (trade_balance.total * trade_ticker.current_price) + base_balance.total portfolio_usd = portfolio_btc * btc_price.price_usd p = Portfolio( usd=float(portfolio_usd), btc=float(portfolio_btc) ) p.save() def update_bitcoin_price(self): logging.info('Updating Bitcoin price') btc = BitcoinPrice(price_usd=float(self.get_bitcoin_price())) btc.save() def start_market_maker(self): logging.info('Starting market maker') latest = Ticker.select().where(Ticker.trade_pair == self.trade_pair).order_by(Ticker.date.desc()).get() trade_type = 'market_maker' active_orders = len(self.get_active_orders()) if active_orders >= self.active_order_limit: logging.info(f'Too many active orders in place ({active_orders}). Skipping.') return False if latest.spread_sats >= self.spread_target: bid_amount = uniform(self.trade_amount, self.trade_amount * self.amount_multiplier) ask_amount = uniform(self.trade_amount, self.trade_amount * self.amount_multiplier) bid_price = '{:.8f}'.format(latest.bid + self.satoshi * self.satoshi_multiplier) ask_price = '{:.8f}'.format(latest.ask - self.satoshi * self.satoshi_multiplier) logging.info(f'Submitting buy order for {bid_amount} at {bid_price} {self.trade_pair}') buy = self.submit_order('buy', self.trade_pair, bid_amount, bid_price) logging.debug(buy) if 'uuid' in buy: uuid = buy['uuid'] active = True else: uuid = None active = False if buy['success']: _bo = Order( trade_pair=self.trade_pair, trade_type=trade_type, buy=True, quantity=bid_amount, price=bid_price, uuid=uuid, active=active ) _bo.save() logging.info(f'Stored buy order as ID {_bo.id}') sleep(3) logging.info(f'Submitting sell order for {ask_amount} at {ask_price} {self.trade_pair}') sell = self.submit_order('sell', self.trade_pair, ask_amount, ask_price) logging.debug(sell) if 'uuid' in sell: uuid = sell['uuid'] active = True else: uuid = None active = False if sell['success']: _so = Order( trade_pair=self.trade_pair, trade_type=trade_type, buy=False, quantity=ask_amount, price=ask_price, uuid=uuid, active=active ) _so.save() logging.info(f'Stored sell order as ID {_so.id}') else: logging.info(sell) else: logging.info(buy) else: logging.info(f'Not enough bid-ask spread ({latest.spread_sats} sats). Skipping market maker.') if __name__ == '__main__': parser = ArgumentParser(description='Helpful TradeOgre trading script') parser.add_argument('--market-maker', action='store_true', help='Put in buy/sell orders') parser.add_argument('--balances', action='store_true', help='Update coin balances of both base and currency') parser.add_argument('--update-orders', action='store_true', help='Update status of orders') parser.add_argument('--market-data', action='store_true', help='Update market data') parser.add_argument('--calculate-earnings', action='store_true', help='Calculate earnings from all trades') parser.add_argument('--update-bitcoin-price', action='store_true', help='Update Bitcoin price (USD)') parser.add_argument('--run', action='store_true', help='Run continuously') args = parser.parse_args() t = Trader() orders_counter = 0 balances_counter = 0 bitcoin_counter = 0 if not args.run and args.update_orders: t.reconcile_orders() t.update_orders() if not args.run and args.market_maker: t.start_market_maker() if not args.run and args.balances: t.store_balances() t.update_portfolio() if not args.run and args.market_data: t.store_market_data() if not args.run and args.calculate_earnings: t.calculate_earnings() if not args.run and args.update_bitcoin_price: t.update_bitcoin_price() if args.run: while True: try: t.store_market_data() except Exception as e: logging.info('[ERROR] Unable to store market data!', e) # update bitcoin price every 10 runs if args.update_bitcoin_price: if bitcoin_counter == 10: try: t.update_bitcoin_price() bitcoin_counter = 0 except Exception as e: logging.info('Unable to update Bitcoin price!', e) # update orders every 4 runs if args.update_orders: if orders_counter == 4: try: t.update_orders() t.calculate_earnings() logging.info('[ORDERS] Resetting orders counter') orders_counter = 0 except Exception as e: logging.info('[ERROR] Unable to update orders!', e) # update balances every 2 runs if args.balances: if balances_counter == 2: try: t.store_balances() t.update_portfolio() logging.info('[BALANCE] Resetting balances counter') balances_counter = 0 except Exception as e: logging.info('[ERROR] Unable to update balances!', e) # start market maker every run if args.market_maker: try: t.start_market_maker() except Exception as e: logging.info('[ERROR] Unable to start market maker!', e) orders_counter += 1 balances_counter += 1 bitcoin_counter += 1 # sleep sleep_time = int(getenv('SLEEP_TIME', 1)) logging.info(f'Sleeping for {sleep_time} seconds') sleep(sleep_time)