From 80b8ca4b20f67c4328f4d9e236ee70da46caa6cc Mon Sep 17 00:00:00 2001 From: isysd Date: Mon, 15 Aug 2016 19:41:30 -0500 Subject: [PATCH 1/3] experimental --- Makefile | 6 +- bots/__init__.py | 0 bots/mm.py | 95 +++++ setup.py | 8 +- supervisord.conf | 47 +++ test/test_commands.py | 359 +++++++++---------- test/test_manager.py | 69 ++-- trade_manager/__init__.py | 1 + trade_manager/cli.py | 105 ++++-- test/helpers.py => trade_manager/helper.py | 92 +++-- trade_manager/plugin.py | 389 +++++++++++++++------ 11 files changed, 791 insertions(+), 380 deletions(-) create mode 100644 bots/__init__.py create mode 100644 bots/mm.py create mode 100644 supervisord.conf rename test/helpers.py => trade_manager/helper.py (69%) diff --git a/Makefile b/Makefile index 1bd4425..d6ca5c0 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,9 @@ makebase = if [ ! -d ~/.tapp ]; \ makedirs = if [ ! -d ~/.tapp/trademanager ]; \ then \ mkdir ~/.tapp/trademanager; \ - mkdir ~/.tapp/test; \ + mkdir ~/.tapp/helper; \ cp cfg.ini ~/.tapp/trademanager; \ - cp cfg.ini ~/.tapp/test; \ + cp cfg.ini ~/.tapp/helper; \ fi installprereqs = if [ ! -d $(1)ledger ]; \ @@ -54,4 +54,4 @@ purge: rm -rf test/*.pyc *.egg *~ *pyc test/*~ .eggs rm -f .coverage* rm -rf ~/.tapp/trademanager - rm -rf ~/.tapp/test + rm -rf ~/.tapp/helper diff --git a/bots/__init__.py b/bots/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bots/mm.py b/bots/mm.py new file mode 100644 index 0000000..87f4208 --- /dev/null +++ b/bots/mm.py @@ -0,0 +1,95 @@ +""" +Ready to go plans for managing your trades. + +WARNING: Use at your own risk! +No promise of financial gain is made, and any losses from use of this software are your responsibility. +""" +import time +from ledger import Amount +from tappmq.tappmq import get_running_workers +from trade_manager.plugin import get_balances, get_market_vol_shares, get_usd_value, red, create_order, sync_balances + +MINMM = Amount("5 USD") +MINORDER = Amount("1 USD") + + +def fib_fan(side, amount, ticker, session): + def calc_price(sid, index, offset): + if sid == 'ask': + return index * (Amount("1 %s" % index.commodity) + Amount("%s %s" % (offset, index.commodity)) / 100) + else: + return index * (Amount("1 %s" % index.commodity) - Amount("%s %s" % (offset, index.commodity)) / 100) + + usdamount = get_usd_value(amount) + if usdamount <= MINORDER: + print "ignoring dusty order %s worth %s" % (usdamount, usdamount) + return + fibseq = [1, 2, 3, 5, 8, 13] + index = ticker.calculate_index() + base = ticker.market.split("_")[0] + if usdamount / Amount("{0} USD".format(len(fibseq))) <= MINORDER: + price = calc_price(side, index, fibseq[int(round(len(fibseq) / 2))]) + if side == 'bid': + amount = Amount("{0:.8f} {1}".format(amount.to_double(), base)) / \ + Amount("{0:.8f} {1}".format(index.to_double(), base)) + # usdval = get_usd_value(amount) + # print "{0} {1} @ {2:0.6f} {3} worth ${4:0.2f})".format(side, amount, price.to_double(), ticker.market, + # usdval.to_double()) + create_order(ticker.exchange, price=price, amount=amount, market=ticker.market, side=side, session=session) + else: + if side == 'bid': + amount = Amount("{0:.8f} {1}".format((amount / len(fibseq)).to_double(), base)) / \ + Amount("{0:.8f} {1}".format(index.to_double(), base)) + else: + amount /= len(fibseq) + for fib in fibseq: + price = calc_price(side, index, fib) + create_order(ticker.exchange, price=price, amount=amount, market=ticker.market, side=side, session=session) + # usdval = get_usd_value(amount) * price / index + # print "{0} {1} @ {2:0.6f} {3} worth ${4:0.2f})".format(side, amount, price.to_double(), + # ticker.market, usdval.to_double()) + sync_balances(ticker.exchange) + + +def mm(exchange, callback, session): + print "__________%s mm %s__________" % (exchange, time.asctime(time.gmtime(time.time()))) + bals = get_balances(exchange, session=session) + if bals is not None: + available = bals[1] + for amount in available: + comm = str(amount.commodity) + try: + value = get_usd_value(amount) + except TypeError as e: + print e + continue + if value <= MINMM: + print "ignoring dusty %s worth %s" % (amount, value) + continue + vshares = get_market_vol_shares(exchange, comm) + for market in vshares: + if market == 'total': + continue + vshare = vshares[market]['vol_share'] + if market.find(comm) == 0: # amount is base, so we sell + tosell = amount * Amount("%s %s" % (vshare, comm)) + tosellval = get_usd_value(tosell) + # print "sell {0} out of {1} on {2} ({3:0.2f}% worth ${4:0.2f})".format(tosell, amount, market, + # vshare * 100, + # tosellval.to_double()) + callback('ask', tosell, vshares[market]['ticker'], session) + if market.find(comm) >= 3: # amount is quote, so we buy + base = market.split("_")[1] + tobuy = Amount("%s %s" % (amount, base)) * Amount("%s %s" % (vshare, base)) + tobuyval = get_usd_value(tobuy) + # print "spend {0} out of {1} on {2} ({3:0.2f}% worth ${4:0.2f})".format(tobuy, amount, market, + # vshare * 100, + # tobuyval.to_double()) + callback('bid', tobuy, vshares[market]['ticker'], session) + + +if __name__ == "__main__": + from trade_manager import ses, EXCHANGES, ses, ses + + for exch in get_running_workers(EXCHANGES, red=red): + mm(exch, fib_fan, ses) diff --git a/setup.py b/setup.py index 9ae0d4a..b82a690 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='trade_manager', - version='0.0.4', + version='0.0.5', packages=['trade_manager'], url='https://github.com/gitguild/trade-manager', license='MIT', @@ -19,10 +19,12 @@ setup_requires=['pytest-runner'], install_requires=[ 'sqlalchemy>=1.0.9', - 'hashlib', 'jsonschema', + 'hashlib', + 'jsonschema', 'alchemyjsonschema', 'redis', - 'python-daemon' + 'python-daemon', + 'supervisor' # 'secp256k1==0.11'#, # "bitjws==0.6.3.1", # "flask>=0.10.0", diff --git a/supervisord.conf b/supervisord.conf new file mode 100644 index 0000000..02e18a9 --- /dev/null +++ b/supervisord.conf @@ -0,0 +1,47 @@ +[unix_http_server] +file = /tmp/supervisor_helper.sock + +[supervisorctl] +serverurl = unix:///tmp/supervisor_helper.sock + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisord] +logfile = /home/ira/.tapp/supervisord.log +childlogdir = /home/ira/.tapp/ +pidfile = /home/ira/.tapp/supervisord.pid +loglevel = debug + +[program:helper] +command=python trade_manager/helper.py +autostart=false + +[group:bitfinex] +programs=bitfinexm,bitfinexl +autostart=false + +[program:bitfinexm] +command=bitfinexm + +[program:bitfinexl] +command=bitfinexl + +[program:kraken] +command=krakenm +autostart=false + +[group:poloniex] +programs=poloniexm,poloniexl +autostart=false + +[program:poloniexm] +command=poloniexm + +[program:poloniexl] +command=poloniexl + +[eventlistener:trade_listener] +command=tapplistener +events=PROCESS_STATE +buffer_size=100 diff --git a/test/test_commands.py b/test/test_commands.py index 2973b44..d2095a9 100644 --- a/test/test_commands.py +++ b/test/test_commands.py @@ -1,5 +1,4 @@ import json -import os import time import unittest from ledger import Amount @@ -7,47 +6,29 @@ from jsonschema import validate from sqlalchemy_models import get_schemas, wallet as wm, exchange as em +from tappmq.tappmq import get_status -from test.helpers import TestPlugin -from trade_manager import ses +from trade_manager.helper import TestPlugin, start_test_man, stop_test_man from trade_manager.cli import handle_command -from trade_manager.plugin import get_orders, get_status, get_trades, sync_ticker, get_debits, sync_balances, \ - get_credits, \ +from trade_manager.plugin import get_orders, get_trades, sync_ticker, get_debits, sync_balances, get_credits, \ make_ledger, get_ticker, get_balances, create_order, sync_orders, cancel_orders, sync_credits, sync_debits, \ - sync_trades + sync_trades, submit_order -tp = TestPlugin(session=ses) SCHEMAS = get_schemas() - -def start_test_man(): - os.system("python test/helpers.py start") - status = 'blocked' - countdown = 30 - while status != 'running' and countdown > 0: - countdown -= 1 - status = get_status('test') - time.sleep(0.01) - - -def stop_test_man(): - os.system("python test/helpers.py stop") - status = 'running' - countdown = 30 - while status != 'blocked' and countdown > 0: - countdown -= 1 - status = get_status('test') - time.sleep(0.01) +tp = TestPlugin() +tp.setup_connections() def test_status(): - status = get_status('test') + stop_test_man() + status = get_status('helper') assert status == 'stopped' start_test_man() - status = get_status('test') + status = get_status('helper') assert status == 'running' stop_test_man() - status = get_status('test') + status = get_status('helper') assert status == 'stopped' @@ -56,37 +37,35 @@ def test_ledger(): tp.session.query(wm.Credit).delete() tp.session.query(wm.Debit).delete() tp.session.commit() - tp.session.close() tp.sync_credits() tp.sync_debits() tp.sync_trades() - trades = get_trades('test', session=tp.session) + trades = get_trades('helper', session=tp.session) countdown = 30 while len(trades) != 1 and countdown > 0: countdown -= 1 - trades = get_trades('test', session=tp.session) + trades = get_trades('helper', session=tp.session) if len(trades) != 1: time.sleep(0.01) - tp.session.close() credit = get_credits(session=tp.session)[0] debit = get_debits(session=tp.session)[0] trade = trades[0] - ledger = make_ledger('test') - hardledger = """{0} test credit BTC - Assets:test:BTC:credit 1.00000000 BTC + ledger = make_ledger('helper') + hardledger = """{0} helper credit BTC + Assets:helper:BTC:credit 1.00000000 BTC Equity:Wallet:BTC:debit -1.00000000 BTC -{1} test debit BTC - Assets:test:BTC:debit -1.00000000 BTC +{1} helper debit BTC + Assets:helper:BTC:debit -1.00000000 BTC Equity:Wallet:BTC:credit 1.00000000 BTC P {2} BTC 100.00000000 USD P {2} USD 0.01000000 BTC -{2} test BTC_USD buy - ; - Assets:test:USD -1.00000000 USD @ 0.01000000 BTC +{2} helper BTC_USD buy + ; + Assets:helper:USD -1.00000000 USD @ 0.01000000 BTC FX:BTC_USD:buy 1.00000000 USD @ 0.01000000 BTC - Assets:test:BTC 0.01000000 BTC @ 100.00000000 USD + Assets:helper:BTC 0.01000000 BTC @ 100.00000000 USD FX:BTC_USD:buy -0.01000000 BTC @ 100.00000000 USD """.format(credit.time.strftime('%Y/%m/%d %H:%M:%S'), debit.time.strftime('%Y/%m/%d %H:%M:%S'), @@ -101,30 +80,30 @@ def setUp(self): def tearDown(self): stop_test_man() - def test_ticker(self): - sync_ticker('test', 'BTC_USD') - ticker = get_ticker('test', 'BTC_USD') - tick = json.loads(ticker) - assert validate(tick, SCHEMAS['Ticker']) is None - def test_balance(self): - sync_balances('test') - total, available = get_balances('test', session=tp.session) + sync_balances('helper') + total, available = get_balances('helper', session=tp.session) + countdown = 300 + while total is None and countdown > 0: + countdown -= 1 + try: + total, available = get_balances('helper', session=tp.session) + except Exception: + pass + total, available = get_balances('helper', session=tp.session) assert isinstance(total, Balance) assert isinstance(available, Balance) for amount in total: assert amount >= available.commodity_amount(amount.commodity) - def test_order_lifecycle(self): - order = create_order('test', 100, 0.1, 'BTC_USD', 'bid', session=tp.session) + def test_cancel_order_order_id(self): + order = create_order('helper', 100, 0.1, 'BTC_USD', 'bid', session=tp.session, submit=False) assert isinstance(order.id, int) assert isinstance(order.price, Amount) assert order.price == Amount("100 USD") assert order.state == 'pending' - porder = get_orders(oid=order.id, session=tp.session) - assert len(porder) == 1 - assert porder[0].state == 'pending' - sync_orders('test') + tp.session.close() + submit_order('helper', order.id) oorder = get_orders(oid=order.id, session=tp.session) countdown = 30 while oorder[0].state == 'pending' and countdown > 0: @@ -135,28 +114,27 @@ def test_order_lifecycle(self): tp.session.close() assert len(oorder) == 1 assert oorder[0].state == 'open' - cancel_orders('test', oid=order.id) - countdown = 30 - corder = get_orders('test', order_id=oorder[0].order_id, session=tp.session) - while corder[0].state != 'closed' and countdown > 0: + assert 'helper|' in oorder[0].order_id + tp.session.close() + cancel_orders('helper', order_id=oorder[0].order_id) + corder = get_orders(oid=order.id, session=tp.session) + while (len(corder) == 0 or corder[0].state != 'closed') and countdown > 0: countdown -= 1 - corder = get_orders('test', order_id=oorder[0].order_id, session=tp.session) + corder = get_orders(oid=order.id, session=tp.session) if corder[0].state != 'closed': time.sleep(0.01) tp.session.close() assert len(corder) == 1 assert corder[0].state == 'closed' - def test_cancel_order_order_id(self): - order = create_order('test', 100, 0.1, 'BTC_USD', 'bid', session=tp.session) + def test_cancel_order_order_id_no_prefix(self): + order = create_order('helper', 100, 0.1, 'BTC_USD', 'bid', session=tp.session, submit=False) assert isinstance(order.id, int) assert isinstance(order.price, Amount) assert order.price == Amount("100 USD") assert order.state == 'pending' - porder = get_orders(oid=order.id, session=tp.session) - assert len(porder) == 1 - assert porder[0].state == 'pending' - sync_orders('test') + tp.session.close() + submit_order('helper', order.id) oorder = get_orders(oid=order.id, session=tp.session) countdown = 30 while oorder[0].state == 'pending' and countdown > 0: @@ -167,58 +145,54 @@ def test_cancel_order_order_id(self): tp.session.close() assert len(oorder) == 1 assert oorder[0].state == 'open' - cancel_orders('test', order_id=oorder[0].order_id) - corder = get_orders('test', order_id=order.order_id.split("|")[1], session=tp.session) - while corder[0].state != 'closed' and countdown > 0: + assert 'helper|' in oorder[0].order_id + tp.session.close() + cancel_orders('helper', order_id=oorder[0].order_id.split("|")[1]) + corder = get_orders(oid=oorder[0].id, session=tp.session) + while (len(corder) == 0 or corder[0].state != 'closed') and countdown > 0: countdown -= 1 - corder = get_orders('test', order_id=order.order_id.split("|")[1], session=tp.session) + corder = get_orders(oid=oorder[0].id, session=tp.session) if corder[0].state != 'closed': time.sleep(0.01) tp.session.close() assert len(corder) == 1 assert corder[0].state == 'closed' - def test_cancel_order_order_id_no_prefix(self): - order = create_order('test', 100, 0.1, 'BTC_USD', 'bid', session=tp.session) - assert isinstance(order.id, int) - assert isinstance(order.price, Amount) - assert order.price == Amount("100 USD") - assert order.state == 'pending' - porder = get_orders(oid=order.id, session=tp.session) - assert len(porder) == 1 - assert porder[0].state == 'pending' - sync_orders('test') - oorder = get_orders(oid=order.id, session=tp.session) + def test_cancel_orders_by_market(self): + create_order('helper', 100, 0.1, 'BTC_USD', 'bid', session=tp.session, submit=False) + create_order('helper', 100, 0.1, 'BTC_USD', 'bid', session=tp.session, submit=False) + obids = len(get_orders(side='bid', state='pending', session=tp.session)) + assert obids >= 2 + create_order('helper', 100, 0.1, 'BTC_USD', 'ask', session=tp.session, submit=False) + create_order('helper', 100, 0.1, 'BTC_USD', 'ask', session=tp.session, submit=False) + create_order('helper', 100, 0.1, 'DASH_BTC', 'ask', session=tp.session, submit=False) + oasks = len(get_orders(side='ask', state='pending', session=tp.session)) + assert oasks >= 3 + tp.session.close() + cancel_orders('helper', market='BTC_USD') + orders = len(get_orders(market='BTC_USD', state='pending', session=tp.session)) countdown = 30 - while oorder[0].state == 'pending' and countdown > 0: - countdown -= 1 - oorder = get_orders(oid=order.id, session=tp.session) - if oorder[0].state == 'pending': - time.sleep(0.01) - tp.session.close() - assert len(oorder) == 1 - assert oorder[0].state == 'open' - cancel_orders('test', order_id=order.order_id.split("|")[1]) - corder = get_orders(oid=order.id, session=tp.session) - while corder[0].state != 'closed' and countdown > 0: + while orders != 0 and countdown > 0: countdown -= 1 - corder = get_orders(oid=order.id, session=tp.session) - if corder[0].state != 'closed': + orders = len(get_orders(market='BTC_USD', state='pending', session=tp.session)) + if orders != 0: time.sleep(0.01) tp.session.close() - assert len(corder) == 1 - assert corder[0].state == 'closed' + assert orders == 0 + dorders = len(get_orders(market='DASH_BTC', state='pending', session=tp.session)) + assert dorders >= 1 def test_cancel_orders_by_side(self): - create_order('test', 100, 0.1, 'BTC_USD', 'bid', session=tp.session) - create_order('test', 100, 0.1, 'BTC_USD', 'bid', session=tp.session) + create_order('helper', 100, 0.1, 'BTC_USD', 'bid', session=tp.session, submit=False) + create_order('helper', 100, 0.1, 'BTC_USD', 'bid', session=tp.session, submit=False) obids = len(get_orders(side='bid', state='pending', session=tp.session)) assert obids >= 2 - create_order('test', 100, 0.1, 'BTC_USD', 'ask', session=tp.session) - create_order('test', 100, 0.1, 'BTC_USD', 'ask', session=tp.session) + create_order('helper', 100, 0.1, 'BTC_USD', 'ask', session=tp.session, submit=False) + create_order('helper', 100, 0.1, 'BTC_USD', 'ask', session=tp.session, submit=False) oasks = len(get_orders(side='ask', state='pending', session=tp.session)) assert oasks >= 2 - cancel_orders('test', side='bid') + tp.session.close() + cancel_orders('helper', side='bid') bids = len(get_orders(side='bid', state='pending', session=tp.session)) countdown = 30 while bids != 0 and countdown > 0: @@ -228,6 +202,7 @@ def test_cancel_orders_by_side(self): time.sleep(0.01) tp.session.close() assert bids == 0 + tp.session.close() asks = len(get_orders(side='ask', state='pending', session=tp.session)) countdown = 30 while asks != 0 and countdown > 0: @@ -239,78 +214,89 @@ def test_cancel_orders_by_side(self): assert asks > 0 assert oasks == asks - def test_cancel_orders_by_market(self): - create_order('test', 100, 0.1, 'BTC_USD', 'bid', session=tp.session) - create_order('test', 100, 0.1, 'BTC_USD', 'bid', session=tp.session) - obids = len(get_orders(side='bid', state='pending', session=tp.session)) - assert obids >= 2 - create_order('test', 100, 0.1, 'BTC_USD', 'ask', session=tp.session) - create_order('test', 100, 0.1, 'BTC_USD', 'ask', session=tp.session) - create_order('test', 100, 0.1, 'DASH_BTC', 'ask', session=tp.session) - oasks = len(get_orders(side='ask', state='pending', session=tp.session)) - assert oasks >= 3 - cancel_orders('test', market='BTC_USD') - bids = len(get_orders(side='bid', state='pending', session=tp.session)) - countdown = 30 - while bids != 0 and countdown > 0: - countdown -= 1 - bids = len(get_orders(side='bid', state='pending', session=tp.session)) - if bids != 0: - time.sleep(0.01) - tp.session.close() - assert bids == 0 - asks = len(get_orders(side='ask', state='pending', session=tp.session)) - countdown = 30 - while asks != 0 and countdown > 0: + def test_order_lifecycle(self): + order = create_order('helper', 100, 0.1, 'BTC_USD', 'bid', session=tp.session, submit=False) + assert isinstance(order.id, int) + assert isinstance(order.price, Amount) + assert order.price == Amount("100 USD") + assert order.state == 'pending' + porder = get_orders(oid=order.id, session=tp.session) + assert len(porder) == 1 + assert porder[0].state == 'pending' + tp.session.close() + submit_order('helper', order.id) + oorder = get_orders(oid=order.id, session=tp.session) + countdown = 300 + while (len(oorder) == 0 or oorder[0].state != 'open') and countdown > 0: countdown -= 1 - asks = len(get_orders(side='ask', state='pending', session=tp.session)) - if asks != 0: + oorder = get_orders(oid=order.id, session=tp.session) + if oorder[0].state != 'open': time.sleep(0.01) tp.session.close() - assert asks >= 1 - - def test_book(self): - pass - - def test_sync_trades(self): - trades = len(get_trades('test', session=tp.session)) - sync_trades('test') - newtrades = len(get_trades('test', session=tp.session)) + assert len(oorder) == 1 + assert oorder[0].state == 'open' + tp.session.close() + cancel_orders('helper', oid=order.id) countdown = 30 - while newtrades == trades and countdown > 0: + corder = get_orders('helper', order_id=oorder[0].order_id, session=tp.session) + while corder[0].state != 'closed' and countdown > 0: countdown -= 1 - newtrades = len(get_trades('test', session=tp.session)) - if newtrades == trades: + corder = get_orders('helper', order_id=oorder[0].order_id, session=tp.session) + if corder[0].state != 'closed': time.sleep(0.01) tp.session.close() - assert newtrades > trades + assert len(corder) == 1 + assert corder[0].state == 'closed' def test_sync_credits(self): - credits = len(get_credits('test', session=tp.session)) - sync_credits('test') - newcreds = len(get_credits('test', session=tp.session)) + credits = len(get_credits('helper', session=tp.session)) + sync_credits('helper') + newcreds = len(get_credits('helper', session=tp.session)) countdown = 30 while newcreds == credits and countdown > 0: countdown -= 1 - newcreds = len(get_credits('test', session=tp.session)) + newcreds = len(get_credits('helper', session=tp.session)) if newcreds == credits: time.sleep(0.01) - tp.session.close() assert newcreds > credits def test_sync_debits(self): - debits = len(get_debits('test', session=tp.session)) - sync_debits('test') - newdebs = len(get_debits('test', session=tp.session)) + debits = len(get_debits('helper', session=tp.session)) + sync_debits('helper') + newdebs = len(get_debits('helper', session=tp.session)) countdown = 30 while newdebs == debits and countdown > 0: countdown -= 1 - newdebs = len(get_debits('test', session=tp.session)) + newdebs = len(get_debits('helper', session=tp.session)) if newdebs == debits: time.sleep(0.01) - tp.session.close() assert newdebs > debits + def test_sync_trades(self): + trades = len(get_trades('helper', session=tp.session)) + sync_trades('helper') + newtrades = len(get_trades('helper', session=tp.session)) + countdown = 30 + while newtrades == trades and countdown > 0: + countdown -= 1 + newtrades = len(get_trades('helper', session=tp.session)) + if newtrades == trades: + time.sleep(0.01) + assert newtrades > trades + + def test_ticker(self): + sync_ticker('helper', 'BTC_USD') + tick = None + countdown = 300 + while tick is None and countdown > 0: + countdown -= 1 + ticker = get_ticker('helper', 'BTC_USD') + try: + tick = json.loads(ticker) + except (ValueError, TypeError): + pass + assert validate(tick, SCHEMAS['Ticker']) is None + class TestCLI(unittest.TestCase): def setUp(self): @@ -319,78 +305,83 @@ def setUp(self): def tearDown(self): stop_test_man() - def test_ticker(self): - sync_ticker('test', 'BTC_USD') - ticker = "" - countdown = 30 - while (isinstance(ticker, int) or ticker == '') and countdown > 0: - countdown -= 1 - time.sleep(0.1) - ticker = handle_command(['ticker', 'get', '-e', 'test']) - assert '{"volume": 1000.0, "last": 100.0, "exchange": "test", "bid": 99.0, "high": 110.0, ' \ - '"low": 90.0, "time": "' in ticker - assert '", "ask": 101.0, "market": "BTC_USD"}' in ticker - def test_balance(self): - sync_balances('test') + sync_balances('helper') balance = "" countdown = 30 while (isinstance(balance, int) or balance == '') and countdown > 0: countdown -= 1 time.sleep(0.1) - balance = handle_command(['balance', 'get', '-e', 'test']) + balance = handle_command(['balance', 'get', '-e', 'helper'], session=tp.session) assert str(balance) == "['0', '0']" def test_order_lifecycle(self): - order = str(handle_command(['order', 'create', 'bid', '100', '0.1', 'BTC_USD', 'test'])) + order = str(handle_command(['order', 'create', 'bid', '100', '0.1', 'BTC_USD', 'helper'], session=tp.session)) order_id = order[order.find("order_id") + 10: order.find("state") - 3] exporder = " 0: + while (len(oorder) == 0 or oorder[0].state != 'open') and countdown > 0: countdown -= 1 - oorder = get_orders('test', order_id=order_id, session=tp.session) - if len(oorder) == 0 or oorder[0].state == 'pending': + oorder = get_orders('helper', order_id=order_id, session=tp.session) + if len(oorder) == 0 or oorder[0].state != 'open': time.sleep(0.01) tp.session.close() assert len(oorder) == 1 assert oorder[0].state == 'open' - clioorder = str(handle_command(['order', 'get', '-e', 'test', '--order_id', order_id.split("|")[1]])) + clioorder = str( + handle_command(['order', 'get', '-e', 'helper', '--order_id', order_id.split("|")[1]], session=tp.session)) assert exporder in clioorder.strip('[]') - handle_command(['order', 'cancel', 'test', '--order_id', order_id.replace('tmp', 'test').split("|")[1]]) + handle_command(['order', 'cancel', 'helper', '--order_id', order_id.replace('tmp', 'helper').split("|")[1]], + session=tp.session) exporder = exporder.replace("open", "closed") countdown = 30 - corder = get_orders('test', order_id=oorder[0].order_id, session=tp.session) + tp.session.close() + corder = get_orders('helper', order_id=oorder[0].order_id, session=tp.session) while corder[0].state != 'closed' and countdown > 0: countdown -= 1 - corder = get_orders('test', order_id=oorder[0].order_id, session=tp.session) + corder = get_orders('helper', order_id=oorder[0].order_id, session=tp.session) if corder[0].state != 'closed': time.sleep(0.01) tp.session.close() assert len(corder) == 1 assert corder[0].state == 'closed' - clicorder = str(handle_command(['order', 'get', '-e', 'test', '--order_id', order_id.split("|")[1]])) - assert exporder.replace("pending", "closed").replace('tmp', 'test') in clicorder.strip('[]') + clicorder = str( + handle_command(['order', 'get', '-e', 'helper', '--order_id', order_id.split("|")[1]], session=tp.session)) + assert exporder in clicorder.strip('[]') def test_sync_trades(self): - trades = len(get_trades('test', session=tp.session)) - handle_command(['trade', 'sync', '-e', 'test']) - newtrades = len(get_trades('test', session=tp.session)) + trades = len(get_trades('helper', session=tp.session)) + handle_command(['trade', 'sync', '-e', 'helper'], session=tp.session) + tp.session.close() + newtrades = len(get_trades('helper', session=tp.session)) countdown = 30 while newtrades == trades and countdown > 0: countdown -= 1 - newtrades = len(get_trades('test', session=tp.session)) + newtrades = len(get_trades('helper', session=tp.session)) if newtrades == trades: time.sleep(0.01) tp.session.close() assert newtrades > trades + + def test_ticker(self): + sync_ticker('helper', 'BTC_USD') + ticker = "" + countdown = 30 + while (isinstance(ticker, int) or ticker == '') and countdown > 0: + countdown -= 1 + time.sleep(0.1) + ticker = handle_command(['ticker', 'get', '-e', 'helper'], session=tp.session) + assert '{"volume": 1000.0, "last": 100.0, "exchange": "helper", "bid": 99.0, "high": 110.0, ' \ + '"low": 90.0, "time": "' in ticker + assert '", "ask": 101.0, "market": "BTC_USD"}' in ticker diff --git a/test/test_manager.py b/test/test_manager.py index 4812e27..1d59ef5 100644 --- a/test/test_manager.py +++ b/test/test_manager.py @@ -5,23 +5,24 @@ from jsonschema import validate from sqlalchemy_models import get_schemas, exchange as em -from test.helpers import TestPlugin, make_base_id +from trade_manager.helper import TestPlugin, make_base_id from trade_manager.plugin import get_ticker, get_balances, get_orders, get_trades tp = TestPlugin() +tp.setup_connections() SCHEMAS = get_schemas() def test_ticker(): tp.sync_ticker('BTC_USD') - ticker = get_ticker('test', 'BTC_USD') + ticker = get_ticker('helper', 'BTC_USD') tick = json.loads(ticker) assert validate(tick, SCHEMAS['Ticker']) is None def test_balance(): tp.sync_balances() - total, available = get_balances('test', session=tp.session) + total, available = get_balances('helper', session=tp.session) assert isinstance(total, Balance) assert isinstance(available, Balance) for amount in total: @@ -29,7 +30,7 @@ def test_balance(): def test_order_lifecycle(): - order = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'test', order_id=make_base_id(l=10)) + order = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'helper', order_id=make_base_id(l=10)) assert isinstance(order.price, Amount) assert order.price == Amount("100 USD") assert order.state == 'pending' @@ -41,25 +42,25 @@ def test_order_lifecycle(): assert isinstance(torder.id, int) assert isinstance(torder.price, Amount) assert torder.price == Amount("100 USD") - assert torder.state == 'pending' + assert torder.state == 'open' def test_cancel_order_order_id(): - order = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'test', order_id=make_base_id(l=10)) + order = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'helper', order_id=make_base_id(l=10)) tp.session.add(order) tp.session.commit() order = tp.create_order(order.id) assert isinstance(order.id, int) assert isinstance(order.price, Amount) assert order.price == Amount("100 USD") - assert order.state == 'pending' - porder = get_orders(oid=order.id, session=tp.session) - assert len(porder) == 1 - assert porder[0].state == 'pending' - tp.sync_orders() - oorder = get_orders(oid=order.id, session=tp.session) - assert len(oorder) == 1 - assert oorder[0].state == 'open' + assert order.state == 'open' + # porder = get_orders(oid=order.id, session=tp.session) + # assert len(porder) == 1 + # assert porder[0].state == 'open' + # tp.sync_orders() + # oorder = get_orders(oid=order.id, session=tp.session) + # assert len(oorder) == 1 + # assert oorder[0].state == 'open' tp.cancel_orders(order_id=order.order_id) corder = get_orders(oid=order.id, session=tp.session) assert len(corder) == 1 @@ -67,17 +68,17 @@ def test_cancel_order_order_id(): def test_cancel_order_order_id_no_prefix(): - order = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'test', order_id=make_base_id(l=10)) + order = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'helper', order_id=make_base_id(l=10)) tp.session.add(order) tp.session.commit() order = tp.create_order(order.id) assert isinstance(order.id, int) assert isinstance(order.price, Amount) assert order.price == Amount("100 USD") - assert order.state == 'pending' - porder = get_orders(oid=order.id, session=tp.session) - assert len(porder) == 1 - assert porder[0].state == 'pending' + assert order.state == 'open' + # porder = get_orders(oid=order.id, session=tp.session) + # assert len(porder) == 1 + # assert porder[0].state == 'pending' tp.cancel_orders(order_id=order.order_id.split("|")[1]) corder = get_orders(oid=order.id, session=tp.session) assert len(corder) == 1 @@ -85,9 +86,9 @@ def test_cancel_order_order_id_no_prefix(): def test_cancel_orders_by_side(): - order = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'test', order_id=make_base_id(l=10)) + order = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'helper', order_id=make_base_id(l=10)) tp.session.add(order) - order2 = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'test', order_id=make_base_id(l=10)) + order2 = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'helper', order_id=make_base_id(l=10)) tp.session.add(order2) tp.session.commit() tp.create_order(order.id) @@ -95,9 +96,9 @@ def test_cancel_orders_by_side(): obids = tp.session.query(em.LimitOrder).filter(em.LimitOrder.side == 'bid') \ .filter(em.LimitOrder.state != 'closed').count() assert obids >= 2 - order = em.LimitOrder(100, 0.1, 'BTC_USD', 'ask', 'test', order_id=make_base_id(l=10)) + order = em.LimitOrder(100, 0.1, 'BTC_USD', 'ask', 'helper', order_id=make_base_id(l=10)) tp.session.add(order) - order2 = em.LimitOrder(100, 0.1, 'BTC_USD', 'ask', 'test', order_id=make_base_id(l=10)) + order2 = em.LimitOrder(100, 0.1, 'BTC_USD', 'ask', 'helper', order_id=make_base_id(l=10)) tp.session.add(order2) tp.session.commit() tp.create_order(order.id) @@ -116,9 +117,9 @@ def test_cancel_orders_by_side(): def test_cancel_orders_by_market(): - order = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'test', order_id=make_base_id(l=10)) + order = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'helper', order_id=make_base_id(l=10)) tp.session.add(order) - order2 = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'test', order_id=make_base_id(l=10)) + order2 = em.LimitOrder(100, 0.1, 'BTC_USD', 'bid', 'helper', order_id=make_base_id(l=10)) tp.session.add(order2) tp.session.commit() tp.create_order(order.id) @@ -126,11 +127,11 @@ def test_cancel_orders_by_market(): obids = tp.session.query(em.LimitOrder).filter(em.LimitOrder.side == 'bid') \ .filter(em.LimitOrder.state != 'closed').count() assert obids >= 2 - order = em.LimitOrder(100, 0.1, 'BTC_USD', 'ask', 'test', order_id=make_base_id(l=10)) + order = em.LimitOrder(100, 0.1, 'BTC_USD', 'ask', 'helper', order_id=make_base_id(l=10)) tp.session.add(order) - order2 = em.LimitOrder(100, 0.1, 'BTC_USD', 'ask', 'test', order_id=make_base_id(l=10)) + order2 = em.LimitOrder(100, 0.1, 'BTC_USD', 'ask', 'helper', order_id=make_base_id(l=10)) tp.session.add(order2) - order3 = em.LimitOrder(100, 0.1, 'DASH_BTC', 'ask', 'test', order_id=make_base_id(l=10)) + order3 = em.LimitOrder(100, 0.1, 'DASH_BTC', 'ask', 'helper', order_id=make_base_id(l=10)) tp.session.add(order3) tp.session.commit() tp.create_order(order.id) @@ -149,15 +150,15 @@ def test_cancel_orders_by_market(): def test_get_trades(): - trade = em.Trade(make_base_id(l=10), 'test', 'BTC_USD', 'buy', 0.1, 100, 0, 'quote') + trade = em.Trade(make_base_id(l=10), 'helper', 'BTC_USD', 'buy', 0.1, 100, 0, 'quote') assert isinstance(trade.price, Amount) assert trade.price == Amount("100 USD") tp.session.add(trade) trade2 = em.Trade(make_base_id(l=10), 'kraken', 'BTC_USD', 'sell', 0.1, 100, 0, 'quote') tp.session.add(trade2) - trade3 = em.Trade(make_base_id(l=10), 'test', 'DASH_BTC', 'buy', 0.1, 100, 0, 'quote') + trade3 = em.Trade(make_base_id(l=10), 'helper', 'DASH_BTC', 'buy', 0.1, 100, 0, 'quote') tp.session.add(trade3) - trade4 = em.Trade(make_base_id(l=10), 'test', 'BTC_USD', 'buy', 0.1, 100, 0, 'quote') + trade4 = em.Trade(make_base_id(l=10), 'helper', 'BTC_USD', 'buy', 0.1, 100, 0, 'quote') tp.session.add(trade4) tp.session.commit() assert isinstance(trade.id, int) @@ -170,7 +171,7 @@ def test_get_trades(): assert len(trades) == 1 assert trades[0].id == trade.id - trades = get_trades('test', trade_id=trade.trade_id.split("|")[1], session=tp.session) + trades = get_trades('helper', trade_id=trade.trade_id.split("|")[1], session=tp.session) assert len(trades) == 1 assert trades[0].id == trade.id @@ -183,7 +184,3 @@ def test_get_trades(): assert len(trades) >= 1 for trade in trades: assert trade.market == 'DASH_BTC' - - -def test_book(): - pass diff --git a/trade_manager/__init__.py b/trade_manager/__init__.py index a5f4d0f..251bd91 100644 --- a/trade_manager/__init__.py +++ b/trade_manager/__init__.py @@ -9,3 +9,4 @@ setup_database(eng, modules=[wm, em, um]) NETWORK_COMMODITY_MAP = {'BTC': 'Bitcoin', 'DASH': 'Dash', 'ETH': 'Ethereum', 'LTC': 'Litecoin'} +EXCHANGES = ['kraken', 'bitfinex', 'poloniex'] diff --git a/trade_manager/cli.py b/trade_manager/cli.py index 459d68c..cc84900 100644 --- a/trade_manager/cli.py +++ b/trade_manager/cli.py @@ -1,11 +1,11 @@ import argparse import sys - +import time +from ledger import Amount from tapp_config import setup_redis - from trade_manager import ses from trade_manager.plugin import sync_ticker, get_ticker, sync_orders, make_ledger, get_orders, cancel_orders, \ - get_balances, sync_balances, get_trades, sync_trades, create_order + get_balances, sync_balances, get_trades, sync_trades, create_order, sync_credits, sync_debits red = setup_redis() @@ -13,7 +13,7 @@ def handle_ticker_command(argv, parsers): parser = argparse.ArgumentParser(parents=parsers) parser.add_argument("subcommand", choices=["get", "sync"], help='The order sub-command to run.') - parser.add_argument("-m", default="BTC_USD", help='The market to get a ticker for.') + parser.add_argument("-m", help='The market to get a ticker for.') parser.add_argument("-e", help='The exchange to get a ticker for.') args = parser.parse_args(argv) if args.subcommand == "get": @@ -22,15 +22,22 @@ def handle_ticker_command(argv, parsers): sync_ticker(args.e, args.m) -def handle_ledger_command(argv, parsers): +def handle_ledger_command(argv, parsers, session=ses): parser = argparse.ArgumentParser(parents=parsers) + parser.add_argument("subcommand", choices=["get", "sync"], help='The ledger sub-command to run.') parser.add_argument("-e", help='The exchange to get a ledger for.') + parser.add_argument('--rescan', dest='rescan', action='store_true') + parser.add_argument('--no-rescan', dest='rescan', action='store_false') + parser.set_defaults(rescan=False) args = parser.parse_args(argv) - exchange = args.e - return make_ledger(exchange=exchange) + if args.subcommand == "get": + return make_ledger(exchange=args.e, session=session) + elif args.subcommand == "sync": + sync_credits(exchange=args.e, rescan=args.rescan) + sync_debits(exchange=args.e, rescan=args.rescan) -def handle_order_command(argv, parsers): +def handle_order_command(argv, parsers, session=ses): oparser = argparse.ArgumentParser(parents=parsers, add_help=False) oparser.add_argument("subcommand", choices=['get', 'sync', 'create', 'cancel'], help='Order sub-commands') @@ -38,23 +45,23 @@ def handle_order_command(argv, parsers): # parsers.append(oparser) parsers = [oparser] if args.subcommand == 'get': - return handle_get_order(argv, parsers) + return handle_get_order(argv, parsers, session=session) elif args.subcommand == 'sync': return handle_sync_order(argv, parsers) elif args.subcommand == 'create': - return handle_create_order(argv, parsers) + return handle_create_order(argv, parsers, session=session) elif args.subcommand == 'cancel': return handle_cancel_order(argv, parsers) -def handle_get_order(argv, parsers): +def handle_get_order(argv, parsers, session=ses): oparser = argparse.ArgumentParser(parents=parsers) oparser.add_argument("--oid", help='The order id.') oparser.add_argument("--order_id", help='The order order_id.') oparser.add_argument("-m", help='The order market.') oparser.add_argument("-e", help='The order exchange.') args = oparser.parse_args(argv) - return get_orders(exchange=args.e, market=args.m, oid=args.oid, order_id=args.order_id, session=ses) + return get_orders(exchange=args.e, market=args.m, oid=args.oid, order_id=args.order_id, session=session) def handle_sync_order(argv, parsers): @@ -68,7 +75,7 @@ def handle_sync_order(argv, parsers): sync_orders(args.e, data) -def handle_create_order(argv, parsers): +def handle_create_order(argv, parsers, session=ses): oparser = argparse.ArgumentParser(parents=parsers) oparser.add_argument("side", choices=['bid', 'ask'], help='The order side') oparser.add_argument("amount", help='The order amount') @@ -76,7 +83,7 @@ def handle_create_order(argv, parsers): oparser.add_argument("market", help='The order market') oparser.add_argument("exchange", help='The order exchange') args = oparser.parse_args(argv) - return create_order(args.exchange, float(args.price), float(args.amount), args.market, args.side, session=ses) + return create_order(args.exchange, float(args.price), float(args.amount), args.market, args.side, session=session) def handle_cancel_order(argv, parsers): @@ -90,39 +97,76 @@ def handle_cancel_order(argv, parsers): return cancel_orders(args.e, args.m, side=args.s, oid=args.oid, order_id=args.order_id) -def handle_balance_command(argv, parsers): +def handle_balance_command(argv, parsers, session=ses): bparser = argparse.ArgumentParser(parents=parsers) - bparser.add_argument("subcommand", choices=["get", "sync"], help='The balance sub-command to run.') + bparser.add_argument("subcommand", choices=["get", "sync", "summary"], help='The balance sub-command to run.') bparser.add_argument("-e", help='The exchange.') bparser.add_argument("-c", help='The currency.') args = bparser.parse_args(argv) if args.subcommand == 'get': - bals = get_balances(args.e, session=ses) + bals = get_balances(exchange=args.e, currency=args.c, session=session) rbals = [] for bal in bals: rbals.append(bal.to_string()) return rbals elif args.subcommand == 'sync': return sync_balances(args.e) - - -def handle_trade_command(argv, parsers): + elif args.subcommand == 'summary': + return get_balance_summary(session=session) + + +def get_balance_summary(session=ses): + bals = get_balances(session=session) + resp = "\n_______ %s _______\n" % time.asctime(time.gmtime(time.time())) + usdtotal = Amount("0 USD") + details = {} + for amount in bals[0]: + comm = str(amount.commodity) + if comm == 'USD': + inde = Amount("1 USD") + details['USD'] = {'index': inde, 'amount': amount} + usdtotal = usdtotal + amount + else: + ticker = get_ticker(market="%s_USD" % comm, red=red) + if not ticker: + resp += "skipping inactive bal %s\n" % amount + continue + inde = ticker.calculate_index() + details[comm] = {'index': inde, 'amount': Amount("%s USD" % amount.number()) * inde} + usdtotal = usdtotal + details[comm]['amount'] + resp += "\nTotal Value:\t$%s\n\n" % usdtotal.number() + + for amount in bals[0]: + comm = str(amount.commodity) + if comm in details: + damount = details[comm]['amount'].to_double() + percent = (details[comm]['amount'] / usdtotal * Amount("100 USD")).to_double() + resp += "{0:16s}\t==\t${1:8.2f} ({2:3.2f}%)\t@ ${3:8.4f}\n".format(amount, damount, + percent, + details[comm]['index'].to_double()) + return resp + + +def handle_trade_command(argv, parsers, session=ses): tparser = argparse.ArgumentParser(parents=parsers) tparser.add_argument("subcommand", choices=["get", "sync"], help='The trade sub-command to run.') tparser.add_argument("-m", help='The market to get trades for.') tparser.add_argument("-e", help='The exchange to get trades for.') tparser.add_argument("--tid", help='The trade id to get.') + tparser.add_argument('--rescan', dest='rescan', action='store_true') + tparser.add_argument('--no-rescan', dest='rescan', action='store_false') + tparser.set_defaults(rescan=False) args = tparser.parse_args(argv) if args.subcommand == "get": - return get_trades(args.e, args.m, args.tid, session=ses) + return get_trades(args.e, args.m, args.tid, session=session) elif args.subcommand == "sync": - return sync_trades(args.e, args.m) + return sync_trades(exchange=args.e, market=args.m, rescan=args.rescan) -def handle_command(argv=sys.argv[1:]): +def handle_command(argv=sys.argv[1:], session=ses): parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("command", choices=['ticker', 'ledger', 'order', 'trade', 'balance', 'address'], + parser.add_argument("command", choices=['ticker', 'ledger', 'order', 'trade', 'balance', 'address', 'isysd'], help="'%(prog)s help' for usage details") if len(argv) == 0: parser.print_help() @@ -132,13 +176,18 @@ def handle_command(argv=sys.argv[1:]): if args.command == 'ticker': return handle_ticker_command(argv, [parser]) elif args.command == 'ledger': - return handle_ledger_command(argv, [parser]) + return handle_ledger_command(argv, [parser], session=session) elif args.command == 'order': - return handle_order_command(argv, [parser]) + return handle_order_command(argv, [parser], session=session) elif args.command == 'trade': - return handle_trade_command(argv, [parser]) + return handle_trade_command(argv, [parser], session=session) elif args.command == 'balance': - return handle_balance_command(argv, [parser]) + return handle_balance_command(argv, [parser], session=session) + elif args.command == 'isysd': # TODO + r = 10 + for i in range(r): + #create_order('bitfinex', 0.36+float(i)/100, 1000, 'BFX_USD', 'ask', session=session) + create_order('bitfinex', 0.32 - float(i) / 100, 1000, 'BFX_USD', 'bid', session=session) if __name__ == "__main__": diff --git a/test/helpers.py b/trade_manager/helper.py similarity index 69% rename from test/helpers.py rename to trade_manager/helper.py index eb10a01..6362a15 100755 --- a/test/helpers.py +++ b/trade_manager/helper.py @@ -1,18 +1,41 @@ import datetime +import os import random -import signal import string +import time from ledger import Amount from ledger import Balance -from daemon import runner -from sqlalchemy_models import jsonify2 +from sqlalchemy_models import jsonify2, create_session_engine, setup_database from sqlalchemy_models.util import filter_query_by_attr +from tappmq.tappmq import get_status -from trade_manager import em, wm, ses +from trade_manager import em, wm, um from trade_manager.plugin import ExchangePluginBase +def start_test_man(name='helper'): + command = name if name != 'test' else 'helper' + os.system("supervisorctl start %s" % command) + status = 'stopped' + countdown = 100 # 10 seconds + while status != 'running' and countdown > 0: + countdown -= 1 + status = get_status(name) + time.sleep(0.01) + + +def stop_test_man(name='helper'): + command = name if name != 'test' else 'helper' + os.system("supervisorctl stop %s" % command) + status = 'running' + countdown = 100 # 10 seconds + while status != 'stopped' and countdown > 0: + countdown -= 1 + status = get_status(name) + time.sleep(0.01) + + def make_base_id(l=10): tid = "tid" for i in range(l): @@ -24,7 +47,7 @@ class TestPlugin(ExchangePluginBase): """ An Exchange Plugin for testing purposes only. Fakes all data. """ - NAME = 'Test' + NAME = 'Helper' _user = None session = None @@ -47,7 +70,7 @@ def sync_book(cls, market=None): """ pass - def cancel_orders(self, oid=None, order_id=None, side=None, market=None): + def cancel_orders(self, oid=None, order_id=None, side=None, market=None, price=None): """ Cancel all orders, or optionally just those matching the parameters. :return: True if orders were successfully canceled or no orders exist, @@ -58,15 +81,26 @@ def cancel_orders(self, oid=None, order_id=None, side=None, market=None): .filter(em.LimitOrder.state != 'closed') order_id = order_id if order_id is None or ("|" in str(order_id)) \ else '%s|%s' % (self.NAME.lower(), order_id) + self.logger.debug(order_id) + if order_id: + assert "|" in order_id query = filter_query_by_attr(query, em.LimitOrder, 'id', oid) query = filter_query_by_attr(query, em.LimitOrder, 'order_id', order_id) query = filter_query_by_attr(query, em.LimitOrder, 'side', side) query = filter_query_by_attr(query, em.LimitOrder, 'market', market) + # TODO filter by price if side present for order in query: if side is not None: assert order.side == side order.state = 'closed' - self.session.commit() + self.logger.debug("order closed %s: %s" % (order.id, order)) + # self.session.add(order) + try: + self.session.commit() + except Exception as e: + self.logger.exception(e) + self.session.rollback() + self.session.flush() def create_order(self, oid): """ @@ -75,8 +109,20 @@ def create_order(self, oid): :return: The unique order id given by the exchange :rtype: str """ - order = self.session.query(em.LimitOrder).filter(em.LimitOrder.id == oid).one_or_none() - order.load_commodities() + order = self.session.query(em.LimitOrder).filter(em.LimitOrder.id == oid).first() + # self.logger.debug("order %s: %s" % (order.id, order)) + if order is not None: + order.state = 'open' + order.order_id = "%s|%s" % (order.exchange, order.order_id.split("|")[1]) + self.logger.debug("created order %s: %s" % (order.id, order)) + self.session.add(order) + try: + self.session.commit() + except Exception as e: + self.logger.exception(e) + self.session.rollback() + self.session.flush() + order.load_commodities() return order def sync_orders(self, market=None): @@ -88,20 +134,28 @@ def sync_orders(self, market=None): :return: a list of open orders as Order objects. :rtype: list """ + self.logger.debug("sync orders w market %s" % market) oorders = self.session.query(em.LimitOrder).filter(em.LimitOrder.exchange == self.NAME.lower()) \ .filter(em.LimitOrder.state == 'open') oorders = filter_query_by_attr(oorders, em.LimitOrder, 'market', market) for oorder in oorders: + self.logger.debug("closed order %s" % oorder.id) oorder.state = 'closed' self.session.add(oorder) porders = self.session.query(em.LimitOrder).filter(em.LimitOrder.exchange == self.NAME.lower()) \ .filter(em.LimitOrder.state == 'pending') porders = filter_query_by_attr(porders, em.LimitOrder, 'market', market) for porder in porders: + self.logger.debug("opened order %s" % porder.id) porder.state = 'open' porder.order_id = porder.order_id.replace("tmp", self.NAME.lower()) self.session.add(porder) - self.session.commit() + try: + self.session.commit() + except Exception as e: + self.logger.exception(e) + self.session.rollback() + self.session.flush() def sync_balances(self): """ @@ -129,7 +183,7 @@ def sync_ticker(self, market=None): :rtype: Ticker """ tick = em.Ticker(99, 101, 110, 90, 1000, - 100, market, 'test') + 100, market, 'helper') jtick = jsonify2(tick, 'Ticker') self.red.set('%s_%s_ticker' % (self.NAME.lower(), market), jtick) @@ -138,7 +192,7 @@ def sync_trades(self): :return: a list of trades, possibly only a subset of them. """ trade = em.Trade(make_base_id(), self.NAME.lower(), 'BTC_USD', 'buy', 0.01, 100, 0, 'quote') - print trade + # self.logger.debug("trade: %s" % trade) self.session.add(trade) self.session.commit() @@ -146,9 +200,9 @@ def sync_credits(self): """ :return: a list of credits, possibly only a subset of them. """ - credit = wm.Credit(1, make_base_id(10), 'BTC', 'Bitcoin', 'unconfirmed', "test", make_base_id(10), + credit = wm.Credit(1, make_base_id(10), 'BTC', 'Bitcoin', 'unconfirmed', 'helper', make_base_id(10), self.manager_user.id, time=datetime.datetime.utcnow()) - print credit + # self.logger.debug("credit: %s" % credit) self.session.add(credit) self.session.commit() @@ -156,18 +210,16 @@ def sync_debits(self): """ :return: a list of debits, possibly only a subset of them. """ - debit = wm.Debit(1, 0, make_base_id(10), 'BTC', 'Bitcoin', 'unconfirmed', "test", make_base_id(10), + debit = wm.Debit(1, 0, make_base_id(10), 'BTC', 'Bitcoin', 'unconfirmed', 'helper', make_base_id(10), self.manager_user.id, time=datetime.datetime.utcnow()) - print debit + # self.logger.debug("debit: %s" % debit) self.session.add(debit) self.session.commit() def main(): - tp = TestPlugin(session=ses) - daemon_runner = runner.DaemonRunner(tp) - daemon_runner.daemon_context.signal_map[signal.SIGTERM] = tp.terminate - daemon_runner.do_action() + tp = TestPlugin() + tp.run() if __name__ == "__main__": diff --git a/trade_manager/plugin.py b/trade_manager/plugin.py index 85f1464..ec4b851 100644 --- a/trade_manager/plugin.py +++ b/trade_manager/plugin.py @@ -1,25 +1,36 @@ import collections import json -import os import time from ledger import commodities, Balance -from sqlalchemy_models import sa, Base, util, create_session_engine -from sqlalchemy_models.util import filter_query_by_attr +import datetime + +from alchemyjsonschema.dictify import jsonify +from ledger import Amount + +from sqlalchemy_models import sa, Base, create_session_engine, jsonify2 +from sqlalchemy_models.util import filter_query_by_attr, multiply_tickers from tapp_config import get_config, setup_redis -from tappmq import publish, subscription_handler +from tappmq.tappmq import publish, MQHandlerBase, get_running_workers +from trade_manager import em, um, wm, ses, EXCHANGES -from trade_manager import em, um, wm, ses +red = setup_redis() -def set_status(name, status='loading'): - red = setup_redis() - if status in ['loading', 'running', 'stopped']: - print("setting %s_status to %s" % (name.lower(), status)) - red.set("%s_status" % name.lower(), status) +def get_order_by_order_id(order_id, exchange, session=None): + if session is None: + session = ses + if "|" in order_id: + order_id = order_id.split("|")[1] + order = session.query(em.LimitOrder)\ + .filter(em.LimitOrder.order_id == "%s|%s" % (exchange.lower(), order_id)).one_or_none() + if order is None: + order = session.query(em.LimitOrder).filter( + em.LimitOrder.order_id == "tmp|%s" % order_id).one_or_none() + return order -class ExchangePluginBase(object): +class ExchangePluginBase(MQHandlerBase): """ A parent class for Exchange Manager Plugins. Plugins should inherit from this class, and overwrite all of the methods @@ -30,72 +41,17 @@ class ExchangePluginBase(object): _user = None session = None - def __init__(self, key=None, secret=None, session=None, red=None, cfg=None): - self.cfg = get_config(self.NAME) if cfg is not None else get_config('trade_manager') - self.key = key if key is not None else self.cfg.get(self.NAME.lower(), 'key') - self.secret = secret if secret is not None else self.cfg.get(self.NAME.lower(), 'secret') - self.session = session if session is not None else create_session_engine(cfg=self.cfg)[0] - self.red = setup_redis() if red is None else red - # self.logger = setup_logging(self.cfg) - - markets = json.loads(self.cfg.get(self.NAME.lower(), 'live_pairs')) + def __init__(self, key=None, secret=None, session=None, engine=None, red=None, cfg=None): + super(ExchangePluginBase, self).__init__(key=key, secret=secret, session=session, engine=engine, red=red, + cfg=cfg) + # ensure all are active in redis + add_active_markets(self.NAME.lower(), json.loads(self.cfg.get(self.NAME.lower(), 'active_markets'))) + self.active_markets = get_active_markets(self.NAME.lower()) self.active_currencies = set() - for mark in markets: + for mark in self.active_markets: self.active_currencies = self.active_currencies.union(set(mark.split("_"))) assert len(self.active_currencies) > 0 - self.stdin_path = '/dev/null' - self.stdout_path = os.path.join(self.cfg.get('log', 'DATA_DIR'), 'stdout.log') - self.stderr_path = os.path.join(self.cfg.get('log', 'DATA_DIR'), 'stderr.log') - self.pidfile_path = os.path.join(self.cfg.get('log', 'DATA_DIR'), 'manager.pid') - self.pidfile_timeout = 5 - - """ - Daemonization and process management section. Do not override. - """ - - @property - def manager_user(self): - """ - Get the User associated with this exchange Manager. - This User is the owner of Credits, Debits, and other records for the exchange. - - :rtype: User - :return: The Manager User - """ - if not self._user: - # try to get existing user - self._user = self.session.query(um.User).filter(um.User.username == '%sManager' % self.NAME) \ - .first() - if not self._user: - # create a new user - userpubkey = self.cfg.get(self.NAME.lower(), 'userpubkey') - self._user = util.create_user('%sManager' % self.NAME, userpubkey, self.session) - return self._user - - def run(self): - """ - Run this manager as a daemon. Subscribes to a redis channel matching self.NAME - and processes messages received there. - """ - set_status(self.NAME.lower(), 'loading') - # TODO syncronize before subscribing.. - set_status(self.NAME.lower(), 'running') - subscription_handler(self.NAME.lower(), client=self) - - # noinspection PyUnusedLocal - @classmethod - def terminate(cls, signal, stack): - """ - A termination handler that marks the plugin status as "stopped". - - :param signal: The OS signal number received. - :param stack: The frame object at the point the signal was received. - :raises: SystemExit - """ - set_status(cls.NAME.lower(), 'stopped') - raise SystemExit("Stopped (SIGTERM)") - """ Optional nonce helpers. Most exchanges can simply use a timestamp. """ @@ -191,12 +147,69 @@ def quote_commodity(cls, market): """ return commodities.find_or_create(cls.format_market(market).split("_")[1]) + """ + Normalization helpers. + """ + def add_trade(self, market, tid, trade_side, price, amount, fee, fee_side, dtime): + tofind = '%s|%s' % (self.NAME.lower(), tid) + found = self.session.query(em.Trade) \ + .filter(em.Trade.trade_id == tofind).count() + if found != 0: + self.logger.debug("; %s already known" % tid) + return + trade = em.Trade(tid, self.NAME.lower(), market, trade_side, amount, price, fee, fee_side, dtime) + self.session.add(trade) + self.logger.info("added trade %s" % trade) + return trade + + def update_balance(self, currency, total, available=None, reference=""): + bal = self.session.query(wm.Balance).filter(wm.Balance.user_id == self.manager_user.id) \ + .filter(wm.Balance.currency == currency).one_or_none() + if not bal: + if available is None: + available = 0 + bal = wm.Balance(total, available, currency, reference, self.manager_user.id) + self.session.add(bal) + else: + bal.load_commodities() + bal.total = total + bal.time = datetime.datetime.utcnow() + if available is not None: + bal.available = available + self.logger.info("balance set %s" % bal) + + def add_order(self, price, amount, market, side, order_id=None, create_time=None, + change_time=None, exec_amount=0, state='pending'): + prefix = 'tmp' if state == 'pending' else self.NAME.lower() + order = self.session.query(em.LimitOrder) \ + .filter(em.LimitOrder.order_id == '%s|%s' % (prefix, order_id)).one_or_none() + if order is not None: + try: + assert order.market == market + assert order.side == side + except AssertionError: + return + if order.exec_amount.to_double() == exec_amount and order.state == state: + return + order.order_id = "%s|%s" % (self.NAME.lower(), order_id) + order.price = price + order.amount = amount + order.change_time = datetime.datetime.utcnow() + order.state = state + order.exec_amount = exec_amount + else: + order = em.LimitOrder(price, amount, market, side, self.NAME.lower(), order_id, create_time, change_time, + exec_amount, state) + self.session.add(order) + self.logger.info("added order %s" % order) + return order + """ Action methods, and passive methods for synchronizing data. Override each of these! """ - def cancel_orders(self, oid=None, side=None, market=None): + def cancel_orders(self, oid=None, order_id=None, side=None, market=None, price=None): """ Cancel all orders, or optionally just those matching the parameters. :return: True if orders were successfully canceled or no orders exist, @@ -205,6 +218,13 @@ def cancel_orders(self, oid=None, side=None, market=None): """ raise NotImplementedError() + def cancel_stale_orders(self): + for market in self.active_markets: + rticker = get_ticker(self.NAME, market=market, red=self.red) # use the redis version of ticker + ticker = em.Ticker.from_json(rticker) + index = ticker.get_index(market) + self.cancel_orders(market=market, price=index) + def create_order(self, oid): """ Create a new order of a given market for a given size, at a certain price @@ -269,10 +289,150 @@ def sync_book(cls, market=None): """ -def get_ticker(exchange, market="BTC_USD", red=None): +def set_preferred_exchange(market, exchange): + red.set('%s_preferred_exchange' % market, exchange) + + +def get_preferred_exchange(market): + exchange = red.get('%s_preferred_exchange' % market) + if exchange is None or exchange == "": + for ex in EXCHANGES: + active_markets = red.get('%s_active_markets' % ex) + if active_markets is not None and market in json.loads(active_markets): + return ex + else: + return exchange + + +def set_active_markets(exchange, active_markets): + red.set('%s_active_markets' % exchange, active_markets) + + +def add_active_market(exchange, market): + add_active_markets(exchange, [market]) + + +def add_active_markets(exchange, markets): + marks = get_active_markets(exchange) + active_markets = json.dumps(list(set(marks + markets))) + red.set('%s_active_markets' % exchange, active_markets) + + +def get_active_markets(exchange): + active_markets = red.get('%s_active_markets' % exchange) + if active_markets is not None and len(active_markets) > 2: # is not "[]" + markets = json.loads(active_markets) + else: + cfg = get_config(name=exchange) + markets = json.loads(cfg.get(exchange, 'active_markets')) + return markets + + +def set_commodity_config(commodity, weight=1.0, cfloor=0.0, ctarget=0.0, cceil=0.0): + detail = {'weight': weight, 'floor': cfloor, 'target': ctarget, 'ceil': cceil} + red.set('%s_config' % commodity, detail) + + +def get_commodity_config(commodity): + comm_cfg = red.get('%s_config' % commodity) + detail = {'weight': 1.0, 'floor': 0.0, 'target': 0.0, 'ceil': 0.0} + + def merge_details(default, tomerge): + for key in tomerge: + default[key] = tomerge[key] + return default + + if comm_cfg is not None: + return merge_details(detail, json.loads(comm_cfg)) + else: + return detail + + +def get_usd_value(amount, price=None): + if not isinstance(amount, Amount): + raise TypeError("requires an Amount argument") + comm = str(amount.commodity) + if comm == 'USD': + return amount + elif comm != '': + if price is None: + ticker = get_ticker(market="%s_USD" % comm, red=red) + if ticker is None: + raise TypeError("inactive commodity %s" % comm) + else: + price = ticker.calculate_index() + return Amount("%s USD" % amount.number()) * price + + +def get_weighted_usd_volume(ticker): + if isinstance(ticker, dict): + ticker = em.Ticker.from_dict(ticker) + if isinstance(ticker, str): + ticker = em.Ticker.from_json(ticker) + base = str(ticker.volume.commodity) + quote = str(ticker.last.commodity) + base_comm_cfg = get_commodity_config(base) + quote_comm_cfg = get_commodity_config(quote) + weight = Amount("%s USD" % base_comm_cfg['weight']) * Amount("%s USD" % quote_comm_cfg['weight']) + if 'USD' in base: # flexible for USDT, but is this a potential conflict? + return weight * Amount("%s USD" % ticker.volume.number()) + elif 'USD' in quote: # flexible for USDT, but is this a potential conflict? + return weight * Amount("%s USD" % ticker.volume.number()) * ticker.calculate_index() + else: + usdprice = get_usd_value(Amount("1 %s" % base)) + return Amount("%s USD" % usdprice.number()) * Amount("%s USD" % ticker.volume.number()) * weight + + +def get_market_vol_shares(exchange, c=None): + vols = {'total': Amount("0 USD")} + + for market in get_active_markets(exchange): + if c is None: + tick = get_ticker(exchange, market.upper()) + vols[market] = {'USD_volume': get_weighted_usd_volume(tick), 'ticker': tick} + vols['total'] += vols[market]['USD_volume'] + elif c.upper() in market.upper(): + tick = get_ticker(exchange, market.upper()) + # pair = market.split("_") + uvol = get_weighted_usd_volume(tick) + vols[market] = {'USD_volume': uvol, 'ticker': tick} + vols['total'] += vols[market]['USD_volume'] + for market in vols: + if market == 'total': + continue + if vols['total'] > 0: + vols[market]['vol_share'] = (vols[market]['USD_volume'] / vols['total']).to_double() + else: + vols[market]['vol_share'] = 0 + return vols + + +def get_ticker(exchange=None, market="BTC_USD", red=None): if red is None: red = setup_redis() - return red.get('%s_%s_ticker' % (exchange.lower(), market)) + + def safe_get_ticker(exch, market, red): + gt = red.get('%s_%s_ticker' % (exch, market)) + base, quote = market.split("_") + if gt is not None: + return em.Ticker.from_json(gt) + elif quote == "USD": + t1mark = "%s_BTC" % base + t1ex = get_preferred_exchange(t1mark) + t1r = red.get('%s_%s_ticker' % (t1ex, t1mark)) + if t1r is None: + return + t1 = em.Ticker.from_json(t1r) + t2 = get_ticker(market='BTC_USD', red=red) + if t2 is None: + return + return multiply_tickers(t1, t2) + + if exchange is None: + exch = get_preferred_exchange(market) + return safe_get_ticker(exch, market, red) + else: + return safe_get_ticker(exchange.lower(), market, red) def get_trades(exchange=None, market=None, tid=None, trade_id=None, session=None): @@ -296,7 +456,7 @@ def get_credits(exchange=None, address=None, currency=None, ref_id=None, session session, eng = create_session_engine() query = session.query(wm.Credit) query = filter_query_by_attr(query, wm.Credit, 'ref_id', ref_id) - query = filter_query_by_attr(query, wm.Credit, 'reference', exchange) + query = filter_query_by_attr(query, wm.Credit, 'network', exchange) query = filter_query_by_attr(query, wm.Credit, 'address', address) query = filter_query_by_attr(query, wm.Credit, 'currency', currency) resp = [] @@ -310,7 +470,7 @@ def get_debits(exchange=None, address=None, currency=None, ref_id=None, session= session, eng = create_session_engine() query = session.query(wm.Debit) query = filter_query_by_attr(query, wm.Debit, 'ref_id', ref_id) - query = filter_query_by_attr(query, wm.Debit, 'reference', exchange) + query = filter_query_by_attr(query, wm.Debit, 'network', exchange) query = filter_query_by_attr(query, wm.Debit, 'address', address) query = filter_query_by_attr(query, wm.Debit, 'currency', currency) resp = [] @@ -337,32 +497,45 @@ def get_orders(exchange=None, market=None, side=None, oid=None, order_id=None, s return resp -def get_balances(exchange=None, session=None): +def get_balances(exchange=None, currency=None, session=None): if session is None: session, eng = create_session_engine() query = session.query(wm.Balance) + if currency is not None: + query = filter_query_by_attr(query, wm.Balance, 'currency', currency) if exchange is not None: query = query.join(um.User).filter(um.User.username == "%sManager" % exchange.lower()) total = Balance() available = Balance() for bal in query: - total += bal.total - available += bal.available + total = total + bal.total + available = available + bal.available return total, available -def create_order(exchange, price, amount, market, side, session=None): - if session is None: - session, eng = create_session_engine() +def create_order(exchange, price, amount, market, side, session, submit=True, expire=None): order = em.LimitOrder(price, amount, market, side, exchange.lower()) session.add(order) - session.commit() - data = {'oid': order.id} - publish(exchange, 'create_order', data) + try: + session.commit() + except Exception as e: + print e + session.rollback() + session.flush() order.load_commodities() + if submit: + submit_order(exchange, order.id, expire=expire) return order +def submit_order(exchange, oid, expire=None): + assert isinstance(oid, int) + data = {'oid': oid} + if expire is not None: + data['expire'] = expire + publish(exchange, 'create_order', data) + + def cancel_orders(exchange, market=None, oid=None, side=None, order_id=None): data = {} if order_id is not None: @@ -382,8 +555,18 @@ def sync_orders(exchange, data=None): publish(exchange, 'sync_orders', data) -def sync_ticker(exchange, market="BTC_USD"): - publish(exchange, 'sync_ticker', {'market': market}) +def sync_ticker(exchange=None, market=None): + def sync_exchange_ticker(ex, mark=None): + if mark is None: + for mark in get_active_markets(ex): + publish(ex, 'sync_ticker', {'market': mark}) + else: + publish(ex, 'sync_ticker', {'market': mark}) + if exchange is None: + for exch in get_running_workers(EXCHANGES, red=red): + sync_exchange_ticker(exch, market) + else: + sync_exchange_ticker(exchange, market) def sync_balances(exchange, data=None): @@ -392,36 +575,37 @@ def sync_balances(exchange, data=None): publish(exchange, 'sync_balances', data) -def sync_trades(exchange, market=None): - data = {} +def sync_trades(exchange, market=None, rescan=False): + data = {'rescan': rescan} if market is not None: data['market'] = market publish(exchange, 'sync_trades', data) -def sync_credits(exchange): - data = {} +def sync_credits(exchange, rescan=False): + data = {'rescan': rescan} publish(exchange, 'sync_credits', data) -def sync_debits(exchange): - data = {} +def sync_debits(exchange, rescan=False): + data = {'rescan': rescan} publish(exchange, 'sync_debits', data) -def make_ledger(exchange=None): +def make_ledger(exchange=None, session=ses): """ Make a ledger-cli style ledger for the given exchange. Accounts for all trades, debits and credits in the database. :param str exchange: The exchange to filter for. (optional) + :param session: The sqlalchemy session. :rtype: str :return: The ledger string. """ ledger = "" - trades = ses.query(em.Trade) - credits = ses.query(wm.Credit) - debits = ses.query(wm.Debit) + trades = session.query(em.Trade) + credits = session.query(wm.Credit) + debits = session.query(wm.Debit) if exchange is not None: trades = trades.filter(em.Trade.exchange == exchange) credits = credits.filter(wm.Credit.reference == exchange) @@ -442,10 +626,3 @@ def make_ledger(exchange=None): ledger += entries[entry].get_ledger_entry() return ledger - - -def get_status(exchange, red=None): - if red is None: - red = setup_redis() - status = red.get("%s_status" % exchange.lower()) - return status if status is not None else 'stopped' From 1940ffcc4d54f5126e7f1f3d11e36758dde57288 Mon Sep 17 00:00:00 2001 From: isysd Date: Tue, 16 Aug 2016 12:54:11 -0500 Subject: [PATCH 2/3] tests passing --- test/test_commands.py | 81 ++++++------ test/test_manager.py | 53 +++++--- trade_manager/cli.py | 22 +++- trade_manager/helper.py | 10 +- trade_manager/plugin.py | 277 ++++++++++++++++++++++------------------ 5 files changed, 246 insertions(+), 197 deletions(-) diff --git a/test/test_commands.py b/test/test_commands.py index d2095a9..63f84b3 100644 --- a/test/test_commands.py +++ b/test/test_commands.py @@ -4,9 +4,11 @@ from ledger import Amount from ledger import Balance +from alchemyjsonschema.dictify import datetime_rfc3339 from jsonschema import validate -from sqlalchemy_models import get_schemas, wallet as wm, exchange as em +from sqlalchemy_models import get_schemas, wallet as wm, exchange as em, jsonify2 from tappmq.tappmq import get_status +from test.test_manager import check_test_ticker from trade_manager.helper import TestPlugin, start_test_man, stop_test_man from trade_manager.cli import handle_command @@ -18,6 +20,7 @@ tp = TestPlugin() tp.setup_connections() +tp.setup_logger() def test_status(): @@ -62,14 +65,15 @@ def test_ledger(): P {2} BTC 100.00000000 USD P {2} USD 0.01000000 BTC {2} helper BTC_USD buy - ; + ; Assets:helper:USD -1.00000000 USD @ 0.01000000 BTC FX:BTC_USD:buy 1.00000000 USD @ 0.01000000 BTC Assets:helper:BTC 0.01000000 BTC @ 100.00000000 USD FX:BTC_USD:buy -0.01000000 BTC @ 100.00000000 USD """.format(credit.time.strftime('%Y/%m/%d %H:%M:%S'), debit.time.strftime('%Y/%m/%d %H:%M:%S'), - trade.time.strftime('%Y/%m/%d %H:%M:%S'), trade.trade_id) + trade.time.strftime('%Y/%m/%d %H:%M:%S'), + datetime_rfc3339(trade.time), trade.trade_id) assert ledger == hardledger @@ -161,53 +165,53 @@ def test_cancel_order_order_id_no_prefix(self): def test_cancel_orders_by_market(self): create_order('helper', 100, 0.1, 'BTC_USD', 'bid', session=tp.session, submit=False) create_order('helper', 100, 0.1, 'BTC_USD', 'bid', session=tp.session, submit=False) - obids = len(get_orders(side='bid', state='pending', session=tp.session)) + obids = len(get_orders(side='bid', exchange='helper', state='pending', session=tp.session)) assert obids >= 2 create_order('helper', 100, 0.1, 'BTC_USD', 'ask', session=tp.session, submit=False) create_order('helper', 100, 0.1, 'BTC_USD', 'ask', session=tp.session, submit=False) create_order('helper', 100, 0.1, 'DASH_BTC', 'ask', session=tp.session, submit=False) - oasks = len(get_orders(side='ask', state='pending', session=tp.session)) + oasks = len(get_orders(side='ask', exchange='helper', state='pending', session=tp.session)) assert oasks >= 3 tp.session.close() cancel_orders('helper', market='BTC_USD') - orders = len(get_orders(market='BTC_USD', state='pending', session=tp.session)) + orders = len(get_orders(market='BTC_USD', exchange='helper', state='pending', session=tp.session)) countdown = 30 while orders != 0 and countdown > 0: countdown -= 1 - orders = len(get_orders(market='BTC_USD', state='pending', session=tp.session)) + orders = len(get_orders(market='BTC_USD', exchange='helper', state='pending', session=tp.session)) if orders != 0: time.sleep(0.01) tp.session.close() assert orders == 0 - dorders = len(get_orders(market='DASH_BTC', state='pending', session=tp.session)) + dorders = len(get_orders(market='DASH_BTC', exchange='helper', state='pending', session=tp.session)) assert dorders >= 1 def test_cancel_orders_by_side(self): create_order('helper', 100, 0.1, 'BTC_USD', 'bid', session=tp.session, submit=False) create_order('helper', 100, 0.1, 'BTC_USD', 'bid', session=tp.session, submit=False) - obids = len(get_orders(side='bid', state='pending', session=tp.session)) + obids = len(get_orders(side='bid', exchange='helper', state='pending', session=tp.session)) assert obids >= 2 create_order('helper', 100, 0.1, 'BTC_USD', 'ask', session=tp.session, submit=False) create_order('helper', 100, 0.1, 'BTC_USD', 'ask', session=tp.session, submit=False) - oasks = len(get_orders(side='ask', state='pending', session=tp.session)) + oasks = len(get_orders(side='ask', exchange='helper', state='pending', session=tp.session)) assert oasks >= 2 tp.session.close() cancel_orders('helper', side='bid') - bids = len(get_orders(side='bid', state='pending', session=tp.session)) + bids = len(get_orders(side='bid', exchange='helper', state='pending', session=tp.session)) countdown = 30 while bids != 0 and countdown > 0: countdown -= 1 - bids = len(get_orders(side='bid', state='pending', session=tp.session)) + bids = len(get_orders(side='bid', exchange='helper', state='pending', session=tp.session)) if bids != 0: time.sleep(0.01) tp.session.close() assert bids == 0 tp.session.close() - asks = len(get_orders(side='ask', state='pending', session=tp.session)) + asks = len(get_orders(side='ask', exchange='helper', state='pending', session=tp.session)) countdown = 30 while asks != 0 and countdown > 0: countdown -= 1 - asks = len(get_orders(side='ask', state='pending', session=tp.session)) + asks = len(get_orders(side='ask', exchange='helper', state='pending', session=tp.session)) if asks != 0: time.sleep(0.01) tp.session.close() @@ -248,54 +252,53 @@ def test_order_lifecycle(self): assert len(corder) == 1 assert corder[0].state == 'closed' - def test_sync_credits(self): - credits = len(get_credits('helper', session=tp.session)) + def test_sync_credits(self, rescan=False): + credits = len(get_credits(exchange='helper', session=tp.session)) sync_credits('helper') - newcreds = len(get_credits('helper', session=tp.session)) + newcreds = len(get_credits(exchange='helper', session=tp.session)) countdown = 30 while newcreds == credits and countdown > 0: countdown -= 1 - newcreds = len(get_credits('helper', session=tp.session)) + newcreds = len(get_credits(exchange='helper', session=tp.session)) if newcreds == credits: time.sleep(0.01) + tp.session.close() assert newcreds > credits - def test_sync_debits(self): - debits = len(get_debits('helper', session=tp.session)) + def test_sync_debits(self, rescan=False): + debits = len(get_debits(exchange='helper', session=tp.session)) sync_debits('helper') - newdebs = len(get_debits('helper', session=tp.session)) + newdebs = len(get_debits(exchange='helper', session=tp.session)) countdown = 30 while newdebs == debits and countdown > 0: countdown -= 1 - newdebs = len(get_debits('helper', session=tp.session)) + newdebs = len(get_debits(exchange='helper', session=tp.session)) if newdebs == debits: time.sleep(0.01) + tp.session.close() assert newdebs > debits - def test_sync_trades(self): - trades = len(get_trades('helper', session=tp.session)) + def test_sync_trades(self, rescan=False): + trades = len(get_trades(exchange='helper', session=tp.session)) sync_trades('helper') - newtrades = len(get_trades('helper', session=tp.session)) + newtrades = len(get_trades(exchange='helper', session=tp.session)) countdown = 30 while newtrades == trades and countdown > 0: countdown -= 1 - newtrades = len(get_trades('helper', session=tp.session)) + newtrades = len(get_trades(exchange='helper', session=tp.session)) if newtrades == trades: time.sleep(0.01) + tp.session.close() assert newtrades > trades def test_ticker(self): - sync_ticker('helper', 'BTC_USD') - tick = None + sync_ticker('helper', market='BTC_USD') + ticker = None countdown = 300 - while tick is None and countdown > 0: + while ticker is None and countdown > 0: countdown -= 1 - ticker = get_ticker('helper', 'BTC_USD') - try: - tick = json.loads(ticker) - except (ValueError, TypeError): - pass - assert validate(tick, SCHEMAS['Ticker']) is None + ticker = get_ticker('helper', market='BTC_USD') + check_test_ticker(ticker) class TestCLI(unittest.TestCase): @@ -375,13 +378,11 @@ def test_sync_trades(self): assert newtrades > trades def test_ticker(self): - sync_ticker('helper', 'BTC_USD') + sync_ticker('helper', market='BTC_USD') ticker = "" countdown = 30 while (isinstance(ticker, int) or ticker == '') and countdown > 0: countdown -= 1 time.sleep(0.1) - ticker = handle_command(['ticker', 'get', '-e', 'helper'], session=tp.session) - assert '{"volume": 1000.0, "last": 100.0, "exchange": "helper", "bid": 99.0, "high": 110.0, ' \ - '"low": 90.0, "time": "' in ticker - assert '", "ask": 101.0, "market": "BTC_USD"}' in ticker + ticker = handle_command(['ticker', 'get', '-e', 'helper', '-m', 'BTC_USD'], session=tp.session) + check_test_ticker(ticker) diff --git a/test/test_manager.py b/test/test_manager.py index 1d59ef5..5f338ca 100644 --- a/test/test_manager.py +++ b/test/test_manager.py @@ -3,21 +3,42 @@ from ledger import Balance from jsonschema import validate -from sqlalchemy_models import get_schemas, exchange as em +from sqlalchemy_models import get_schemas, exchange as em, jsonify2 from trade_manager.helper import TestPlugin, make_base_id from trade_manager.plugin import get_ticker, get_balances, get_orders, get_trades tp = TestPlugin() tp.setup_connections() +tp.setup_logger() SCHEMAS = get_schemas() +def check_test_ticker(ticker, market='BTC_USD'): + base, quote = market.split("_") + assert hasattr(ticker, 'bid') + assert isinstance(ticker.bid, Amount) + assert str(ticker.bid.commodity) == quote + assert hasattr(ticker, 'ask') + assert isinstance(ticker.ask, Amount) + assert str(ticker.bid.commodity) == quote + assert hasattr(ticker, 'high') + assert isinstance(ticker.high, Amount) + assert str(ticker.bid.commodity) == quote + assert hasattr(ticker, 'low') + assert isinstance(ticker.low, Amount) + assert str(ticker.bid.commodity) == quote + assert hasattr(ticker, 'volume') + assert isinstance(ticker.volume, Amount) + assert str(ticker.volume.commodity) == base + assert hasattr(ticker, 'market') + assert ticker.market == market + + def test_ticker(): tp.sync_ticker('BTC_USD') - ticker = get_ticker('helper', 'BTC_USD') - tick = json.loads(ticker) - assert validate(tick, SCHEMAS['Ticker']) is None + ticker = get_ticker('helper', market='BTC_USD') + check_test_ticker(ticker) def test_balance(): @@ -93,8 +114,7 @@ def test_cancel_orders_by_side(): tp.session.commit() tp.create_order(order.id) tp.create_order(order2.id) - obids = tp.session.query(em.LimitOrder).filter(em.LimitOrder.side == 'bid') \ - .filter(em.LimitOrder.state != 'closed').count() + obids = len(get_orders(exchange='helper', side='bid', state='open', session=tp.session)) assert obids >= 2 order = em.LimitOrder(100, 0.1, 'BTC_USD', 'ask', 'helper', order_id=make_base_id(l=10)) tp.session.add(order) @@ -103,15 +123,12 @@ def test_cancel_orders_by_side(): tp.session.commit() tp.create_order(order.id) tp.create_order(order2.id) - oasks = tp.session.query(em.LimitOrder).filter(em.LimitOrder.side == 'ask') \ - .filter(em.LimitOrder.state != 'closed').count() + oasks = len(get_orders(exchange='helper', side='ask', state='open', session=tp.session)) assert oasks >= 2 tp.cancel_orders(side='bid') - bids = tp.session.query(em.LimitOrder).filter(em.LimitOrder.side == 'bid') \ - .filter(em.LimitOrder.state != 'closed').count() + bids = len(get_orders(exchange='helper', side='bid', state='open', session=tp.session)) assert bids == 0 - asks = tp.session.query(em.LimitOrder).filter(em.LimitOrder.side == 'ask') \ - .filter(em.LimitOrder.state != 'closed').count() + asks = len(get_orders(exchange='helper', side='ask', state='open', session=tp.session)) assert asks > 0 assert oasks == asks @@ -124,8 +141,7 @@ def test_cancel_orders_by_market(): tp.session.commit() tp.create_order(order.id) tp.create_order(order2.id) - obids = tp.session.query(em.LimitOrder).filter(em.LimitOrder.side == 'bid') \ - .filter(em.LimitOrder.state != 'closed').count() + obids = len(get_orders(exchange='helper', side='bid', state='open', session=tp.session)) assert obids >= 2 order = em.LimitOrder(100, 0.1, 'BTC_USD', 'ask', 'helper', order_id=make_base_id(l=10)) tp.session.add(order) @@ -137,15 +153,12 @@ def test_cancel_orders_by_market(): tp.create_order(order.id) tp.create_order(order2.id) tp.create_order(order3.id) - oasks = tp.session.query(em.LimitOrder).filter(em.LimitOrder.side == 'ask') \ - .filter(em.LimitOrder.state != 'closed').count() + oasks = len(get_orders(exchange='helper', side='ask', state='open', session=tp.session)) assert oasks >= 3 tp.cancel_orders(market='BTC_USD') - bids = tp.session.query(em.LimitOrder).filter(em.LimitOrder.side == 'bid') \ - .filter(em.LimitOrder.state != 'closed').count() + bids = len(get_orders(exchange='helper', side='bid', state='open', session=tp.session)) assert bids == 0 - asks = tp.session.query(em.LimitOrder).filter(em.LimitOrder.side == 'ask') \ - .filter(em.LimitOrder.state != 'closed').count() + asks = len(get_orders(exchange='helper', side='ask', state='open', session=tp.session)) assert asks >= 1 diff --git a/trade_manager/cli.py b/trade_manager/cli.py index cc84900..3979af8 100644 --- a/trade_manager/cli.py +++ b/trade_manager/cli.py @@ -164,9 +164,22 @@ def handle_trade_command(argv, parsers, session=ses): return sync_trades(exchange=args.e, market=args.m, rescan=args.rescan) +def handle_config_command(argv, parsers): + parser = argparse.ArgumentParser(parents=parsers) + parser.add_argument("subcommand", choices=["get", "set"], help='The order sub-command to run.') + parser.add_argument("target", choices=["pref_ex", ""], help='The order sub-command to run.') + parser.add_argument("-m", help='The market to get a ticker for.') + parser.add_argument("-e", help='The exchange to get a ticker for.') + args = parser.parse_args(argv) + if args.subcommand == "get": + return get_ticker(args.e, args.m, red=red) + elif args.subcommand == "sync": + sync_ticker(args.e, args.m) + + def handle_command(argv=sys.argv[1:], session=ses): parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("command", choices=['ticker', 'ledger', 'order', 'trade', 'balance', 'address', 'isysd'], + parser.add_argument("command", choices=['ticker', 'ledger', 'order', 'trade', 'balance', 'address', 'config'], help="'%(prog)s help' for usage details") if len(argv) == 0: parser.print_help() @@ -183,11 +196,8 @@ def handle_command(argv=sys.argv[1:], session=ses): return handle_trade_command(argv, [parser], session=session) elif args.command == 'balance': return handle_balance_command(argv, [parser], session=session) - elif args.command == 'isysd': # TODO - r = 10 - for i in range(r): - #create_order('bitfinex', 0.36+float(i)/100, 1000, 'BFX_USD', 'ask', session=session) - create_order('bitfinex', 0.32 - float(i) / 100, 1000, 'BFX_USD', 'bid', session=session) + elif args.command == 'config': + return handle_config_command(argv, [parser], session=session) if __name__ == "__main__": diff --git a/trade_manager/helper.py b/trade_manager/helper.py index 6362a15..c43552e 100755 --- a/trade_manager/helper.py +++ b/trade_manager/helper.py @@ -187,7 +187,7 @@ def sync_ticker(self, market=None): jtick = jsonify2(tick, 'Ticker') self.red.set('%s_%s_ticker' % (self.NAME.lower(), market), jtick) - def sync_trades(self): + def sync_trades(self, rescan=False): """ :return: a list of trades, possibly only a subset of them. """ @@ -196,21 +196,21 @@ def sync_trades(self): self.session.add(trade) self.session.commit() - def sync_credits(self): + def sync_credits(self, rescan=False): """ :return: a list of credits, possibly only a subset of them. """ - credit = wm.Credit(1, make_base_id(10), 'BTC', 'Bitcoin', 'unconfirmed', 'helper', make_base_id(10), + credit = wm.Credit(1, make_base_id(10), 'BTC', 'helper', 'unconfirmed', 'helper', make_base_id(10), self.manager_user.id, time=datetime.datetime.utcnow()) # self.logger.debug("credit: %s" % credit) self.session.add(credit) self.session.commit() - def sync_debits(self): + def sync_debits(self, rescan=False): """ :return: a list of debits, possibly only a subset of them. """ - debit = wm.Debit(1, 0, make_base_id(10), 'BTC', 'Bitcoin', 'unconfirmed', 'helper', make_base_id(10), + debit = wm.Debit(1, 0, make_base_id(10), 'BTC', 'helper', 'unconfirmed', 'helper', make_base_id(10), self.manager_user.id, time=datetime.datetime.utcnow()) # self.logger.debug("debit: %s" % debit) self.session.add(debit) diff --git a/trade_manager/plugin.py b/trade_manager/plugin.py index ec4b851..665bcd9 100644 --- a/trade_manager/plugin.py +++ b/trade_manager/plugin.py @@ -17,19 +17,6 @@ red = setup_redis() -def get_order_by_order_id(order_id, exchange, session=None): - if session is None: - session = ses - if "|" in order_id: - order_id = order_id.split("|")[1] - order = session.query(em.LimitOrder)\ - .filter(em.LimitOrder.order_id == "%s|%s" % (exchange.lower(), order_id)).one_or_none() - if order is None: - order = session.query(em.LimitOrder).filter( - em.LimitOrder.order_id == "tmp|%s" % order_id).one_or_none() - return order - - class ExchangePluginBase(MQHandlerBase): """ A parent class for Exchange Manager Plugins. @@ -318,6 +305,21 @@ def add_active_markets(exchange, markets): red.set('%s_active_markets' % exchange, active_markets) +def rem_active_market(exchange, market): + rem_active_markets(exchange, [market]) + + +def rem_active_markets(exchange, markets): + active_markets = get_active_markets(exchange) + for mark in markets: + try: + active_markets.remove(mark) + except ValueError: + # safe to ignore; market was already inactive + continue + red.set('%s_active_markets' % exchange, active_markets) + + def get_active_markets(exchange): active_markets = red.get('%s_active_markets' % exchange) if active_markets is not None and len(active_markets) > 2: # is not "[]" @@ -348,65 +350,6 @@ def merge_details(default, tomerge): return detail -def get_usd_value(amount, price=None): - if not isinstance(amount, Amount): - raise TypeError("requires an Amount argument") - comm = str(amount.commodity) - if comm == 'USD': - return amount - elif comm != '': - if price is None: - ticker = get_ticker(market="%s_USD" % comm, red=red) - if ticker is None: - raise TypeError("inactive commodity %s" % comm) - else: - price = ticker.calculate_index() - return Amount("%s USD" % amount.number()) * price - - -def get_weighted_usd_volume(ticker): - if isinstance(ticker, dict): - ticker = em.Ticker.from_dict(ticker) - if isinstance(ticker, str): - ticker = em.Ticker.from_json(ticker) - base = str(ticker.volume.commodity) - quote = str(ticker.last.commodity) - base_comm_cfg = get_commodity_config(base) - quote_comm_cfg = get_commodity_config(quote) - weight = Amount("%s USD" % base_comm_cfg['weight']) * Amount("%s USD" % quote_comm_cfg['weight']) - if 'USD' in base: # flexible for USDT, but is this a potential conflict? - return weight * Amount("%s USD" % ticker.volume.number()) - elif 'USD' in quote: # flexible for USDT, but is this a potential conflict? - return weight * Amount("%s USD" % ticker.volume.number()) * ticker.calculate_index() - else: - usdprice = get_usd_value(Amount("1 %s" % base)) - return Amount("%s USD" % usdprice.number()) * Amount("%s USD" % ticker.volume.number()) * weight - - -def get_market_vol_shares(exchange, c=None): - vols = {'total': Amount("0 USD")} - - for market in get_active_markets(exchange): - if c is None: - tick = get_ticker(exchange, market.upper()) - vols[market] = {'USD_volume': get_weighted_usd_volume(tick), 'ticker': tick} - vols['total'] += vols[market]['USD_volume'] - elif c.upper() in market.upper(): - tick = get_ticker(exchange, market.upper()) - # pair = market.split("_") - uvol = get_weighted_usd_volume(tick) - vols[market] = {'USD_volume': uvol, 'ticker': tick} - vols['total'] += vols[market]['USD_volume'] - for market in vols: - if market == 'total': - continue - if vols['total'] > 0: - vols[market]['vol_share'] = (vols[market]['USD_volume'] / vols['total']).to_double() - else: - vols[market]['vol_share'] = 0 - return vols - - def get_ticker(exchange=None, market="BTC_USD", red=None): if red is None: red = setup_redis() @@ -435,6 +378,75 @@ def safe_get_ticker(exch, market, red): return safe_get_ticker(exchange.lower(), market, red) +def submit_order(exchange, oid, expire=None): + assert isinstance(oid, int) + data = {'oid': oid} + if expire is not None: + data['expire'] = expire + publish(exchange, 'create_order', data) + + +def cancel_orders(exchange, market=None, oid=None, side=None, order_id=None): + data = {} + if order_id is not None: + data['order_id'] = order_id if "|" in str(order_id) else '%s|%s' % (exchange.lower(), order_id) + if oid is not None: + data['oid'] = oid + if side is not None: + data['side'] = side + if market is not None: + data['market'] = market + publish(exchange, 'cancel_orders', data) + + +def sync_orders(exchange, data=None): + if data is None: + data = {} + publish(exchange, 'sync_orders', data) + + +def sync_ticker(exchange=None, market=None): + def sync_exchange_ticker(ex, mark=None): + if mark is None: + for mark in get_active_markets(ex): + publish(ex, 'sync_ticker', {'market': mark}) + else: + publish(ex, 'sync_ticker', {'market': mark}) + if exchange is None: + for exch in get_running_workers(EXCHANGES, red=red): + sync_exchange_ticker(exch, market) + else: + sync_exchange_ticker(exchange, market) + + +def sync_balances(exchange, data=None): + if data is None: + data = {} + publish(exchange, 'sync_balances', data) + + +def sync_trades(exchange, market=None, rescan=False): + data = {'rescan': rescan} + if market is not None: + data['market'] = market + publish(exchange, 'sync_trades', data) + + +def sync_credits(exchange, rescan=False): + data = {'rescan': rescan} + publish(exchange, 'sync_credits', data) + + +def sync_debits(exchange, rescan=False): + data = {'rescan': rescan} + publish(exchange, 'sync_debits', data) + + +""" +DB helpers +""" + + def get_trades(exchange=None, market=None, tid=None, trade_id=None, session=None): if session is None: session, eng = create_session_engine() @@ -497,6 +509,19 @@ def get_orders(exchange=None, market=None, side=None, oid=None, order_id=None, s return resp +def get_order_by_order_id(order_id, exchange, session=None): + if session is None: + session = ses + if "|" in order_id: + order_id = order_id.split("|")[1] + order = session.query(em.LimitOrder)\ + .filter(em.LimitOrder.order_id == "%s|%s" % (exchange.lower(), order_id)).one_or_none() + if order is None: + order = session.query(em.LimitOrder).filter( + em.LimitOrder.order_id == "tmp|%s" % order_id).one_or_none() + return order + + def get_balances(exchange=None, currency=None, session=None): if session is None: session, eng = create_session_engine() @@ -528,68 +553,68 @@ def create_order(exchange, price, amount, market, side, session, submit=True, ex return order -def submit_order(exchange, oid, expire=None): - assert isinstance(oid, int) - data = {'oid': oid} - if expire is not None: - data['expire'] = expire - publish(exchange, 'create_order', data) - - -def cancel_orders(exchange, market=None, oid=None, side=None, order_id=None): - data = {} - if order_id is not None: - data['order_id'] = order_id if "|" in str(order_id) else '%s|%s' % (exchange.lower(), order_id) - if oid is not None: - data['oid'] = oid - if side is not None: - data['side'] = side - if market is not None: - data['market'] = market - publish(exchange, 'cancel_orders', data) +""" +Math helpers +""" -def sync_orders(exchange, data=None): - if data is None: - data = {} - publish(exchange, 'sync_orders', data) +def get_usd_value(amount, price=None): + if not isinstance(amount, Amount): + raise TypeError("requires an Amount argument") + comm = str(amount.commodity) + if comm == 'USD': + return amount + elif comm != '': + if price is None: + ticker = get_ticker(market="%s_USD" % comm, red=red) + if ticker is None: + raise TypeError("inactive commodity %s" % comm) + else: + price = ticker.calculate_index() + return Amount("%s USD" % amount.number()) * price -def sync_ticker(exchange=None, market=None): - def sync_exchange_ticker(ex, mark=None): - if mark is None: - for mark in get_active_markets(ex): - publish(ex, 'sync_ticker', {'market': mark}) - else: - publish(ex, 'sync_ticker', {'market': mark}) - if exchange is None: - for exch in get_running_workers(EXCHANGES, red=red): - sync_exchange_ticker(exch, market) +def get_weighted_usd_volume(ticker): + if isinstance(ticker, dict): + ticker = em.Ticker.from_dict(ticker) + if isinstance(ticker, str): + ticker = em.Ticker.from_json(ticker) + base = str(ticker.volume.commodity) + quote = str(ticker.last.commodity) + base_comm_cfg = get_commodity_config(base) + quote_comm_cfg = get_commodity_config(quote) + weight = Amount("%s USD" % base_comm_cfg['weight']) * Amount("%s USD" % quote_comm_cfg['weight']) + if 'USD' in base: # flexible for USDT, but is this a potential conflict? + return weight * Amount("%s USD" % ticker.volume.number()) + elif 'USD' in quote: # flexible for USDT, but is this a potential conflict? + return weight * Amount("%s USD" % ticker.volume.number()) * ticker.calculate_index() else: - sync_exchange_ticker(exchange, market) - - -def sync_balances(exchange, data=None): - if data is None: - data = {} - publish(exchange, 'sync_balances', data) - - -def sync_trades(exchange, market=None, rescan=False): - data = {'rescan': rescan} - if market is not None: - data['market'] = market - publish(exchange, 'sync_trades', data) - + usdprice = get_usd_value(Amount("1 %s" % base)) + return Amount("%s USD" % usdprice.number()) * Amount("%s USD" % ticker.volume.number()) * weight -def sync_credits(exchange, rescan=False): - data = {'rescan': rescan} - publish(exchange, 'sync_credits', data) +def get_market_vol_shares(exchange, c=None): + vols = {'total': Amount("0 USD")} -def sync_debits(exchange, rescan=False): - data = {'rescan': rescan} - publish(exchange, 'sync_debits', data) + for market in get_active_markets(exchange): + if c is None: + tick = get_ticker(exchange, market.upper()) + vols[market] = {'USD_volume': get_weighted_usd_volume(tick), 'ticker': tick} + vols['total'] += vols[market]['USD_volume'] + elif c.upper() in market.upper(): + tick = get_ticker(exchange, market.upper()) + # pair = market.split("_") + uvol = get_weighted_usd_volume(tick) + vols[market] = {'USD_volume': uvol, 'ticker': tick} + vols['total'] += vols[market]['USD_volume'] + for market in vols: + if market == 'total': + continue + if vols['total'] > 0: + vols[market]['vol_share'] = (vols[market]['USD_volume'] / vols['total']).to_double() + else: + vols[market]['vol_share'] = 0 + return vols def make_ledger(exchange=None, session=ses): From 4460689fb16d8b47071d1e81921ad662fc98f0a1 Mon Sep 17 00:00:00 2001 From: isysd Date: Wed, 17 Aug 2016 13:10:28 -0500 Subject: [PATCH 3/3] rearranged for tidiness --- Makefile | 112 +++++++++------ example_cfg.ini | 27 ++++ setup.py | 10 +- supervisord.conf | 30 ++-- {trade_manager => test}/helper.py | 25 +++- test/test_cli.py | 101 ++++++++++++++ test/{test_commands.py => test_manage.py} | 163 ++-------------------- test/{test_manager.py => test_plugin.py} | 87 +++++++----- test_supervisord.conf | 23 +++ trade_manager/plugin.py | 9 +- 10 files changed, 338 insertions(+), 249 deletions(-) create mode 100644 example_cfg.ini rename {trade_manager => test}/helper.py (90%) create mode 100644 test/test_cli.py rename test/{test_commands.py => test_manage.py} (61%) rename test/{test_manager.py => test_plugin.py} (76%) create mode 100644 test_supervisord.conf diff --git a/Makefile b/Makefile index d6ca5c0..732fb63 100644 --- a/Makefile +++ b/Makefile @@ -1,57 +1,87 @@ -makebase = if [ ! -d ~/.tapp ]; \ +PYTHON_VERSION=2.7 # not used... +SHELL = /bin/bash # for pushd, but would probably be better to avoid this +PREREQDIR=. + +makelog = if [ ! -d /var/log/trademanager/ ]; \ + then \ + sudo mkdir /var/log/trademanager/; \ + fi + +makerun = if [ ! -d /var/run/trademanager/ ]; \ then \ - mkdir ~/.tapp; \ + sudo mkdir /var/run/trademanager/; \ fi -makedirs = if [ ! -d ~/.tapp/trademanager ]; \ +installledger = if [ ! -d $(1)/ledger ]; \ then \ - mkdir ~/.tapp/trademanager; \ - mkdir ~/.tapp/helper; \ - cp cfg.ini ~/.tapp/trademanager; \ - cp cfg.ini ~/.tapp/helper; \ + git clone git://github.com/ledger/ledger.git $(1)/ledger; \ + pushd $(1)/ledger; \ + if [ $(VIRTUAL_ENV) ]; \ + then \ + ./acprep --prefix=$(VIRTUAL_ENV) --python update; \ + else \ + ./acprep --python update; \ + fi; \ + make; \ + make install; \ + popd; \ fi -installprereqs = if [ ! -d $(1)ledger ]; \ - git clone git://github.com/ledger/ledger.git \ - pushd $(1)ledger \ - ./acprep --python update \ - popd \ - export PYTHONPATH=$PYTHONPATH:$(readlink -f $(1)ledger) \ - if [ ! -d $(1)libsecp256k1 ]; \ - git clone git://github.com/bitcoin/secp256k1.git libsecp256k1 \ - pushd $(1)libsecp256k1 \ - git checkout d7eb1ae96dfe9d497a26b3e7ff8b6f58e61e400a \ - ./autogen.sh \ - ./configure --enable-module-recovery --enable-module-ecdh --enable-module-schnorr \ - make \ - make install \ - popd \ - INCLUDE_DIR=$(readlink -f $(1)libsecp256k1/include) \ - LIB_DIR=$(readlink -f $(1)libsecp256k1/.libs) \ - LD_LIBRARY_PATH=$(readlink -f $(1)libsecp256k1/.libs) +installsecp256k1 = if [ ! -d $(1)/libsecp256k1 ]; \ + then \ + git clone git://github.com/bitcoin/secp256k1.git $(1)/libsecp256k1; \ + pushd $(1)/libsecp256k1; \ + git checkout d7eb1ae96dfe9d497a26b3e7ff8b6f58e61e400a; \ + ./autogen.sh; \ + if [ $(VIRTUAL_ENV) ]; \ + then \ + ./configure --enable-module-recovery --enable-module-ecdh --enable-module-schnorr --prefix=$(VIRTUAL_ENV); \ + else \ + ./configure --enable-module-recovery --enable-module-ecdh --enable-module-schnorr; \ + fi; \ + make; \ + make install; \ + popd; \ + fi + +installlocalgit= if [ -d $(1) ]; \ + then \ + pushd $(1); \ + python setup.py install; \ + popd; \ + else \ + git clone $(2) $(1); \ + pushd $(1); \ + python setup.py install; \ + popd; \ + fi + +makeclean = rm -rf .cache build dist *.egg-info test/__pycache__ \ + rm -rf test/*.pyc *.egg *~ *pyc test/*~ .eggs \ + rm -f .coverage* build: python setup.py build +hackpendingPRs: + $(call installlocalgit, $(shell readlink -f ../../tapp/tappmq/ https://github.com/GitGuild/tappmq.git)) + $(call installlocalgit, $(shell readlink -f ../../tapp/sqlalchemy-models/ https://github.com/GitGuild/sqlalchemy-models.git)) + +dependencies: + $(call installledger, $(PREREQDIR)) + $(call installsecp256k1, $(PREREQDIR)) + install: - $(call makebase, "") - $(call makedirs, "") - #if [ $(target) ] \ - #then \ - # $(call installprereqs, $(target)) \ - #else \ - # $(call installprereqs, ./) \ - #fi + $(call makelog, "") + $(call makerun, "") python setup.py -v install clean: - rm -rf .cache build dist *.egg-info test/__pycache__ - rm -rf test/*.pyc *.egg *~ *pyc test/*~ .eggs - rm -f .coverage* + $(call makeclean, "") purge: - rm -rf .cache build dist *.egg-info test/__pycache__ - rm -rf test/*.pyc *.egg *~ *pyc test/*~ .eggs - rm -f .coverage* - rm -rf ~/.tapp/trademanager - rm -rf ~/.tapp/helper + $(call makeclean, "") + sudo rm -rf /var/run/trademanager + sudo rm -rf /var/log/trademanager + rm -rf $(PREREQDIR)/ledger + rm -rf $(PREREQDIR)/libsecp256k1 diff --git a/example_cfg.ini b/example_cfg.ini new file mode 100644 index 0000000..38c7290 --- /dev/null +++ b/example_cfg.ini @@ -0,0 +1,27 @@ +[db] +SA_ENGINE_URI: postgresql://postgres@trading + +[log] +LOGFILE: +LOGLEVEL: INFO + +[helper] +KEY: pubkey +SECRET: secret +active_markets: ["BTC_USD", "DASH_BTC"] +userpubkey: 1asdfasdfasdf + +[poloniex] +KEY: key +SECRET: secret +active_markets: ["BTC_USD", "DASH_BTC", "DASH_USD", "ETH_BTC", "ETH_USD", "LTC_BTC", "LTC_USD", "LSK_BTC", "FCT_BTC", "LBC_BTC", "ETC_BTC", "ETC_USD", "OMNI_BTC"] + +[bitfinex] +KEY: key +SECRET: secret +active_markets: ["BTC_USD", "ETH_USD", "ETH_BTC", "LTC_BTC", "LTC_USD", "BFX_USD", "BFX_BTC", "ETC_BTC", "ETC_USD"] + +[kraken] +KEY: key +SECRET: secret +active_markets: ["BTC_USD", "ETH_USD", "ETH_BTC", "ETC_BTC", "ETC_USD"] diff --git a/setup.py b/setup.py index b82a690..d069be2 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name='trade_manager', - version='0.0.5', + version='0.0.5.2', packages=['trade_manager'], url='https://github.com/gitguild/trade-manager', license='MIT', @@ -19,12 +19,16 @@ setup_requires=['pytest-runner'], install_requires=[ 'sqlalchemy>=1.0.9', + 'psycopg2', 'hashlib', 'jsonschema', 'alchemyjsonschema', 'redis', - 'python-daemon', - 'supervisor' + 'supervisor', + 'secp256k1', + 'cffi', + 'tappmq', + 'sqlalchemy_models' # 'secp256k1==0.11'#, # "bitjws==0.6.3.1", # "flask>=0.10.0", diff --git a/supervisord.conf b/supervisord.conf index 02e18a9..31deabf 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -1,45 +1,53 @@ [unix_http_server] -file = /tmp/supervisor_helper.sock +file = /tmp/supervisor_tm.sock [supervisorctl] -serverurl = unix:///tmp/supervisor_helper.sock +serverurl = unix:///tmp/supervisor_tm.sock [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [supervisord] -logfile = /home/ira/.tapp/supervisord.log -childlogdir = /home/ira/.tapp/ -pidfile = /home/ira/.tapp/supervisord.pid +logfile = /var/log/trademanager/supervisord.log +childlogdir = /var/log/trademanager/ +pidfile = /var/run/trademanager/supervisord.pid loglevel = debug -[program:helper] -command=python trade_manager/helper.py -autostart=false - [group:bitfinex] programs=bitfinexm,bitfinexl -autostart=false [program:bitfinexm] command=bitfinexm +autorestart=true +stopasgroup=true +autostart=false [program:bitfinexl] command=bitfinexl +autorestart=true +stopasgroup=true +autostart=false [program:kraken] command=krakenm +autorestart=true +stopasgroup=true autostart=false [group:poloniex] programs=poloniexm,poloniexl -autostart=false [program:poloniexm] command=poloniexm +autorestart=true +stopasgroup=true +autostart=false [program:poloniexl] command=poloniexl +autorestart=true +stopasgroup=true +autostart=false [eventlistener:trade_listener] command=tapplistener diff --git a/trade_manager/helper.py b/test/helper.py similarity index 90% rename from trade_manager/helper.py rename to test/helper.py index c43552e..f905363 100755 --- a/trade_manager/helper.py +++ b/test/helper.py @@ -16,7 +16,7 @@ def start_test_man(name='helper'): command = name if name != 'test' else 'helper' - os.system("supervisorctl start %s" % command) + os.system("supervisorctl -c test_supervisord.conf start %s" % command) status = 'stopped' countdown = 100 # 10 seconds while status != 'running' and countdown > 0: @@ -27,7 +27,7 @@ def start_test_man(name='helper'): def stop_test_man(name='helper'): command = name if name != 'test' else 'helper' - os.system("supervisorctl stop %s" % command) + os.system("supervisorctl -c test_supervisord.conf stop %s" % command) status = 'running' countdown = 100 # 10 seconds while status != 'stopped' and countdown > 0: @@ -224,3 +224,24 @@ def main(): if __name__ == "__main__": main() + + +def check_test_ticker(ticker, market='BTC_USD'): + base, quote = market.split("_") + assert hasattr(ticker, 'bid') + assert isinstance(ticker.bid, Amount) + assert str(ticker.bid.commodity) == quote + assert hasattr(ticker, 'ask') + assert isinstance(ticker.ask, Amount) + assert str(ticker.bid.commodity) == quote + assert hasattr(ticker, 'high') + assert isinstance(ticker.high, Amount) + assert str(ticker.bid.commodity) == quote + assert hasattr(ticker, 'low') + assert isinstance(ticker.low, Amount) + assert str(ticker.bid.commodity) == quote + assert hasattr(ticker, 'volume') + assert isinstance(ticker.volume, Amount) + assert str(ticker.volume.commodity) == base + assert hasattr(ticker, 'market') + assert ticker.market == market \ No newline at end of file diff --git a/test/test_cli.py b/test/test_cli.py new file mode 100644 index 0000000..c756d5a --- /dev/null +++ b/test/test_cli.py @@ -0,0 +1,101 @@ +import time +import unittest + +from helper import TestPlugin, start_test_man, stop_test_man +from sqlalchemy_models import get_schemas +from test.helper import check_test_ticker +from trade_manager.cli import handle_command +from trade_manager.plugin import get_orders, get_trades, sync_ticker, sync_balances + +SCHEMAS = get_schemas() + +tp = TestPlugin() +tp.setup_connections() +tp.setup_logger() + + +class TestCLI(unittest.TestCase): + def setUp(self): + start_test_man() + + def tearDown(self): + stop_test_man() + + def test_balance(self): + sync_balances('helper') + balance = "" + countdown = 30 + while (isinstance(balance, int) or balance == '') and countdown > 0: + countdown -= 1 + time.sleep(0.1) + balance = handle_command(['balance', 'get', '-e', 'helper'], session=tp.session) + assert str(balance) == "['0', '0']" + + def test_order_lifecycle(self): + order = str(handle_command(['order', 'create', 'bid', '100', '0.1', 'BTC_USD', 'helper'], session=tp.session)) + order_id = order[order.find("order_id") + 10: order.find("state") - 3] + exporder = " 0: + countdown -= 1 + oorder = get_orders('helper', order_id=order_id, session=tp.session) + if len(oorder) == 0 or oorder[0].state != 'open': + time.sleep(0.01) + tp.session.close() + assert len(oorder) == 1 + assert oorder[0].state == 'open' + clioorder = str( + handle_command(['order', 'get', '-e', 'helper', '--order_id', order_id.split("|")[1]], session=tp.session)) + assert exporder in clioorder.strip('[]') + handle_command(['order', 'cancel', 'helper', '--order_id', order_id.replace('tmp', 'helper').split("|")[1]], + session=tp.session) + exporder = exporder.replace("open", "closed") + countdown = 30 + tp.session.close() + corder = get_orders('helper', order_id=oorder[0].order_id, session=tp.session) + while corder[0].state != 'closed' and countdown > 0: + countdown -= 1 + corder = get_orders('helper', order_id=oorder[0].order_id, session=tp.session) + if corder[0].state != 'closed': + time.sleep(0.01) + tp.session.close() + assert len(corder) == 1 + assert corder[0].state == 'closed' + clicorder = str( + handle_command(['order', 'get', '-e', 'helper', '--order_id', order_id.split("|")[1]], session=tp.session)) + assert exporder in clicorder.strip('[]') + + def test_sync_trades(self): + trades = len(get_trades('helper', session=tp.session)) + handle_command(['trade', 'sync', '-e', 'helper'], session=tp.session) + tp.session.close() + newtrades = len(get_trades('helper', session=tp.session)) + countdown = 30 + while newtrades == trades and countdown > 0: + countdown -= 1 + newtrades = len(get_trades('helper', session=tp.session)) + if newtrades == trades: + time.sleep(0.01) + tp.session.close() + assert newtrades > trades + + def test_ticker(self): + sync_ticker('helper', market='BTC_USD') + ticker = "" + countdown = 30 + while (isinstance(ticker, int) or ticker == '') and countdown > 0: + countdown -= 1 + time.sleep(0.1) + ticker = handle_command(['ticker', 'get', '-e', 'helper', '-m', 'BTC_USD'], session=tp.session) + check_test_ticker(ticker) diff --git a/test/test_commands.py b/test/test_manage.py similarity index 61% rename from test/test_commands.py rename to test/test_manage.py index 63f84b3..cc1fff5 100644 --- a/test/test_commands.py +++ b/test/test_manage.py @@ -1,22 +1,18 @@ -import json -import time -import unittest -from ledger import Amount +from unittest import TestCase + from ledger import Balance -from alchemyjsonschema.dictify import datetime_rfc3339 -from jsonschema import validate -from sqlalchemy_models import get_schemas, wallet as wm, exchange as em, jsonify2 -from tappmq.tappmq import get_status -from test.test_manager import check_test_ticker +from ledger import Amount + +import time -from trade_manager.helper import TestPlugin, start_test_man, stop_test_man -from trade_manager.cli import handle_command -from trade_manager.plugin import get_orders, get_trades, sync_ticker, get_debits, sync_balances, get_credits, \ - make_ledger, get_ticker, get_balances, create_order, sync_orders, cancel_orders, sync_credits, sync_debits, \ - sync_trades, submit_order +from tappmq.tappmq import get_status +from test.helper import stop_test_man, start_test_man, TestPlugin, check_test_ticker +from test.test_plugin import check_test_ticker +from trade_manager.plugin import get_balances, create_order, submit_order, get_orders, get_credits, sync_credits, \ + get_debits, sync_debits, get_trades, sync_trades, sync_ticker, get_ticker, cancel_orders +from trade_manager.plugin import sync_balances -SCHEMAS = get_schemas() tp = TestPlugin() tp.setup_connections() @@ -35,49 +31,7 @@ def test_status(): assert status == 'stopped' -def test_ledger(): - tp.session.query(em.Trade).delete() - tp.session.query(wm.Credit).delete() - tp.session.query(wm.Debit).delete() - tp.session.commit() - tp.sync_credits() - tp.sync_debits() - tp.sync_trades() - trades = get_trades('helper', session=tp.session) - countdown = 30 - while len(trades) != 1 and countdown > 0: - countdown -= 1 - trades = get_trades('helper', session=tp.session) - if len(trades) != 1: - time.sleep(0.01) - credit = get_credits(session=tp.session)[0] - debit = get_debits(session=tp.session)[0] - trade = trades[0] - ledger = make_ledger('helper') - hardledger = """{0} helper credit BTC - Assets:helper:BTC:credit 1.00000000 BTC - Equity:Wallet:BTC:debit -1.00000000 BTC - -{1} helper debit BTC - Assets:helper:BTC:debit -1.00000000 BTC - Equity:Wallet:BTC:credit 1.00000000 BTC - -P {2} BTC 100.00000000 USD -P {2} USD 0.01000000 BTC -{2} helper BTC_USD buy - ; - Assets:helper:USD -1.00000000 USD @ 0.01000000 BTC - FX:BTC_USD:buy 1.00000000 USD @ 0.01000000 BTC - Assets:helper:BTC 0.01000000 BTC @ 100.00000000 USD - FX:BTC_USD:buy -0.01000000 BTC @ 100.00000000 USD - -""".format(credit.time.strftime('%Y/%m/%d %H:%M:%S'), debit.time.strftime('%Y/%m/%d %H:%M:%S'), - trade.time.strftime('%Y/%m/%d %H:%M:%S'), - datetime_rfc3339(trade.time), trade.trade_id) - assert ledger == hardledger - - -class TestPluginRunning(unittest.TestCase): +class TestPluginRunning(TestCase): def setUp(self): start_test_man() @@ -252,7 +206,7 @@ def test_order_lifecycle(self): assert len(corder) == 1 assert corder[0].state == 'closed' - def test_sync_credits(self, rescan=False): + def test_sync_credits(self): credits = len(get_credits(exchange='helper', session=tp.session)) sync_credits('helper') newcreds = len(get_credits(exchange='helper', session=tp.session)) @@ -265,7 +219,7 @@ def test_sync_credits(self, rescan=False): tp.session.close() assert newcreds > credits - def test_sync_debits(self, rescan=False): + def test_sync_debits(self): debits = len(get_debits(exchange='helper', session=tp.session)) sync_debits('helper') newdebs = len(get_debits(exchange='helper', session=tp.session)) @@ -278,7 +232,7 @@ def test_sync_debits(self, rescan=False): tp.session.close() assert newdebs > debits - def test_sync_trades(self, rescan=False): + def test_sync_trades(self): trades = len(get_trades(exchange='helper', session=tp.session)) sync_trades('helper') newtrades = len(get_trades(exchange='helper', session=tp.session)) @@ -299,90 +253,3 @@ def test_ticker(self): countdown -= 1 ticker = get_ticker('helper', market='BTC_USD') check_test_ticker(ticker) - - -class TestCLI(unittest.TestCase): - def setUp(self): - start_test_man() - - def tearDown(self): - stop_test_man() - - def test_balance(self): - sync_balances('helper') - balance = "" - countdown = 30 - while (isinstance(balance, int) or balance == '') and countdown > 0: - countdown -= 1 - time.sleep(0.1) - balance = handle_command(['balance', 'get', '-e', 'helper'], session=tp.session) - assert str(balance) == "['0', '0']" - - def test_order_lifecycle(self): - order = str(handle_command(['order', 'create', 'bid', '100', '0.1', 'BTC_USD', 'helper'], session=tp.session)) - order_id = order[order.find("order_id") + 10: order.find("state") - 3] - exporder = " 0: - countdown -= 1 - oorder = get_orders('helper', order_id=order_id, session=tp.session) - if len(oorder) == 0 or oorder[0].state != 'open': - time.sleep(0.01) - tp.session.close() - assert len(oorder) == 1 - assert oorder[0].state == 'open' - clioorder = str( - handle_command(['order', 'get', '-e', 'helper', '--order_id', order_id.split("|")[1]], session=tp.session)) - assert exporder in clioorder.strip('[]') - handle_command(['order', 'cancel', 'helper', '--order_id', order_id.replace('tmp', 'helper').split("|")[1]], - session=tp.session) - exporder = exporder.replace("open", "closed") - countdown = 30 - tp.session.close() - corder = get_orders('helper', order_id=oorder[0].order_id, session=tp.session) - while corder[0].state != 'closed' and countdown > 0: - countdown -= 1 - corder = get_orders('helper', order_id=oorder[0].order_id, session=tp.session) - if corder[0].state != 'closed': - time.sleep(0.01) - tp.session.close() - assert len(corder) == 1 - assert corder[0].state == 'closed' - clicorder = str( - handle_command(['order', 'get', '-e', 'helper', '--order_id', order_id.split("|")[1]], session=tp.session)) - assert exporder in clicorder.strip('[]') - - def test_sync_trades(self): - trades = len(get_trades('helper', session=tp.session)) - handle_command(['trade', 'sync', '-e', 'helper'], session=tp.session) - tp.session.close() - newtrades = len(get_trades('helper', session=tp.session)) - countdown = 30 - while newtrades == trades and countdown > 0: - countdown -= 1 - newtrades = len(get_trades('helper', session=tp.session)) - if newtrades == trades: - time.sleep(0.01) - tp.session.close() - assert newtrades > trades - - def test_ticker(self): - sync_ticker('helper', market='BTC_USD') - ticker = "" - countdown = 30 - while (isinstance(ticker, int) or ticker == '') and countdown > 0: - countdown -= 1 - time.sleep(0.1) - ticker = handle_command(['ticker', 'get', '-e', 'helper', '-m', 'BTC_USD'], session=tp.session) - check_test_ticker(ticker) diff --git a/test/test_manager.py b/test/test_plugin.py similarity index 76% rename from test/test_manager.py rename to test/test_plugin.py index 5f338ca..71561ee 100644 --- a/test/test_manager.py +++ b/test/test_plugin.py @@ -1,12 +1,12 @@ -import json -from ledger import Amount -from ledger import Balance +import time +from ledger import Amount, Balance -from jsonschema import validate -from sqlalchemy_models import get_schemas, exchange as em, jsonify2 +from alchemyjsonschema.dictify import datetime_rfc3339 -from trade_manager.helper import TestPlugin, make_base_id -from trade_manager.plugin import get_ticker, get_balances, get_orders, get_trades +from helper import TestPlugin, make_base_id +from sqlalchemy_models import get_schemas, exchange as em, wallet as wm +from test.helper import check_test_ticker +from trade_manager.plugin import get_ticker, get_balances, get_orders, get_trades, make_ledger, get_debits, get_credits tp = TestPlugin() tp.setup_connections() @@ -14,27 +14,6 @@ SCHEMAS = get_schemas() -def check_test_ticker(ticker, market='BTC_USD'): - base, quote = market.split("_") - assert hasattr(ticker, 'bid') - assert isinstance(ticker.bid, Amount) - assert str(ticker.bid.commodity) == quote - assert hasattr(ticker, 'ask') - assert isinstance(ticker.ask, Amount) - assert str(ticker.bid.commodity) == quote - assert hasattr(ticker, 'high') - assert isinstance(ticker.high, Amount) - assert str(ticker.bid.commodity) == quote - assert hasattr(ticker, 'low') - assert isinstance(ticker.low, Amount) - assert str(ticker.bid.commodity) == quote - assert hasattr(ticker, 'volume') - assert isinstance(ticker.volume, Amount) - assert str(ticker.volume.commodity) == base - assert hasattr(ticker, 'market') - assert ticker.market == market - - def test_ticker(): tp.sync_ticker('BTC_USD') ticker = get_ticker('helper', market='BTC_USD') @@ -75,13 +54,6 @@ def test_cancel_order_order_id(): assert isinstance(order.price, Amount) assert order.price == Amount("100 USD") assert order.state == 'open' - # porder = get_orders(oid=order.id, session=tp.session) - # assert len(porder) == 1 - # assert porder[0].state == 'open' - # tp.sync_orders() - # oorder = get_orders(oid=order.id, session=tp.session) - # assert len(oorder) == 1 - # assert oorder[0].state == 'open' tp.cancel_orders(order_id=order.order_id) corder = get_orders(oid=order.id, session=tp.session) assert len(corder) == 1 @@ -97,9 +69,6 @@ def test_cancel_order_order_id_no_prefix(): assert isinstance(order.price, Amount) assert order.price == Amount("100 USD") assert order.state == 'open' - # porder = get_orders(oid=order.id, session=tp.session) - # assert len(porder) == 1 - # assert porder[0].state == 'pending' tp.cancel_orders(order_id=order.order_id.split("|")[1]) corder = get_orders(oid=order.id, session=tp.session) assert len(corder) == 1 @@ -197,3 +166,45 @@ def test_get_trades(): assert len(trades) >= 1 for trade in trades: assert trade.market == 'DASH_BTC' + + +def test_ledger(): + tp.session.query(em.Trade).delete() + tp.session.query(wm.Credit).delete() + tp.session.query(wm.Debit).delete() + tp.session.commit() + tp.sync_credits() + tp.sync_debits() + tp.sync_trades() + trades = get_trades('helper', session=tp.session) + countdown = 30 + while len(trades) != 1 and countdown > 0: + countdown -= 1 + trades = get_trades('helper', session=tp.session) + if len(trades) != 1: + time.sleep(0.01) + credit = get_credits(session=tp.session)[0] + debit = get_debits(session=tp.session)[0] + trade = trades[0] + ledger = make_ledger('helper') + hardledger = """{0} helper credit BTC + Assets:helper:BTC:credit 1.00000000 BTC + Equity:Wallet:BTC:debit -1.00000000 BTC + +{1} helper debit BTC + Assets:helper:BTC:debit -1.00000000 BTC + Equity:Wallet:BTC:credit 1.00000000 BTC + +P {2} BTC 100.00000000 USD +P {2} USD 0.01000000 BTC +{2} helper BTC_USD buy + ; + Assets:helper:USD -1.00000000 USD @ 0.01000000 BTC + FX:BTC_USD:buy 1.00000000 USD @ 0.01000000 BTC + Assets:helper:BTC 0.01000000 BTC @ 100.00000000 USD + FX:BTC_USD:buy -0.01000000 BTC @ 100.00000000 USD + +""".format(credit.time.strftime('%Y/%m/%d %H:%M:%S'), debit.time.strftime('%Y/%m/%d %H:%M:%S'), + trade.time.strftime('%Y/%m/%d %H:%M:%S'), + datetime_rfc3339(trade.time), trade.trade_id) + assert ledger == hardledger diff --git a/test_supervisord.conf b/test_supervisord.conf new file mode 100644 index 0000000..6977798 --- /dev/null +++ b/test_supervisord.conf @@ -0,0 +1,23 @@ +[unix_http_server] +file = /tmp/supervisor_helper.sock + +[supervisorctl] +serverurl = unix:///tmp/supervisor_helper.sock + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisord] +logfile = /tmp/test_supervisord.log +childlogdir = /tmp +pidfile = /tmp/test_supervisord.pid +loglevel = debug + +[program:helper] +command=python test/helper.py +autostart=false + +[eventlistener:trade_listener] +command=tapplistener +events=PROCESS_STATE +buffer_size=100 diff --git a/trade_manager/plugin.py b/trade_manager/plugin.py index 665bcd9..6427a2b 100644 --- a/trade_manager/plugin.py +++ b/trade_manager/plugin.py @@ -1,14 +1,11 @@ import collections +import datetime import json import time -from ledger import commodities, Balance - -import datetime - -from alchemyjsonschema.dictify import jsonify from ledger import Amount +from ledger import commodities, Balance -from sqlalchemy_models import sa, Base, create_session_engine, jsonify2 +from sqlalchemy_models import sa, Base, create_session_engine from sqlalchemy_models.util import filter_query_by_attr, multiply_tickers from tapp_config import get_config, setup_redis from tappmq.tappmq import publish, MQHandlerBase, get_running_workers