Example #1
0
    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)
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
    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)
Example #6
0
    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)
Example #7
0
    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)
Example #8
0
    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)
Example #9
0
 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
Example #10
0
    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)
Example #11
0
    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)
Example #12
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)
Example #13
0
    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)
Example #14
0
    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)
Example #15
0
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
Example #17
0
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))