diff --git a/totrader/factory.py b/totrader/factory.py index 326f395..78c1d0f 100644 --- a/totrader/factory.py +++ b/totrader/factory.py @@ -8,10 +8,11 @@ def create_app(): @app.before_serving async def startup(): - from totrader.routes import meta, tasks + from totrader.routes import meta, tasks, api from totrader import filters app.register_blueprint(meta.bp) app.register_blueprint(tasks.bp) + app.register_blueprint(api.bp) app.register_blueprint(filters.bp) return app \ No newline at end of file diff --git a/totrader/models.py b/totrader/models.py index 77342e1..6b3b7f4 100644 --- a/totrader/models.py +++ b/totrader/models.py @@ -51,6 +51,24 @@ class Order(Model): class Meta: database = db +class Trade(Model): + trade_pair = CharField() + trade_type = CharField() + buy = BooleanField() + quantity = DoubleField() + price = DoubleField() + date = DateTimeField() + + class Meta: + database = db + +class BitcoinPrice(Model): + price = DoubleField() + date = DateTimeField(default=datetime.utcnow) + + class Meta: + database = db + class Earning(Model): trade_pair = CharField() quantity = DoubleField() @@ -59,4 +77,4 @@ class Earning(Model): class Meta: database = db -db.create_tables([Ticker, Balance, Order, Earning]) +db.create_tables([Ticker, Balance, Order, Earning, Trade, BitcoinPrice]) diff --git a/totrader/routes/api.py b/totrader/routes/api.py index d1d8c62..f50e789 100644 --- a/totrader/routes/api.py +++ b/totrader/routes/api.py @@ -1,13 +1,38 @@ -from quart import Blueprint, current_app +from quart import Blueprint, current_app, jsonify from totrader.models import * from totrader.tasks import trader -bp = Blueprint('tasks', 'tasks', url_prefix='/api/tasks') +bp = Blueprint('api', 'api', url_prefix='/api/v1') -@bp.route('/store_ticker_data') -async def store_ticker_data(): - current_app.add_background_task(trader.store_ticker_data) - return '1' +@bp.route('/get_ticker_data') +async def get_ticker_data(): + ticker = Ticker.select().order_by(Ticker.date.desc()).limit(1).first() + return jsonify({ + 'price': ticker.current_price, + 'volume': ticker.volume, + 'bid': ticker.bid, + 'ask': ticker.ask, + 'spread_sats': ticker.spread_sats, + 'spread_btc': ticker.spread_btc, + 'spread_perc': ticker.spread_perc, + 'date': ticker.date + }) + +@bp.route('/get_balances') +async def get_balances(): + return jsonify({}) + +@bp.route('/get_bitcoin_price') +async def get_bitcoin_price(): + return jsonify({}) + +@bp.route('/get_orders') +async def get_orders(): + return jsonify({}) + +@bp.route('/get_trade_history') +async def get_trade_history(): + return jsonify({}) diff --git a/totrader/routes/tasks.py b/totrader/routes/tasks.py index d1d8c62..6a5ad9c 100644 --- a/totrader/routes/tasks.py +++ b/totrader/routes/tasks.py @@ -10,4 +10,25 @@ bp = Blueprint('tasks', 'tasks', url_prefix='/api/tasks') @bp.route('/store_ticker_data') async def store_ticker_data(): current_app.add_background_task(trader.store_ticker_data) - return '1' + return 'ok' + +@bp.route('/store_balances') +async def store_balances(): + current_app.add_background_task(trader.store_balances) + return 'ok' + +@bp.route('/store_orders') +async def store_orders(): + current_app.add_background_task(trader.reconcile_orders) + current_app.add_background_task(trader.update_orders) + return 'ok' + +@bp.route('/store_trade_history') +async def store_trade_history(): + current_app.add_background_task(trader.update_trade_history) + return 'ok' + +@bp.route('/store_bitcoin_price') +async def store_bitcoin_price(): + current_app.add_background_task(trader.update_bitcoin_price) + return 'ok' diff --git a/totrader/tasks.py b/totrader/tasks.py index 8130df8..c88dda2 100644 --- a/totrader/tasks.py +++ b/totrader/tasks.py @@ -45,8 +45,76 @@ class Trader(TradeOgre): spread_perc=res['spread_perc'] ) t.save() - logging.info(f'[MARKET] Stored market data as ID {t.id}') - return True + logging.info(f'[MARKET] Stored ticker data: {t.id}') + + def store_balances(self): + for currency in self.base_currency, self.trade_currency: + logging.info(f'[BALANCE] Storing balances 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 balances: {b.id}') + + 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.select().where(Order.uuid == order['uuid']).first(): + 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.') + + def update_trade_history(self): + logging.info('Updating trade history for the ticker') + for trade in self.get_history(self.trade_pair): + tr = Trade( + trade_pair=self.trade_pair, + trade_type=trade['type'], + buy=trade['type'] == 'buy', + quantity=float(trade['quantity']), + price=float(trade['price']), + date=datetime.utcfromtimestamp(trade['date']) + ) + tr.save() + logging.info('Trade added to the database') + + def update_bitcoin_price(self): + logging.info('Updating Bitcoin price') + r = self.get_bitcoin_price() + bp = BitcoinPrice( + price=float(r['price']) + ) + bp.save() trader = Trader() \ No newline at end of file diff --git a/totrader/templates/index.html b/totrader/templates/index.html index f88b2e3..a5cfa18 100644 --- a/totrader/templates/index.html +++ b/totrader/templates/index.html @@ -27,23 +27,83 @@ } } + // new Noty({ + // theme: 'relax', + // layout: 'topCenter', + // text: 'yo doggy', + // timeout: 4500 + // }).show(); + render() { + // fetch latest data for the UI + let updateAllData; + // trigger background tasks to store data + let storeMarketData; + let storeBalances; + let storeBitcoinPrice; + let storeOrderStatuses; + let storeTradeHistory; + + function getAllData() { + console.log('updating data'); + fetch('{{ url_for("api.get_ticker_data") }}') + .then((response) => response.json()) + .then((res) => { + console.log(res); + }); + fetch('{{ url_for("api.get_balances") }}'); + fetch('{{ url_for("api.get_bitcoin_price") }}'); + fetch('{{ url_for("api.get_orders") }}'); + fetch('{{ url_for("api.get_trade_history") }}'); + } + + if (this.state.looping) { + storeMarketData = setInterval(function() { + console.log('updating ticker data'); + fetch('{{ url_for("tasks.store_ticker_data") }}'); + }, 30000); + storeBalances = setInterval(function() { + console.log('storing balances'); + fetch('{{ url_for("tasks.store_balances") }}'); + }, 50000); + storeBitcoinPrice = setInterval(function() { + console.log('storing bitcoin price'); + fetch('{{ url_for("tasks.store_bitcoin_price") }}'); + }, 300000); + storeOrderStatuses = setInterval(function() { + console.log('storing order statuses'); + fetch('{{ url_for("tasks.store_orders") }}'); + }, 30000); + storeTradeHistory = setInterval(function() { + console.log('storing trade history'); + fetch('{{ url_for("tasks.store_trade_history") }}'); + }, 60000); + updateAllData = setInterval(getAllData(), 10000); + } + return(
-

we trade a lil {{ trade_currency }}

+

we trade a lil {{ trade_currency | lower }}

{this.state.looping && ( -

market making is started

+
+

market making is started

+ +
) || (
-

market making is paused

+

market making is stopped

+ this.setState({looping: true}); + getAllData(); + }}>Start
)}
@@ -53,7 +113,7 @@ // this.setState(prevState => { // let jasper = { ...prevState.jasper }; // creating copy of state variable jasper - // jasper.name = 'someothername'; // update the name property, assign a new value + // jasper.name = 'someothername'; // store the name property, assign a new value // return { jasper }; // return new object jasper object // }); @@ -65,10 +125,4 @@ // fetch('{{ url_for("tasks.store_ticker_data") }}'); // }, 10000) - // store market data - 10s - // update balances - 30s - // update bitcoin price - 120s - // update order status - 30s - // get trade history - 60s - \ No newline at end of file diff --git a/totrader/tradeogre.py b/totrader/tradeogre.py index 42c67ab..c47fbc5 100644 --- a/totrader/tradeogre.py +++ b/totrader/tradeogre.py @@ -57,3 +57,17 @@ class TradeOgre(object): route = f'/account/orders' data = {'market': pair} return self.req(route, 'post', data) + + def get_bitcoin_price(self): + url = 'https://api.coingecko.com/api/v3/coins/wownero' + headers = {'accept': 'application/json'} + data = { + 'localization': False, + 'tickers': False, + 'market_data': True, + 'community_data': False, + 'developer_data': False, + 'sparkline': False + } + r = requests_get(url, headers=headers, data=data) + return {'price': r.json()['market_data']['current_price']['usd']}