def read(reloadable_config: ReloadableConfig, spread_feed: Feed, history: History): assert (isinstance(reloadable_config, ReloadableConfig)) assert (isinstance(history, History)) try: config = reloadable_config.get_config(spread_feed.get()[0]) buy_bands = list(map(BuyBand, config['buyBands'])) buy_limits = SideLimits( config['buyLimits'] if 'buyLimits' in config else [], history.buy_history) sell_bands = list(map(SellBand, config['sellBands'])) sell_limits = SideLimits( config['sellLimits'] if 'sellLimits' in config else [], history.sell_history) except Exception as e: logging.getLogger().warning( f"Config file is invalid ({e}). Treating the config file as it has no bands." ) buy_bands = [] buy_limits = SideLimits([], history.buy_history) sell_bands = [] sell_limits = SideLimits([], history.buy_history) return Bands(buy_bands=buy_bands, buy_limits=buy_limits, sell_bands=sell_bands, sell_limits=sell_limits)
def read(reloadable_config: ReloadableConfig, spread_feed: Feed, control_feed: Feed, history: History): assert (isinstance(reloadable_config, ReloadableConfig)) assert (isinstance(spread_feed, Feed)) assert (isinstance(control_feed, Feed)) assert (isinstance(history, History)) try: config = reloadable_config.get_config(spread_feed.get()[0]) control_feed_value = control_feed.get()[0] buy_bands = list(map(BuyBand, config['buyBands'])) buy_limits = SideLimits( config['buyLimits'] if 'buyLimits' in config else [], history.buy_history) sell_bands = list(map(SellBand, config['sellBands'])) sell_limits = SideLimits( config['sellLimits'] if 'sellLimits' in config else [], history.sell_history) if 'canBuy' not in control_feed_value or 'canSell' not in control_feed_value: logging.getLogger().warning( "Control feed expired. Assuming no buy bands and no sell bands." ) buy_bands = [] sell_bands = [] else: if not control_feed_value['canBuy']: logging.getLogger().warning( "Control feed says we shall not buy. Assuming no buy bands." ) buy_bands = [] if not control_feed_value['canSell']: logging.getLogger().warning( "Control feed says we shall not sell. Assuming no sell bands." ) sell_bands = [] except Exception as e: logging.getLogger().exception( f"Config file is invalid ({e}). Treating the config file as it has no bands." ) buy_bands = [] buy_limits = SideLimits([], history.buy_history) sell_bands = [] sell_limits = SideLimits([], history.buy_history) return Bands(buy_bands=buy_bands, buy_limits=buy_limits, sell_bands=sell_bands, sell_limits=sell_limits)
def __init__(self, reloadable_config: ReloadableConfig, history: History): assert (isinstance(reloadable_config, ReloadableConfig)) assert (isinstance(history, History)) config = reloadable_config.get_config() self.buy_bands = list(map(BuyBand, config['buyBands'])) self.buy_limits = SideLimits( config['buyLimits'] if 'buyLimits' in config else [], history.buy_history) self.sell_bands = list(map(SellBand, config['sellBands'])) self.sell_limits = SideLimits( config['sellLimits'] if 'sellLimits' in config else [], history.sell_history) if self._bands_overlap(self.buy_bands) or self._bands_overlap( self.sell_bands): raise Exception(f"Bands in the config file overlap")
def sample_limits(self): return SideLimits([{ 'amount': 100, 'period': '1h' }, { 'amount': 500, 'period': '1d' }], SideHistory())
def __init__(self, reloadable_config: ReloadableConfig, spread_feed: Feed, history: History): assert(isinstance(reloadable_config, ReloadableConfig)) assert(isinstance(history, History)) try: config = reloadable_config.get_config(spread_feed.get()[0]) self.buy_bands = list(map(BuyBand, config['buyBands'])) self.buy_limits = SideLimits(config['buyLimits'] if 'buyLimits' in config else [], history.buy_history) self.sell_bands = list(map(SellBand, config['sellBands'])) self.sell_limits = SideLimits(config['sellLimits'] if 'sellLimits' in config else [], history.sell_history) except Exception as e: self.logger.warning(f"Config file is invalid ({e}). Treating the config file as it has no bands.") self.buy_bands = [] self.buy_limits = SideLimits([], history.buy_history) self.sell_bands = [] self.sell_limits = SideLimits([], history.buy_history) if self._bands_overlap(self.buy_bands) or self._bands_overlap(self.sell_bands): self.logger.warning("Bands in the config file overlap. Treating the config file as it has no bands.") self.buy_bands = [] self.sell_bands = []
class Bands: logger = logging.getLogger() def __init__(self, reloadable_config: ReloadableConfig, spread_feed: Feed, history: History): assert(isinstance(reloadable_config, ReloadableConfig)) assert(isinstance(history, History)) try: config = reloadable_config.get_config(spread_feed.get()[0]) self.buy_bands = list(map(BuyBand, config['buyBands'])) self.buy_limits = SideLimits(config['buyLimits'] if 'buyLimits' in config else [], history.buy_history) self.sell_bands = list(map(SellBand, config['sellBands'])) self.sell_limits = SideLimits(config['sellLimits'] if 'sellLimits' in config else [], history.sell_history) except Exception as e: self.logger.warning(f"Config file is invalid ({e}). Treating the config file as it has no bands.") self.buy_bands = [] self.buy_limits = SideLimits([], history.buy_history) self.sell_bands = [] self.sell_limits = SideLimits([], history.buy_history) if self._bands_overlap(self.buy_bands) or self._bands_overlap(self.sell_bands): self.logger.warning("Bands in the config file overlap. Treating the config file as it has no bands.") self.buy_bands = [] self.sell_bands = [] def _excessive_sell_orders(self, our_sell_orders: list, target_price: Wad): """Return sell orders which need to be cancelled to bring total amounts within all sell bands below maximums.""" assert(isinstance(our_sell_orders, list)) assert(isinstance(target_price, Wad)) for band in self.sell_bands: for order in band.excessive_orders(our_sell_orders, target_price): yield order def _excessive_buy_orders(self, our_buy_orders: list, target_price: Wad): """Return buy orders which need to be cancelled to bring total amounts within all buy bands below maximums.""" assert(isinstance(our_buy_orders, list)) assert(isinstance(target_price, Wad)) for band in self.buy_bands: for order in band.excessive_orders(our_buy_orders, target_price): yield order def _outside_any_band_orders(self, orders: list, bands: list, target_price: Wad): """Return buy or sell orders which need to be cancelled as they do not fall into any buy or sell band.""" assert(isinstance(orders, list)) assert(isinstance(bands, list)) assert(isinstance(target_price, Wad)) for order in orders: if not any(band.includes(order, target_price) for band in bands): yield order def cancellable_orders(self, our_buy_orders: list, our_sell_orders: list, target_price: Price) -> list: assert(isinstance(our_buy_orders, list)) assert(isinstance(our_sell_orders, list)) assert(isinstance(target_price, Price)) if target_price.buy_price is None: self.logger.warning("Cancelling all buy orders as no buy price is available.") buy_orders_to_cancel = our_buy_orders else: buy_orders_to_cancel = list(itertools.chain(self._excessive_buy_orders(our_buy_orders, target_price.buy_price), self._outside_any_band_orders(our_buy_orders, self.buy_bands, target_price.buy_price))) if target_price.sell_price is None: self.logger.warning("Cancelling all sell orders as no sell price is available.") sell_orders_to_cancel = our_sell_orders else: sell_orders_to_cancel = list(itertools.chain(self._excessive_sell_orders(our_sell_orders, target_price.sell_price), self._outside_any_band_orders(our_sell_orders, self.sell_bands, target_price.sell_price))) return buy_orders_to_cancel + sell_orders_to_cancel def new_orders(self, our_buy_orders: list, our_sell_orders: list, our_buy_balance: Wad, our_sell_balance: Wad, target_price: Price) -> Tuple[list, Wad, Wad]: assert(isinstance(our_buy_orders, list)) assert(isinstance(our_sell_orders, list)) assert(isinstance(our_buy_balance, Wad)) assert(isinstance(our_sell_balance, Wad)) assert(isinstance(target_price, Price)) if target_price is not None: new_buy_orders, missing_buy_amount = self._new_buy_orders(our_buy_orders, our_buy_balance, target_price.buy_price) \ if target_price.buy_price is not None \ else ([], Wad(0)) new_sell_orders, missing_sell_amount = self._new_sell_orders(our_sell_orders, our_sell_balance, target_price.sell_price) \ if target_price.sell_price is not None \ else ([], Wad(0)) return new_buy_orders + new_sell_orders, missing_buy_amount, missing_sell_amount else: return [], Wad(0), Wad(0) def _new_sell_orders(self, our_sell_orders: list, our_sell_balance: Wad, target_price: Wad): """Return sell orders which need to be placed to bring total amounts within all sell bands above minimums.""" assert(isinstance(our_sell_orders, list)) assert(isinstance(our_sell_balance, Wad)) assert(isinstance(target_price, Wad)) new_orders = [] limit_amount = self.sell_limits.available_limit(time.time()) missing_amount = Wad(0) for band in self.sell_bands: orders = [order for order in our_sell_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_sell_balance, limit_amount) buy_amount = pay_amount * price missing_amount += Wad.max((band.avg_amount - total_amount) - our_sell_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 sell order") our_sell_balance = our_sell_balance - pay_amount limit_amount = limit_amount - pay_amount new_orders.append(NewOrder(is_sell=True, price=price, pay_amount=pay_amount, buy_amount=buy_amount, confirm_function=lambda: self.sell_limits.use_limit(time.time(), pay_amount))) return new_orders, missing_amount 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 @staticmethod def total_amount(orders): return reduce(operator.add, map(lambda order: order.remaining_sell_amount, orders), Wad(0)) @staticmethod def _bands_overlap(bands: list): def two_bands_overlap(band1, band2): return band1.min_margin < band2.max_margin and band2.min_margin < band1.max_margin for band1 in bands: if len(list(filter(lambda band2: two_bands_overlap(band1, band2), bands))) > 1: return True return False
def no_limits(self): return SideLimits([], SideHistory())