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