def test_base_differences_start_xrp_xlm_usdt_rates(self): targets = { 'XRP': 50, 'XLM': 40, 'USDT': 10, } current = { 'XRP': 100, 'XLM': 100, 'USDT': 50, } rates = { 'XRP/USDT': 0.1, 'XLM/USDT': 0.4, } exchange = DummyExchange(targets.keys(), current, rates) portfolio = Portfolio.make_portfolio(targets, exchange) expected = { 'XRP': 40, 'XLM': 0, 'USDT': -40, } self.assertEqual(portfolio.differences_quote, expected)
def test_create_portfolio_needs_balancing3(self): exchange = DummyExchange(self.targets.keys(), self.balances) portfolio = Portfolio.make_portfolio(self.targets2, exchange, threshold=20) self.assertFalse(portfolio.needs_balancing)
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)
def test_create_portfolio_defaults(self): exchange = DummyExchange(self.targets.keys(), self.targets) portfolio = Portfolio.make_portfolio(self.targets, exchange) self.assertEqual(portfolio.threshold, 1.0) self.assertEqual(portfolio.quote_currency, 'USDT') self.assertEqual(portfolio.exchange, exchange) self.assertEqual(portfolio.targets, self.targets)
def test_create_portfolio_custom(self): exchange = DummyExchange(self.targets.keys(), self.targets) portfolio = Portfolio.make_portfolio(self.targets, exchange, 2.0, 'BTC') self.assertEqual(portfolio.threshold, 2.0) self.assertEqual(portfolio.quote_currency, 'BTC') self.assertEqual(portfolio.exchange, exchange) self.assertEqual(portfolio.targets, self.targets)
def test_create_portfolio_differences_quote2(self): exchange = DummyExchange(self.targets.keys(), self.balances) portfolio = Portfolio.make_portfolio(self.targets2, exchange) expected = { 'XRP': -50, 'XLM': -50, 'USDT': 100, } self.assertEqual(portfolio.differences_quote, expected)
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_base_differences_start_xrp_xlm_usdt_rates3(self): targets = { 'XRP': 45, 'XLM': 45, 'USDT': 10, } current = { 'XRP': 40, 'XLM': 40, 'USDT': 20, } rates = { 'XRP/USDT': 0.5, 'XLM/USDT': 0.5, 'USDT/USDT': 1.0, } exchange = DummyExchange(targets.keys(), current, rates) portfolio = Portfolio.make_portfolio(targets, exchange) expected = { 'XRP': 7, 'XLM': 7, 'USDT': -14, } self.assertEqual(portfolio.differences_quote, expected) final_base = {} final_targets = {} total_base = 0 for cur in current: symbol = "{}/{}".format(cur, portfolio.quote_currency) final_base[cur] = current[cur] * exchange.rates[symbol]['mid'] final_base[cur] += portfolio.differences_quote[cur] total_base += final_base[cur] for cur in final_base: final_targets[cur] = (final_base[cur] / total_base) * 100 self.assertEqual(targets, final_targets)
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 test_create_portfolio_needs_balancing2(self): exchange = DummyExchange(self.targets.keys(), self.balances) portfolio = Portfolio.make_portfolio(self.targets2, exchange) self.assertTrue(portfolio.needs_balancing)
def test_create_portfolio_metric_zero(self): exchange = DummyExchange(self.targets.keys(), self.zero_balances) portfolio = Portfolio.make_portfolio(self.targets, exchange) self.assertEqual(portfolio.balance_rms_error, 0)
def test_create_portfolio_metric2(self): exchange = DummyExchange(self.targets.keys(), self.balances) portfolio = Portfolio.make_portfolio(self.targets2, exchange) self.assertAlmostEqual(portfolio.balance_rms_error, 7.071067, 5)
def test_create_portfolio_balances_pct_zero(self): exchange = DummyExchange(self.targets.keys(), self.zero_balances) portfolio = Portfolio.make_portfolio(self.targets, exchange) self.assertEqual(portfolio.balances_pct, self.zero_balances) self.assertNotEqual(portfolio.balances_pct, self.targets)
def test_create_portfolio_valuation_quote(self): exchange = DummyExchange(self.targets.keys(), self.balances) portfolio = Portfolio.make_portfolio(self.targets, exchange) self.assertEqual(portfolio.valuation_quote, 1000)
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))
'BTC': 20, 'ETH': 20, 'USD': 20, } 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
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))