コード例 #1
0
class HitBTCMarketMakerKeeper:
    """Keeper acting as a market maker on HitBTC."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='hitbtc-market-maker-keeper')

        parser.add_argument(
            "--hitbtc-api-server",
            type=str,
            default="https://api.hitbtc.com",
            help=
            "Address of the HitBTC API server (default: 'https://api.hitbtc.com')"
        )

        parser.add_argument("--hitbtc-api-key",
                            type=str,
                            required=True,
                            help="API key for the HitBTC API")

        parser.add_argument("--hitbtc-secret-key",
                            type=str,
                            required=True,
                            help="Secret key for the HitBTC API")

        parser.add_argument(
            "--hitbtc-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the HitBTC API (in seconds, default: 9.5)")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--control-feed",
                            type=str,
                            help="Source of control feed")

        parser.add_argument(
            "--control-feed-expiry",
            type=int,
            default=86400,
            help="Maximum age of the control feed (in seconds, default: 86400)"
        )

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        parser.add_argument(
            "--telegram-log-config-file",
            type=str,
            required=False,
            help=
            "config file for send logs to telegram chat (e.g. 'telegram_conf.json')",
            default=None)

        parser.add_argument(
            "--keeper-name",
            type=str,
            required=False,
            help="market maker keeper name (e.g. 'Uniswap_V2_MDTETH')",
            default="hitbtc")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.control_feed = create_control_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.history = History()
        self.hitbtc_api = HitBTCApi(
            api_server=self.arguments.hitbtc_api_server,
            api_key=self.arguments.hitbtc_api_key,
            secret_key=self.arguments.hitbtc_secret_key,
            timeout=self.arguments.hitbtc_timeout)

        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.hitbtc_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(
            lambda: self.hitbtc_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.hitbtc_api.cancel_order(order.order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def shutdown(self):
        self.order_book_manager.cancel_all_orders()

    def pair(self):
        return self.arguments.pair

    def pair_separator(self) -> int:
        return 4 if self.arguments.pair.startswith('TUSD') else 3

    def token_sell(self) -> str:
        return self.arguments.pair[:self.pair_separator()]

    def token_buy(self) -> str:
        return self.arguments.pair[self.pair_separator():]

    def our_available_balance(self, our_balances: dict, token: str) -> Wad:
        token_balances = list(
            filter(lambda coin: coin['currency'].upper() == token,
                   our_balances))
        if token_balances:
            return Wad.from_number(token_balances[0]['available'])
        else:
            return Wad(0)

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def synchronize_orders(self):
        bands = Bands.read(self.bands_config, self.spread_feed,
                           self.control_feed, self.history)
        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()

        # Cancel orders
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)
        if len(cancellable_orders) > 0:
            self.order_book_manager.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        new_orders = bands.new_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            our_buy_balance=self.our_available_balance(order_book.balances,
                                                       self.token_buy()),
            our_sell_balance=self.our_available_balance(
                order_book.balances, self.token_sell()),
            target_price=target_price)[0]

        self.place_orders(new_orders)

    def place_orders(self, new_orders: List[NewOrder]):
        def place_order_function(new_order_to_be_placed):
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            order_id = self.hitbtc_api.place_order(
                self.pair(), new_order_to_be_placed.is_sell,
                new_order_to_be_placed.price, amount)

            return Order(order_id=order_id,
                         status='new',
                         timestamp=0.0,
                         pair=self.pair(),
                         is_sell=new_order_to_be_placed.is_sell,
                         price=new_order_to_be_placed.price,
                         amount=amount,
                         filled_amount=Wad(0))

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
コード例 #2
0
class GOPAXMarketMakerKeeper:

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='gopax-market-maker-keeper')

        parser.add_argument(
            "--gopax-api-server",
            type=str,
            default="https://api.gopax.co.kr",
            help=
            "Address of the GOPAX API server (default: 'https://api.gopax.co.kr')"
        )

        parser.add_argument("--gopax-api-key",
                            type=str,
                            required=True,
                            help="API key for the GOPAX API")

        parser.add_argument("--gopax-api-secret",
                            type=str,
                            required=True,
                            help="API secret for the GOPAX API")

        parser.add_argument(
            "--gopax-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the GOPAX API (in seconds, default: 9.5)")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.history = History()
        self.gopax_api = GOPAXApi(api_server=self.arguments.gopax_api_server,
                                  api_key=self.arguments.gopax_api_key,
                                  api_secret=self.arguments.gopax_api_secret,
                                  timeout=self.arguments.gopax_timeout)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)

        self.order_book_manager = OrderBookManager(refresh_frequency=10)
        self.order_book_manager.get_orders_with(self.get_orders)
        self.order_book_manager.get_balances_with(
            lambda: self.gopax_api.get_balances())
        self.order_book_manager.start()

    def main(self):
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def shutdown(self):
        #TODO I don't think this approach makes sure all orders will always get cancelled!!
        while True:
            try:
                our_orders = self.gopax_api.get_orders(self.pair())
            except:
                continue

            if len(our_orders) == 0:
                break

            self.cancel_orders(our_orders)
            self.order_book_manager.wait_for_order_cancellation()

    def pair(self):
        return self.arguments.pair.upper()

    def token_sell(self) -> str:
        return self.arguments.pair.split('-')[0].upper()

    def token_buy(self) -> str:
        return self.arguments.pair.split('-')[1].upper()

    def get_orders(self) -> list:
        return list(
            map(lambda order: self.gopax_api.get_order(order.order_id),
                self.gopax_api.get_orders(self.pair())))

    def our_available_balance(self, our_balances: list, token: str) -> Wad:
        return Wad.from_number(
            next(filter(lambda coin: coin['asset'] == token,
                        our_balances))['avail'])

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def synchronize_orders(self):
        bands = Bands(self.bands_config, self.spread_feed, self.history)
        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()

        # Cancel orders
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)
        if len(cancellable_orders) > 0:
            self.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        self.place_orders(
            bands.new_orders(
                our_buy_orders=self.our_buy_orders(order_book.orders),
                our_sell_orders=self.our_sell_orders(order_book.orders),
                our_buy_balance=self.our_available_balance(
                    order_book.balances, self.token_buy()),
                our_sell_balance=self.our_available_balance(
                    order_book.balances, self.token_sell()),
                target_price=target_price)[0])

    def cancel_orders(self, orders):
        for order in orders:
            self.order_book_manager.cancel_order(
                order.order_id,
                lambda order=order: self.gopax_api.cancel_order(order.order_id
                                                                ))

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            pair = self.pair()
            is_sell = new_order_to_be_placed.is_sell
            price = new_order_to_be_placed.price
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount

            new_order_id = self.gopax_api.place_order(pair=pair,
                                                      is_sell=is_sell,
                                                      price=price,
                                                      amount=amount)

            return Order(order_id=new_order_id,
                         pair=pair,
                         is_sell=is_sell,
                         price=price,
                         amount=amount,
                         amount_remaining=amount)

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
コード例 #3
0
class GateIOMarketMakerKeeper:
    """Keeper acting as a market maker on Gate.io."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='gateio-market-maker-keeper')

        parser.add_argument(
            "--gateio-api-server",
            type=str,
            default="https://data.gate.io",
            help=
            "Address of the Gate.io API server (default: 'https://data.gate.io')"
        )

        parser.add_argument("--gateio-api-key",
                            type=str,
                            required=True,
                            help="API key for the Gate.io API")

        parser.add_argument("--gateio-secret-key",
                            type=str,
                            required=True,
                            help="Secret key for the Gate.io API")

        parser.add_argument(
            "--gateio-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the Gate.io API (in seconds, default: 9.5)")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.history = History()
        self.gateio_api = GateIOApi(
            api_server=self.arguments.gateio_api_server,
            api_key=self.arguments.gateio_api_key,
            secret_key=self.arguments.gateio_secret_key,
            timeout=self.arguments.gateio_timeout)

        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.gateio_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(
            lambda: self.gateio_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.gateio_api.cancel_order(self.pair(), order.
                                                       order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

        self._last_order_creation = 0

    def main(self):
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def shutdown(self):
        self.order_book_manager.cancel_all_orders()

    def pair(self):
        return self.arguments.pair.lower()

    def token_sell(self) -> str:
        return self.arguments.pair.split('_')[0].upper()

    def token_buy(self) -> str:
        return self.arguments.pair.split('_')[1].upper()

    def our_available_balance(self, our_balances: dict, token: str) -> Wad:
        try:
            return Wad.from_number(our_balances['available'][token])
        except KeyError:
            return Wad(0)

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    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()

        # Cancel orders
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)
        if len(cancellable_orders) > 0:
            self.order_book_manager.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        new_orders = bands.new_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            our_buy_balance=self.our_available_balance(order_book.balances,
                                                       self.token_buy()),
            our_sell_balance=self.our_available_balance(
                order_book.balances, self.token_sell()),
            target_price=target_price)[0]

        if len(new_orders) > 0:
            if self.can_create_orders():
                self.place_orders(new_orders)
                self.register_order_creation()
            else:
                self.logger.info(
                    "Too little time elapsed from last order creation, waiting..."
                )

    # Unfortunately the gate.io API does not immediately reflect the fact that our orders have
    # been placed. In order to avoid placing orders twice we explicitly wait some time here.
    def can_create_orders(self) -> bool:
        return time.time() - self._last_order_creation > 15

    def register_order_creation(self):
        self._last_order_creation = time.time()

    def place_orders(self, new_orders: List[NewOrder]):
        def place_order_function(new_order_to_be_placed):
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            order_id = self.gateio_api.place_order(
                self.pair(), new_order_to_be_placed.is_sell,
                new_order_to_be_placed.price, amount)

            return Order(order_id=order_id,
                         timestamp=0,
                         pair=self.pair(),
                         is_sell=new_order_to_be_placed.is_sell,
                         price=new_order_to_be_placed.price,
                         amount=amount,
                         amount_symbol=self.token_sell(),
                         money=amount * new_order_to_be_placed.price,
                         money_symbol=self.token_buy(),
                         initial_amount=amount,
                         filled_amount=Wad(0))

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
コード例 #4
0
class EToroMarketMakerKeeper:
    """Keeper acting as a market maker on eToro."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='eToro-market-maker-keeper')

        parser.add_argument("--etoro-api-server",
                            type=str,
                            required=True,
                            help="Address of the eToro API server")

        parser.add_argument("--etoro-account",
                            type=str,
                            default="*****@*****.**",
                            help="Username for eToroX account")

        parser.add_argument("--etoro-api-key",
                            type=str,
                            required=True,
                            help="API key for the eToro API")

        parser.add_argument(
            "--etoro-secret-key",
            type=argparse.FileType('r'),
            required=True,
            help="RSA Private Key for signing requests to the eToroX API")

        parser.add_argument(
            "--etoro-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the eToro API (in seconds, default: 9.5)")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--control-feed",
                            type=str,
                            help="Source of control feed")

        parser.add_argument(
            "--control-feed-expiry",
            type=int,
            default=86400,
            help="Maximum age of the control feed (in seconds, default: 86400)"
        )

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        parser.add_argument(
            "--telegram-log-config-file",
            type=str,
            required=False,
            help=
            "config file for send logs to telegram chat (e.g. 'telegram_conf.json')",
            default=None)

        parser.add_argument(
            "--keeper-name",
            type=str,
            required=False,
            help="market maker keeper name (e.g. 'Uniswap_V2_MDTETH')",
            default="etoro")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.control_feed = create_control_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.history = History()
        self.etoro_api = EToroApi(api_server=self.arguments.etoro_api_server,
                                  account=self.arguments.etoro_account,
                                  api_key=self.arguments.etoro_api_key,
                                  secret_key=self.arguments.etoro_secret_key,
                                  timeout=self.arguments.etoro_timeout)

        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.etoro_api.get_orders(self._join_string(self.pair()),
                                              "open"))
        self.order_book_manager.get_balances_with(
            lambda: self.etoro_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.etoro_api.cancel_order(order.order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def shutdown(self):
        self.order_book_manager.cancel_all_orders()

    def pair(self):
        return self.arguments.pair.lower()

    def token_sell(self) -> str:
        return self.arguments.pair.split('_')[0].lower()

    def token_buy(self) -> str:
        return self.arguments.pair.split('_')[1].lower()

    def our_available_balance(self, our_balances: list, token: str) -> Wad:
        balance = list(filter(lambda x: x['currency'] == token,
                              our_balances))[0]['balance']
        return Wad.from_number(balance)

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def synchronize_orders(self):
        bands = Bands.read(self.bands_config, self.spread_feed,
                           self.control_feed, self.history)
        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()

        # Cancel orders
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)
        if len(cancellable_orders) > 0:
            self.order_book_manager.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        self.place_orders(
            bands.new_orders(
                our_buy_orders=self.our_buy_orders(order_book.orders),
                our_sell_orders=self.our_sell_orders(order_book.orders),
                our_buy_balance=self.our_available_balance(
                    order_book.balances, self.token_buy()),
                our_sell_balance=self.our_available_balance(
                    order_book.balances, self.token_sell()),
                target_price=target_price)[0])

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            side = "sell" if new_order_to_be_placed.is_sell == True else "buy"
            order_id = self.etoro_api.place_order(
                pair=self._join_string(self.pair()),
                side=side,
                price=new_order_to_be_placed.price,
                amount=amount)

            timestamp = datetime.now(tz=timezone.utc).isoformat()

            return Order(str(order_id), timestamp,
                         self._join_string(self.pair()),
                         new_order_to_be_placed.is_sell,
                         new_order_to_be_placed.price, amount)

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))

    # Return lower case concatted string. Assumes inputted string is an _ delimited pair
    def _join_string(self, string: str) -> str:
        assert (isinstance(string, str))
        return "".join(string.split('_')).lower()
コード例 #5
0
class BiboxMarketSurfer:
    """Keeper acting as a market maker on Bibox."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='bibox-market-maker-keeper')

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the surfer will operate")

        parser.add_argument(
            "--output-path",
            type=str,
            required=True,
            help="output file path of the completed order result")

        # reserved for old program
        # parser.add_argument("--price-feed", type=str, required=True,
        #                     help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.orderlog = Logger(self.arguments.output_path + "SurferResult_" +
                               self.arguments.pair,
                               level='info')

        try:
            f = open(self.arguments.config, 'r')
            config = json.loads(f.read())
            f.close()

            self.bibox_api = BiboxApi(api_server=config["bibox_api_server"],
                                      api_key=config["bibox_api_key"],
                                      secret=config["bibox_secret"],
                                      timeout=config["bibox_timeout"])

            for pair in config['pairs']:
                if pair['pair'] == self.arguments.pair:
                    self.total_amount = pair["total_amount"]
                    # percent of total amount of each transaction or each order
                    self.each_order_percent = pair["each_order_percent"]
                    self.arbitrage_percent = pair["arbitrage_percent"]
                    # the order count of sell or buy bands must less than limit
                    self.band_order_limit = pair["band_order_limit"]

        except Exception as e:
            logging.getLogger().warning(
                f"Config file is invalid ({e}). Treating the config file as it has no bands."
            )

        self.history = History()
        self.bands_config = ReloadableConfig(self.arguments.config)
        # self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.local_orders = []
        self.each_order_amount = self.total_amount * self.each_order_percent

        # To implement abstract function with different exchanges API
        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.bibox_api.get_orders(pair=self.pair(), retry=True))
        self.order_book_manager.get_balances_with(
            lambda: self.bibox_api.coin_list(retry=True))
        self.order_book_manager.cancel_orders_with(
            lambda order: self.bibox_api.cancel_order(order.order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        # Place new orders while initialize the whole surfer system
        self.initialize_orders(self.each_order_amount, self.arbitrage_percent,
                               self.band_order_limit)
        time.sleep(
            5
        )  # wait for order book manager to get placed orders 足够时间保证系统稳定返回,这个时间还是不一定够
        self.local_orders = self.order_book_manager.get_order_book().orders

        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.every(10, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def initialize_orders(self, each_order_amount, arbitrage_percent,
                          band_order_limit):
        orders = []
        i = 1
        base_price = self.get_last_price(self.pair())
        while band_order_limit + 1 > i:
            # place sell order
            price = float(base_price) * (1 + arbitrage_percent * i)
            # pay_amount = Wad.min(band.avg_amount - total_amount, our_sell_balance, limit_amount)
            pay_amount = each_order_amount * self.amount_disguise(
            )  #bix amount
            pay_amount = pay_amount + self.suffix_amount_identify()
            buy_amount = pay_amount * price  #eth money
            # print(pay_amount)

            orders.append(
                NewOrder(is_sell=True,
                         price=Wad.from_number(price),
                         amount=Wad.from_number(pay_amount),
                         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)))

            # place buy order, pay attention to rotate bix - eth
            price = float(base_price) * (1 - arbitrage_percent * i)
            # pay_amount = Wad.min(band.avg_amount - total_amount, our_sell_balance, limit_amount)
            tmp = each_order_amount * self.amount_disguise()
            pay_amount = tmp * price  #eth money 25
            buy_amount = tmp  #bix amount 0.05
            buy_amount = buy_amount + self.suffix_amount_identify()
            # print(buy_amount)
            orders.append(
                NewOrder(is_sell=False,
                         price=Wad.from_number(price),
                         amount=Wad.from_number(buy_amount),
                         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)))
            i = i + 1

        self.place_orders(orders)
        # 偶尔有 bug,提交的完成慢,导致 local orders 比 下一次 获取回来少, initial_delay 加长时间到15秒,时间太长也麻烦,
        # 会导致一开始提交就成交的那部分订单不会存到 local orders
        # 需要换地方,order_book_manager更新不及时的情况下,会导致返回的订单数据不全,或者订单里的参数默认为0的情况
        # self.local_orders = self.order_book_manager.get_order_book().orders

    @staticmethod
    def amount_disguise():
        rand = [
            0.8, 0.84, 0.88, 0.92, 0.95, 0.99, 1.03, 1.06, 1.09, 1.12, 1.16,
            1.2
        ]
        return rand[random.randint(0, 11)]

    # suffix unique amount number to identify different buy/sell order pairs for result performance statics
    @staticmethod
    def suffix_amount_identify():
        return round(random.random() / 10.0, 10)

    def get_last_price(self, pair):
        return self.bibox_api.ticker(pair)['last']

    def shutdown(self):
        self.order_book_manager.cancel_all_orders(final_wait_time=30)

    def pair(self):
        return self.arguments.pair.upper()

    def token_sell(self) -> str:
        return self.arguments.pair.split('_')[0].upper()

    def token_buy(self) -> str:
        return self.arguments.pair.split('_')[1].upper()

    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 our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def count_sell_orders(self, our_orders: list) -> int:
        return len(list(filter(lambda order: order.is_sell, our_orders)))

    def count_buy_orders(self, our_orders: list) -> int:
        return len(list(filter(lambda order: not order.is_sell, our_orders)))

    def synchronize_orders(self):
        # bands = Bands.read(self.bands_config, self.spread_feed, self.history)
        order_book = self.order_book_manager.get_order_book()

        self.logger.info(
            "---**----synchronize_orders: The length of local_orders " +
            str(self.local_orders.__len__()))
        self.logger.info(
            "---**----synchronize_orders: The length 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订单时,但是两者并不相等时,才会让本地订单等于远程订单;
        # 这种一般是远程订单比本地订单多,往往比如人工在系统提交了新的订单
        # 这段是必须的,比如程序起步的时候,同时提交一批订单,导致速度慢,导致 local_orders 只有真正订单的一部分
        if completed_order_ids.__len__() == 0:
            if local_order_ids.__len__() != order_book_ids.__len__():
                self.logger.info(
                    "---**---update local order with remote order while no completed order in past cycle"
                )
                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))
        self.logger.info("---**---The number of completed orders is " +
                         str(len(completed_orders)))
        self.logger.info("---**---Below is/are completed orders : ")
        self.logger.info(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

        # order_book.orders 按照从低到高价格排序,获取价格最低(也就是最远)的1个订单作为要取消的订单;
        sorted_buy_orders = sorted(self.our_buy_orders(order_book.orders),
                                   key=lambda order: order.price)
        # order_book.orders 按照从高到低价格排序,获取价格最高(也就是最远)的 1 个订单作为要取消的订单;
        sorted_sell_orders = sorted(self.our_sell_orders(order_book.orders),
                                    key=lambda order: order.price,
                                    reverse=True)

        # if (self.local_orders.__len__() - len(order_book.orders) > 0):
        if len(completed_orders) > 0:
            self.logger.info(
                "---**---some orders have been completed, and new should be submitted"
            )
            new_orders = []
            cancellable_orders = []
            step = 1
            buy_index = 0
            sell_index = 0
            count_sell_order = self.count_sell_orders(order_book.orders)
            count_buy_order = self.count_buy_orders(order_book.orders)

            for cod in completed_orders:
                # print(type(cod))
                # print(cod.is_sell)
                # the completed order is sell order, buy order should be placed
                cancel_order_count = 0

                self.orderlog.logger.info(" - " + str(cod.is_sell) + " - " +
                                          str(cod.price) + " - " +
                                          str(cod.amount) + " - " +
                                          str(cod.price * cod.amount))
                if cod.is_sell:
                    #(1-1 buy) place buy order, pay attention to rotate bix - eth
                    price = float(cod.price) * (1 - self.arbitrage_percent)
                    self.logger.info("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),
                                 amount=Wad.from_number(buy_amount),
                                 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)))

                    #(1-2 buy) 取消超出订单数量上限的buy订单,价格最远的订单--就是最低价格的订单
                    buyo = count_buy_order + 1 - self.band_order_limit
                    self.logger.info(
                        "Number of exceed the upper limit of buy order: " +
                        str(buyo))
                    # 加上后面这个条件,防止buy订单也成交,当前的订单列表里实际已经没有那么多数量的 buy 订单供取消
                    # 比如同一周期,buy 订单被吃了1,sell 订单被吃了3个,那么剩下的buy订单就不会被取消3次了
                    if buyo > 0 and len(sorted_buy_orders) > buy_index:
                        # order_book.orders 按照从低到高价格排序,获取价格最低(也就是最远)的1个订单作为要取消的订单;
                        # sorted_buy_orders = sorted(self.our_buy_orders(order_book.orders),
                        #                             key=lambda order: order.price)
                        print(sorted_buy_orders)
                        self.logger.info(
                            "Cancel buy order which exceed band order limit ")
                        self.logger.info(
                            str(sorted_buy_orders[buy_index].order_id))
                        self.logger.info(str(sorted_buy_orders[buy_index]))
                        print(type(sorted_buy_orders[buy_index]))
                        cancellable_orders.append(sorted_buy_orders[buy_index])
                        # self.bibox_api.cancel_order(sorted_buy_orders[index].order_id)
                        buy_index = buy_index + 1
                    else:  #如果没有对应取消订单,则 buy 订单数量增加
                        count_buy_order = count_buy_order + 1

                    #(2 sell) 以当前价格为基数,重新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)
                    band_sell_order_gap = self.band_order_limit - count_sell_order
                    self.logger.info(
                        "The number of sell order which need to submit " +
                        str(band_sell_order_gap))
                    # while band_sell_order_gap > 0: # 外部已经有循环了,不需要这个循环了,否则在多订单被吃时,会加倍补充
                    # 这里只需要判断,控制数量就够了
                    if band_sell_order_gap > 0:
                        current_price = self.get_last_price(self.pair())
                        self.logger.info("---**---current price is " +
                                         str(current_price))
                        price = float(current_price) * (
                            1 + self.arbitrage_percent *
                            (step + count_sell_order))
                        self.logger.info(
                            "The higher price of new sell order is " +
                            str(price))
                        pay_amount = self.each_order_amount * self.amount_disguise(
                        )  # bix amount
                        pay_amount = pay_amount + self.suffix_amount_identify(
                        )  # add unique identify
                        buy_amount = pay_amount * price  # eth money
                        new_orders.append(
                            NewOrder(is_sell=True,
                                     price=Wad.from_number(price),
                                     amount=Wad.from_number(pay_amount),
                                     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)))
                        count_sell_order = count_sell_order + 1

                else:  # buy order had been completed
                    #(1-1 sell) to place a corresponding sell order
                    price = float(cod.price) * (1 + self.arbitrage_percent)
                    self.logger.info(
                        "To submit new sell order with arbitrage price: " +
                        str(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),
                                 amount=Wad.from_number(pay_amount),
                                 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)))

                    #(1-2 sell) 取消超出band order limits数量的 sell 订单
                    self.logger.info(
                        "--------debug--------------Number of exceed the upper limit of sell order: "
                        + str(count_sell_order + 1 - self.band_order_limit))
                    sello = count_sell_order + 1 - self.band_order_limit
                    if sello > 0 and len(sorted_sell_orders) > sell_index:
                        # order_book.orders 按照从高到低价格排序,获取价格最高(也就是最远)的 1 个订单作为要取消的订单;
                        # sorted_sell_orders = sorted(self.our_sell_orders(order_book.orders),
                        #                             key=lambda order: order.price,
                        #                             reverse=True)
                        print(sorted_sell_orders)
                        self.logger.info(
                            "Cancel sell order which exceed band order limit ")
                        self.logger.info(
                            str(sorted_sell_orders[sell_index].order_id))
                        self.logger.info(str(sorted_sell_orders[sell_index]))
                        cancellable_orders.append(
                            sorted_sell_orders[sell_index])
                        # self.bibox_api.cancel_order(sorted_sell_orders[index].order_id)
                        sell_index = sell_index + 1
                    else:
                        count_sell_order = count_sell_order + 1

                    #(2 buy) 以当前价格为基数,重新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
                    self.logger.info(
                        "The number of buy order which need to submit " +
                        str(band_buy_order_gap))
                    if band_buy_order_gap > 0:
                        #基础价格放在循环里的话,能快速反映当前价格,特保是激烈波动的时候;但是增加了请求次数
                        current_price = self.get_last_price(self.pair())
                        price = float(current_price) * (
                            1 - self.arbitrage_percent *
                            (step + count_buy_order))
                        self.logger.info(
                            "The lower price order of new buy order is " +
                            str(price))
                        tmp = self.each_order_amount * self.amount_disguise()
                        pay_amount = tmp * price  # eth money 25
                        buy_amount = tmp  # bix amount 0.05
                        buy_amount = buy_amount + self.suffix_amount_identify(
                        )  # add unique identify
                        new_orders.append(
                            NewOrder(is_sell=False,
                                     price=Wad.from_number(price),
                                     amount=Wad.from_number(buy_amount),
                                     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)))

                        # band_buy_order_gap = band_buy_order_gap - 1
                        count_buy_order = count_buy_order + 1
                step = step + 1

            # Cancel orders
            if len(cancellable_orders) > 0:
                self.order_book_manager.cancel_orders(cancellable_orders)
                time.sleep(1)
            while order_book.orders_being_placed or order_book.orders_being_cancelled:
                self.logger.info(
                    "Sleep 1 s while order manager is in progress after cancelling exceed orders"
                )
                time.sleep(1)

            # Submit new orders
            self.place_orders(new_orders)
            time.sleep(1)
            while order_book.orders_being_placed or order_book.orders_being_cancelled:
                self.logger.info(
                    "Sleep 1 s while order manager is in progress after placing new orders"
                )
                time.sleep(1)

            # 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 对比,所以应该是解决了才对

            # 默认有3秒的刷新周期,导致被成功取消的订单,不会马上出现在order_book_manager
            # 导致 local 比真实的多了,使得取消的订单在下一次被误认为是成交了
            # 以上虽然 while 循环了,但是如果 while 循环的时候,有订单被成交了,这个订单就会丢掉,被误认为是撤销的订单了
            self.logger.info(
                "Update local order with latest remote order at the end of synchronize cycle"
            )
            self.local_orders = self.order_book_manager.get_order_book().orders

            # Cancel orders
            # cancellable_orders = bands.cancellable_orders(our_buy_orders=self.our_buy_orders(order_book.orders),
            #                                               our_sell_orders=self.our_sell_orders(order_book.orders),
            #                                               target_price=target_price)

            # if len(cancellable_orders) > 0:
            # self.order_book_manager.cancel_orders(cancellable_orders)
            # print("there is " + str(len(cancellable_orders)) + " orders should be cancelled")

            # Place new orders
        # self.place_orders(bands.new_orders(our_buy_orders=self.our_buy_orders(order_book.orders),
        #                                    our_sell_orders=self.our_sell_orders(order_book.orders),
        #                                    our_buy_balance=self.our_available_balance(order_book.balances, self.token_buy()),
        #                                    our_sell_balance=self.our_available_balance(order_book.balances, self.token_sell()),
        #                                    target_price=target_price)[0])

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            amount_symbol = self.token_sell()
            money = new_order_to_be_placed.buy_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.pay_amount
            money_symbol = self.token_buy()

            new_order_id = self.bibox_api.place_order(
                is_sell=new_order_to_be_placed.is_sell,
                amount=amount,
                amount_symbol=amount_symbol,
                money=money,
                money_symbol=money_symbol)

            return Order(new_order_id, 0, new_order_to_be_placed.is_sell,
                         Wad(money / amount), amount, amount_symbol, money,
                         money_symbol)

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))

    def get_price(self, pair):
        self.bibox_api.get_all_trades()
コード例 #6
0
class LeverjMarketMakerKeeper:
    """Keeper acting as a market maker on leverj."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='leverj-market-maker-keeper')

        parser.add_argument(
            "--leverj-api-server",
            type=str,
            default="https://test.leverj.io",
            help=
            "Address of the leverj API server (default: 'https://test.leverj.io')"
        )

        parser.add_argument("--account-id",
                            type=str,
                            default="",
                            help="Address of leverj api account id")

        parser.add_argument("--api-key",
                            type=str,
                            default="",
                            help="Address of leverj api key")

        parser.add_argument("--api-secret",
                            type=str,
                            default="",
                            help="Address of leverj api secret")

        parser.add_argument(
            "--leverj-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the Leverj API (in seconds, default: 9.5)")

        parser.add_argument("--rpc-host",
                            type=str,
                            default="localhost",
                            help="JSON-RPC host (default: `localhost')")

        parser.add_argument("--rpc-port",
                            type=int,
                            default=8545,
                            help="JSON-RPC port (default: `8545')")

        parser.add_argument("--rpc-timeout",
                            type=int,
                            default=10,
                            help="JSON-RPC timeout (in seconds, default: 10)")

        parser.add_argument(
            "--eth-from",
            type=str,
            required=True,
            help="Ethereum account from which to watch our trades")

        parser.add_argument(
            "--eth-key",
            type=str,
            nargs='*',
            help=
            "Ethereum private key(s) to use (e.g. 'key_file=aaa.json,pass_file=aaa.pass')"
        )

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--control-feed",
                            type=str,
                            help="Source of control feed")

        parser.add_argument(
            "--control-feed-expiry",
            type=int,
            default=86400,
            help="Maximum age of the control feed (in seconds, default: 86400)"
        )

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--leverage",
                            type=float,
                            default=1.0,
                            help="Leverage chosen for futures orders")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)

        self.web3 = Web3(
            HTTPProvider(
                endpoint_uri=
                f"http://{self.arguments.rpc_host}:{self.arguments.rpc_port}",
                request_kwargs={"timeout": self.arguments.rpc_timeout}))

        self.web3.eth.defaultAccount = self.arguments.eth_from
        register_keys(self.web3, self.arguments.eth_key)

        setup_logging(self.arguments)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.control_feed = create_control_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)
        self.target_price_lean = Wad(0)
        self.leverage = self.arguments.leverage

        self.history = History()

        self.leverj_api = LeverjFuturesAPI(
            web3=self.web3,
            api_server=self.arguments.leverj_api_server,
            account_id=self.arguments.account_id,
            api_key=self.arguments.api_key,
            api_secret=self.arguments.api_secret,
            timeout=self.arguments.leverj_timeout)

        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.leverj_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(
            lambda: self.leverj_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.leverj_api.cancel_order(order.order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(1)
            lifecycle.on_startup(self.startup)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def startup(self):
        quote_increment = self.leverj_api.get_tickSize(self.pair())
        self.precision = -(int(log10(float(quote_increment))) + 1)

    def shutdown(self):
        self.order_book_manager.cancel_all_orders()

    def pair(self):
        name_to_id_map = {'BTCDAI': '1', 'ETHDAI': '2'}
        return name_to_id_map[self.arguments.pair.upper()]

    def token_sell(self) -> str:
        return self.arguments.pair.upper()[:3]

    def token_buy(self) -> str:
        return self.arguments.pair.upper()[3:]

    def allocated_balance(self, token: str) -> Wad:
        quote_asset_address = self.leverj_api.get_product(
            self.pair())["quote"]["address"]
        # for perpetual contracts, the quote balance is allocated across instruments and sides to enter into trades
        total_available = self.leverj_api.get_plasma_balance(
            quote_asset_address)
        # adjust for leverage
        total_available = Decimal(total_available) * Decimal(self.leverage)
        self.logger.debug(f'total_available: {total_available}')
        return self._allocate_to_pair(total_available).get(token)

    def _allocate_to_pair(self, total_available):
        # total number of instruments across which the total_available balance is distributed
        # total_available is denominated in quote units
        total_number_of_instruments = 1

        # there are 2 partitions for allocation per instrument
        # the dai amount is divided in 2, one for the buy side and another for the sell side
        number_of_partitions_for_allocation = Wad.from_number(
            total_number_of_instruments * 2)

        # buffer_adjustment_factor is a small intentional buffer to avoid allocating the maximum possible.
        # the allocated amount is a little smaller than the maximum possible allocation
        # and that is determined by the buffer_adjustment_factor
        buffer_adjustment_factor = Wad.from_number(1.05)

        base = self.arguments.pair.upper()[:3]
        quote = self.arguments.pair.upper()[3:]
        target_price = self.price_feed.get_price()
        product = self.leverj_api.get_product(self.pair())
        minimum_order_quantity = self.leverj_api.get_minimum_order_quantity(
            self.pair())
        minimum_quantity_wad = Wad.from_number(minimum_order_quantity)

        if ((base == product['baseSymbol'])
                and (quote == product['quoteSymbol'])):
            if ((target_price is None) or (target_price.buy_price is None)
                    or (target_price.sell_price is None)):
                base_allocation = Wad(0)
                quote_allocation = Wad(0)
                self.logger.debug(
                    f'target_price not available to calculate allocations')
            else:
                average_price = (target_price.buy_price +
                                 target_price.sell_price) / Wad.from_number(2)
                # at 1x average_price * minimum_quantity_wad is the minimum_required_balance
                # multiplying this minimum_required_balance by 2 to avoid sending very small orders to the exchange
                minimum_required_balance = average_price * minimum_quantity_wad * Wad.from_number(
                    2)
                # conversion_divisor is the divisor that determines how many chunks should Dai be distributed into.
                # It considers the price of the base to convert into base denomination.
                conversion_divisor = average_price * number_of_partitions_for_allocation * buffer_adjustment_factor
                open_position_for_base = self.leverj_api.get_position_in_wad(
                    base)
                total_available_wad = Wad.from_number(
                    Decimal(total_available) /
                    Decimal(Decimal(10)**Decimal(18)))
                base_allocation = total_available_wad / conversion_divisor
                quote_allocation = total_available_wad / number_of_partitions_for_allocation
                self.logger.debug(
                    f'open_position_for_base: {open_position_for_base}')
                # bids are made basis quote_allocation and asks basis base_allocation
                # if open position is net long then quote_allocation is adjusted.
                # if open position is net too long then target_price is adjusted to reduce price of the asks/offers
                if (open_position_for_base.value > 0):
                    open_position_for_base_in_quote = open_position_for_base * average_price
                    net_adjusted_quote_value = quote_allocation.value - abs(
                        open_position_for_base_in_quote.value)
                    self.logger.debug(
                        f'net_adjusted_quote_value: {net_adjusted_quote_value}'
                    )
                    quote_allocation = Wad(
                        net_adjusted_quote_value
                    ) if net_adjusted_quote_value > minimum_required_balance.value else Wad(
                        0)
                    # if open position is within 1 Wad range or more than quote allocations then target price is leaned down by 0.1 percent
                    if Wad(net_adjusted_quote_value) < Wad(1):
                        self.target_price_lean = Wad.from_number(0.999)
                    else:
                        self.target_price_lean = Wad(0)
                elif (open_position_for_base.value < 0):
                    # if open position is net short then base_allocation is adjusted
                    # if open position is net too short then target_price is adjusted to increase price of the bids
                    net_adjusted_base_value = base_allocation.value - abs(
                        open_position_for_base.value)
                    minimum_required_balance_in_base = minimum_required_balance / average_price
                    self.logger.debug(
                        f'net_adjusted_base_value: {net_adjusted_base_value}')
                    base_allocation = Wad(
                        net_adjusted_base_value
                    ) if net_adjusted_base_value > minimum_required_balance_in_base.value else Wad(
                        0)
                    # if open position is within 1 Wad range or more than base allocations then target price is leaned up by 0.1 percent
                    if Wad(net_adjusted_base_value) < Wad(1):
                        self.target_price_lean = Wad.from_number(1.001)
                    else:
                        self.target_price_lean = Wad(0)
        else:
            base_allocation = Wad(0)
            quote_allocation = Wad(0)

        allocation = {base: base_allocation, quote: quote_allocation}
        self.logger.debug(f'allocation: {allocation}')
        return allocation

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def adjust_target_price(self, target_price: Price) -> Price:
        assert (isinstance(target_price, Price))
        target_price_lean = self.target_price_lean
        if ((target_price is None) or (target_price.buy_price is None)
                or (target_price.sell_price is None)):
            return target_price
        if target_price_lean.value == 0:
            return target_price
        else:
            self.logger.debug(f'target_price_lean: {target_price_lean}')
            adjusted_target_price = target_price
            adjusted_target_price.buy_price = (
                target_price.buy_price) * target_price_lean
            adjusted_target_price.sell_price = (
                target_price.sell_price) * target_price_lean
            return adjusted_target_price

    def synchronize_orders(self):
        bands = Bands.read(self.bands_config, self.spread_feed,
                           self.control_feed, self.history)

        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()
        target_price = self.adjust_target_price(target_price)
        self.logger.debug(
            f'target_price buy_price: {target_price.buy_price}, target_price sell_price: {target_price.sell_price}'
        )
        # Cancel orders
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)
        if len(cancellable_orders) > 0:
            self.order_book_manager.cancel_orders(cancellable_orders)
            return

        # 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.info(
                "Order book is in progress, not placing new orders")
            return

        # Place new orders
        new_orders = bands.new_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            our_buy_balance=self.allocated_balance(self.token_buy()),
            our_sell_balance=self.allocated_balance(self.token_sell()),
            target_price=target_price)[0]
        self.place_orders(new_orders)

    def place_orders(self, new_orders: List[NewOrder]):
        def place_order_function(new_order_to_be_placed):
            price = round(new_order_to_be_placed.price, self.precision + 2)
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            self.logger.debug(f'amount: {amount}')
            leverage_in_wad = Wad.from_number(self.leverage)
            order_id = str(
                self.leverj_api.place_order(self.pair(), price, 'LMT',
                                            new_order_to_be_placed.is_sell,
                                            price, amount, leverage_in_wad,
                                            False))
            return Order(order_id=order_id,
                         pair=self.pair(),
                         is_sell=new_order_to_be_placed.is_sell,
                         price=price,
                         amount=amount)

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
コード例 #7
0
class OkexMarketMakerKeeper:
    """Keeper acting as a market maker on OKEX."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='okex-market-maker-keeper')

        parser.add_argument(
            "--okex-api-server",
            type=str,
            default="https://www.okex.com",
            help=
            "Address of the OKEX API server (default: 'https://www.okex.com')")

        parser.add_argument("--okex-api-key",
                            type=str,
                            required=True,
                            help="API key for the OKEX API")

        parser.add_argument("--okex-secret-key",
                            type=str,
                            required=True,
                            help="Secret key for the OKEX API")

        parser.add_argument("--okex-password",
                            type=str,
                            required=True,
                            help="Password for the OKEX API key")

        parser.add_argument(
            "--okex-timeout",
            type=float,
            default=9.5,
            help="Timeout for accessing the OKEX API (in seconds, default: 9.5)"
        )

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--control-feed",
                            type=str,
                            help="Source of control feed")

        parser.add_argument(
            "--control-feed-expiry",
            type=int,
            default=86400,
            help="Maximum age of the control feed (in seconds, default: 86400)"
        )

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.control_feed = create_control_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.history = History()
        self.okex_api = OKEXApi(api_server=self.arguments.okex_api_server,
                                api_key=self.arguments.okex_api_key,
                                secret_key=self.arguments.okex_secret_key,
                                password=self.arguments.okex_password,
                                timeout=self.arguments.okex_timeout)

        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.okex_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(
            lambda: self.okex_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.okex_api.cancel_order(self.pair(), order.
                                                     order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def shutdown(self):
        self.order_book_manager.cancel_all_orders()

    def pair(self):
        return self.arguments.pair.lower()

    def token_sell(self) -> str:
        return self.arguments.pair.split('_')[0].lower()

    def token_buy(self) -> str:
        return self.arguments.pair.split('_')[1].lower()

    def our_available_balance(self, our_balances: dict, token: str) -> Wad:
        return Wad.from_number(our_balances[token.upper()]['available'])

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def synchronize_orders(self):
        bands = Bands.read(self.bands_config, self.spread_feed,
                           self.control_feed, self.history)
        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()

        # Cancel orders
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)
        if len(cancellable_orders) > 0:
            self.order_book_manager.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        self.place_orders(
            bands.new_orders(
                our_buy_orders=self.our_buy_orders(order_book.orders),
                our_sell_orders=self.our_sell_orders(order_book.orders),
                our_buy_balance=self.our_available_balance(
                    order_book.balances, self.token_buy()),
                our_sell_balance=self.our_available_balance(
                    order_book.balances, self.token_sell()),
                target_price=target_price)[0])

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            order_id = self.okex_api.place_order(
                pair=self.pair(),
                is_sell=new_order_to_be_placed.is_sell,
                price=new_order_to_be_placed.price,
                amount=amount)

            return Order(str(order_id), 0, self.pair(),
                         new_order_to_be_placed.is_sell,
                         new_order_to_be_placed.price, amount, Wad(0))

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
コード例 #8
0
class LeverjMarketMakerKeeper:
    """Keeper acting as a market maker on leverj."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='leverj-market-maker-keeper')

        parser.add_argument(
            "--leverj-api-server",
            type=str,
            default="https://test.leverj.io",
            help=
            "Address of the leverj API server (default: 'https://test.leverj.io')"
        )

        parser.add_argument("--account-id",
                            type=str,
                            default="",
                            help="Address of leverj api account id")

        parser.add_argument("--api-key",
                            type=str,
                            default="",
                            help="Address of leverj api key")

        parser.add_argument("--api-secret",
                            type=str,
                            default="",
                            help="Address of leverj api secret")

        parser.add_argument(
            "--leverj-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the Leverj API (in seconds, default: 9.5)")

        parser.add_argument("--rpc-host",
                            type=str,
                            default="localhost",
                            help="JSON-RPC host (default: `localhost')")

        parser.add_argument("--rpc-port",
                            type=int,
                            default=8545,
                            help="JSON-RPC port (default: `8545')")

        parser.add_argument("--rpc-timeout",
                            type=int,
                            default=10,
                            help="JSON-RPC timeout (in seconds, default: 10)")

        parser.add_argument(
            "--eth-from",
            type=str,
            required=True,
            help="Ethereum account from which to watch our trades")

        parser.add_argument(
            "--eth-key",
            type=str,
            nargs='*',
            help=
            "Ethereum private key(s) to use (e.g. 'key_file=aaa.json,pass_file=aaa.pass')"
        )

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--control-feed",
                            type=str,
                            help="Source of control feed")

        parser.add_argument(
            "--control-feed-expiry",
            type=int,
            default=86400,
            help="Maximum age of the control feed (in seconds, default: 86400)"
        )

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)

        if "infura" in self.arguments.rpc_host:
            self.web3 = Web3(
                HTTPProvider(
                    endpoint_uri=f"http://{self.arguments.rpc_host}",
                    request_kwargs={"timeout": self.arguments.rpc_timeout}))
        else:
            self.web3 = Web3(
                HTTPProvider(
                    endpoint_uri=
                    f"http://{self.arguments.rpc_host}:{self.arguments.rpc_port}",
                    request_kwargs={"timeout": self.arguments.rpc_timeout}))

        self.web3.eth.defaultAccount = self.arguments.eth_from
        register_keys(self.web3, self.arguments.eth_key)

        setup_logging(self.arguments)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.control_feed = create_control_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.history = History()

        self.leverj_api = LeverjAPI(
            web3=self.web3,
            api_server=self.arguments.leverj_api_server,
            account_id=self.arguments.account_id,
            api_key=self.arguments.api_key,
            api_secret=self.arguments.api_secret,
            timeout=self.arguments.leverj_timeout)

        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.leverj_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(
            lambda: self.leverj_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.leverj_api.cancel_order(order.order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(1)
            lifecycle.on_startup(self.startup)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def startup(self):
        quote_increment = 1 / (self.leverj_api.get_product(
            self.arguments.pair)["ticksperpoint"])
        self.precision = -(int(log10(float(quote_increment))) + 1)

    def shutdown(self):
        self.order_book_manager.cancel_all_orders()

    def pair(self):
        return self.arguments.pair.upper()

    def token_sell(self) -> str:
        if self.arguments.pair.startswith("USDC"):
            return "USDC"
        return self.arguments.pair[:3]

    def token_buy(self) -> str:
        if self.arguments.pair.startswith("USDC"):
            return self.arguments.pair[4:]
        return self.arguments.pair[3:]

    def our_available_balance(self, our_balances: dict, token: str) -> Wad:
        for key in our_balances:
            if our_balances[key]['symbol'] == token:
                if (token == "LEV") or (token == "FEE"):
                    return Wad(int(our_balances[key]['available']) * 10**9)
                elif (token == "USDC") or (token == "USDT"):
                    return Wad(int(our_balances[key]['available']) * 10**12)
                elif (token == "WBTC"):
                    return Wad(int(our_balances[key]['available']) * 10**10)
                elif (token == "GUSD"):
                    return Wad(int(our_balances[key]['available']) * 10**16)
                else:
                    return Wad(int(our_balances[key]['available']))

        return Wad(0)

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def synchronize_orders(self):
        bands = Bands.read(self.bands_config, self.spread_feed,
                           self.control_feed, self.history)

        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()
        # Cancel orders
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)
        if len(cancellable_orders) > 0:
            self.order_book_manager.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        new_orders = bands.new_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            our_buy_balance=self.our_available_balance(order_book.balances,
                                                       self.token_buy()),
            our_sell_balance=self.our_available_balance(
                order_book.balances, self.token_sell()),
            target_price=target_price)[0]
        self.place_orders(new_orders)

    def place_orders(self, new_orders: List[NewOrder]):
        def place_order_function(new_order_to_be_placed):
            price = round(new_order_to_be_placed.price, self.precision + 2)
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            order_id = str(
                self.leverj_api.place_order(self.pair(),
                                            new_order_to_be_placed.is_sell,
                                            price, amount))
            return Order(order_id=order_id,
                         pair=self.pair(),
                         is_sell=new_order_to_be_placed.is_sell,
                         price=price,
                         amount=amount)

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
コード例 #9
0
class CoinoneMarketMakerKeeper(CEXKeeperAPI):
    """
    Keeper acting as a market maker on Coinone.
    """
    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='coinone-market-maker-keeper')

        parser.add_argument(
            "--coinone-api-server",
            type=str,
            default="https://api.coinone.co.kr",
            help=
            "Address of the Coinone API server (default: 'https://api.coinone.co.kr')"
        )

        parser.add_argument("--coinone-access-token",
                            type=str,
                            required=True,
                            help="API access token for the Coinone API")

        parser.add_argument("--coinone-secret-key",
                            type=str,
                            required=True,
                            help="API secret key for the Coinone API")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--control-feed",
                            type=str,
                            help="Source of control feed")

        parser.add_argument(
            "--control-feed-expiry",
            type=int,
            default=86400,
            help="Maximum age of the control feed (in seconds, default: 86400)"
        )

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)

        self.coinone_api = CoinoneApi(
            api_server=self.arguments.coinone_api_server,
            access_token=self.arguments.coinone_access_token,
            secret_key=self.arguments.coinone_secret_key)

        super().__init__(self.arguments, self.coinone_api)

    # override init as cancel_orders() has a non standard interface
    def init_order_book_manager(self, arguments, coinone_api):
        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.coinone_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(
            lambda: self.coinone_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.coinone_api.cancel_order(
                order.order_id, self.pair(), order.price, order.amount, order.
                is_sell))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)

        self.order_book_manager.pair = self.pair()
        self.order_book_manager.start()

    def pair(self):
        return self.arguments.pair

    def token_sell(self) -> str:
        return self.arguments.pair.split('-')[0].lower()

    def token_buy(self) -> str:
        return self.arguments.pair.split('-')[1].lower()

    def our_available_balance(self, our_balances: dict, token: str) -> Wad:
        return Wad.from_number(float(our_balances[token.lower()]["avail"]))

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount

            order_id = self.coinone_api.place_order(
                pair=self.pair().upper(),
                is_sell=new_order_to_be_placed.is_sell,
                price=new_order_to_be_placed.price,
                amount=amount)

            return Order(str(order_id), int(time.time()), self.pair(),
                         new_order_to_be_placed.is_sell,
                         new_order_to_be_placed.price, amount)

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
コード例 #10
0
class KrakenMarketMakerKeeper:
    """Keeper acting as a market maker on Kraken."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='kraken-market-maker-keeper')

        parser.add_argument(
            "--kraken-api-server",
            type=str,
            default="https://api.kraken.com",
            help=
            "Address of the kraken API server (default: 'https://api.kraken.com')"
        )

        parser.add_argument("--kraken-api-key",
                            type=str,
                            required=True,
                            help="API key for the kraken API")

        parser.add_argument("--kraken-secret-key",
                            type=str,
                            required=True,
                            help="Secret key for the kraken API")

        parser.add_argument(
            "--kraken-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the kraken API (in seconds, default: 9.5)")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--control-feed",
                            type=str,
                            help="Source of control feed")

        parser.add_argument(
            "--control-feed-expiry",
            type=int,
            default=86400,
            help="Maximum age of the control feed (in seconds, default: 86400)"
        )

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        parser.add_argument(
            "--telegram-log-config-file",
            type=str,
            required=False,
            help=
            "config file for send logs to telegram chat (e.g. 'telegram_conf.json')",
            default=None)

        parser.add_argument(
            "--keeper-name",
            type=str,
            required=False,
            help="market maker keeper name (e.g. 'Uniswap_V2_MDTETH')",
            default="kraken")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.control_feed = create_control_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.history = History()

        self.kraken_api = KrakenApi(
            api_server=self.arguments.kraken_api_server,
            api_key=self.arguments.kraken_api_key,
            secret_key=self.arguments.kraken_secret_key,
            timeout=self.arguments.kraken_timeout)

        self.assets = self.kraken_api.get_assets()

        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.kraken_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(
            lambda: self.kraken_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.kraken_api.cancel_order(order.order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.on_startup(self.startup)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def startup(self):
        # Get decimals for pair.
        self.pair_precision = self.kraken_api.get_markets()[
            self.pair()]['pair_decimals']

    def shutdown(self):
        self.order_book_manager.cancel_all_orders()

    def pair(self):
        return self.arguments.pair.upper()

    def token_sell(self) -> str:
        return self.arguments.pair[:3]

    def token_buy(self) -> str:
        return self.arguments.pair[3:]

    def our_available_balance(self, our_balances: dict, token: str) -> Wad:
        for symbol in self.assets:
            if self.assets[symbol][
                    'altname'] == token and symbol in our_balances:
                return Wad.from_number(our_balances[symbol])

        return Wad.from_number(0)

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def synchronize_orders(self):
        bands = Bands.read(self.bands_config, self.spread_feed,
                           self.control_feed, self.history)

        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()
        # Cancel orders
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)
        if len(cancellable_orders) > 0:
            self.order_book_manager.cancel_orders(cancellable_orders)
            return

        # 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

        # In case of Kraken, balances returned by `our_total_balance` still contain amounts "locked"
        # by currently open orders, so we need to explicitly subtract these amounts.
        our_buy_orders = self.our_buy_orders(order_book.orders)
        our_sell_orders = self.our_sell_orders(order_book.orders)

        our_buy_balance = self.our_available_balance(
            order_book.balances,
            self.token_buy()) - Bands.total_amount(our_buy_orders)
        our_sell_balance = self.our_available_balance(
            order_book.balances,
            self.token_sell()) - Bands.total_amount(our_sell_orders)

        # Place new orders
        new_orders = bands.new_orders(our_buy_orders=our_buy_orders,
                                      our_sell_orders=our_sell_orders,
                                      our_buy_balance=our_buy_balance,
                                      our_sell_balance=our_sell_balance,
                                      target_price=target_price)[0]

        self.place_orders(new_orders)

    def place_orders(self, new_orders: List[NewOrder]):
        def place_order_function(new_order_to_be_placed):
            price = round(new_order_to_be_placed.price, self.pair_precision)
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount

            order_id = self.kraken_api.place_order(
                self.pair(), new_order_to_be_placed.is_sell, price, amount)

            return Order(order_id=order_id,
                         pair=self.pair(),
                         is_sell=new_order_to_be_placed.is_sell,
                         price=price,
                         amount=amount,
                         filled_amount=Wad(0))

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
コード例 #11
0
class BiboxMarketMakerKeeper:
    """Keeper acting as a market maker on Bibox."""

    logger = logging.getLogger()

    def __init__(self, args: list, **kwargs):
        parser = argparse.ArgumentParser(prog='bibox-market-maker-keeper')

        parser.add_argument(
            "--bibox-api-server",
            type=str,
            default="https://api.bibox.com",
            help=
            "Address of the Bibox API server (default: 'https://api.bibox.com')"
        )

        parser.add_argument("--bibox-api-key",
                            type=str,
                            required=True,
                            help="API key for the Bibox API")

        parser.add_argument("--bibox-secret",
                            type=str,
                            required=True,
                            help="Secret for the Bibox API")

        parser.add_argument(
            "--bibox-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the Bibox API (in seconds, default: 9.5)")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.history = History()
        self.bibox_api = BiboxApi(api_server=self.arguments.bibox_api_server,
                                  api_key=self.arguments.bibox_api_key,
                                  secret=self.arguments.bibox_secret,
                                  timeout=self.arguments.bibox_timeout)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(
            self.arguments.price_feed, self.arguments.price_feed_expiry, None)

        self.order_book_manager = OrderBookManager(refresh_frequency=3)
        self.order_book_manager.get_orders_with(
            lambda: self.bibox_api.get_orders(pair=self.pair(), retry=True))
        self.order_book_manager.get_balances_with(
            lambda: self.bibox_api.coin_list(retry=True))
        self.order_book_manager.start()

    def main(self):
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def shutdown(self):
        while True:
            try:
                our_orders = self.bibox_api.get_orders(self.pair(), retry=True)
            except:
                continue

            if len(our_orders) == 0:
                break

            self.cancel_orders(our_orders)
            self.order_book_manager.wait_for_order_cancellation()

    def pair(self):
        return self.arguments.pair.upper()

    def token_sell(self) -> str:
        return self.arguments.pair.split('_')[0].upper()

    def token_buy(self) -> str:
        return self.arguments.pair.split('_')[1].upper()

    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 our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def synchronize_orders(self):
        bands = Bands(self.bands_config, self.history)
        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()

        if target_price is None:
            self.logger.warning(
                "Cancelling all orders as no price feed available.")
            self.cancel_orders(order_book.orders)
            return

        # Cancel orders
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)
        if len(cancellable_orders) > 0:
            self.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        self.place_orders(
            bands.new_orders(
                our_buy_orders=self.our_buy_orders(order_book.orders),
                our_sell_orders=self.our_sell_orders(order_book.orders),
                our_buy_balance=self.our_available_balance(
                    order_book.balances, self.token_buy()),
                our_sell_balance=self.our_available_balance(
                    order_book.balances, self.token_sell()),
                target_price=target_price)[0])

    def cancel_orders(self, orders):
        for order in orders:
            self.order_book_manager.cancel_order(
                order.order_id,
                lambda order=order: self.bibox_api.cancel_order(order.order_id
                                                                ))

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            amount_symbol = self.token_sell()
            money = new_order_to_be_placed.buy_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.pay_amount
            money_symbol = self.token_buy()

            new_order_id = self.bibox_api.place_order(
                is_sell=new_order_to_be_placed.is_sell,
                amount=amount,
                amount_symbol=amount_symbol,
                money=money,
                money_symbol=money_symbol)

            return Order(new_order_id, 0, new_order_to_be_placed.is_sell,
                         Wad(0), amount, amount_symbol, money, money_symbol)

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
コード例 #12
0
class OkexMarketSurfer:
    """Keeper acting as a market maker on OKEX."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='okex-market-maker-keeper')

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the surfer will operate")

        parser.add_argument(
            "--output-path",
            type=str,
            required=True,
            help="output file path of the completed order result")

        # reserved for old program
        # parser.add_argument("--price-feed", type=str, required=True,
        #                     help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.orderlog = Logger(self.arguments.output_path + "SurferResult_" +
                               self.arguments.pair,
                               level='info')

        try:
            f = open(self.arguments.config, 'r')
            config = json.loads(f.read())
            print(config)
            f.close()

            self.okex_api = OKEXApi(api_server=config["okex_api_server"],
                                    api_key=config["okex_api_key"],
                                    secret_key=config["okex_secret_key"],
                                    timeout=config["okex_timeout"])
            print("--------------")
            print(config['pairs'])

            for pair in config['pairs']:
                print(pair)
                if pair['pair'] == self.arguments.pair:
                    self.total_amount = pair["total_amount"]
                    # percent of total amount of each transaction or each order
                    self.each_order_percent = pair["each_order_percent"]
                    self.arbitrage_percent = pair["arbitrage_percent"]
                    # the order count of sell or buy bands must less than limit
                    self.band_order_limit = pair["band_order_limit"]

        except Exception as e:
            logging.getLogger().warning(
                f"Config file is invalid ({e}). Treating the config file as it has no bands."
            )

        self.history = History()
        self.bands_config = ReloadableConfig(self.arguments.config)

        self.spread_feed = create_spread_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.local_orders = []
        self.each_order_amount = self.total_amount * self.each_order_percent

        # To implement abstract function with different exchanges API
        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.okex_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(
            lambda: self.okex_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.okex_api.cancel_order(self.pair(), order.
                                                     order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        # Place new orders while initialize the whole surfer system
        self.initialize_orders(self.each_order_amount, self.arbitrage_percent,
                               self.band_order_limit)
        time.sleep(
            5)  # wait for order book manager to get placed orders 足够时间保证系统稳定返回
        self.local_orders = self.order_book_manager.get_order_book().orders

        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.every(10, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def initialize_orders(self, each_order_amount, arbitrage_percent,
                          band_order_limit):
        orders = []
        i = 1
        base_price = self.get_last_price(self.pair())
        while band_order_limit + 1 > i:
            # place sell order
            price = float(base_price) * (1 + arbitrage_percent * i)
            # pay_amount = Wad.min(band.avg_amount - total_amount, our_sell_balance, limit_amount)
            pay_amount = each_order_amount * self.amount_disguise(
            )  # bix amount
            pay_amount = pay_amount + self.suffix_amount_identify()
            buy_amount = pay_amount * price  # eth money
            # print(pay_amount)

            orders.append(
                NewOrder(is_sell=True,
                         price=Wad.from_number(price),
                         amount=Wad.from_number(pay_amount),
                         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)))

            # place buy order, pay attention to rotate bix - eth
            price = float(base_price) * (1 - arbitrage_percent * i)
            # pay_amount = Wad.min(band.avg_amount - total_amount, our_sell_balance, limit_amount)
            tmp = each_order_amount * self.amount_disguise()
            pay_amount = tmp * price  # eth money 25
            buy_amount = tmp  # bix amount 0.05
            buy_amount = buy_amount + self.suffix_amount_identify()
            # print(buy_amount)
            orders.append(
                NewOrder(is_sell=False,
                         price=Wad.from_number(price),
                         amount=Wad.from_number(buy_amount),
                         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)))
            i = i + 1

        self.place_orders(orders)
        # 偶尔有 bug,提交的完成慢,导致 local orders 比 下一次 获取回来少, initial_delay 加长时间到15秒,时间太长也麻烦,
        # 会导致一开始提交就成交的那部分订单不会存到 local orders
        # 需要换地方,order_book_manager更新不及时的情况下,会导致返回的订单数据不全,或者订单里的参数默认为0的情况
        # self.local_orders = self.order_book_manager.get_order_book().orders

    @staticmethod
    def amount_disguise():
        rand = [
            0.8, 0.84, 0.88, 0.92, 0.95, 0.99, 1.03, 1.06, 1.09, 1.12, 1.16,
            1.2
        ]
        return rand[random.randint(0, 11)]

    # suffix unique amount number to identify different buy/sell order pairs for result performance statics
    @staticmethod
    def suffix_amount_identify():
        return round(random.random() / 10.0, 10)

    def get_last_price(self, pair):
        return self.okex_api.ticker(pair)["ticker"]['last']

    def shutdown(self):
        self.order_book_manager.cancel_all_orders()

    def pair(self):
        return self.arguments.pair.lower()

    def token_sell(self) -> str:
        return self.arguments.pair.split('_')[0].lower()

    def token_buy(self) -> str:
        return self.arguments.pair.split('_')[1].lower()

    def our_available_balance(self, our_balances: dict, token: str) -> Wad:
        return Wad.from_number(our_balances['free'][token])

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def count_sell_orders(self, our_orders: list) -> int:
        return len(list(filter(lambda order: order.is_sell, our_orders)))

    def count_buy_orders(self, our_orders: list) -> int:
        return len(list(filter(lambda order: not order.is_sell, our_orders)))

    def synchronize_orders(self):
        # bands = Bands.read(self.bands_config, self.spread_feed, self.history)
        order_book = self.order_book_manager.get_order_book()

        self.logger.info("---**---The length of local_orders " +
                         str(self.local_orders.__len__()))
        self.logger.info("---**---The length 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)

        self.order_book_manager.get_order_book()
        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__():
                self.logger.info("---**---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))
        self.logger.info("---**---The lenght of completed orders " +
                         str(len(completed_orders)))
        self.logger.info(completed_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:
            self.logger.info("--------- some orders have been done --------")
            new_orders = []
            step = 1
            count_sell_order = self.count_sell_orders(order_book.orders)
            count_buy_order = self.count_buy_orders(order_book.orders)

            for cod in completed_orders:
                # print(type(cod))
                # print(cod.is_sell)
                # the completed order is sell order, buy order should be placed

                self.orderlog.logger.info(" - " + str(cod.is_sell) + " - " +
                                          str(cod.price) + " - " +
                                          str(cod.amount) + " - " +
                                          str(cod.price * cod.amount))
                if cod.is_sell:
                    # place buy order, pay attention to rotate bix - eth
                    price = float(cod.price) * (1 - self.arbitrage_percent)
                    self.logger.info(
                        "----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),
                                 amount=Wad.from_number(buy_amount),
                                 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)
                    band_sell_order_gap = self.band_order_limit - count_sell_order
                    self.logger.info("---sell band gap---- " +
                                     str(band_sell_order_gap))
                    # while band_sell_order_gap > 0: # 外部已经有循环了,不需要这个循环了,否则在多订单被吃时,会加倍补充
                    # 这里只需要判断,控制数量就够了
                    if band_sell_order_gap > 0:
                        current_price = self.get_last_price(self.pair())
                        self.logger.info("------current price---- " +
                                         str(current_price))
                        price = float(current_price) * (
                            1 + self.arbitrage_percent *
                            (step + count_sell_order))
                        self.logger.info("----higher price to sell--- " +
                                         str(price))
                        pay_amount = self.each_order_amount * self.amount_disguise(
                        )  # bix amount
                        pay_amount = pay_amount + self.suffix_amount_identify(
                        )  # add unique identify
                        buy_amount = pay_amount * price  # eth money
                        new_orders.append(
                            NewOrder(is_sell=True,
                                     price=Wad.from_number(price),
                                     amount=Wad.from_number(pay_amount),
                                     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
                        count_sell_order = count_sell_order + 1

                else:  # buy order had been completed
                    # to place a sell order
                    price = float(cod.price) * (1 + self.arbitrage_percent)
                    self.logger.info("----price--- sell--- " + str(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),
                                 amount=Wad.from_number(pay_amount),
                                 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
                    self.logger.info("---buy band gap----" +
                                     str(band_buy_order_gap))

                    # while band_buy_order_gap > 0:
                    if band_buy_order_gap > 0:
                        # 基础价格放在循环里的话,能快速反映当前价格,特保是激烈波动的时候;但是增加了请求次数
                        current_price = self.get_last_price(self.pair())
                        price = float(current_price) * (
                            1 - self.arbitrage_percent *
                            (step + count_buy_order))
                        self.logger.info("----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
                        buy_amount = buy_amount + self.suffix_amount_identify(
                        )  # add unique identify
                        new_orders.append(
                            NewOrder(is_sell=False,
                                     price=Wad.from_number(price),
                                     amount=Wad.from_number(buy_amount),
                                     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)))

                        # band_buy_order_gap = band_buy_order_gap - 1
                        count_buy_order = count_buy_order + 1
                    step = step + 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 对比,所以应该是解决了才对
            self.logger.info("-----update local order------")
            self.local_orders = self.order_book_manager.get_order_book().orders

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            money = new_order_to_be_placed.buy_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.pay_amount
            order_id = self.okex_api.place_order(
                pair=self.pair(),
                is_sell=new_order_to_be_placed.is_sell,
                price=new_order_to_be_placed.price,
                amount=amount)

            return Order(order_id, 0, self.pair(),
                         new_order_to_be_placed.is_sell,
                         new_order_to_be_placed.price, amount, Wad(money))

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
コード例 #13
0
class OasisMarketMakerKeeper:
    """Keeper acting as a market maker on OasisDEX."""

    logger = logging.getLogger()

    def __init__(self, args: list, **kwargs):
        parser = argparse.ArgumentParser(prog='oasis-market-maker-keeper')

        parser.add_argument("--rpc-host", type=str, default="localhost",
                            help="JSON-RPC host (default: `localhost')")

        parser.add_argument("--rpc-port", type=int, default=8545,
                            help="JSON-RPC port (default: `8545')")

        parser.add_argument("--rpc-timeout", type=int, default=10,
                            help="JSON-RPC timeout (in seconds, default: 10)")

        parser.add_argument("--eth-from", type=str, required=True,
                            help="Ethereum account from which to send transactions")

        parser.add_argument("--tub-address", type=str, required=False,
                            help="Ethereum address of the Tub contract")

        parser.add_argument("--oasis-address", type=str, required=True,
                            help="Ethereum address of the OasisDEX contract")

        parser.add_argument("--buy-token-address", type=str, required=True,
                            help="Ethereum address of the buy token")

        parser.add_argument("--sell-token-address", type=str, required=True,
                            help="Ethereum address of the sell token")

        parser.add_argument("--config", type=str, required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed", type=str, required=True,
                            help="Source of price feed")

        parser.add_argument("--price-feed-expiry", type=int, default=120,
                            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed", type=str,
                            help="Source of spread feed")

        parser.add_argument("--spread-feed-expiry", type=int, default=3600,
                            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--order-history", type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument("--order-history-every", type=int, default=30,
                            help="Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument("--round-places", type=int, default=2,
                            help="Number of decimal places to round order prices to (default=2)")

        parser.add_argument("--min-eth-balance", type=float, default=0,
                            help="Minimum ETH balance below which keeper will cease operation")

        parser.add_argument("--gas-price", type=int, default=0,
                            help="Gas price (in Wei)")

        parser.add_argument("--smart-gas-price", dest='smart_gas_price', action='store_true',
                            help="Use smart gas pricing strategy, based on the ethgasstation.info feed")

        parser.add_argument("--debug", dest='debug', action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.web3 = kwargs['web3'] if 'web3' in kwargs else Web3(HTTPProvider(endpoint_uri=f"http://{self.arguments.rpc_host}:{self.arguments.rpc_port}",
                                                                              request_kwargs={"timeout": self.arguments.rpc_timeout}))
        self.web3.eth.defaultAccount = self.arguments.eth_from
        self.our_address = Address(self.arguments.eth_from)
        self.otc = MatchingMarket(web3=self.web3, address=Address(self.arguments.oasis_address))

        tub = Tub(web3=self.web3, address=Address(self.arguments.tub_address)) \
            if self.arguments.tub_address is not None else None

        self.token_buy = ERC20Token(web3=self.web3, address=Address(self.arguments.buy_token_address))
        self.token_sell = ERC20Token(web3=self.web3, address=Address(self.arguments.sell_token_address))
        self.min_eth_balance = Wad.from_number(self.arguments.min_eth_balance)
        self.bands_config = ReloadableConfig(self.arguments.config)
        self.gas_price = GasPriceFactory().create_gas_price(self.arguments)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments, tub)
        self.spread_feed = create_spread_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(self.arguments)

        self.history = History()
        self.order_book_manager = OrderBookManager(refresh_frequency=3)
        self.order_book_manager.get_orders_with(lambda: self.our_orders())
        self.order_book_manager.enable_history_reporting(self.order_history_reporter, self.our_buy_orders, self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        with Lifecycle(self.web3) as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.on_startup(self.startup)
            lifecycle.on_block(self.on_block)
            lifecycle.every(3, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def startup(self):
        self.approve()

    @retry(delay=5, logger=logger)
    def shutdown(self):
        self.cancel_all_orders()

    def on_block(self):
        # This method is present only so the lifecycle binds the new block listener, which makes
        # it then terminate the keeper if no new blocks have been arriving for 300 seconds.
        pass

    def approve(self):
        """Approve OasisDEX to access our balances, so we can place orders."""
        self.otc.approve([self.token_sell, self.token_buy], directly(gas_price=self.gas_price))

    def our_available_balance(self, token: ERC20Token) -> Wad:
        return token.balance_of(self.our_address)

    def our_orders(self):
        return list(filter(lambda order: order.maker == self.our_address,
                           self.otc.get_orders(self.token_sell.address, self.token_buy.address) +
                           self.otc.get_orders(self.token_buy.address, self.token_sell.address)))

    def our_sell_orders(self, our_orders: list):
        return list(filter(lambda order: order.buy_token == self.token_buy.address and
                                         order.pay_token == self.token_sell.address, our_orders))

    def our_buy_orders(self, our_orders: list):
        return list(filter(lambda order: order.buy_token == self.token_sell.address and
                                         order.pay_token == self.token_buy.address, our_orders))

    def synchronize_orders(self):
        # If market is closed, cancel all orders but do not terminate the keeper.
        if self.otc.is_closed():
            self.logger.warning("Market is closed. Cancelling all orders.")
            self.cancel_all_orders()
            return

        # If keeper balance is below `--min-eth-balance`, cancel all orders but do not terminate
        # the keeper, keep processing blocks as the moment the keeper gets a top-up it should
        # resume activity straight away, without the need to restart it.
        if eth_balance(self.web3, self.our_address) < self.min_eth_balance:
            self.logger.warning("Keeper ETH balance below minimum. Cancelling all orders.")
            self.cancel_all_orders()
            return

        bands = Bands(self.bands_config, self.spread_feed, self.history)
        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()

        # If there are any orders to be cancelled, cancel them. It is deliberate that we wait with topping-up
        # bands until the next block. This way we would create new orders based on the most recent price and
        # order book state. We could theoretically retrieve both (`target_price` and `our_orders`) again here,
        # but it just seems cleaner to do it in one place instead of in two.
        cancellable_orders = bands.cancellable_orders(our_buy_orders=self.our_buy_orders(order_book.orders),
                                                      our_sell_orders=self.our_sell_orders(order_book.orders),
                                                      target_price=target_price)

        if len(cancellable_orders) > 0:
            self.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        self.place_orders(bands.new_orders(our_buy_orders=self.our_buy_orders(order_book.orders),
                                           our_sell_orders=self.our_sell_orders(order_book.orders),
                                           our_buy_balance=self.our_available_balance(self.token_buy),
                                           our_sell_balance=self.our_available_balance(self.token_sell),
                                           target_price=target_price)[0])

    def cancel_all_orders(self):
        # Wait for the order book to stabilize
        while True:
            order_book = self.order_book_manager.get_order_book()
            if not order_book.orders_being_cancelled and not order_book.orders_being_placed:
                break

        # Cancel all open orders
        self.cancel_orders(self.order_book_manager.get_order_book().orders)
        self.order_book_manager.wait_for_order_cancellation()

    def cancel_orders(self, orders):
        for order in orders:
            self.order_book_manager.cancel_order(order.order_id, lambda order=order: self.otc.kill(order.order_id).transact(gas_price=self.gas_price).successful)

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed: NewOrder):
            assert(isinstance(new_order_to_be_placed, NewOrder))

            if new_order_to_be_placed.is_sell:
                pay_token = self.token_sell.address
                buy_token = self.token_buy.address
            else:
                pay_token = self.token_buy.address
                buy_token = self.token_sell.address

            transact = self.otc.make(pay_token=pay_token, pay_amount=new_order_to_be_placed.pay_amount,
                                     buy_token=buy_token, buy_amount=new_order_to_be_placed.buy_amount).transact(gas_price=self.gas_price)

            if transact is not None and transact.successful and transact.result is not None:
                return Order(market=self.otc,
                             order_id=transact.result,
                             maker=self.our_address,
                             pay_token=pay_token,
                             pay_amount=new_order_to_be_placed.pay_amount,
                             buy_token=buy_token,
                             buy_amount=new_order_to_be_placed.buy_amount,
                             timestamp=0)
            else:
                return None

        for new_order in new_orders:
            self.order_book_manager.place_order(lambda new_order=new_order: place_order_function(new_order))
コード例 #14
0
class DyDxMarketMakerKeeper(CEXKeeperAPI):
    """
    Keeper acting as a market maker on DyDx.
    Although portions of DyDx are onchain, 
    full order book functionality requires offchain components.
    """
    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='dydx-market-maker-keeper')

        parser.add_argument(
            "--dydx-api-server",
            type=str,
            required=True,
            help="Address of the Eth RPC node used for Dydx connection")

        parser.add_argument("--dydx-private-key",
                            type=str,
                            required=True,
                            help="API key for the DyDx API")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--control-feed",
                            type=str,
                            help="Source of control feed")

        parser.add_argument(
            "--control-feed-expiry",
            type=int,
            default=86400,
            help="Maximum age of the control feed (in seconds, default: 86400)"
        )

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)

        self.dydx_api = DydxApi(node=self.arguments.dydx_api_server,
                                private_key=self.arguments.dydx_private_key)

        self.market_info = self.dydx_api.get_markets()

        super().__init__(self.arguments, self.dydx_api)

    def pair(self):
        return self.arguments.pair

    def init_order_book_manager(self, arguments, pyex_api):
        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: pyex_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(
            lambda: pyex_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: pyex_api.cancel_order(order.order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def token_sell(self) -> str:
        return self.arguments.pair.split('-')[0].lower()

    def token_buy(self) -> str:
        return self.arguments.pair.split('-')[1].lower()

    def our_available_balance(self, our_balances: dict, token: str) -> Wad:
        if token == 'weth':
            token = 'eth'

        return list(
            filter(lambda x: x['currency'] == token.upper(),
                   our_balances))[0]['wad']

    def _should_place_order(self, new_order: dict,
                            minimum_order_size: Wad) -> bool:
        amount = new_order.pay_amount if new_order.is_sell else new_order.buy_amount
        return amount > minimum_order_size

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            order_id = self.dydx_api.place_order(
                pair=self.pair().upper(),
                is_sell=new_order_to_be_placed.is_sell,
                price=Wad.__float__(new_order_to_be_placed.price),
                amount=Wad.__float__(amount))

            return Order(str(order_id), int(time.time()), self.pair(),
                         new_order_to_be_placed.is_sell,
                         new_order_to_be_placed.price, amount)

        for new_order in new_orders:
            amount = new_order.pay_amount if new_order.is_sell else new_order.buy_amount
            side = 'Sell' if new_order.is_sell else 'Buy'
            minimum_order_size = Wad(
                int(self.market_info[self.pair().upper()]
                    ['smallOrderThreshold']))
            if self._should_place_order(new_order, minimum_order_size):
                self.order_book_manager.place_order(
                    lambda new_order=new_order: place_order_function(new_order
                                                                     ))
            else:
                logging.info(
                    f"New {side} Order below size minimum of {minimum_order_size}. Order of amount {amount} ignored."
                )

    def synchronize_orders(self):
        bands = Bands.read(self.bands_config, self.spread_feed,
                           self.control_feed, self.history)
        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()

        # Cancel orders
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)
        if len(cancellable_orders) > 0:
            self.order_book_manager.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        self.place_orders(
            bands.new_orders(
                our_buy_orders=self.our_buy_orders(order_book.orders),
                our_sell_orders=self.our_sell_orders(order_book.orders),
                our_buy_balance=self.our_available_balance(
                    order_book.balances, self.token_buy()),
                our_sell_balance=self.our_available_balance(
                    order_book.balances, self.token_sell()),
                target_price=target_price)[0])
コード例 #15
0
class ParadexMarketMakerKeeper:
    """Keeper acting as a market maker on Paradex."""

    logger = logging.getLogger()

    def __init__(self, args: list, **kwargs):
        parser = argparse.ArgumentParser(prog='paradex-market-maker-keeper')

        parser.add_argument("--rpc-host",
                            type=str,
                            default="localhost",
                            help="JSON-RPC host (default: `localhost')")

        parser.add_argument("--rpc-port",
                            type=int,
                            default=8545,
                            help="JSON-RPC port (default: `8545')")

        parser.add_argument("--rpc-timeout",
                            type=int,
                            default=10,
                            help="JSON-RPC timeout (in seconds, default: 10)")

        parser.add_argument(
            "--eth-from",
            type=str,
            required=True,
            help="Ethereum account from which to send transactions")

        parser.add_argument(
            "--exchange-address",
            type=str,
            required=True,
            help="Ethereum address of the 0x Exchange contract")

        parser.add_argument(
            "--paradex-api-server",
            type=str,
            default='https://api.paradex.io/consumer',
            help=
            "Address of the Paradex API (default: 'https://api.paradex.io/consumer')"
        )

        parser.add_argument("--paradex-api-key",
                            type=str,
                            required=True,
                            help="API key for the Paradex API")

        parser.add_argument(
            "--paradex-api-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the Paradex API (in seconds, default: 9.5)")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--buy-token-address",
                            type=str,
                            required=True,
                            help="Ethereum address of the buy token")

        parser.add_argument("--sell-token-address",
                            type=str,
                            required=True,
                            help="Ethereum address of the sell token")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--order-expiry",
            type=int,
            required=True,
            help="Expiration time of created orders (in seconds)")

        parser.add_argument("--gas-price",
                            type=int,
                            default=0,
                            help="Gas price (in Wei)")

        parser.add_argument(
            "--smart-gas-price",
            dest='smart_gas_price',
            action='store_true',
            help=
            "Use smart gas pricing strategy, based on the ethgasstation.info feed"
        )

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.web3 = kwargs['web3'] if 'web3' in kwargs else Web3(
            HTTPProvider(
                endpoint_uri=
                f"http://{self.arguments.rpc_host}:{self.arguments.rpc_port}",
                request_kwargs={"timeout": self.arguments.rpc_timeout}))
        self.web3.eth.defaultAccount = self.arguments.eth_from
        self.our_address = Address(self.arguments.eth_from)

        self.pair = self.arguments.pair.upper()
        self.token_buy = ERC20Token(web3=self.web3,
                                    address=Address(
                                        self.arguments.buy_token_address))
        self.token_sell = ERC20Token(web3=self.web3,
                                     address=Address(
                                         self.arguments.sell_token_address))
        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_max_decimals = None
        self.amount_max_decimals = None
        self.gas_price = GasPriceFactory().create_gas_price(self.arguments)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.history = History()
        self.zrx_exchange = ZrxExchangeV2(web3=self.web3,
                                          address=Address(
                                              self.arguments.exchange_address))
        self.paradex_api = ParadexApi(self.zrx_exchange,
                                      self.arguments.paradex_api_server,
                                      self.arguments.paradex_api_key,
                                      self.arguments.paradex_api_timeout)

        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency, max_workers=1)
        self.order_book_manager.get_orders_with(
            lambda: self.paradex_api.get_orders(self.pair))
        self.order_book_manager.get_balances_with(lambda: self.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.paradex_api.cancel_order(order.order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        with Lifecycle(self.web3) as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.on_startup(self.startup)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def startup(self):
        self.approve()

        # Get maximum number of decimals for prices and amounts.
        # Paradex API enforces it.
        markets = self.paradex_api.get_markets()
        market = next(filter(lambda item: item['symbol'] == self.pair,
                             markets))

        self.price_max_decimals = market['priceMaxDecimals']
        self.amount_max_decimals = market['amountMaxDecimals']

    def shutdown(self):
        self.order_book_manager.cancel_all_orders()

    def approve(self):
        self.zrx_exchange.approve([self.token_sell, self.token_buy],
                                  directly(gas_price=self.gas_price))

    def get_balances(self):
        return self.token_sell.balance_of(
            self.our_address), self.token_buy.balance_of(self.our_address)

    def our_total_sell_balance(self, balances) -> Wad:
        return balances[0]

    def our_total_buy_balance(self, balances) -> Wad:
        return balances[1]

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    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()

        # Cancel orders
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)
        if len(cancellable_orders) > 0:
            self.order_book_manager.cancel_orders(cancellable_orders)
            return

        # 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

        # In case of Paradex, balances returned by `our_total_balance` still contain amounts "locked"
        # by currently open orders, so we need to explicitly subtract these amounts.
        our_buy_balance = self.our_total_buy_balance(
            order_book.balances) - Bands.total_amount(
                self.our_buy_orders(order_book.orders))
        our_sell_balance = self.our_total_sell_balance(
            order_book.balances) - Bands.total_amount(
                self.our_sell_orders(order_book.orders))

        # Place new orders
        self.place_orders(
            bands.new_orders(
                our_buy_orders=self.our_buy_orders(order_book.orders),
                our_sell_orders=self.our_sell_orders(order_book.orders),
                our_buy_balance=our_buy_balance,
                our_sell_balance=our_sell_balance,
                target_price=target_price)[0])

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            price = round(new_order_to_be_placed.price,
                          self.price_max_decimals)
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            amount = round(amount, self.amount_max_decimals)
            order_id = self.paradex_api.place_order(
                pair=self.pair,
                is_sell=new_order_to_be_placed.is_sell,
                price=price,
                amount=amount,
                expiry=self.arguments.order_expiry)

            return Order(order_id, self.pair, new_order_to_be_placed.is_sell,
                         price, amount, amount)

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
コード例 #16
0
class GOPAXMarketMakerKeeper:

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='gopax-market-maker-keeper')

        parser.add_argument(
            "--gopax-api-server",
            type=str,
            default="https://api.gopax.co.kr",
            help=
            "Address of the GOPAX API server (default: 'https://api.gopax.co.kr')"
        )

        parser.add_argument("--gopax-api-key",
                            type=str,
                            required=True,
                            help="API key for the GOPAX API")

        parser.add_argument("--gopax-api-secret",
                            type=str,
                            required=True,
                            help="API secret for the GOPAX API")

        parser.add_argument(
            "--gopax-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the GOPAX API (in seconds, default: 9.5)")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--control-feed",
                            type=str,
                            help="Source of control feed")

        parser.add_argument(
            "--control-feed-expiry",
            type=int,
            default=86400,
            help="Maximum age of the control feed (in seconds, default: 86400)"
        )

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        parser.add_argument(
            "--telegram-log-config-file",
            type=str,
            required=False,
            help=
            "config file for send logs to telegram chat (e.g. 'telegram_conf.json')",
            default=None)

        parser.add_argument(
            "--keeper-name",
            type=str,
            required=False,
            help="market maker keeper name (e.g. 'Uniswap_V2_MDTETH')",
            default="gopax")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.history = History()
        self.gopax_api = GOPAXApi(api_server=self.arguments.gopax_api_server,
                                  api_key=self.arguments.gopax_api_key,
                                  api_secret=self.arguments.gopax_api_secret,
                                  timeout=self.arguments.gopax_timeout)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.control_feed = create_control_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency, max_workers=1)
        self.order_book_manager.get_orders_with(self.get_orders)
        self.order_book_manager.get_balances_with(
            lambda: self.gopax_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.gopax_api.cancel_order(order.order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def shutdown(self):
        self.order_book_manager.cancel_all_orders()

    def pair(self):
        return self.arguments.pair.upper()

    def token_sell(self) -> str:
        return self.arguments.pair.split('-')[0].upper()

    def token_buy(self) -> str:
        return self.arguments.pair.split('-')[1].upper()

    def get_orders(self) -> list:
        return list(
            map(lambda order: self.gopax_api.get_order(order.order_id),
                self.gopax_api.get_orders(self.pair())))

    def our_available_balance(self, our_balances: list, token: str) -> Wad:
        return Wad.from_number(
            next(filter(lambda coin: coin['asset'] == token,
                        our_balances))['avail'])

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def synchronize_orders(self):
        bands = Bands.read(self.bands_config, self.spread_feed,
                           self.control_feed, self.history)
        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()

        # Cancel orders
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)
        if len(cancellable_orders) > 0:
            self.order_book_manager.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        self.place_orders(
            bands.new_orders(
                our_buy_orders=self.our_buy_orders(order_book.orders),
                our_sell_orders=self.our_sell_orders(order_book.orders),
                our_buy_balance=self.our_available_balance(
                    order_book.balances, self.token_buy()),
                our_sell_balance=self.our_available_balance(
                    order_book.balances, self.token_sell()),
                target_price=target_price)[0])

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            pair = self.pair()
            is_sell = new_order_to_be_placed.is_sell
            price = new_order_to_be_placed.price
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount

            if self.token_buy() == 'KRW':
                price = round(
                    price / Wad.from_number(500)) * Wad.from_number(500)

            if self.token_buy() == 'DAI':
                price = round(price, 2)

            new_order_id = self.gopax_api.place_order(pair=pair,
                                                      is_sell=is_sell,
                                                      price=price,
                                                      amount=amount)

            return Order(order_id=new_order_id,
                         pair=pair,
                         is_sell=is_sell,
                         price=price,
                         amount=amount,
                         amount_remaining=amount)

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
コード例 #17
0
class BitsoMarketMakerKeeper:
    """Keeper acting as a market maker on Bitso."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='bitso-market-maker-keeper')

        parser.add_argument(
            "--bitso-api-server",
            type=str,
            default="https://api.bitso.com",
            help=
            "Address of the bitso API server (default: 'https://api.bitso.com')"
        )

        parser.add_argument("--bitso-api-key",
                            type=str,
                            required=True,
                            help="API key for the Bitso API")

        parser.add_argument(
            "--bitso-secret-key",
            type=str,
            required=True,
            help="RSA Private Key for signing requests to the BitsoX API")

        parser.add_argument(
            "--bitso-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the Bitso API (in seconds, default: 9.5)")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--control-feed",
                            type=str,
                            help="Source of control feed")

        parser.add_argument(
            "--control-feed-expiry",
            type=int,
            default=86400,
            help="Maximum age of the control feed (in seconds, default: 86400)"
        )

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.control_feed = create_control_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.history = History()
        self.bitso_api = BitsoApi(api_server=self.arguments.bitso_api_server,
                                  api_key=self.arguments.bitso_api_key,
                                  secret_key=self.arguments.bitso_secret_key,
                                  timeout=self.arguments.bitso_timeout)

        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.bitso_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(
            lambda: self.bitso_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.bitso_api.cancel_order(order.order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def shutdown(self):
        self.order_book_manager.cancel_all_orders()

    def pair(self):
        return self.arguments.pair.lower()

    def token_sell(self) -> str:
        return self.arguments.pair.split('_')[0].lower()

    def token_buy(self) -> str:
        return self.arguments.pair.split('_')[1].lower()

    def our_available_balance(self, our_balances: list, token: str) -> Wad:
        balance = list(filter(lambda x: x['currency'] == token,
                              our_balances))[0]['total']
        return Wad.from_number(balance)

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def synchronize_orders(self):
        bands = Bands.read(self.bands_config, self.spread_feed,
                           self.control_feed, self.history)
        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()

        # Cancel orders
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)
        if len(cancellable_orders) > 0:
            self.order_book_manager.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        self.place_orders(
            bands.new_orders(
                our_buy_orders=self.our_buy_orders(order_book.orders),
                our_sell_orders=self.our_sell_orders(order_book.orders),
                our_buy_balance=self.our_available_balance(
                    order_book.balances, self.token_buy()),
                our_sell_balance=self.our_available_balance(
                    order_book.balances, self.token_sell()),
                target_price=target_price)[0])

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount

            # Convert wad to float as Bitso limits amount decimal places to 8, and price to 2
            float_price = round(Wad.__float__(new_order_to_be_placed.price), 2)
            float_amount = round(Wad.__float__(amount), 8)

            side = "sell" if new_order_to_be_placed.is_sell == True else "buy"
            order_id = self.bitso_api.place_order(book=self.pair(),
                                                  side=side,
                                                  price=float_price,
                                                  amount=float_amount)

            timestamp = datetime.now(tz=timezone.utc).isoformat()

            return Order(str(order_id), timestamp, self.pair(),
                         new_order_to_be_placed.is_sell,
                         new_order_to_be_placed.price, amount)

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
コード例 #18
0
class GOPAXMarketMakerKeeper:

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='gopax-market-maker-keeper')

        parser.add_argument("--gopax-api-server", type=str, default="https://api.gopax.co.kr",
                            help="Address of the GOPAX API server (default: 'https://api.gopax.co.kr')")

        parser.add_argument("--gopax-api-key", type=str, required=True,
                            help="API key for the GOPAX API")

        parser.add_argument("--gopax-api-secret", type=str, required=True,
                            help="API secret for the GOPAX API")

        parser.add_argument("--gopax-timeout", type=float, default=9.5,
                            help="Timeout for accessing the GOPAX API (in seconds, default: 9.5)")

        parser.add_argument("--pair", type=str, required=True,
                            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config", type=str, required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed", type=str, required=True,
                            help="Source of price feed")

        parser.add_argument("--price-feed-expiry", type=int, default=120,
                            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed", type=str,
                            help="Source of spread feed")

        parser.add_argument("--spread-feed-expiry", type=int, default=3600,
                            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--order-history", type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument("--order-history-every", type=int, default=30,
                            help="Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument("--debug", dest='debug', action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.history = History()
        self.gopax_api = GOPAXApi(api_server=self.arguments.gopax_api_server,
                                  api_key=self.arguments.gopax_api_key,
                                  api_secret=self.arguments.gopax_api_secret,
                                  timeout=self.arguments.gopax_timeout)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(self.arguments)

        self.order_book_manager = OrderBookManager(refresh_frequency=10)
        self.order_book_manager.get_orders_with(self.get_orders)
        self.order_book_manager.get_balances_with(lambda: self.gopax_api.get_balances())
        self.order_book_manager.enable_history_reporting(self.order_history_reporter, self.our_buy_orders, self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def shutdown(self):
        #TODO I don't think this approach makes sure all orders will always get cancelled!!
        while True:
            try:
                our_orders = self.gopax_api.get_orders(self.pair())
            except:
                continue

            if len(our_orders) == 0:
                break

            self.cancel_orders(our_orders)
            self.order_book_manager.wait_for_order_cancellation()

    def pair(self):
        return self.arguments.pair.upper()

    def token_sell(self) -> str:
        return self.arguments.pair.split('-')[0].upper()

    def token_buy(self) -> str:
        return self.arguments.pair.split('-')[1].upper()

    def get_orders(self) -> list:
        return list(map(lambda order: self.gopax_api.get_order(order.order_id), self.gopax_api.get_orders(self.pair())))

    def our_available_balance(self, our_balances: list, token: str) -> Wad:
        return Wad.from_number(next(filter(lambda coin: coin['asset'] == token, our_balances))['avail'])

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def synchronize_orders(self):
        bands = Bands(self.bands_config, self.spread_feed, self.history)
        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()

        # Cancel orders
        cancellable_orders = bands.cancellable_orders(our_buy_orders=self.our_buy_orders(order_book.orders),
                                                      our_sell_orders=self.our_sell_orders(order_book.orders),
                                                      target_price=target_price)
        if len(cancellable_orders) > 0:
            self.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        self.place_orders(bands.new_orders(our_buy_orders=self.our_buy_orders(order_book.orders),
                                           our_sell_orders=self.our_sell_orders(order_book.orders),
                                           our_buy_balance=self.our_available_balance(order_book.balances, self.token_buy()),
                                           our_sell_balance=self.our_available_balance(order_book.balances, self.token_sell()),
                                           target_price=target_price)[0])

    def cancel_orders(self, orders):
        for order in orders:
            self.order_book_manager.cancel_order(order.order_id, lambda order=order: self.gopax_api.cancel_order(order.order_id))

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            pair = self.pair()
            is_sell = new_order_to_be_placed.is_sell
            price = new_order_to_be_placed.price
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount

            new_order_id = self.gopax_api.place_order(pair=pair,
                                                      is_sell=is_sell,
                                                      price=price,
                                                      amount=amount)

            return Order(order_id=new_order_id,
                         pair=pair,
                         is_sell=is_sell,
                         price=price,
                         amount=amount,
                         amount_remaining=amount)

        for new_order in new_orders:
            self.order_book_manager.place_order(lambda new_order=new_order: place_order_function(new_order))
コード例 #19
0
class OasisMarketMakerKeeper:
    """Keeper acting as a market maker on OasisDEX, on the W-ETH/SAI pair."""

    logger = logging.getLogger()

    def __init__(self, args: list, **kwargs):
        parser = argparse.ArgumentParser(prog='oasis-market-maker-keeper')

        parser.add_argument("--rpc-host",
                            type=str,
                            default="localhost",
                            help="JSON-RPC host (default: `localhost')")

        parser.add_argument("--rpc-port",
                            type=int,
                            default=8545,
                            help="JSON-RPC port (default: `8545')")

        parser.add_argument("--rpc-timeout",
                            type=int,
                            default=10,
                            help="JSON-RPC timeout (in seconds, default: 10)")

        parser.add_argument(
            "--eth-from",
            type=str,
            required=True,
            help="Ethereum account from which to send transactions")

        parser.add_argument("--tub-address",
                            type=str,
                            required=True,
                            help="Ethereum address of the Tub contract")

        parser.add_argument("--oasis-address",
                            type=str,
                            required=True,
                            help="Ethereum address of the OasisDEX contract")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument(
            "--round-places",
            type=int,
            default=2,
            help="Number of decimal places to round order prices to (default=2)"
        )

        parser.add_argument(
            "--min-eth-balance",
            type=float,
            default=0,
            help="Minimum ETH balance below which keeper will cease operation")

        parser.add_argument("--gas-price",
                            type=int,
                            default=0,
                            help="Gas price (in Wei)")

        parser.add_argument(
            "--smart-gas-price",
            dest='smart_gas_price',
            action='store_true',
            help=
            "Use smart gas pricing strategy, based on the ethgasstation.info feed"
        )

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.web3 = kwargs['web3'] if 'web3' in kwargs else Web3(
            HTTPProvider(
                endpoint_uri=
                f"http://{self.arguments.rpc_host}:{self.arguments.rpc_port}",
                request_kwargs={"timeout": self.arguments.rpc_timeout}))
        self.web3.eth.defaultAccount = self.arguments.eth_from
        self.our_address = Address(self.arguments.eth_from)
        self.otc = MatchingMarket(web3=self.web3,
                                  address=Address(
                                      self.arguments.oasis_address))
        self.tub = Tub(web3=self.web3,
                       address=Address(self.arguments.tub_address))
        self.sai = ERC20Token(web3=self.web3, address=self.tub.sai())
        self.gem = ERC20Token(web3=self.web3, address=self.tub.gem())

        self.min_eth_balance = Wad.from_number(self.arguments.min_eth_balance)
        self.bands_config = ReloadableConfig(self.arguments.config)
        self.gas_price = GasPriceFactory().create_gas_price(self.arguments)
        self.price_feed = PriceFeedFactory().create_price_feed(
            self.arguments.price_feed, self.arguments.price_feed_expiry,
            self.tub)

        self.order_book_manager = OrderBookManager(refresh_frequency=3)
        self.order_book_manager.get_orders_with(lambda: self.our_orders())
        self.order_book_manager.start()

    def main(self):
        with Lifecycle(self.web3) as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.on_startup(self.startup)
            lifecycle.on_block(self.on_block)
            lifecycle.every(3, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def startup(self):
        self.approve()

    @retry(delay=5, logger=logger)
    def shutdown(self):
        self.cancel_all_orders()

    def on_block(self):
        # This method is present only so the lifecycle binds the new block listener, which makes
        # it then terminate the keeper if no new blocks have been arriving for 300 seconds.
        pass

    def approve(self):
        """Approve OasisDEX to access our balances, so we can place orders."""
        self.otc.approve(
            [self.token_sell(), self.token_buy()],
            directly(gas_price=self.gas_price))

    def price(self) -> Wad:
        return self.price_feed.get_price()

    def token_sell(self) -> ERC20Token:
        return self.gem

    def token_buy(self) -> ERC20Token:
        return self.sai

    def our_available_balance(self, token: ERC20Token) -> Wad:
        return token.balance_of(self.our_address)

    def our_orders(self):
        return list(
            filter(
                lambda order: order.maker == self.our_address,
                self.otc.get_orders(self.token_sell().address,
                                    self.token_buy().address) +
                self.otc.get_orders(self.token_buy().address,
                                    self.token_sell().address)))

    def our_sell_orders(self, our_orders: list):
        return list(
            filter(
                lambda order: order.buy_token == self.token_buy().address and
                order.pay_token == self.token_sell().address, our_orders))

    def our_buy_orders(self, our_orders: list):
        return list(
            filter(
                lambda order: order.buy_token == self.token_sell().address and
                order.pay_token == self.token_buy().address, our_orders))

    def synchronize_orders(self):
        # If market is closed, cancel all orders but do not terminate the keeper.
        if self.otc.is_closed():
            self.logger.warning("Market is closed. Cancelling all orders.")
            self.cancel_all_orders()
            return

        # If keeper balance is below `--min-eth-balance`, cancel all orders but do not terminate
        # the keeper, keep processing blocks as the moment the keeper gets a top-up it should
        # resume activity straight away, without the need to restart it.
        if eth_balance(self.web3, self.our_address) < self.min_eth_balance:
            self.logger.warning(
                "Keeper ETH balance below minimum. Cancelling all orders.")
            self.cancel_all_orders()
            return

        bands = Bands(self.bands_config)
        order_book = self.order_book_manager.get_order_book()
        target_price = self.price()

        # If the is no target price feed, cancel all orders but do not terminate the keeper.
        # The moment the price feed comes back, the keeper will resume placing orders.
        if target_price is None:
            self.logger.warning(
                "No price feed available. Cancelling all orders.")
            self.cancel_all_orders()
            return

        # If there are any orders to be cancelled, cancel them. It is deliberate that we wait with topping-up
        # bands until the next block. This way we would create new orders based on the most recent price and
        # order book state. We could theoretically retrieve both (`target_price` and `our_orders`) again here,
        # but it just seems cleaner to do it in one place instead of in two.
        cancellable_orders = bands.cancellable_orders(
            our_buy_orders=self.our_buy_orders(order_book.orders),
            our_sell_orders=self.our_sell_orders(order_book.orders),
            target_price=target_price)

        if len(cancellable_orders) > 0:
            self.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        self.create_orders(
            bands.new_orders(
                our_buy_orders=self.our_buy_orders(order_book.orders),
                our_sell_orders=self.our_sell_orders(order_book.orders),
                our_buy_balance=self.our_available_balance(self.token_buy()),
                our_sell_balance=self.our_available_balance(self.token_sell()),
                target_price=target_price)[0])

    def cancel_all_orders(self):
        # Wait for the order book to stabilize
        while True:
            order_book = self.order_book_manager.get_order_book()
            if not order_book.orders_being_cancelled and not order_book.orders_being_placed:
                break

        # Cancel all open orders
        self.cancel_orders(self.order_book_manager.get_order_book().orders)
        self.order_book_manager.wait_for_order_cancellation()

    def cancel_orders(self, orders):
        for order in orders:
            self.order_book_manager.cancel_order(
                order.order_id, lambda: self.otc.kill(order.order_id).transact(
                    gas_price=self.gas_price).successful)

    def create_orders(self, new_orders):
        def place_order_function(new_order: NewOrder):
            assert (isinstance(new_order, NewOrder))

            if new_order.is_sell:
                pay_token = self.token_sell().address
                buy_token = self.token_buy().address
            else:
                pay_token = self.token_buy().address
                buy_token = self.token_sell().address

            transact = self.otc.make(pay_token=pay_token,
                                     pay_amount=new_order.pay_amount,
                                     buy_token=buy_token,
                                     buy_amount=new_order.buy_amount).transact(
                                         gas_price=self.gas_price)

            if transact is not None and transact.successful and transact.result is not None:
                return Order(market=self.otc,
                             order_id=transact.result,
                             maker=self.our_address,
                             pay_token=pay_token,
                             pay_amount=new_order.pay_amount,
                             buy_token=buy_token,
                             buy_amount=new_order.buy_amount,
                             timestamp=0)
            else:
                return None

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda: place_order_function(new_order))
コード例 #20
0
class BiboxMarketSurfer:
    """Keeper acting as a market maker on Bibox."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='bibox-market-maker-keeper')

        parser.add_argument(
            "--bibox-api-server",
            type=str,
            default="https://api.bibox.com",
            help=
            "Address of the Bibox API server (default: 'https://api.bibox.com')"
        )

        parser.add_argument("--bibox-api-key",
                            type=str,
                            required=True,
                            help="API key for the Bibox API")

        parser.add_argument("--bibox-secret",
                            type=str,
                            required=True,
                            help="Secret for the Bibox API")

        parser.add_argument(
            "--bibox-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the Bibox API (in seconds, default: 9.5)")

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        parser.add_argument("--base_price",
                            type=str,
                            default=0.00205375,
                            help="base price while initial base price ")

        parser.add_argument("--total_amount",
                            type=str,
                            default=5,
                            help="the total assets for investing ")

        parser.add_argument(
            "--transaction_percent",
            type=str,
            default=0.02,
            help=
            "percent of total amount of each transaction or each order, fix percent 2%"
        )

        parser.add_argument(
            "--arbitrage_percent",
            type=str,
            default=0.005,
            help=
            "the percent of current pirce as margin between two adjacent price orders, 0.5%"
        )

        parser.add_argument(
            "--order_num",
            type=str,
            default=3,
            help="the number of orders in each sell and buy bands ")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.history = History()
        self.bibox_api = BiboxApi(api_server=self.arguments.bibox_api_server,
                                  api_key=self.arguments.bibox_api_key,
                                  secret=self.arguments.bibox_secret,
                                  timeout=self.arguments.bibox_timeout)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.local_orders = []
        self.base_price = 0.00243602
        self.total_amount = 2000
        self.each_order_percent = 0.1  # percent of total amount of each transaction or each order
        self.arbitrage_percent = 0.01
        self.band_order_limit = 3  # the order count of sell or buy bands must less than limit
        self.each_order_amount = self.total_amount * self.each_order_percent

        # To implement abstract function with different exchanges API
        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.bibox_api.get_orders(pair=self.pair(), retry=True))
        self.order_book_manager.get_balances_with(
            lambda: self.bibox_api.coin_list(retry=True))
        self.order_book_manager.cancel_orders_with(
            lambda order: self.bibox_api.cancel_order(order.order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        # Place new orders while initialize the whole surfer system
        self.initialize_orders(self.base_price, self.each_order_amount,
                               self.arbitrage_percent, self.band_order_limit)
        time.sleep(
            5)  # wait for order book manager to get placed orders 足够时间保证系统稳定返回
        self.local_orders = self.order_book_manager.get_order_book().orders

        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.every(10, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def initialize_orders(self, base_price, each_order_amount,
                          arbitrage_percent, band_order_limit):
        orders = []
        i = 1
        while band_order_limit + 1 > i:
            # place sell order
            price = base_price * (1 + arbitrage_percent * i)
            # pay_amount = Wad.min(band.avg_amount - total_amount, our_sell_balance, limit_amount)
            pay_amount = each_order_amount * self.amount_disguise(
            )  #bix amount
            # add unique amount number to identify different order for result performance statics
            pay_amount = pay_amount + self.amount_identify()
            buy_amount = pay_amount * price  #eth money

            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)))

            # place buy order, pay attention to rotate bix - eth
            price = base_price * (1 - arbitrage_percent * i)
            # pay_amount = Wad.min(band.avg_amount - total_amount, our_sell_balance, limit_amount)
            tmp = each_order_amount * self.amount_disguise(
            ) + self.amount_identify()
            pay_amount = tmp * price  #eth money 25
            buy_amount = tmp  #bix amount 0.05
            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)))
            i = i + 1

        self.place_orders(orders)
        # 偶尔有 bug,提交的完成慢,导致 local orders 比 下一次 获取回来少, initial_delay 加长时间到15秒,时间太长也麻烦,
        # 会导致一开始提交就成交的那部分订单不会存到 local orders
        # 需要换地方,order_book_manager更新不及时的情况下,会导致返回的订单数据不全,或者订单里的参数默认为0的情况
        # self.local_orders = self.order_book_manager.get_order_book().orders

    @staticmethod
    def amount_disguise():
        rand = [
            0.8, 0.84, 0.88, 0.92, 0.95, 0.99, 1.03, 1.06, 1.09, 1.12, 1.16,
            1.2
        ]
        return rand[random.randint(0, 11)]

    @staticmethod
    def amount_identify():
        return round(random.random() / 10000.0, 10)

    def shutdown(self):
        self.order_book_manager.cancel_all_orders(final_wait_time=30)

    def pair(self):
        return self.arguments.pair.upper()

    def token_sell(self) -> str:
        return self.arguments.pair.split('_')[0].upper()

    def token_buy(self) -> str:
        return self.arguments.pair.split('_')[1].upper()

    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 our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def count_sell_orders(self, our_orders: list) -> int:
        return len(list(filter(lambda order: order.is_sell, our_orders)))

    def count_buy_orders(self, our_orders: list) -> int:
        return len(list(filter(lambda order: not order.is_sell, our_orders)))

    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 = []
            step = 1
            count_sell_order = self.count_sell_orders(order_book.orders)
            count_buy_order = self.count_buy_orders(order_book.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))
                    # while band_sell_order_gap > 0: # 外部已经有循环了,不需要这个循环了,否则在多订单被吃时,会加倍补充
                    # 这里只需要判断,控制数量就够了
                    if 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
                        count_sell_order = count_sell_order + 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))

                    # while band_buy_order_gap > 0:
                    if 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)))

                        # band_buy_order_gap = band_buy_order_gap - 1
                        count_buy_order = count_buy_order + 1
                    step = step + 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

            # Cancel orders
            # cancellable_orders = bands.cancellable_orders(our_buy_orders=self.our_buy_orders(order_book.orders),
            #                                               our_sell_orders=self.our_sell_orders(order_book.orders),
            #                                               target_price=target_price)

            # if len(cancellable_orders) > 0:
            # self.order_book_manager.cancel_orders(cancellable_orders)
            # print("there is " + str(len(cancellable_orders)) + " orders should be cancelled")

            # Place new orders
        # self.place_orders(bands.new_orders(our_buy_orders=self.our_buy_orders(order_book.orders),
        #                                    our_sell_orders=self.our_sell_orders(order_book.orders),
        #                                    our_buy_balance=self.our_available_balance(order_book.balances, self.token_buy()),
        #                                    our_sell_balance=self.our_available_balance(order_book.balances, self.token_sell()),
        #                                    target_price=target_price)[0])

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            amount_symbol = self.token_sell()
            money = new_order_to_be_placed.buy_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.pay_amount
            money_symbol = self.token_buy()

            new_order_id = self.bibox_api.place_order(
                is_sell=new_order_to_be_placed.is_sell,
                amount=amount,
                amount_symbol=amount_symbol,
                money=money,
                money_symbol=money_symbol)

            return Order(new_order_id, 0, new_order_to_be_placed.is_sell,
                         Wad(money / amount), amount, amount_symbol, money,
                         money_symbol)

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))

    def get_price(self, pair):
        self.bibox_api.get_all_trades()
コード例 #21
0
class KucoinMarketMakerKeeper:
    """Keeper acting as a market maker on kucoin."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='kucoin-market-maker-keeper')

        parser.add_argument("--kucoin-api-server", type=str, default="https://api.kucoin.com",
                            help="Address of the kucoin API server (default: 'https://api.kucoin.com')")

        parser.add_argument("--kucoin-api-key", type=str, required=True,
                            help="API key for the kucoin API")

        parser.add_argument("--kucoin-secret-key", type=str, required=True,
                            help="Secret key for the kucoin API")

        parser.add_argument("--kucoin-timeout", type=float, default=9.5,
                            help="Timeout for accessing the kucoin API (in seconds, default: 9.5)")

        parser.add_argument("--pair", type=str, required=True,
                            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config", type=str, required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed", type=str, required=True,
                            help="Source of price feed")

        parser.add_argument("--price-feed-expiry", type=int, default=120,
                            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed", type=str,
                            help="Source of spread feed")

        parser.add_argument("--spread-feed-expiry", type=int, default=3600,
                            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--control-feed", type=str,
                            help="Source of control feed")

        parser.add_argument("--control-feed-expiry", type=int, default=86400,
                            help="Maximum age of the control feed (in seconds, default: 86400)")

        parser.add_argument("--order-history", type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument("--order-history-every", type=int, default=30,
                            help="Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument("--refresh-frequency", type=int, default=3,
                            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug", dest='debug', action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)
        self.spread_feed = create_spread_feed(self.arguments)
        self.control_feed = create_control_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(self.arguments)

        self.history = History()

        self.kucoin_api = KucoinApi(api_server=self.arguments.kucoin_api_server,
                                    api_key=self.arguments.kucoin_api_key,
                                    secret_key=self.arguments.kucoin_secret_key,
                                    timeout=self.arguments.kucoin_timeout)

        self.order_book_manager = OrderBookManager(refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(lambda: self.kucoin_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(lambda: self.kucoin_api.get_balances())
        self.order_book_manager.cancel_orders_with(lambda order: self.kucoin_api.cancel_order(order.order_id,
                                                                                              order.is_sell,
                                                                                              self.pair()))
        self.order_book_manager.enable_history_reporting(self.order_history_reporter, self.our_buy_orders,
                                                         self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.on_startup(self.startup)
            lifecycle.every(1, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def startup(self):
        # Get maximum number of decimals for prices and amounts.
        self.price_precision = self.kucoin_api.get_coin_info(self.token_buy())['tradePrecision']
        self.amount_precision = self.kucoin_api.get_coin_info(self.token_sell())['tradePrecision']

    def shutdown(self):
        self.order_book_manager.cancel_all_orders()

    def pair(self):
        return self.arguments.pair.upper()

    def token_sell(self) -> str:
        return self.arguments.pair.split('-')[0].upper()

    def token_buy(self) -> str:
        return self.arguments.pair.split('-')[1].upper()

    def our_available_balance(self, our_balances: dict, token: str) -> Wad:
        token_balances = list(filter(lambda coin: coin['coinType'].upper() == token, our_balances))
        if token_balances:
            return Wad.from_number(self.round_down(token_balances[0]['balance'], self.amount_precision))
        else:
            return Wad(0)

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    def synchronize_orders(self):
        bands = Bands.read(self.bands_config, self.spread_feed, self.control_feed, self.history)

        order_book = self.order_book_manager.get_order_book()
        target_price = self.price_feed.get_price()
        # Cancel orders
        cancellable_orders = bands.cancellable_orders(our_buy_orders=self.our_buy_orders(order_book.orders),
                                                      our_sell_orders=self.our_sell_orders(order_book.orders),
                                                      target_price=target_price)
        if len(cancellable_orders) > 0:
            self.order_book_manager.cancel_orders(cancellable_orders)
            return

        # 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

        # Place new orders
        new_orders = bands.new_orders(our_buy_orders=self.our_buy_orders(order_book.orders),
                                      our_sell_orders=self.our_sell_orders(order_book.orders),
                                      our_buy_balance=self.our_available_balance(order_book.balances, self.token_buy()),
                                      our_sell_balance=self.our_available_balance(order_book.balances, self.token_sell()),
                                      target_price=target_price)[0]

        self.place_orders(new_orders)

    def place_orders(self, new_orders: List[NewOrder]):
        def place_order_function(new_order_to_be_placed):
            price = round(new_order_to_be_placed.price, self.price_precision)
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            amount = round(amount, self.amount_precision)

            order_id = self.kucoin_api.place_order(self.pair(), new_order_to_be_placed.is_sell, price, amount)

            return Order(order_id=order_id,
                         pair=self.pair(),
                         is_sell=new_order_to_be_placed.is_sell,
                         price=price,
                         amount=amount)

        for new_order in new_orders:
            self.order_book_manager.place_order(lambda new_order=new_order: place_order_function(new_order))

    @staticmethod
    def round_down(num, precision):
        multiplier = pow(10, precision)
        return floor(num * multiplier) / multiplier
コード例 #22
0
class OkexMarketTrading:
    """Keeper acting as a market maker on OKEX."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='okex-market-trading')

        parser.add_argument(
            "--okex-api-server",
            type=str,
            default="https://www.okex.com",
            help=
            "Address of the OKEX API server (default: 'https://www.okex.com')")

        parser.add_argument("--okex-api-key",
                            type=str,
                            required=True,
                            help="API key for the OKEX API")

        parser.add_argument("--okex-secret-key",
                            type=str,
                            required=True,
                            help="Secret key for the OKEX API")

        parser.add_argument("--okex-passphrase",
                            type=str,
                            required=True,
                            help="Passphrase for the OKEX API")

        parser.add_argument(
            "--okex-timeout",
            type=float,
            default=9.5,
            help="Timeout for accessing the OKEX API (in seconds, default: 9.5)"
        )

        parser.add_argument(
            "--pair",
            type=str,
            required=True,
            help="Token pair (sell/buy) on which the keeper will operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            required=True,
                            help="Source of price feed")

        parser.add_argument(
            "--price-feed-expiry",
            type=int,
            default=120,
            help="Maximum age of the price feed (in seconds, default: 120)")

        parser.add_argument("--spread-feed",
                            type=str,
                            help="Source of spread feed")

        parser.add_argument(
            "--spread-feed-expiry",
            type=int,
            default=3600,
            help="Maximum age of the spread feed (in seconds, default: 3600)")

        parser.add_argument("--control-feed",
                            type=str,
                            help="Source of control feed")

        parser.add_argument(
            "--control-feed-expiry",
            type=int,
            default=86400,
            help="Maximum age of the control feed (in seconds, default: 86400)"
        )

        parser.add_argument("--order-history",
                            type=str,
                            help="Endpoint to report active orders to")

        parser.add_argument(
            "--order-history-every",
            type=int,
            default=30,
            help=
            "Frequency of reporting active orders (in seconds, default: 30)")

        parser.add_argument(
            "--refresh-frequency",
            type=int,
            default=3,
            help="Order book refresh frequency (in seconds, default: 3)")

        parser.add_argument("--debug",
                            dest='debug',
                            action='store_true',
                            help="Enable debug output")

        self.arguments = parser.parse_args(args)
        setup_logging(self.arguments)

        self.bands_config = ReloadableConfig(self.arguments.config)
        self.price_feed = PriceFeedFactory().create_price_feed(self.arguments)

        # TODO://这两个参数干嘛的
        self.spread_feed = create_spread_feed(self.arguments)
        self.control_feed = create_control_feed(self.arguments)
        self.order_history_reporter = create_order_history_reporter(
            self.arguments)

        self.history = History()
        self.okex_api = OKEXApi(api_server=self.arguments.okex_api_server,
                                api_key=self.arguments.okex_api_key,
                                secret_key=self.arguments.okex_secret_key,
                                passphrase=self.arguments.okex_passphrase,
                                timeout=self.arguments.okex_timeout)

        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.okex_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(
            lambda: self.okex_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.okex_api.cancel_order(self.pair(), order.
                                                     order_id))
        self.order_book_manager.enable_history_reporting(
            self.order_history_reporter, self.our_buy_orders,
            self.our_sell_orders)
        self.order_book_manager.start()

    def main(self):
        balances = self.okex_api.get_balances()
        self.logger.info(f"balances:{balances}")
        balances = self.okex_api.get_balances()
        self.order_book_manager.cancel_all_orders()
        self.logger.info(f"refresh balances:{balances}")
        with Lifecycle() as lifecycle:
            lifecycle.initial_delay(10)
            lifecycle.every(5, self.synchronize_orders)
            lifecycle.on_shutdown(self.shutdown)

    def shutdown(self):
        self.order_book_manager.cancel_all_orders()

    def pair(self):
        return self.arguments.pair.upper()

    def token_sell(self) -> str:
        return self.arguments.pair.split('-')[0].upper()

    def token_buy(self) -> str:
        return self.arguments.pair.split('-')[1].upper()

    def our_available_balance(self, our_balances: list, token: str) -> Wad:
        for item in our_balances:
            if token == item['currency']:
                return Wad.from_number(item['available'])
        return Wad(0)

    def our_sell_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: order.is_sell, our_orders))

    def our_buy_orders(self, our_orders: list) -> list:
        return list(filter(lambda order: not order.is_sell, our_orders))

    # 在买一和卖一间隔中下单自动成交
    def synchronize_orders(self):

        # 交易触发规则:随机触发。产生一个随机数,若命中概率则交易
        current_time = time.strftime("%H")
        freq_dict = {
            '00': np.random.randint(50, 200),
            '01': np.random.randint(50, 100),
            '02': np.random.randint(10, 100),
            '03': np.random.randint(10, 50),
            '04': np.random.randint(10, 50),
            '05': np.random.randint(10, 50),
            '06': np.random.randint(50, 100),
            '07': np.random.randint(100, 200),
            '08': np.random.randint(100, 200),
            '09': np.random.randint(100, 300),
            '10': np.random.randint(100, 300),
            '11': np.random.randint(100, 300),
            '12': np.random.randint(100, 300),
            '13': np.random.randint(100, 300),
            '14': np.random.randint(100, 300),
            '15': np.random.randint(100, 300),
            '16': np.random.randint(100, 300),
            '17': np.random.randint(100, 300),
            '18': np.random.randint(100, 300),
            '19': np.random.randint(100, 300),
            '20': np.random.randint(100, 300),
            '21': np.random.randint(100, 200),
            '22': np.random.randint(100, 200),
            '23': np.random.randint(100, 200)
        }
        freq = freq_dict[current_time]
        hit_number = np.random.random()
        hit_range = freq / (12 * 60.0)
        do_trade = True if hit_number < hit_range else False

        if not do_trade:
            total_freq = reduce(lambda x, y: x + y, list(freq_dict.values()))
            logging.debug(
                f"NOT HIT. total freq is {total_freq} per day. hit_number={hit_number}, hit_range={hit_range}"
            )
            return
        logging.info(
            f"[DO TRADING]hit_number={hit_number}, hit_range={hit_range}")

        order_book = self.order_book_manager.get_order_book()
        current_price = self.price_feed.get_price()
        if current_price.buy_price is None or current_price.sell_price is None:
            self.logger.warning(
                "Current_price:buy_price or sell_price is None")
            return
        logging.info(f"Current_price: {current_price}")

        # 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

        # 只会使用到buy_bands的一个配置,同时应用于买和卖,买卖统一数量
        bands = Bands.read(self.bands_config, self.spread_feed,
                           self.control_feed, self.history)
        band = bands.buy_bands[0]
        price_gap = current_price.sell_price - current_price.buy_price

        # 确定交易的数量和价格
        trade_price = current_price.buy_price + Wad.from_number(
            np.random.uniform(0, float(price_gap)))
        trade_amount = Wad.from_number(
            np.random.uniform(float(band.min_amount), float(band.max_amount)))

        # Place new orders
        new_orders = self.create_new_orders(
            trade_amount=trade_amount,
            trade_price=trade_price,
            our_buy_balance=self.our_available_balance(order_book.balances,
                                                       self.token_buy()),
            our_sell_balance=self.our_available_balance(
                order_book.balances, self.token_sell()),
            band=band)
        self.place_orders(new_orders)

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount
            order_id = self.okex_api.place_order(
                pair=self.pair(),
                is_sell=new_order_to_be_placed.is_sell,
                price=new_order_to_be_placed.price,
                amount=amount)

            return Order(order_id, 0.0, self.pair(),
                         new_order_to_be_placed.is_sell,
                         new_order_to_be_placed.price, amount, Wad(0))

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))

    def create_new_orders(self, trade_amount: Wad, trade_price: Wad,
                          our_buy_balance: Wad, our_sell_balance: Wad,
                          band: Band) -> list:
        assert (isinstance(our_buy_balance, Wad))
        assert (isinstance(our_sell_balance, Wad))
        assert (isinstance(trade_price, Wad))

        # 1、构建需要创建的订单
        new_buy_orders = []
        buy_amount = trade_amount
        # pay_amount要付出的token数量, 买单时如usdt
        pay_amount = Wad.min(buy_amount * trade_price, our_buy_balance)
        if (trade_price > Wad(0)) and (pay_amount > Wad(0)) and (buy_amount >
                                                                 Wad(0)):
            new_buy_orders.append(
                NewOrder(is_sell=False,
                         price=trade_price,
                         amount=buy_amount,
                         pay_amount=pay_amount,
                         buy_amount=buy_amount,
                         band=band,
                         confirm_function=lambda: self.buy_limits.use_limit(
                             time.time(), pay_amount)))
            logging.info(
                "Trading new_buy_order, price:%s, buy_amount:%s, pay_amount:%s"
                % (trade_price, buy_amount, pay_amount))

        # 2、构建等量的卖出订单
        new_sell_orders = []
        # pay_amount要付出的token数量,卖单时如tokenx
        pay_amount = Wad.min(trade_amount, our_sell_balance)
        buy_amount = pay_amount * trade_price
        if (trade_price > Wad(0)) and (pay_amount > Wad(0)) and (buy_amount >
                                                                 Wad(0)):
            self.logger.info(
                f"Trading creating new sell order amount {pay_amount} with price {trade_price}"
            )
            new_buy_orders.append(
                NewOrder(is_sell=True,
                         price=trade_price,
                         amount=pay_amount,
                         pay_amount=pay_amount,
                         buy_amount=buy_amount,
                         band=band,
                         confirm_function=lambda: self.buy_limits.use_limit(
                             time.time(), pay_amount)))
            logging.info(
                "Trading new_sell_order, price:%s, buy_amount:%s, pay_amount:%s"
                % (trade_price, buy_amount, pay_amount))

        # 先放卖单,再放买单
        return new_sell_orders + new_buy_orders