mirror of
https://git.wownero.com/lza_menace/totrader.git
synced 2024-08-15 00:33:13 +00:00
314 lines
12 KiB
Python
Executable file
314 lines
12 KiB
Python
Executable file
#!/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)
|