def execute(self, targets, current, rates, fee=0.001, max_orders=5, mode='mid'): exchange = DummyExchange(targets.keys(), current, rates, fee) portfolio = Portfolio.make_portfolio(targets, exchange) balancer = SimpleBalancer() return balancer.balance(portfolio, exchange, max_orders=max_orders, mode=mode)
def test_run_trade(self): targets = { 'XRP': 45, 'XLM': 45, 'USDT': 10, } current = {'XRP': 400, 'XLM': 400, 'USDT': 200} rates = { 'XRP/USDT': 1.0, 'XLM/USDT': 1.0, 'XLM/XRP': 1.0, } exchange = DummyExchange(targets.keys(), current, rates, 0.001) portfolio = Portfolio.make_portfolio(targets, exchange) balancer = SimpleBalancer() executor = Executor(portfolio, exchange, balancer) res = executor.run(force=True, trade=True) # Test the orders we get are correct expected = [ Order('XLM/USDT', 'BUY', 50, 1.0), Order('XRP/USDT', 'BUY', 50, 1.0), ] self.assertEqual(res['orders'], expected) self.assertEqual(exchange.balances['XRP'], 449.95) self.assertEqual(exchange.balances['XLM'], 449.95) self.assertEqual(exchange.balances['USDT'], 100)
} targets = { 'XRP': 80, 'USD': 20, } for t in range(10, 100, 10): threshold = t / 10.0 exchange = BacktestExchange('/Development/crypto_balancer/data/*.json', balances.copy()) portfolio = Portfolio.make_portfolio(targets, exchange, threshold, quote_currency="USD") balancer = SimpleBalancer() num_trades = 0 while portfolio.needs_balancing: res = balancer.balance(portfolio, exchange, accuracy=True, max_orders=4) for order in res['orders']: try: r = exchange.execute_order(order) num_trades += 1 except ValueError: pass portfolio.sync_balances()
def main(args=None): config = configparser.ConfigParser() config.read('config.ini') def exchange_choices(): return set(config.sections()) & set(exchanges) parser = argparse.ArgumentParser( description='Balance holdings on an exchange.') parser.add_argument('--trade', action="store_true", help='Actually place orders') parser.add_argument('--force', action="store_true", help='Force rebalance') parser.add_argument('--max_orders', default=5, help='Maximum number of orders to perform in ' 'rebalance') parser.add_argument('--valuebase', default='USDT', help='Currency to value portfolio in') parser.add_argument('--cancel', action="store_true", help='Cancel open orders first') parser.add_argument('--mode', choices=['mid', 'passive', 'cheap'], default='mid', help='Mode to place orders') parser.add_argument('exchange', choices=exchange_choices()) args = parser.parse_args() config = config[args.exchange] try: targets = [x.split() for x in config['targets'].split('\n')] targets = dict([[a, float(b)] for (a, b) in targets]) except ValueError: logger.error("Targets format invalid") sys.exit(1) total_target = sum(targets.values()) if total_target != 100: logger.error( "Total target needs to equal 100, it is {}".format(total_target)) sys.exit(1) valuebase = config.get('valuebase') or args.valuebase exchange = CCXTExchange(args.exchange, targets.keys(), config['api_key'], config['api_secret']) print("Connected to exchange: {}".format(exchange.name)) print() if args.cancel: print("Cancelling open orders...") for order in exchange.cancel_orders(): print("Cancelled order:", order['symbol'], order['id']) print() threshold = float(config['threshold']) max_orders = int(args.max_orders) portfolio = Portfolio.make_portfolio(targets, exchange, threshold, valuebase) print("Current Portfolio:") for cur in portfolio.balances: bal = portfolio.balances[cur] pct = portfolio.balances_pct[cur] tgt = targets[cur] print(" {:<6s} {:<8.2f} ({:>5.2f} / {:>5.2f}%)".format( cur, bal, pct, tgt)) print() print(" Total value: {:.2f} {}".format(portfolio.valuation_quote, portfolio.quote_currency)) balancer = SimpleBalancer() executor = Executor(portfolio, exchange, balancer) res = executor.run(force=args.force, trade=args.trade, max_orders=max_orders, mode=args.mode) print(" Balance RMS error: {:.2g} / {:.2g}".format( res['initial_portfolio'].balance_rms_error, threshold)) print(" Balance Max error: {:.2g} / {:.2g}".format( res['initial_portfolio'].balance_max_error, threshold)) print() if not portfolio.needs_balancing and not args.force: print("No balancing needed") sys.exit(0) print("Balancing needed{}:".format(" [FORCED]" if args.force else "")) print() print("Proposed Portfolio:") portfolio = res['proposed_portfolio'] if not portfolio: print("Could not calculate a better portfolio") sys.exit(0) for cur in portfolio.balances: bal = portfolio.balances[cur] pct = portfolio.balances_pct[cur] tgt = targets[cur] print(" {:<6s} {:<8.2f} ({:>5.2f} / {:>5.2f}%)".format( cur, bal, pct, tgt)) print() print(" Total value: {:.2f} {}".format(portfolio.valuation_quote, portfolio.quote_currency)) print(" Balance RMS error: {:.2g} / {:.2g}".format( res['proposed_portfolio'].balance_rms_error, threshold)) print(" Balance Max error: {:.2g} / {:.2g}".format( res['proposed_portfolio'].balance_max_error, threshold)) total_fee = '%s' % float('%.4g' % res['total_fee']) print(" Total fees to re-balance: {} {}".format(total_fee, portfolio.quote_currency)) print() print("Orders:") if args.trade: for order in res['success']: print(" Submitted: {}".format(order)) for order in res['errors']: print(" Failed: {}".format(order)) else: for order in res['orders']: print(" " + str(order))
def create_executor(self, targets, current, rates, fee=0.001): exchange = DummyExchange(targets.keys(), current, rates, fee) portfolio = Portfolio.make_portfolio(targets, exchange) balancer = SimpleBalancer() executor = Executor(portfolio, exchange, balancer) return executor
def balancing(portfolio_config, portfolio_name, trade, force, max_orders, cancel, log_dir, mode): set_up_logger(log_dir, portfolio_name) try: targets = [x.split() for x in portfolio_config['targets'].split('\n')] targets = dict([[a, float(b)] for (a, b) in targets]) except ValueError: logger.error("Targets format invalid") sys.exit(1) total_target = sum(targets.values()) if total_target != 100: logger.error("Total target needs to equal 100, it is {}" .format(total_target)) return valuebase = portfolio_config.get('valuebase') or 'USDT' exchange = CCXTExchange(portfolio_config['exchange'], targets.keys(), portfolio_config['api_key'], portfolio_config['api_secret']) logger.info("Connected to exchange: {}".format(exchange.name)) if cancel: logger.info("Cancelling open orders...") for order in exchange.cancel_orders(): logger.info("Cancelled order:", order['symbol'], order['id']) threshold = float(portfolio_config['threshold']) max_orders = int(max_orders) portfolio = Portfolio.make_portfolio(targets, exchange, threshold, valuebase) logger.info("Current Portfolio:") for cur in portfolio.balances: bal = portfolio.balances[cur] pct = portfolio.balances_pct[cur] tgt = targets[cur] logger.info(" {:<6s} {:<8.6f} ({:>5.2f} / {:>5.2f}%)".format(cur, bal, pct, tgt)) logger.info("\n") logger.info(" Total value: {:.2f} {}".format(portfolio.valuation_quote, portfolio.quote_currency)) balancer = SimpleBalancer() executor = Executor(portfolio, exchange, balancer) res = executor.run(force=force, trade=trade, max_orders=max_orders, mode=mode) logger.info(" Balance RMS error: {:.2g} / {:.2g}".format( res['initial_portfolio'].balance_rms_error, threshold)) logger.info(" Balance Max error: {:.2g} / {:.2g}".format( res['initial_portfolio'].balance_max_error, threshold)) if not portfolio.needs_balancing and not force: logger.info("\nNo balancing needed") return logger.info("\nBalancing needed{}:".format(" [FORCED]" if force else "")) logger.info("Proposed Portfolio:") portfolio = res['proposed_portfolio'] if not portfolio: logger.info("Could not calculate a better portfolio") return for cur in portfolio.balances: bal = portfolio.balances[cur] pct = portfolio.balances_pct[cur] tgt = targets[cur] logger.info(" {:<6s} {:<8.6f} ({:>5.2f} / {:>5.2f}%)" .format(cur, bal, pct, tgt)) logger.info(" Total value: {:.2f} {}".format(portfolio.valuation_quote, portfolio.quote_currency)) logger.info(" Balance RMS error: {:.2g} / {:.2g}".format( res['proposed_portfolio'].balance_rms_error, threshold)) logger.info(" Balance Max error: {:.2g} / {:.2g}".format( res['proposed_portfolio'].balance_max_error, threshold)) total_fee = '%s' % float('%.4g' % res['total_fee']) logger.info(" Total fees to re-balance: {} {}" .format(total_fee, portfolio.quote_currency)) logger.info("\n") logger.info("Orders:") if trade: for order in res['success']: logger.info(" Submitted: {}".format(order)) for order in res['errors']: logger.info(" Failed: {}".format(order)) else: for order in res['orders']: logger.info(" " + str(order))