#!/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 Ticker, Balance, Order, Earning 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 base_currency = getenv('BASE_CURRENCY', 'BTC') trade_currency = getenv('TRADE_CURRENCY', 'WOW') trade_pair = f'{base_currency}-{trade_currency}' trade_amount = float(getenv('TRADE_AMOUNT', 200)) spread_target = float(getenv('SPREAD_TARGET', 4)) 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'[MARKET] 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'[MARKET] Stored market data as ID {t.id}') return t def store_balance(self, currency): logging.info(f'[BALANCE] Storing balance for currency {currency}') res = self.get_balance(currency) logging.debug(res) b = Balance( currency=currency, total=res['balance'], available=res['available'] ) b.save() logging.info(f'[BALANCE] Stored market data as ID {b.id}') return b def store_balances(self): for cur in self.base_currency, self.trade_currency: self.store_balance(cur) sleep(3) def get_active_orders(self): logging.info('[ORDERS] 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('[ORDERS] 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'[ORDERS] Saved order {order["uuid"]} to the database') def update_orders(self): logging.info('[ORDERS] 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 start_market_maker(self): logging.info('[MARKET MAKER] 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'[MARKET MAKER] 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) ask_price = '{:.8f}'.format(latest.ask - self.satoshi) logging.info(f'[MARKET MAKER] 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'[MARKET MAKER] Stored buy order as ID {_bo.id}') sleep(3) logging.info(f'[MARKET MAKER] 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'[MARKET MAKER] Stored sell order as ID {_so.id}') else: logging.info(sell) else: logging.info(buy) else: logging.info(f'[MARKET MAKER] 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('--run', action='store_true', help='Run continuously') args = parser.parse_args() t = Trader() orders_counter = 0 balances_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() if not args.run and args.market_data: t.store_market_data() if not args.run and args.calculate_earnings: t.calculate_earnings() if args.run: while True: try: t.store_market_data() except Exception as e: logging.info('[ERROR] Unable to store market data!', 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() 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 # sleep sleep_time = int(getenv('SLEEP_TIME', 20)) logging.info(f'Sleeping for {sleep_time} seconds') sleep(sleep_time)