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))
Пример #2
0
class OkexMarketMakerKeeper:
    """Keeper acting as a market maker on OKEX."""

    logger = logging.getLogger()

    def __init__(self, args: list, **kwargs):
        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(
            "--pair",
            type=str,
            required=True,
            help="Token pair on which the keeper should operate")

        parser.add_argument("--config",
                            type=str,
                            required=True,
                            help="Buy/sell bands configuration file")

        parser.add_argument("--price-feed",
                            type=str,
                            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)

        logging.basicConfig(
            format='%(asctime)-15s %(levelname)-8s %(message)s',
            level=(logging.DEBUG if self.arguments.debug else logging.INFO))
        logging.getLogger('urllib3.connectionpool').setLevel(logging.INFO)
        logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(
            logging.INFO)

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

        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,
                                timeout=9.5)

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

    def startup(self):
        self.our_orders()
        self.logger.info(f"OKEX API key seems to be valid")
        self.logger.info(
            f"Keeper configured to work on the '{self.pair()}' pair")

    def shutdown(self):
        self.cancel_orders(self.our_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_balances(self) -> dict:
        return self.okex_api.get_balances()

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

    def our_orders(self) -> list:
        return self.okex_api.get_orders(self.pair())

    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)
        our_balances = self.our_balances()
        our_orders = self.our_orders()
        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(our_orders)
            return

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

        # Place new orders
        self.create_orders(
            bands.new_orders(
                our_buy_orders=self.our_buy_orders(our_orders),
                our_sell_orders=self.our_sell_orders(our_orders),
                our_buy_balance=self.our_balance(our_balances,
                                                 self.token_buy()),
                our_sell_balance=self.our_balance(our_balances,
                                                  self.token_sell()),
                target_price=target_price))

    def cancel_orders(self, orders):
        for order in orders:
            self.okex_api.cancel_order(self.pair(), order.order_id)

    def create_orders(self, orders):
        for order in orders:
            amount = order.pay_amount if order.is_sell else order.buy_amount
            self.okex_api.place_order(pair=self.pair(),
                                      is_sell=order.is_sell,
                                      price=order.price,
                                      amount=amount)
Пример #3
0
        fill_status = f"with {order.filled_amount} filled" if order.filled_amount > Wad(
            0) else "unfilled"
        print(f"[{index}] {order.order_id} {side} {str(order.amount)[:9]} "
              f"at {str(order.price)[:12]} "
              f"on {datetime.datetime.utcfromtimestamp(order.timestamp)} " +
              fill_status)
        #f"page {order.page}")


def print_trades(trades):
    for trade in trades:
        side = "sell" if trade.is_sell else "buy "
        print(f"{side} {str(trade.amount)[:9]} {trade.amount_symbol} "
              f"at {str(trade.price)[:12]} "
              f"{pair.split('_')[1]} "
              f"on {datetime.datetime.utcfromtimestamp(trade.timestamp)} "
              f"({trade.trade_id})")


# Gets open orders
orders = okex.get_orders(pair)
print_orders(orders)
# Gets all orders
# orders = okex.get_orders_history(pair, 9)
# print_orders(orders)

# trades = okex.get_trades(pair)
# print_trades(trades[:3])
# trades = okex.get_all_trades(pair)
# print_trades(trades[:3])
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-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("--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.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,
                                timeout=self.arguments.okex_timeout)

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

    def startup(self):
        self.our_orders()
        self.logger.info(f"OKEX API key seems to be valid")
        self.logger.info(
            f"Keeper configured to work on the '{self.pair()}' pair")

    @retry(delay=5, logger=logger)
    def shutdown(self):
        self.cancel_orders(self.our_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_balances(self) -> dict:
        return self.okex_api.get_balances()

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

    def our_orders(self) -> list:
        return self.okex_api.get_orders(self.pair())

    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)
        our_balances = self.our_balances()
        our_orders = self.our_orders()
        target_price = self.price_feed.get_price()

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

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

    def cancel_orders(self, orders):
        for order in orders:
            self.okex_api.cancel_order(self.pair(), order.order_id)

    def place_orders(self, new_orders):
        for new_order in new_orders:
            amount = new_order.pay_amount if new_order.is_sell else new_order.buy_amount
            self.okex_api.place_order(pair=self.pair(),
                                      is_sell=new_order.is_sell,
                                      price=new_order.price,
                                      amount=amount)
Пример #5
0
class TestOKEX:
    def setup_method(self):
        self.okex = OKEXApi(api_server="localhost",
                            api_key="00000000-0000-0000-0000-000000000000",
                            secret_key="DEAD000000000000000000000000DEAD",
                            password="******",
                            timeout=15.5)

    def test_order(self):
        price = Wad.from_number(4.8765)
        amount = Wad.from_number(0.222)
        filled_amount = Wad.from_number(0.153)
        order = Order(order_id="153153",
                      timestamp=int(time.time()),
                      pair="MKR-ETH",
                      is_sell=False,
                      price=price,
                      amount=amount,
                      filled_amount=filled_amount)
        assert (order.price == order.sell_to_buy_price)
        assert (order.price == order.buy_to_sell_price)
        assert (order.remaining_buy_amount == amount - filled_amount)
        assert (order.remaining_sell_amount == (amount - filled_amount) *
                price)

    def test_ticker(self, mocker):
        pair = "mkr_usdt"
        mocker.patch("requests.get", side_effect=OkexMockServer.handle_request)
        response = self.okex.ticker(pair)
        assert (str(response["instrument_id"]).lower().replace('-',
                                                               '_') == pair)
        assert (float(response["best_ask"]) > 0)
        assert (response["instrument_id"] == response["product_id"])

    def test_depth(self, mocker):
        pair = "mkr_usdt"
        mocker.patch("requests.get", side_effect=OkexMockServer.handle_request)
        response = self.okex.depth(pair)
        assert ("bids" in response)
        assert ("asks" in response)
        assert (len(response["bids"]) > 0)
        assert (len(response["asks"]) > 0)

    def test_candles(self, mocker):
        pair = "mkr_usdt"
        mocker.patch("requests.get", side_effect=OkexMockServer.handle_request)
        response = self.okex.candles(pair, "1min")
        assert (len(response) > 0)
        for item in response:
            assert (isinstance(item, Candle))
            assert (item.timestamp > 0)
            assert (float(item.open) > 0)
            assert (float(item.high) > 0)
            assert (float(item.low) > 0)
            assert (float(item.close) > 0)

    def test_get_balances(self, mocker):
        mocker.patch("requests.get", side_effect=OkexMockServer.handle_request)
        response = self.okex.get_balances()
        assert (len(response) > 0)
        assert ("MKR" in response)
        assert ("ETH" in response)

    @staticmethod
    def check_orders(orders):
        by_oid = {}
        duplicate_count = 0
        duplicate_first_found = -1
        missorted_found = False
        last_timestamp = 0
        for index, order in enumerate(orders):
            assert (isinstance(order, Order))
            assert (order.order_id is not None)
            assert (order.timestamp > 0)
            # An order cannot be filled for more than the order amount
            assert (order.filled_amount <= order.amount)

            # Check for duplicates and missorted orders
            if order.order_id in by_oid:
                duplicate_count += 1
                if duplicate_first_found < 0:
                    duplicate_first_found = index
            else:
                by_oid[order.order_id] = order
                if not missorted_found and last_timestamp > 0:
                    if order.timestamp > last_timestamp:
                        print(f"missorted order found at index {index}")
                        missorted_found = True
                last_timestamp = order.timestamp

        if duplicate_count > 0:
            print(f"{duplicate_count} duplicate orders were found, "
                  f"starting at index {duplicate_first_found}")
        else:
            print("no duplicates were found")
        assert (duplicate_count == 0)
        assert (missorted_found is False)

    def test_get_orders(self, mocker):
        pair = "mkr_eth"
        mocker.patch("requests.get", side_effect=OkexMockServer.handle_request)
        response = self.okex.get_orders(pair)
        assert (len(response) > 0)
        for order in response:
            # Open orders cannot be completed filled
            assert (order.filled_amount < order.amount)
            assert (isinstance(order.is_sell, bool))
            assert (order.price > Wad(0))
        TestOKEX.check_orders(response)

    def test_get_all_orders(self, mocker):
        pair = "mkr_eth"
        mocker.patch("requests.get", side_effect=OkexMockServer.handle_request)
        response = self.okex.get_orders_history(pair, 99)
        assert (len(response) > 0)
        for order in response:
            assert (isinstance(order.is_sell, bool))
            assert (order.price > Wad(0))
        TestOKEX.check_orders(response)

    def test_order_placement_and_cancellation(self, mocker):
        pair = "mkr_usdt"
        mocker.patch("requests.post",
                     side_effect=OkexMockServer.handle_request)
        order_id = self.okex.place_order(pair, True, Wad.from_number(639.3),
                                         Wad.from_number(0.15))
        assert (isinstance(order_id, str))
        assert (order_id is not None)
        cancel_result = self.okex.cancel_order(pair, order_id)
        assert (cancel_result)

    @staticmethod
    def check_trades(trades):
        by_tradeid = {}
        duplicate_count = 0
        duplicate_first_found = -1
        missorted_found = False
        last_timestamp = 0
        for index, trade in enumerate(trades):
            assert (isinstance(trade, Trade))
            if trade.trade_id in by_tradeid:
                print(f"found duplicate trade {trade.trade_id}")
                duplicate_count += 1
                if duplicate_first_found < 0:
                    duplicate_first_found = index
            else:
                by_tradeid[trade.trade_id] = trade
                if not missorted_found and last_timestamp > 0:
                    if trade.timestamp > last_timestamp:
                        print(f"missorted trade found at index {index}")
                        missorted_found = True
                    last_timestamp = trade.timestamp
        if duplicate_count > 0:
            print(f"{duplicate_count} duplicate trades were found, "
                  f"starting at index {duplicate_first_found}")
        else:
            print("no duplicates were found")
        assert (duplicate_count == 0)
        assert (missorted_found is False)

    def test_get_trades(self, mocker):
        pair = "mkr_eth"
        mocker.patch("requests.get", side_effect=OkexMockServer.handle_request)
        response = self.okex.get_trades(pair)
        assert (len(response) > 0)
        TestOKEX.check_trades(response)

    def test_get_all_trades(self, mocker):
        pair = "mkr_usdt"
        mocker.patch("requests.get", side_effect=OkexMockServer.handle_request)
        response = self.okex.get_all_trades(pair)
        assert (len(response) > 0)
        TestOKEX.check_trades(response)
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))
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-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("--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.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.okex_api = OKEXApi(api_server=self.arguments.okex_api_server,
                                api_key=self.arguments.okex_api_key,
                                secret_key=self.arguments.okex_secret_key,
                                timeout=self.arguments.okex_timeout)

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

    def startup(self):
        self.our_orders()
        self.logger.info(f"OKEX API key seems to be valid")
        self.logger.info(f"Keeper configured to work on the '{self.pair()}' pair")

    @retry(delay=5, logger=logger)
    def shutdown(self):
        self.cancel_orders(self.our_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_balances(self) -> dict:
        return self.okex_api.get_balances()

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

    def our_orders(self) -> list:
        return self.okex_api.get_orders(self.pair())

    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)
        our_balances = self.our_balances()
        our_orders = self.our_orders()
        target_price = self.price_feed.get_price()

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

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

    def cancel_orders(self, orders):
        for order in orders:
            self.okex_api.cancel_order(self.pair(), order.order_id)

    def place_orders(self, new_orders):
        for new_order in new_orders:
            amount = new_order.pay_amount if new_order.is_sell else new_order.buy_amount
            self.okex_api.place_order(pair=self.pair(), is_sell=new_order.is_sell, price=new_order.price, amount=amount)
class OkexMarketStats:
    """Keeper acting as a market maker on OKEX."""

    logger = logging.getLogger()

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

        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("--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.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}")

        self.order_book_manager.cancel_all_orders()
        self.logger.info(f"Refresh balances: {balances}")

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