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)
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])
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
def test_should_support_values_greater_than_uint256(self): Wad(2**256) Wad(2**256 + 1) Wad(2**512)
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)
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'])
def test_should_fail_to_instantiate_from_a_float(self): with pytest.raises(ArithmeticError): assert Wad(10.5)
def test_add(self): assert Wad(1) + Wad(2) == Wad(3)
def test_subtract(self): assert Wad(10) - Wad(2) == Wad(8) assert Wad(1) - Wad(2) == Wad(-1)
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)
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))
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)
def test_should_be_hashable(self): assert is_hashable(Wad(123))
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)
def test_should_support_abs(self): assert abs(Wad(1000)) == Wad(1000) assert abs(Wad(0)) == Wad(0) assert abs(Wad(-1000)) == Wad(1000)
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)
def test_add_should_not_work_with_wads(self): with pytest.raises(ArithmeticError): Ray(1) + Wad(2)
def test_subtract_should_not_work_with_wads(self): with pytest.raises(ArithmeticError): Ray(10) - Wad(2)
def test_subtract_should_not_work_with_rays(self): with pytest.raises(ArithmeticError): Wad(10) - Ray(2)
def test_should_fail_to_divide_by_wads(self): with pytest.raises(ArithmeticError): Ray(4) / Wad(2)
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))
def test_add_should_not_work_with_ints(self): with pytest.raises(ArithmeticError): Wad(1) + 2
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))
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})"
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))
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)
def test_should_fail_to_multiply_by_float(self): with pytest.raises(ArithmeticError): Wad(2) * 3.0
def test_should_support_negative_values(self): Wad(-1)
def test_should_instantiate_from_a_wad(self): assert Ray(Wad(10000000000000000000)) == Ray(10000000000000000000000000000)
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)
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)
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
def test_should_fail_to_divide_by_ints(self): with pytest.raises(ArithmeticError): Wad(4) / 2