Пример #1
0
    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)
Пример #2
0
    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)
Пример #3
0
    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")
Пример #4
0
 def sample_limits(self):
     return SideLimits([{
         'amount': 100,
         'period': '1h'
     }, {
         'amount': 500,
         'period': '1d'
     }], SideHistory())
Пример #5
0
    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 = []
Пример #6
0
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
Пример #7
0
 def no_limits(self):
     return SideLimits([], SideHistory())