Esempio n. 1
0
    def _new_buy_orders(self, our_buy_orders: list, our_buy_balance: Wad, target_price: Wad):
        """Return buy orders which need to be placed to bring total amounts within all buy bands above minimums."""
        assert(isinstance(our_buy_orders, list))
        assert(isinstance(our_buy_balance, Wad))
        assert(isinstance(target_price, Wad))

        new_orders = []
        limit_amount = self.buy_limits.available_limit(time.time())
        missing_amount = Wad(0)

        for band in self.buy_bands:
            orders = [order for order in our_buy_orders if band.includes(order, target_price)]
            total_amount = self.total_amount(orders)
            if total_amount < band.min_amount:
                price = band.avg_price(target_price)
                pay_amount = Wad.min(band.avg_amount - total_amount, our_buy_balance, limit_amount)
                buy_amount = pay_amount / price
                missing_amount += Wad.max((band.avg_amount - total_amount) - our_buy_balance, Wad(0))
                if (pay_amount >= band.dust_cutoff) and (pay_amount > Wad(0)) and (buy_amount > Wad(0)):
                    self.logger.debug(f"Using price {price} for new buy order")

                    our_buy_balance = our_buy_balance - pay_amount
                    limit_amount = limit_amount - pay_amount

                    new_orders.append(NewOrder(is_sell=False, price=price, pay_amount=pay_amount, buy_amount=buy_amount,
                                               confirm_function=lambda: self.buy_limits.use_limit(time.time(), pay_amount)))

        return new_orders, missing_amount
    def test_should_support_config_files_with_variables(self, deployment: Deployment, tmpdir):
        # given
        config_file = BandConfig.with_variables_config(tmpdir)

        # and
        keeper = OasisMarketMakerKeeper(args=args(f"--eth-from {deployment.our_address} "
                                                  f"--tub-address {deployment.tub.address} "
                                                  f"--oasis-address {deployment.otc.address} "
                                                  f"--buy-token-address {deployment.sai.address} "
                                                  f"--sell-token-address {deployment.gem.address} "
                                                  f"--price-feed tub "
                                                  f"--config {config_file}"),
                                        web3=deployment.web3)
        keeper.lifecycle = Lifecycle(web3=keeper.web3)

        # and
        self.mint_tokens(deployment)
        self.set_price(deployment, Wad.from_number(100))

        # when
        keeper.approve()
        self.synchronize_orders_twice(keeper)

        # then
        assert len(deployment.otc.get_orders()) == 1

        # and
        assert self.orders_by_token(deployment, deployment.gem)[0].maker == deployment.our_address
        assert self.orders_by_token(deployment, deployment.gem)[0].pay_amount == Wad.from_number(5.0)
        assert self.orders_by_token(deployment, deployment.gem)[0].pay_token == deployment.gem.address
        assert self.orders_by_token(deployment, deployment.gem)[0].buy_amount == Wad.from_number(520)
        assert self.orders_by_token(deployment, deployment.gem)[0].buy_token == deployment.sai.address
    def test_should_use_individual_buy_and_sell_prices_if_both_available(self):
        # when
        price_feed = WebSocketPriceFeed(FakeFeed({"buyPrice": "120.75", "sellPrice": "130.75"}))

        # then
        assert(price_feed.get_price().buy_price == Wad.from_number(120.75))
        assert(price_feed.get_price().sell_price == Wad.from_number(130.75))
    def test_should_not_create_any_orders_but_not_terminate_if_eth_balance_before_minimum(self, deployment: Deployment, tmpdir):
        # given
        config_file = BandConfig.two_adjacent_bands_config(tmpdir)

        # and
        keeper = OasisMarketMakerKeeper(args=args(f"--eth-from {deployment.our_address} "
                                                  f"--tub-address {deployment.tub.address} "
                                                  f"--oasis-address {deployment.otc.address} "
                                                  f"--buy-token-address {deployment.sai.address} "
                                                  f"--sell-token-address {deployment.gem.address} "
                                                  f"--price-feed tub "
                                                  f"--config {config_file} "
                                                  f"--min-eth-balance 100.0"),
                                        web3=deployment.web3)
        keeper.lifecycle = Lifecycle(web3=keeper.web3)

        # and
        self.mint_tokens(deployment)
        self.set_price(deployment, Wad.from_number(100))

        # and
        self.leave_only_some_eth(deployment, Wad.from_number(10.0))  # there is a 5.0 ETH block reward even in testrpc,
                                                                     # that's why `--min-eth-balance` is higher than 10

        # when
        keeper.approve()
        self.synchronize_orders_twice(keeper)

        # then
        assert len(deployment.otc.get_orders()) == 0
        assert not keeper.lifecycle.terminated_internally
    def test_should_use_same_buy_and_sell_price_if_only_one_price_available(self):
        # when
        price_feed = WebSocketPriceFeed(FakeFeed({"price": "125.75"}))

        # then
        assert(price_feed.get_price().buy_price == Wad.from_number(125.75))
        assert(price_feed.get_price().sell_price == Wad.from_number(125.75))
    def test_should_not_cancel_anything_if_no_orders_to_cancel_regardless_of_price_availability(self, tmpdir):
        # given
        config = BandConfig.sample_config(tmpdir)
        bands = self.create_bands(config)

        # when
        price = Price(buy_price=Wad.from_number(100), sell_price=Wad.from_number(200))
        orders_to_cancel = bands.cancellable_orders([], [], price)
        # then
        assert(orders_to_cancel == [])

        # when
        price = Price(buy_price=Wad.from_number(100), sell_price=None)
        orders_to_cancel = bands.cancellable_orders([], [], price)
        # then
        assert(orders_to_cancel == [])

        # when
        price = Price(buy_price=None, sell_price=Wad.from_number(200))
        orders_to_cancel = bands.cancellable_orders([], [], price)
        # then
        assert(orders_to_cancel == [])

        # when
        price = Price(buy_price=None, sell_price=None)
        orders_to_cancel = bands.cancellable_orders([], [], price)
        # then
        assert(orders_to_cancel == [])
 def test_available_limit_is_always_max_if_no_limits_defined_even_when_orders_are_being_placed(self, no_limits):
     # when
     no_limits.use_limit(self.time_zero, Wad.from_number(5))
     # then
     assert no_limits.available_limit(self.time_zero - 1) == Wad.from_number(2**256 - 1)
     assert no_limits.available_limit(self.time_zero) == Wad.from_number(2**256 - 1)
     assert no_limits.available_limit(self.time_zero + 1) == Wad.from_number(2**256 - 1)
 def test_limit_renews_when_the_slot_is_over(self, sample_limits):
     # when
     sample_limits.use_limit(self.time_zero, Wad.from_number(5))
     # then
     assert sample_limits.available_limit(self.time_zero) == Wad.from_number(95)
     assert sample_limits.available_limit(self.time_zero + 60*60 - 1) == Wad.from_number(95)
     assert sample_limits.available_limit(self.time_zero + 60*60) == Wad.from_number(100)
    def get_price(self) -> Price:
        data, timestamp = self.feed.get()

        try:
            if 'buyPrice' in data:
                buy_price = Wad.from_number(data['buyPrice'])

            elif 'price' in data:
                buy_price = Wad.from_number(data['price'])

            else:
                buy_price = None
        except:
            buy_price = None

        try:
            if 'sellPrice' in data:
                sell_price = Wad.from_number(data['sellPrice'])

            elif 'price' in data:
                sell_price = Wad.from_number(data['price'])

            else:
                sell_price = None
        except:
            sell_price = None

        return Price(buy_price=buy_price, sell_price=sell_price)
Esempio n. 10
0
    def __init__(self, dictionary: dict):
        super()._validate_deprecated_properties(dictionary)

        super().__init__(min_margin=dictionary['minMargin'],
                         avg_margin=dictionary['avgMargin'],
                         max_margin=dictionary['maxMargin'],
                         min_amount=Wad.from_number(dictionary['minAmount']),
                         avg_amount=Wad.from_number(dictionary['avgAmount']),
                         max_amount=Wad.from_number(dictionary['maxAmount']),
                         dust_cutoff=Wad.from_number(dictionary['dustCutoff']))
    def test_should_place_extra_order_only_if_order_brought_below_min(self, deployment: Deployment, tmpdir):
        # given
        config_file = BandConfig.sample_config(tmpdir)

        # and
        keeper = OasisMarketMakerKeeper(args=args(f"--eth-from {deployment.our_address} "
                                                  f"--tub-address {deployment.tub.address} "
                                                  f"--oasis-address {deployment.otc.address} "
                                                  f"--buy-token-address {deployment.sai.address} "
                                                  f"--sell-token-address {deployment.gem.address} "
                                                  f"--price-feed tub "
                                                  f"--config {config_file}"),
                                        web3=deployment.web3)
        keeper.lifecycle = Lifecycle(web3=keeper.web3)

        # and
        self.mint_tokens(deployment)
        self.set_price(deployment, Wad.from_number(100))

        # and
        keeper.approve()
        self.synchronize_orders_twice(keeper)
        assert len(deployment.otc.get_orders()) == 2
        sai_order_id = self.orders_by_token(deployment, deployment.sai)[0].order_id

        # when
        deployment.otc.take(sai_order_id, Wad.from_number(20)).transact()
        # and
        keeper.order_book_manager.wait_for_order_book_refresh()
        # and
        self.synchronize_orders_twice(keeper)
        # then
        assert len(deployment.otc.get_orders()) == 2

        # when
        deployment.otc.take(sai_order_id, Wad.from_number(5)).transact()
        # and
        keeper.order_book_manager.wait_for_order_book_refresh()
        # and
        self.synchronize_orders_twice(keeper)
        # then
        assert len(deployment.otc.get_orders()) == 2

        # when
        deployment.otc.take(sai_order_id, Wad.from_number(1)).transact()
        # and
        keeper.order_book_manager.wait_for_order_book_refresh()
        # and
        self.synchronize_orders_twice(keeper)
        # then
        assert len(deployment.otc.get_orders()) == 3
        assert deployment.otc.get_orders()[2].pay_amount == Wad.from_number(26)
        assert deployment.otc.get_orders()[2].pay_token == deployment.sai.address
        assert deployment.otc.get_orders()[2].buy_amount == Wad(270833333333333333)
        assert deployment.otc.get_orders()[2].buy_token == deployment.gem.address
    def test_should_not_create_orders_if_neither_buy_nor_sell_price_available(self, tmpdir):
        # given
        config = BandConfig.sample_config(tmpdir)
        bands = self.create_bands(config)

        # when
        price = Price(buy_price=None, sell_price=None)
        new_orders, _, _ = bands.new_orders([], [], Wad.from_number(1000000), Wad.from_number(1000000), price)

        # then
        assert(new_orders == [])
    def test_should_handle_only_buy_price_or_only_sell_price(self):
        # when
        price_feed = WebSocketPriceFeed(FakeFeed({"buyPrice": "120.75"}))
        # then
        assert(price_feed.get_price().buy_price == Wad.from_number(120.75))
        assert(price_feed.get_price().sell_price is None)

        # when
        price_feed = WebSocketPriceFeed(FakeFeed({"sellPrice": "130.75"}))
        # then
        assert(price_feed.get_price().buy_price is None)
        assert(price_feed.get_price().sell_price == Wad.from_number(130.75))
    def test_should_default_to_price_if_no_buy_price_or_no_sell_price(self):
        # when
        price_feed = WebSocketPriceFeed(FakeFeed({"price": "125.0", "buyPrice": "120.75"}))
        # then
        assert(price_feed.get_price().buy_price == Wad.from_number(120.75))
        assert(price_feed.get_price().sell_price == Wad.from_number(125.0))

        # when
        price_feed = WebSocketPriceFeed(FakeFeed({"price": "125.0", "sellPrice": "130.75"}))
        # then
        assert(price_feed.get_price().buy_price == Wad.from_number(125.0))
        assert(price_feed.get_price().sell_price == Wad.from_number(130.75))
    def test_value_2(self):
        # given
        price_feed_1 = FakePriceFeed()
        price_feed_2 = FakePriceFeed()
        average_price_feed = AveragePriceFeed([price_feed_1, price_feed_2])

        # and
        price_feed_2.set_price(Wad.from_number(17.5))

        # expect
        assert average_price_feed.get_price().buy_price == Wad.from_number(17.5)
        assert average_price_feed.get_price().sell_price == Wad.from_number(17.5)
    def test_should_cancel_selected_sell_orders_to_bring_the_band_total_below_max_and_closest_to_it(self, deployment: Deployment, tmpdir):
        # given
        config_file = BandConfig.sample_config(tmpdir)

        # and
        keeper = OasisMarketMakerKeeper(args=args(f"--eth-from {deployment.our_address} "
                                                  f"--tub-address {deployment.tub.address} "
                                                  f"--oasis-address {deployment.otc.address} "
                                                  f"--buy-token-address {deployment.sai.address} "
                                                  f"--sell-token-address {deployment.gem.address} "
                                                  f"--price-feed tub "
                                                  f"--config {config_file}"),
                                        web3=deployment.web3)
        keeper.lifecycle = Lifecycle(web3=keeper.web3)

        # and
        self.mint_tokens(deployment)
        self.set_price(deployment, Wad.from_number(100))

        # and
        keeper.approve()
        self.synchronize_orders_twice(keeper)
        assert len(deployment.otc.get_orders()) == 2

        # when [7.5+2.0 = 9.5]
        deployment.otc.make(deployment.gem.address, Wad.from_number(2), deployment.sai.address, Wad.from_number(208)).transact()
        # and
        keeper.order_book_manager.wait_for_order_book_refresh()
        # and
        self.synchronize_orders_twice(keeper)
        # then
        assert len(deployment.otc.get_orders()) == 3

        # when [9.5+0.5 = 10]
        deployment.otc.make(deployment.gem.address, Wad.from_number(0.5), deployment.sai.address, Wad.from_number(52)).transact()
        # and
        keeper.order_book_manager.wait_for_order_book_refresh()
        # and
        self.synchronize_orders_twice(keeper)
        # then
        assert len(deployment.otc.get_orders()) == 4

        # when [10+0.1 = 10.1] --> above max!
        deployment.otc.make(deployment.gem.address, Wad.from_number(0.1), deployment.sai.address, Wad.from_number(10.4)).transact()
        # and
        keeper.order_book_manager.wait_for_order_book_refresh()
        # and
        self.synchronize_orders_twice(keeper)
        # then
        assert len(deployment.otc.get_orders()) == 4
        assert reduce(Wad.__add__, map(lambda order: order.pay_amount, self.orders_by_token(deployment, deployment.gem)), Wad(0)) \
               == Wad.from_number(10.0)
    def test_should_create_only_sell_orders_if_only_sell_price_is_available(self, tmpdir):
        # given
        config = BandConfig.sample_config(tmpdir)
        bands = self.create_bands(config)

        # when
        price = Price(buy_price=None, sell_price=Wad.from_number(200))
        new_orders, _, _ = bands.new_orders([], [], Wad.from_number(1000000), Wad.from_number(1000000), price)

        # then
        assert(len(new_orders) == 1)
        assert(new_orders[0].is_sell is True)
        assert(new_orders[0].price == Wad.from_number(208))
 def our_available_balance(self, our_balances, token: Address) -> Wad:
     if token == EtherDelta.ETH_TOKEN:
         try:
             return Wad.from_number(our_balances['ETH']['available'])
         except KeyError:
             return Wad(0)
     elif token == self.sai.address:
         try:
             return Wad.from_number(our_balances['DAI']['available'])
         except KeyError:
             return Wad(0)
     else:
         raise Exception("Unknown token")
    def test_should_cancel_orders_if_price_disappears(self, tmpdir):
        # given
        config = BandConfig.sample_config(tmpdir)
        bands = self.create_bands(config)

        # and
        buy_order = FakeOrder(Wad.from_number(75), Wad.from_number(96))
        sell_order = FakeOrder(Wad.from_number(7.5), Wad.from_number(208))

        # when
        price = Price(buy_price=Wad.from_number(100), sell_price=Wad.from_number(200))
        orders_to_cancel = bands.cancellable_orders([buy_order], [sell_order], price)
        # then
        assert(orders_to_cancel == [])

        # when
        price = Price(buy_price=Wad.from_number(100), sell_price=None)
        orders_to_cancel = bands.cancellable_orders([buy_order], [sell_order], price)
        # then
        assert(orders_to_cancel == [sell_order])

        # when
        price = Price(buy_price=None, sell_price=Wad.from_number(200))
        orders_to_cancel = bands.cancellable_orders([buy_order], [sell_order], price)
        # then
        assert(orders_to_cancel == [buy_order])

        # when
        price = Price(buy_price=None, sell_price=None)
        orders_to_cancel = bands.cancellable_orders([buy_order], [sell_order], price)
        # then
        assert(orders_to_cancel == [buy_order, sell_order])
Esempio n. 20
0
    def available_limit(self, timestamp: int, side_history: SideHistory):
        assert(isinstance(side_history, SideHistory))

        items = filter(lambda item: timestamp - self.seconds < item['timestamp'] <= timestamp, side_history.get_items())
        used_amount = reduce(Wad.__add__, map(lambda item: item['amount'], items), Wad(0))

        return Wad.max(self.amount - used_amount, Wad(0))
    def test_should_cancel_orders_on_shutdown(self, deployment: Deployment, tmpdir):
        # given
        config_file = BandConfig.sample_config(tmpdir)

        # and
        keeper = OasisMarketMakerKeeper(args=args(f"--eth-from {deployment.our_address} "
                                                  f"--tub-address {deployment.tub.address} "
                                                  f"--oasis-address {deployment.otc.address} "
                                                  f"--buy-token-address {deployment.sai.address} "
                                                  f"--sell-token-address {deployment.gem.address} "
                                                  f"--price-feed tub "
                                                  f"--config {config_file}"),
                                        web3=deployment.web3)
        keeper.lifecycle = Lifecycle(web3=keeper.web3)

        # and
        self.mint_tokens(deployment)
        self.set_price(deployment, Wad.from_number(100))

        # and
        keeper.approve()
        self.synchronize_orders_twice(keeper)
        assert len(deployment.otc.get_orders()) == 2

        # when
        keeper.shutdown()

        # then
        assert len(deployment.otc.get_orders()) == 0
    def test_should_cancel_all_orders_but_not_terminate_if_market_gets_closed(self, deployment: Deployment, tmpdir):
        # given
        config_file = BandConfig.sample_config(tmpdir)

        # and
        keeper = OasisMarketMakerKeeper(args=args(f"--eth-from {deployment.our_address} "
                                                  f"--tub-address {deployment.tub.address} "
                                                  f"--oasis-address {deployment.otc.address} "
                                                  f"--buy-token-address {deployment.sai.address} "
                                                  f"--sell-token-address {deployment.gem.address} "
                                                  f"--price-feed tub "
                                                  f"--config {config_file}"),
                                        web3=deployment.web3)
        keeper.lifecycle = Lifecycle(web3=keeper.web3)

        # and
        self.mint_tokens(deployment)
        self.set_price(deployment, Wad.from_number(100))

        # when
        keeper.approve()
        self.synchronize_orders_twice(keeper)

        # then
        assert len(deployment.otc.get_orders()) == 2

        # when
        deployment.otc._contract.transact().stop()

        # and
        self.synchronize_orders_twice(keeper)

        # then
        assert len(deployment.otc.get_orders()) == 0
        assert not keeper.lifecycle.terminated_internally
    def test_should_use_specified_gas_price_for_all_transactions(self, deployment: Deployment, tmpdir):
        # given
        config_file = BandConfig.sample_config(tmpdir)

        # and
        keeper = OasisMarketMakerKeeper(args=args(f"--eth-from {deployment.our_address} "
                                                  f"--tub-address {deployment.tub.address} "
                                                  f"--oasis-address {deployment.otc.address} "
                                                  f"--buy-token-address {deployment.sai.address} "
                                                  f"--sell-token-address {deployment.gem.address} "
                                                  f"--price-feed tub "
                                                  f"--config {config_file} "
                                                  f"--gas-price 70000000000"),
                                        web3=deployment.web3)
        keeper.lifecycle = Lifecycle(web3=keeper.web3)

        # and
        self.mint_tokens(deployment)
        self.set_price(deployment, Wad.from_number(100))

        # and
        start_block_number = deployment.web3.eth.blockNumber

        # when
        keeper.approve()
        self.synchronize_orders_twice(keeper)
        keeper.shutdown()

        # then
        for block_number in range(start_block_number+1, deployment.web3.eth.blockNumber+1):
            for transaction in deployment.web3.eth.getBlock(block_number, full_transactions=True).transactions:
                assert transaction.gasPrice == 70000000000
    def deposit_for_sell_order(self, missing_sell_amount: Wad):
        # We always want to deposit at least `min_eth_deposit`. If `missing_sell_amount` is less
        # than that, we deposit `min_eth_deposit` anyway.
        if Wad(0) < missing_sell_amount < self.min_eth_deposit:
            missing_sell_amount = self.min_eth_deposit

        # We can never deposit more than our available ETH balance minus `eth_reserve` (reserve for gas).
        depositable_eth = Wad.max(eth_balance(self.web3, self.our_address) - self.eth_reserve, Wad(0))
        missing_sell_amount = Wad.min(missing_sell_amount, depositable_eth)

        # If we still can deposit something, and it's at least `min_eth_deposit`, then we do deposit.
        if missing_sell_amount > Wad(0) and missing_sell_amount >= self.min_eth_deposit:
            receipt = self.idex.deposit(missing_sell_amount).transact(gas_price=self.gas_price)
            return receipt is not None and receipt.successful
        else:
            return False
    def _process_ticker(self, message_obj):
        self._last_price = Wad.from_number(message_obj['price'])
        self._last_timestamp = time.time()

        self.logger.debug(f"Price feed from GDAX is {self._last_price} ({self.product_id})")

        if self._expired:
            self.logger.info(f"Price feed from GDAX ({self.product_id}) became available")
            self._expired = False
    def get_price(self) -> Price:
        total_buy = Wad.from_number(0)
        count_buy = 0

        total_sell = Wad.from_number(0)
        count_sell = 0

        for feed in self.feeds:
            price = feed.get_price()
            if price.buy_price is not None:
                total_buy += price.buy_price
                count_buy += 1

            if price.sell_price is not None:
                total_sell += price.sell_price
                count_sell += 1

        buy_price = total_buy / Wad.from_number(count_buy) if count_buy > 0 else None
        sell_price = total_sell / Wad.from_number(count_sell) if count_sell > 0 else None

        return Price(buy_price=buy_price, sell_price=sell_price)
    def test_should_cancel_orders_owned_by_us(self, deployment: Deployment):
        # given
        keeper = OasisMarketMakerCancel(args=args(f"--eth-from {deployment.web3.eth.defaultAccount} "
                                             f"--oasis-address {deployment.otc.address}"),
                                        web3=deployment.web3)

        # and
        DSToken(web3=deployment.web3, address=deployment.gem.address).mint(Wad.from_number(1000)).transact()
        DSToken(web3=deployment.web3, address=deployment.sai.address).mint(Wad.from_number(1000)).transact()

        # and
        deployment.otc.approve([deployment.gem, deployment.sai], directly())
        deployment.otc.make(deployment.gem.address, Wad.from_number(10), deployment.sai.address, Wad.from_number(5)).transact()
        deployment.otc.make(deployment.sai.address, Wad.from_number(5), deployment.gem.address, Wad.from_number(12)).transact()
        assert len(deployment.otc.get_orders()) == 2

        # when
        keeper.main()

        # then
        assert len(deployment.otc.get_orders()) == 0
    def test_limit_descreases_with_new_orders(self, sample_limits):
        # when
        sample_limits.use_limit(self.time_zero, Wad.from_number(5))
        # then
        assert sample_limits.available_limit(self.time_zero - 1) == Wad.from_number(100)
        assert sample_limits.available_limit(self.time_zero) == Wad.from_number(95)
        assert sample_limits.available_limit(self.time_zero + 1) == Wad.from_number(95)

        # when
        sample_limits.use_limit(self.time_zero + 60, Wad.from_number(10))
        # then
        assert sample_limits.available_limit(self.time_zero + 59) == Wad.from_number(95)
        assert sample_limits.available_limit(self.time_zero + 60) == Wad.from_number(85)
        assert sample_limits.available_limit(self.time_zero + 61) == Wad.from_number(85)
    def test_should_cancel_the_only_sell_order_and_place_a_new_one_if_above_max(self, deployment: Deployment, tmpdir):
        # given
        config_file = BandConfig.sample_config(tmpdir)

        # and
        keeper = OasisMarketMakerKeeper(args=args(f"--eth-from {deployment.our_address} "
                                                  f"--tub-address {deployment.tub.address} "
                                                  f"--oasis-address {deployment.otc.address} "
                                                  f"--buy-token-address {deployment.sai.address} "
                                                  f"--sell-token-address {deployment.gem.address} "
                                                  f"--price-feed tub "
                                                  f"--config {config_file}"),
                                        web3=deployment.web3)
        keeper.lifecycle = Lifecycle(web3=keeper.web3)

        # and
        self.mint_tokens(deployment)
        self.set_price(deployment, Wad.from_number(100))

        # and
        keeper.approve()

        # and
        # [one artificially created order above the max band threshold]
        deployment.otc.make(deployment.gem.address, Wad.from_number(20), deployment.sai.address, Wad.from_number(2080)).transact()
        # and
        keeper.order_book_manager.wait_for_order_book_refresh()

        # when
        self.synchronize_orders_twice(keeper)

        # then
        # [the artificial order gets cancelled, a new one gets created instead]
        assert len(deployment.otc.get_orders()) == 2
        assert self.orders_by_token(deployment, deployment.gem)[0].maker == deployment.our_address
        assert self.orders_by_token(deployment, deployment.gem)[0].pay_amount == Wad.from_number(7.5)
        assert self.orders_by_token(deployment, deployment.gem)[0].pay_token == deployment.gem.address
        assert self.orders_by_token(deployment, deployment.gem)[0].buy_amount == Wad.from_number(780)
        assert self.orders_by_token(deployment, deployment.gem)[0].buy_token == deployment.sai.address
    def test_should_create_orders_in_multiple_bands(self, deployment: Deployment, tmpdir):
        # given
        config_file = BandConfig.two_adjacent_bands_config(tmpdir)

        # and
        keeper = OasisMarketMakerKeeper(args=args(f"--eth-from {deployment.our_address} "
                                                  f"--tub-address {deployment.tub.address} "
                                                  f"--oasis-address {deployment.otc.address} "
                                                  f"--buy-token-address {deployment.sai.address} "
                                                  f"--sell-token-address {deployment.gem.address} "
                                                  f"--price-feed tub "
                                                  f"--config {config_file}"),
                                        web3=deployment.web3)
        keeper.lifecycle = Lifecycle(web3=keeper.web3)

        # and
        self.mint_tokens(deployment)
        self.set_price(deployment, Wad.from_number(100))

        # when
        keeper.approve()
        self.synchronize_orders_twice(keeper)

        # then
        assert len(deployment.otc.get_orders()) == 2

        # and
        assert self.orders_sorted(deployment.otc.get_orders())[0].maker == deployment.our_address
        assert self.orders_sorted(deployment.otc.get_orders())[0].pay_amount == Wad.from_number(7.5)
        assert self.orders_sorted(deployment.otc.get_orders())[0].pay_token == deployment.gem.address
        assert self.orders_sorted(deployment.otc.get_orders())[0].buy_amount == Wad.from_number(780)
        assert self.orders_sorted(deployment.otc.get_orders())[0].buy_token == deployment.sai.address

        # and
        assert self.orders_sorted(deployment.otc.get_orders())[1].maker == deployment.our_address
        assert self.orders_sorted(deployment.otc.get_orders())[1].pay_amount == Wad.from_number(9.5)
        assert self.orders_sorted(deployment.otc.get_orders())[1].pay_token == deployment.gem.address
        assert self.orders_sorted(deployment.otc.get_orders())[1].buy_amount == Wad.from_number(1026)
        assert self.orders_sorted(deployment.otc.get_orders())[1].buy_token == deployment.sai.address
Esempio n. 31
0
 def test_should_support_values_greater_than_uint256(self):
     Wad(2**256)
     Wad(2**256 + 1)
     Wad(2**512)
Esempio n. 32
0
 def test_multiply_by_int(self):
     assert Wad.from_number(2) * 3 == Wad.from_number(6)
     assert Wad.from_number(2) * 1 == Wad.from_number(2)
Esempio n. 33
0
 def our_available_balance(self, our_balances: list, token: str) -> Wad:
     return Wad.from_number(
         next(filter(lambda coin: coin['symbol'] == token,
                     our_balances))['balance'])
Esempio n. 34
0
 def test_should_fail_to_instantiate_from_a_float(self):
     with pytest.raises(ArithmeticError):
         assert Wad(10.5)
Esempio n. 35
0
 def test_add(self):
     assert Wad(1) + Wad(2) == Wad(3)
Esempio n. 36
0
 def test_subtract(self):
     assert Wad(10) - Wad(2) == Wad(8)
     assert Wad(1) - Wad(2) == Wad(-1)
Esempio n. 37
0
 def test_multiply_by_wad(self):
     assert Ray.from_number(2) * Wad.from_number(3) == Ray.from_number(6)
     assert Ray.from_number(2) * Wad(3) == Ray(6000000000)
     assert Ray(2) * Wad(3) == Ray(0)
     assert Ray(2) * Wad(999999999999999999) == Ray(1)
     assert Ray(2) * Wad(1000000000000000000) == Ray(2)
Esempio n. 38
0
 def test_min_value_should_reject_comparison_with_ints(self):
     with pytest.raises(ArithmeticError):
         Wad.min(Wad(10), 20)
     with pytest.raises(ArithmeticError):
         Wad.min(20, Wad(10))
Esempio n. 39
0
 def test_min_value(self):
     assert Wad.min(Wad(10), Wad(20)) == Wad(10)
     assert Wad.min(Wad(25), Wad(15)) == Wad(15)
     assert Wad.min(Wad(25), Wad(15), Wad(5)) == Wad(5)
Esempio n. 40
0
 def test_should_be_hashable(self):
     assert is_hashable(Wad(123))
Esempio n. 41
0
 def test_should_compare_wads_with_each_other(self):
     assert Wad(1000) == Wad(1000)
     assert Wad(1000) != Wad(999)
     assert Wad(1000) > Wad(999)
     assert Wad(999) < Wad(1000)
     assert Wad(999) <= Wad(1000)
     assert Wad(1000) <= Wad(1000)
     assert Wad(1000) >= Wad(1000)
     assert Wad(1000) >= Wad(999)
Esempio n. 42
0
 def test_should_support_abs(self):
     assert abs(Wad(1000)) == Wad(1000)
     assert abs(Wad(0)) == Wad(0)
     assert abs(Wad(-1000)) == Wad(1000)
Esempio n. 43
0
 def test_multiply(self):
     assert Wad.from_number(2) * Wad.from_number(3) == Wad.from_number(6)
     assert Wad.from_number(2) * Wad(3) == Wad(6)
     assert Wad.from_number(2.5) * Wad(3) == Wad(7)
     assert Wad.from_number(2.99999) * Wad(3) == Wad(8)
Esempio n. 44
0
 def test_add_should_not_work_with_wads(self):
     with pytest.raises(ArithmeticError):
         Ray(1) + Wad(2)
Esempio n. 45
0
 def test_subtract_should_not_work_with_wads(self):
     with pytest.raises(ArithmeticError):
         Ray(10) - Wad(2)
Esempio n. 46
0
 def test_subtract_should_not_work_with_rays(self):
     with pytest.raises(ArithmeticError):
         Wad(10) - Ray(2)
Esempio n. 47
0
 def test_should_fail_to_divide_by_wads(self):
     with pytest.raises(ArithmeticError):
         Ray(4) / Wad(2)
Esempio n. 48
0
 def test_max_value_should_reject_comparison_with_rays(self):
     with pytest.raises(ArithmeticError):
         Wad.max(Wad(10), Ray(20))
     with pytest.raises(ArithmeticError):
         Wad.max(Wad(25), Ray(15))
Esempio n. 49
0
 def test_add_should_not_work_with_ints(self):
     with pytest.raises(ArithmeticError):
         Wad(1) + 2
Esempio n. 50
0
 def test_max_value_should_reject_comparison_with_ints(self):
     with pytest.raises(ArithmeticError):
         Wad.max(Wad(10), 20)
     with pytest.raises(ArithmeticError):
         Wad.max(15, Wad(25))
Esempio n. 51
0
 def test_should_have_nice_printable_representation(self):
     for wad in [Wad(1), Wad(100), Wad.from_number(2.5), Wad(-1)]:
         assert repr(wad) == f"Wad({wad.value})"
Esempio n. 52
0
 def test_min_value_should_reject_comparison_with_wads(self):
     with pytest.raises(ArithmeticError):
         Ray.min(Ray(10), Wad(20))
     with pytest.raises(ArithmeticError):
         Ray.min(Wad(25), Ray(15))
Esempio n. 53
0
 def test_round(self):
     assert round(Wad.from_number(123.4567), 2) == Wad.from_number(123.46)
     assert round(Wad.from_number(123.4567), 0) == Wad.from_number(123.0)
     assert round(Wad.from_number(123.4567), -2) == Wad.from_number(100.0)
Esempio n. 54
0
 def test_should_fail_to_multiply_by_float(self):
     with pytest.raises(ArithmeticError):
         Wad(2) * 3.0
Esempio n. 55
0
 def test_should_support_negative_values(self):
     Wad(-1)
Esempio n. 56
0
 def test_should_instantiate_from_a_wad(self):
     assert Ray(Wad(10000000000000000000)) == Ray(10000000000000000000000000000)
Esempio n. 57
0
 def test_max_value(self):
     assert Wad.max(Wad(10), Wad(20)) == Wad(20)
     assert Wad.max(Wad(25), Wad(15)) == Wad(25)
     assert Wad.max(Wad(25), Wad(15), Wad(40)) == Wad(40)
Esempio n. 58
0
 def test_multiply_by_ray(self):
     assert Wad.from_number(2) * Ray.from_number(3) == Wad.from_number(6)
     assert Wad.from_number(2) * Ray(3) == Wad(0)
     assert Wad(2) * Ray(499999999999999999999999999) == Wad(0)
     assert Wad(2) * Ray(500000000000000000000000000) == Wad(1)
     assert Wad(2) * Ray(999999999999999999999999999) == Wad(1)
     assert Wad(2) * Ray(1000000000000000000000000000) == Wad(2)
Esempio n. 59
0
    def synchronize_orders(self):
        bands = Bands.read(self.bands_config, self.spread_feed, self.history)
        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()
        # print(type(self.local_orders))
        # print(self.local_orders)
        # print(type(order_book.orders))
        # print(order_book.orders)

        print("---**---The lenght of local_orders " +
              str(self.local_orders.__len__()))
        print("---**---The lenght of order_book.orders " +
              str(len(order_book.orders)))

        local_order_ids = set(order.order_id for order in self.local_orders)
        order_book_ids = set(order.order_id for order in order_book.orders)

        completed_order_ids = list(local_order_ids - order_book_ids)
        # 如果没有后续的更新 local orders,只有这里的更新模块,肯定有问题的,因为一旦有成交,
        # completed_order_ids 不为0,则永远更新不了local orders 了
        # return if there none order be completed
        # 下面这种情况,只有在order_book订单完全"包含"local_order订单时,但是两者并不相等时,才会让本地订单等于远程订单;
        # 这种一般是远程订单比本地订单多,往往比如人工在系统提交了新的订单
        if completed_order_ids.__len__() == 0:
            if local_order_ids.__len__() != order_book_ids.__len__():
                print("update local order")
                self.local_orders = order_book.orders
            return

        # completed_orders = list(filter(lambda order: order.order_id in completed_order_ids, self.local_orders))
        completed_orders = [
            order for order in self.local_orders
            if order.order_id in completed_order_ids
        ]

        # completed_orders = list(filter(lambda order: order.order_id in local_order_ids, order_book.orders))
        print("---**---The lenght of completed orders " +
              str(len(completed_orders)))
        print(completed_orders)

        # completed_orders_new = list(set(self.local_orders) - set(order_book.orders))
        # print("---**---The lenght of completed new orders " + str(len(completed_orders_new)))
        # print(completed_orders_new)

        # completed_orders = [{'amount': Wad(2220000000000000000),
        #              'amount_symbol': 'BIX',
        #              'created_at': 1528203670000,
        #              'is_sell': True,
        #              'money': Wad(52779250000000000),
        #              'money_symbol': 'ETH',
        #              'order_id': 606026215,
        #              'price': Wad(2294750000000000)}, {'amount': Wad(2990000000000000000),
        #              'amount_symbol': 'BIX',
        #              'created_at': 1528203670000,
        #              'is_sell': False,
        #              'money': Wad(55779250000000000),
        #              'money_symbol': 'ETH',
        #              'order_id': 606026215,
        #              'price': Wad(2394750000000000)}]

        # our_buy_orders = self.our_buy_orders(order_book.orders)
        # our_sell_orders = self.our_sell_orders(order_book.orders)
        # print(our_buy_orders)
        # print(our_sell_orders)
        # Do not place new orders if order book state is not confirmed
        if order_book.orders_being_placed or order_book.orders_being_cancelled:
            self.logger.debug(
                "Order book is in progress, not placing new orders")
            return

        # if (self.local_orders.__len__() - len(order_book.orders) > 0):
        if len(completed_orders) > 0:
            print("--------- some orders have been done --------")
            new_orders = []
            for cod in completed_orders:
                # print(type(cod))
                # print(cod.is_sell)
                # the completed order is sell order, buy order should be placed
                if cod.is_sell:
                    # place buy order, pay attention to rotate bix - eth
                    price = float(cod.price) * (1 - self.arbitrage_percent)
                    print("----to submit a new buy order with price " +
                          str(price))
                    pay_amount = float(cod.amount) * price  # eth money 25
                    buy_amount = float(cod.amount)  # bix amount 0.05
                    new_orders.append(
                        NewOrder(is_sell=False,
                                 price=Wad.from_number(price),
                                 pay_amount=Wad.from_number(pay_amount),
                                 buy_amount=Wad.from_number(buy_amount),
                                 confirm_function=lambda: self.sell_limits.
                                 use_limit(time.time(), pay_amount)))
                    # 以当前价格为基数,重新submit一个高价格的 sell 订单,补充 sell list
                    # place sell a new order with higher price
                    # 需要判断订单的数量是否小于band order limits,并且按照差异补充订单
                    count_sell_order = self.count_sell_orders(
                        order_book.orders)
                    print(count_sell_order)
                    band_sell_order_gap = self.band_order_limit - count_sell_order
                    print("---band gap---- " + str(band_sell_order_gap))
                    step = 1
                    while band_sell_order_gap > 0:
                        current_price = self.bibox_api.get_last_price(
                            self.pair())
                        print("------current price---- " + str(current_price))
                        price = float(current_price) * (
                            1 + self.arbitrage_percent *
                            (step + count_sell_order))
                        print("----higher price to sell--- " + str(price))
                        pay_amount = self.each_order_amount * self.amount_disguise(
                        )  # bix amount
                        buy_amount = pay_amount * price  # eth money
                        new_orders.append(
                            NewOrder(is_sell=True,
                                     price=Wad.from_number(price),
                                     pay_amount=Wad.from_number(pay_amount),
                                     buy_amount=Wad.from_number(buy_amount),
                                     confirm_function=lambda: self.sell_limits.
                                     use_limit(time.time(), pay_amount)))
                        step = step + 1
                        band_sell_order_gap = band_sell_order_gap - 1

                else:  # buy order had been completed
                    # to place a sell order
                    price = float(cod.price) * (1 + self.arbitrage_percent)
                    print("----price--- sell--- ")
                    print(price)
                    pay_amount = float(cod.amount)  # bix amount
                    buy_amount = pay_amount * price  # eth money
                    new_orders.append(
                        NewOrder(is_sell=True,
                                 price=Wad.from_number(price),
                                 pay_amount=Wad.from_number(pay_amount),
                                 buy_amount=Wad.from_number(buy_amount),
                                 confirm_function=lambda: self.sell_limits.
                                 use_limit(time.time(), pay_amount)))
                    # 以当前价格为基数,重新submit一个 buy 订单,补充 buy list
                    # 需要判断订单的数量是否小于band order limits,并且按照差异补充订单
                    count_buy_order = self.count_buy_orders(order_book.orders)
                    band_buy_order_gap = self.band_order_limit - count_buy_order
                    print("---band gap----" + str(band_buy_order_gap))
                    step = 1
                    while band_buy_order_gap > 0:
                        current_price = self.bibox_api.get_last_price(
                            self.pair())
                        price = float(current_price) * (
                            1 - self.arbitrage_percent *
                            (step + count_buy_order))
                        print("----lower price order to buy--- " + str(price))
                        tmp = self.each_order_amount * self.amount_disguise()
                        pay_amount = tmp * price  # eth money 25
                        buy_amount = tmp  # bix amount 0.05
                        new_orders.append(
                            NewOrder(is_sell=False,
                                     price=Wad.from_number(price),
                                     pay_amount=Wad.from_number(pay_amount),
                                     buy_amount=Wad.from_number(buy_amount),
                                     confirm_function=lambda: self.sell_limits.
                                     use_limit(time.time(), pay_amount)))
                        step = step + 1
                        band_buy_order_gap = band_buy_order_gap - 1

            self.place_orders(new_orders)

            # update local orders, 前面有更新模块,与这边不完全相同,尤其是有成交的情况下,必须要更新
            # 是这样吗? 似乎也不是的,有成交的情况下,下一次订单也会让 set(local) - set(order book)=0的,集合相减的特殊之处
            # 如果这样就没有必要了。
            # 是这样简单的复制更新,还是本地自己维护一个 id list 好呢? 也就是把(1)确定成交的从 local 删除;
            # (2)确定提交的add 到本地;
            # 缩进到循环: if len(completed_orders) > 0:,在出现两者不一致的时候,同步更新订单;
            # 但是这个会导致一个问题,就是初始化的订单里,有price 为0,导致两者不一致的情况,怎么办?这里解决了,是通过 order id对比而不是
            # 直接的 order 对比,所以应该是解决了才对
            print("-----update local order------")
            self.local_orders = self.order_book_manager.get_order_book().orders
Esempio n. 60
0
 def test_should_fail_to_divide_by_ints(self):
     with pytest.raises(ArithmeticError):
         Wad(4) / 2