def test_profitability_without_conversion(self): self.clock.remove_iterator(self.strategy) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( [self.market_pair], 0.01, order_size_portfolio_ratio_limit=0.3, logging_options=self.logging_options) self.clock.add_iterator(self.strategy) self.clock.backtest_til(self.start_timestamp + 5) self.assertEqual(0, len(self.strategy.active_bids)) self.assertEqual(0, len(self.strategy.active_asks)) self.assertEqual((False, False), self.strategy.has_market_making_profit_potential( self.market_pair, self.maker_data.order_book, self.taker_data.order_book)) self.clock.remove_iterator(self.strategy) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( [self.market_pair], 0.004, order_size_portfolio_ratio_limit=0.3, logging_options=self.logging_options) self.clock.add_iterator(self.strategy) self.clock.backtest_til(self.start_timestamp + 10) self.assertEqual(1, len(self.strategy.active_bids)) self.assertEqual(1, len(self.strategy.active_asks)) self.assertEqual((True, True), self.strategy.has_market_making_profit_potential( self.market_pair, self.maker_data.order_book, self.taker_data.order_book))
def setUp(self): self.clock: Clock = Clock(ClockMode.BACKTEST, 1.0, self.start_timestamp, self.end_timestamp) self.min_profitbality = 0.005 self.maker_market: BacktestMarket = BacktestMarket() self.taker_market: BacktestMarket = BacktestMarket() self.maker_data: MockOrderBookLoader = MockOrderBookLoader( *self.maker_symbols) self.taker_data: MockOrderBookLoader = MockOrderBookLoader( *self.taker_symbols) self.maker_data.set_balanced_order_book(1.0, 0.5, 1.5, 0.01, 10) self.taker_data.set_balanced_order_book(1.0, 0.5, 1.5, 0.001, 4) self.maker_market.add_data(self.maker_data) self.taker_market.add_data(self.taker_data) self.maker_market.set_balance("COINALPHA", 5) self.maker_market.set_balance("WETH", 5) self.maker_market.set_balance("QETH", 5) self.taker_market.set_balance("COINALPHA", 5) self.taker_market.set_balance("ETH", 5) self.maker_market.set_quantization_param( QuantizationParams(self.maker_symbols[0], 5, 5, 5, 5)) self.taker_market.set_quantization_param( QuantizationParams(self.taker_symbols[0], 5, 5, 5, 5)) self.market_pair: CrossExchangeMarketPair = CrossExchangeMarketPair( MarketTradingPairTuple(self.maker_market, *self.maker_symbols), MarketTradingPairTuple(self.taker_market, *self.taker_symbols), ) logging_options: int = ( CrossExchangeMarketMakingStrategy.OPTION_LOG_ALL & (~CrossExchangeMarketMakingStrategy.OPTION_LOG_NULL_ORDER_SIZE)) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( [self.market_pair], order_size_portfolio_ratio_limit=0.3, min_profitability=self.min_profitbality, logging_options=logging_options, ) self.strategy_with_top_depth_tolerance: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( [self.market_pair], order_size_portfolio_ratio_limit=0.3, min_profitability=self.min_profitbality, logging_options=logging_options, top_depth_tolerance=1) self.logging_options = logging_options self.clock.add_iterator(self.maker_market) self.clock.add_iterator(self.taker_market) self.clock.add_iterator(self.strategy) self.maker_order_fill_logger: EventLogger = EventLogger() self.taker_order_fill_logger: EventLogger = EventLogger() self.cancel_order_logger: EventLogger = EventLogger() self.maker_market.add_listener(MarketEvent.OrderFilled, self.maker_order_fill_logger) self.taker_market.add_listener(MarketEvent.OrderFilled, self.taker_order_fill_logger) self.maker_market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger)
def setUp(self): self.clock: Clock = Clock(ClockMode.BACKTEST, 1.0, self.start_timestamp, self.end_timestamp) self.min_profitbality = Decimal("0.005") self.maker_market: MockPaperExchange = MockPaperExchange() self.taker_market: MockPaperExchange = MockPaperExchange() self.maker_market.set_balanced_order_book(self.maker_trading_pairs[0], 1.0, 0.5, 1.5, 0.01, 10) self.taker_market.set_balanced_order_book(self.taker_trading_pairs[0], 1.0, 0.5, 1.5, 0.001, 4) self.maker_market.set_balance("COINALPHA", 5) self.maker_market.set_balance("WETH", 5) self.maker_market.set_balance("QETH", 5) self.taker_market.set_balance("COINALPHA", 5) self.taker_market.set_balance("ETH", 5) self.maker_market.set_quantization_param(QuantizationParams(self.maker_trading_pairs[0], 5, 5, 5, 5)) self.taker_market.set_quantization_param(QuantizationParams(self.taker_trading_pairs[0], 5, 5, 5, 5)) self.market_pair: CrossExchangeMarketPair = CrossExchangeMarketPair( MarketTradingPairTuple(self.maker_market, *self.maker_trading_pairs), MarketTradingPairTuple(self.taker_market, *self.taker_trading_pairs), ) logging_options: int = ( CrossExchangeMarketMakingStrategy.OPTION_LOG_ALL & (~CrossExchangeMarketMakingStrategy.OPTION_LOG_NULL_ORDER_SIZE) ) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() self.strategy.init_params( [self.market_pair], order_size_portfolio_ratio_limit=Decimal("0.3"), min_profitability=Decimal(self.min_profitbality), logging_options=logging_options, slippage_buffer=Decimal("0"), ) self.strategy_with_top_depth_tolerance: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() self.strategy_with_top_depth_tolerance.init_params( [self.market_pair], order_size_portfolio_ratio_limit=Decimal("0.3"), min_profitability=Decimal(self.min_profitbality), logging_options=logging_options, top_depth_tolerance=1, slippage_buffer=Decimal("0"), ) self.logging_options = logging_options self.clock.add_iterator(self.maker_market) self.clock.add_iterator(self.taker_market) self.clock.add_iterator(self.strategy) self.maker_order_fill_logger: EventLogger = EventLogger() self.taker_order_fill_logger: EventLogger = EventLogger() self.cancel_order_logger: EventLogger = EventLogger() self.maker_order_created_logger: EventLogger = EventLogger() self.taker_order_created_logger: EventLogger = EventLogger() self.maker_market.add_listener(MarketEvent.OrderFilled, self.maker_order_fill_logger) self.taker_market.add_listener(MarketEvent.OrderFilled, self.taker_order_fill_logger) self.maker_market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) self.maker_market.add_listener(MarketEvent.BuyOrderCreated, self.maker_order_created_logger) self.maker_market.add_listener(MarketEvent.SellOrderCreated, self.maker_order_created_logger) self.taker_market.add_listener(MarketEvent.BuyOrderCreated, self.taker_order_created_logger) self.taker_market.add_listener(MarketEvent.SellOrderCreated, self.taker_order_created_logger)
def test_price_and_size_limit_calculation_with_slippage_buffer(self): self.taker_market.set_balance("ETH", 3) self.taker_market.set_balanced_order_book( self.taker_trading_pairs[0], mid_price=Decimal("1.0"), min_price=Decimal("0.5"), max_price=Decimal("1.5"), price_step_size=Decimal("0.1"), volume_step_size=Decimal("100"), ) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() self.strategy.init_params( [self.market_pair], order_size_taker_volume_factor=Decimal("1"), order_size_taker_balance_factor=Decimal("1"), order_size_portfolio_ratio_limit=Decimal("1"), min_profitability=Decimal("0.25"), logging_options=self.logging_options, slippage_buffer=Decimal("0"), order_amount=Decimal("4"), ) strategy_with_slippage_buffer: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() strategy_with_slippage_buffer.init_params( [self.market_pair], order_size_taker_volume_factor=Decimal("1"), order_size_taker_balance_factor=Decimal("1"), order_size_portfolio_ratio_limit=Decimal("1"), min_profitability=Decimal("0.25"), logging_options=self.logging_options, slippage_buffer=Decimal("0.25"), order_amount=Decimal("4"), ) bid_size = self.strategy.get_market_making_size(self.market_pair, True) bid_price = self.strategy.get_market_making_price(self.market_pair, True, bid_size) ask_size = self.strategy.get_market_making_size(self.market_pair, False) ask_price = self.strategy.get_market_making_price(self.market_pair, False, ask_size) slippage_bid_size = strategy_with_slippage_buffer.get_market_making_size(self.market_pair, True) slippage_bid_price = strategy_with_slippage_buffer.get_market_making_price( self.market_pair, True, slippage_bid_size ) slippage_ask_size = strategy_with_slippage_buffer.get_market_making_size(self.market_pair, False) slippage_ask_price = strategy_with_slippage_buffer.get_market_making_price( self.market_pair, False, slippage_ask_size ) self.assertEqual(Decimal("4"), bid_size) # the user size self.assertEqual(Decimal("0.75999"), bid_price) # price = bid_VWAP(4) / profitability = 0.95 / 1.25 self.assertEqual(Decimal("2.8571"), ask_size) # size = balance / (ask_VWAP(3) * slippage) = 3 / (1.05 * 1) self.assertEqual(Decimal("1.3125"), ask_price) # price = ask_VWAP(2.8571) * profitability = 1.05 * 1.25 self.assertEqual(Decimal("4"), slippage_bid_size) # the user size self.assertEqual(Decimal("0.75999"), slippage_bid_price) # price = bid_VWAP(4) / profitability = 0.9 / 1.25 self.assertEqual(Decimal("2.2857"), slippage_ask_size) # size = balance / (ask_VWAP(3) * slippage) = 3 / (1.05 * 1.25) self.assertEqual(Decimal("1.3125"), slippage_ask_price) # price = ask_VWAP(2.2857) * profitability = 1.05 * 1.25
def test_with_conversion(self): self.clock.remove_iterator(self.strategy) self.market_pair: CrossExchangeMarketPair = CrossExchangeMarketPair( MarketTradingPairTuple(self.maker_market, *["COINALPHA-QETH", "COINALPHA", "QETH"]), MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), ) self.maker_market.set_balanced_order_book("COINALPHA-QETH", 1.05, 0.55, 1.55, 0.01, 10) config_map_raw = deepcopy(self.config_map_raw) config_map_raw.min_profitability = Decimal("1") config_map_raw.order_size_portfolio_ratio_limit = Decimal("30") config_map_raw.conversion_rate_mode.taker_to_maker_base_conversion_rate = Decimal( "0.95") config_map = ClientConfigAdapter(config_map_raw) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( ) self.strategy.init_params( config_map=config_map, market_pairs=[self.market_pair], logging_options=self.logging_options, ) self.clock.add_iterator(self.strategy) self.clock.backtest_til(self.start_timestamp + 5) self.assertEqual(1, len(self.strategy.active_bids)) self.assertEqual(1, len(self.strategy.active_asks)) bid_order: LimitOrder = self.strategy.active_bids[0][1] ask_order: LimitOrder = self.strategy.active_asks[0][1] self.assertAlmostEqual(Decimal("1.0417"), round(bid_order.price, 4)) self.assertAlmostEqual(Decimal("1.0636"), round(ask_order.price, 4)) self.assertAlmostEqual(Decimal("2.9286"), round(bid_order.quantity, 4)) self.assertAlmostEqual(Decimal("2.9286"), round(ask_order.quantity, 4))
def test_with_adjust_orders_disabled(self): self.clock.remove_iterator(self.strategy) self.clock.remove_iterator(self.maker_market) self.maker_market: MockPaperExchange = MockPaperExchange() self.maker_market.set_balanced_order_book(self.maker_trading_pairs[0], 1.0, 0.5, 1.5, 0.1, 10) self.taker_market.set_balanced_order_book(self.taker_trading_pairs[0], 1.0, 0.5, 1.5, 0.001, 20) self.market_pair: CrossExchangeMarketPair = CrossExchangeMarketPair( MarketTradingPairTuple(self.maker_market, *self.maker_trading_pairs), MarketTradingPairTuple(self.taker_market, *self.taker_trading_pairs), ) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() self.strategy.init_params( [self.market_pair], order_size_portfolio_ratio_limit=Decimal("0.3"), min_profitability=Decimal("0.005"), logging_options=self.logging_options, adjust_order_enabled=False ) self.maker_market.set_balance("COINALPHA", 5) self.maker_market.set_balance("WETH", 5) self.maker_market.set_balance("QETH", 5) self.maker_market.set_quantization_param(QuantizationParams(self.maker_trading_pairs[0], 4, 4, 4, 4)) self.clock.add_iterator(self.strategy) self.clock.add_iterator(self.maker_market) self.clock.backtest_til(self.start_timestamp + 5) self.assertEqual(1, len(self.strategy.active_bids)) self.assertEqual(1, len(self.strategy.active_asks)) bid_order: LimitOrder = self.strategy.active_bids[0][1] ask_order: LimitOrder = self.strategy.active_asks[0][1] self.assertEqual(Decimal("0.9945"), bid_order.price) self.assertEqual(Decimal("1.006"), ask_order.price) self.assertAlmostEqual(Decimal("3"), round(bid_order.quantity, 4)) self.assertAlmostEqual(Decimal("3"), round(ask_order.quantity, 4))
def test_empty_maker_orderbook(self): self.clock.remove_iterator(self.strategy) self.clock.remove_iterator(self.maker_market) self.maker_market: MockPaperExchange = MockPaperExchange() # Orderbook is empty self.maker_market.new_empty_order_book(self.maker_trading_pairs[0]) self.market_pair: CrossExchangeMarketPair = CrossExchangeMarketPair( MarketTradingPairTuple(self.maker_market, *self.maker_trading_pairs), MarketTradingPairTuple(self.taker_market, *self.taker_trading_pairs), ) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy() self.strategy.init_params( [self.market_pair], order_amount=1, min_profitability=Decimal("0.005"), logging_options=self.logging_options, adjust_order_enabled=False ) self.maker_market.set_balance("COINALPHA", 5) self.maker_market.set_balance("WETH", 5) self.maker_market.set_balance("QETH", 5) self.maker_market.set_quantization_param(QuantizationParams(self.maker_trading_pairs[0], 4, 4, 4, 4)) self.clock.add_iterator(self.strategy) self.clock.add_iterator(self.maker_market) self.clock.backtest_til(self.start_timestamp + 5) self.assertEqual(1, len(self.strategy.active_bids)) self.assertEqual(1, len(self.strategy.active_asks)) bid_order: LimitOrder = self.strategy.active_bids[0][1] ask_order: LimitOrder = self.strategy.active_asks[0][1] # Places orders based on taker orderbook self.assertEqual(Decimal("0.9945"), bid_order.price) self.assertEqual(Decimal("1.006"), ask_order.price) self.assertAlmostEqual(Decimal("1"), round(bid_order.quantity, 4)) self.assertAlmostEqual(Decimal("1"), round(ask_order.quantity, 4))
def test_with_adjust_orders_enabled(self): self.clock.remove_iterator(self.strategy) self.clock.remove_iterator(self.maker_market) self.maker_market: BacktestMarket = BacktestMarket() self.maker_data: MockOrderBookLoader = MockOrderBookLoader(*self.maker_trading_pairs) self.maker_data.set_balanced_order_book(1.0, 0.5, 1.5, 0.1, 10) self.maker_market.add_data(self.maker_data) self.market_pair: CrossExchangeMarketPair = CrossExchangeMarketPair( MarketTradingPairTuple(self.maker_market, *self.maker_trading_pairs), MarketTradingPairTuple(self.taker_market, *self.taker_trading_pairs), ) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( [self.market_pair], order_size_portfolio_ratio_limit=Decimal("0.3"), min_profitability=Decimal("0.005"), logging_options=self.logging_options, ) self.maker_market.set_balance("COINALPHA", 5) self.maker_market.set_balance("WETH", 5) self.maker_market.set_balance("QETH", 5) self.maker_market.set_quantization_param(QuantizationParams(self.maker_trading_pairs[0], 4, 4, 4, 4)) self.clock.add_iterator(self.strategy) self.clock.add_iterator(self.maker_market) self.clock.backtest_til(self.start_timestamp + 5) self.assertEqual(1, len(self.strategy.active_bids)) self.assertEqual(1, len(self.strategy.active_asks)) bid_order: LimitOrder = self.strategy.active_bids[0][1] ask_order: LimitOrder = self.strategy.active_asks[0][1] # place above top bid (at 0.95) self.assertAlmostEqual(Decimal("0.9501"), bid_order.price) # place below top ask (at 1.05) self.assertAlmostEqual(Decimal("1.049"), ask_order.price) self.assertAlmostEqual(Decimal("3"), round(bid_order.quantity, 4)) self.assertAlmostEqual(Decimal("3"), round(ask_order.quantity, 4))
def test_with_conversion(self): self.clock.remove_iterator(self.strategy) self.market_pair: CrossExchangeMarketPair = CrossExchangeMarketPair( MarketTradingPairTuple(self.maker_market, *["COINALPHA-QETH", "COINALPHA", "QETH"]), MarketTradingPairTuple(self.taker_market, *self.taker_trading_pairs), ) self.maker_data: MockOrderBookLoader = MockOrderBookLoader( "COINALPHA-QETH", "COINALPHA", "QETH") self.maker_data.set_balanced_order_book(1.05, 0.55, 1.55, 0.01, 10) self.maker_market.add_data(self.maker_data) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( [self.market_pair], Decimal("0.01"), order_size_portfolio_ratio_limit=Decimal("0.3"), logging_options=self.logging_options) self.clock.add_iterator(self.strategy) self.clock.backtest_til(self.start_timestamp + 5) self.assertEqual(1, len(self.strategy.active_bids)) self.assertEqual(1, len(self.strategy.active_asks)) bid_order: LimitOrder = self.strategy.active_bids[0][1] ask_order: LimitOrder = self.strategy.active_asks[0][1] self.assertAlmostEqual(Decimal("1.0417"), round(bid_order.price, 4)) self.assertAlmostEqual(Decimal("1.0637"), round(ask_order.price, 4)) self.assertAlmostEqual(Decimal("2.9286"), round(bid_order.quantity, 4)) self.assertAlmostEqual(Decimal("2.9286"), round(ask_order.quantity, 4))
def test_profitability_with_conversion(self): self.clock.remove_iterator(self.strategy) self.market_pair: CrossExchangeMarketPair = CrossExchangeMarketPair( *( [self.maker_market] + ["COINALPHA-QETH", "COINALPHA", "QETH"] + [self.taker_market] + self.taker_symbols + [2] ) ) self.maker_data: MockOrderBookLoader = MockOrderBookLoader("COINALPHA-QETH", "COINALPHA", "QETH") self.maker_data.set_balanced_order_book(1.05263, 0.55, 1.55, 0.01, 10) self.maker_market.add_data(self.maker_data) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( [self.market_pair], 0.01, order_size_portfolio_ratio_limit=0.3, logging_options=self.logging_options ) self.clock.add_iterator(self.strategy) self.clock.backtest_til(self.start_timestamp + 5) self.assertEqual(0, len(self.strategy.active_bids)) self.assertEqual(0, len(self.strategy.active_asks)) self.assertEqual((False, False), self.strategy.has_market_making_profit_potential( self.market_pair, self.maker_data.order_book, self.taker_data.order_book )) self.clock.remove_iterator(self.strategy) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( [self.market_pair], 0.003, order_size_portfolio_ratio_limit=0.3, logging_options=self.logging_options ) self.clock.add_iterator(self.strategy) self.clock.backtest_til(self.start_timestamp + 10) self.assertEqual(1, len(self.strategy.active_bids)) self.assertEqual(1, len(self.strategy.active_asks)) self.assertEqual((True, True), self.strategy.has_market_making_profit_potential( self.market_pair, self.maker_data.order_book, self.taker_data.order_book ))
def main(): # Define the data cache path. hummingsim.set_data_path(os.path.join(os.environ["PWD"], "data")) # Define the parameters for the backtest. start = pd.Timestamp("2018-12-12", tz="UTC") end = pd.Timestamp("2019-01-12", tz="UTC") binance_symbol = ("ETHUSDT", "ETH", "USDT") ddex_symbol = ("WETH-DAI", "WETH", "DAI") binance_market = BacktestMarket() ddex_market = BacktestMarket() binance_market.config = MarketConfig(AssetType.BASE_CURRENCY, 0.001, AssetType.QUOTE_CURRENCY, 0.001, {}) ddex_market.config = MarketConfig(AssetType.BASE_CURRENCY, 0.001, AssetType.QUOTE_CURRENCY, 0.001, {}) binance_loader = BinanceOrderBookLoaderV2(*binance_symbol) ddex_loader = DDEXOrderBookLoader(*ddex_symbol) binance_market.add_data(binance_loader) ddex_market.add_data(ddex_loader) binance_market.set_quantization_param( QuantizationParams("ETHUSDT", 5, 3, 5, 3)) ddex_market.set_quantization_param( QuantizationParams("WETH-DAI", 5, 3, 5, 3)) market_pair = CrossExchangeMarketPair(*([ddex_market] + list(ddex_symbol) + [binance_market] + list(binance_symbol))) strategy = CrossExchangeMarketMakingStrategy( [market_pair], 0.003, logging_options=CrossExchangeMarketMakingStrategy. OPTION_LOG_MAKER_ORDER_FILLED) clock = Clock(ClockMode.BACKTEST, start_time=start.timestamp(), end_time=end.timestamp()) clock.add_iterator(binance_market) clock.add_iterator(ddex_market) clock.add_iterator(strategy) binance_market.set_balance("ETH", 10.0) binance_market.set_balance("USDT", 1000.0) ddex_market.set_balance("WETH", 10.0) ddex_market.set_balance("DAI", 1000.0) clock.backtest() binance_loader.close() ddex_loader.close()
def setUp(self): self.clock: Clock = Clock(ClockMode.BACKTEST, 1.0, self.start_timestamp, self.end_timestamp) self.maker_market: BacktestMarket = BacktestMarket() self.taker_market: BacktestMarket = BacktestMarket() self.maker_data: MockOrderBookLoader = MockOrderBookLoader(*self.maker_symbols) self.taker_data: MockOrderBookLoader = MockOrderBookLoader(*self.taker_symbols) self.maker_data.set_balanced_order_book(1.0, 0.5, 1.5, 0.01, 10) self.taker_data.set_balanced_order_book(1.0, 0.5, 1.5, 0.001, 3) self.maker_market.add_data(self.maker_data) self.taker_market.add_data(self.taker_data) self.maker_market.set_balance("COINALPHA", 5) self.maker_market.set_balance("WETH", 5) self.taker_market.set_balance("COINALPHA", 5) self.taker_market.set_balance("ETH", 5) self.maker_market.set_quantization_param( QuantizationParams( self.maker_symbols[0], 5, 5, 5, 5 ) ) self.taker_market.set_quantization_param( QuantizationParams( self.taker_symbols[0], 5, 5, 5, 5 ) ) self.market_pair: CrossExchangeMarketPair = CrossExchangeMarketPair( *( [self.maker_market] + self.maker_symbols + [self.taker_market] + self.taker_symbols + [2] ) ) logging_options: int = (CrossExchangeMarketMakingStrategy.OPTION_LOG_ALL & (~CrossExchangeMarketMakingStrategy.OPTION_LOG_NULL_ORDER_SIZE)) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( [self.market_pair], 0.003, order_size_portfolio_ratio_limit=0.3, logging_options=logging_options ) self.clock.add_iterator(self.maker_market) self.clock.add_iterator(self.taker_market) self.clock.add_iterator(self.strategy) self.maker_order_fill_logger: EventLogger = EventLogger() self.taker_order_fill_logger: EventLogger = EventLogger() self.cancel_order_logger: EventLogger = EventLogger() self.maker_market.add_listener(MarketEvent.OrderFilled, self.maker_order_fill_logger) self.taker_market.add_listener(MarketEvent.OrderFilled, self.taker_order_fill_logger) self.maker_market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger)
def test_with_adjust_orders_enabled(self): self.clock.remove_iterator(self.strategy) self.clock.remove_iterator(self.maker_market) self.maker_market: MockPaperExchange = MockPaperExchange( client_config_map=ClientConfigAdapter(ClientConfigMap())) self.maker_market.set_balanced_order_book(self.trading_pairs_maker[0], 1.0, 0.5, 1.5, 0.1, 10) self.market_pair: CrossExchangeMarketPair = CrossExchangeMarketPair( MarketTradingPairTuple(self.maker_market, *self.trading_pairs_maker), MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), ) config_map_raw = deepcopy(self.config_map_raw) config_map_raw.order_size_portfolio_ratio_limit = Decimal("30") config_map_raw.min_profitability = Decimal("0.5") config_map_raw.adjust_order_enabled = True config_map = ClientConfigAdapter(config_map_raw) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( ) self.strategy.init_params( config_map=config_map, market_pairs=[self.market_pair], logging_options=self.logging_options, ) self.maker_market.set_balance("COINALPHA", 5) self.maker_market.set_balance("WETH", 5) self.maker_market.set_balance("QETH", 5) self.maker_market.set_quantization_param( QuantizationParams(self.trading_pairs_maker[0], 4, 4, 4, 4)) self.clock.add_iterator(self.strategy) self.clock.add_iterator(self.maker_market) self.clock.backtest_til(self.start_timestamp + 5) self.assertEqual(1, len(self.strategy.active_bids)) self.assertEqual(1, len(self.strategy.active_asks)) bid_order: LimitOrder = self.strategy.active_bids[0][1] ask_order: LimitOrder = self.strategy.active_asks[0][1] # place above top bid (at 0.95) self.assertAlmostEqual(Decimal("0.9501"), bid_order.price) # place below top ask (at 1.05) self.assertAlmostEqual(Decimal("1.049"), ask_order.price) self.assertAlmostEqual(Decimal("3"), round(bid_order.quantity, 4)) self.assertAlmostEqual(Decimal("3"), round(ask_order.quantity, 4))
async def start_market_making(self, strategy_name: str): strategy_cm = get_strategy_config_map(strategy_name) if strategy_name == "cross_exchange_market_making": maker_market = strategy_cm.get("maker_market").value.lower() taker_market = strategy_cm.get("taker_market").value.lower() raw_maker_symbol = strategy_cm.get( "maker_market_symbol").value.upper() raw_taker_symbol = strategy_cm.get( "taker_market_symbol").value.upper() min_profitability = strategy_cm.get("min_profitability").value trade_size_override = strategy_cm.get("trade_size_override").value strategy_report_interval = global_config_map.get( "strategy_report_interval").value limit_order_min_expiration = strategy_cm.get( "limit_order_min_expiration").value cancel_order_threshold = strategy_cm.get( "cancel_order_threshold").value active_order_canceling = strategy_cm.get( "active_order_canceling").value top_depth_tolerance_rules = [(re.compile(re_str), value) for re_str, value in strategy_cm.get( "top_depth_tolerance").value] top_depth_tolerance = 0.0 for regex, tolerance_value in top_depth_tolerance_rules: if regex.match(raw_maker_symbol) is not None: top_depth_tolerance = tolerance_value try: maker_assets: Tuple[str, str] = SymbolSplitter.split( maker_market, raw_maker_symbol) taker_assets: Tuple[str, str] = SymbolSplitter.split( taker_market, raw_taker_symbol) except ValueError as e: self.app.log(str(e)) return market_names: List[Tuple[str, str]] = [(maker_market, raw_maker_symbol), (taker_market, raw_taker_symbol)] self._initialize_wallet( token_symbols=list(set(maker_assets + taker_assets))) self._initialize_markets(market_names) self.market_pair = CrossExchangeMarketPair( *([self.markets[maker_market], raw_maker_symbol] + list(maker_assets) + [self.markets[taker_market], raw_taker_symbol] + list(taker_assets) + [top_depth_tolerance])) strategy_logging_options = ( CrossExchangeMarketMakingStrategy.OPTION_LOG_CREATE_ORDER | CrossExchangeMarketMakingStrategy.OPTION_LOG_ADJUST_ORDER | CrossExchangeMarketMakingStrategy.OPTION_LOG_MAKER_ORDER_FILLED | CrossExchangeMarketMakingStrategy.OPTION_LOG_REMOVING_ORDER | CrossExchangeMarketMakingStrategy.OPTION_LOG_STATUS_REPORT | CrossExchangeMarketMakingStrategy.OPTION_LOG_MAKER_ORDER_HEDGED ) self.strategy = CrossExchangeMarketMakingStrategy( market_pairs=[self.market_pair], min_profitability=min_profitability, status_report_interval=strategy_report_interval, logging_options=strategy_logging_options, trade_size_override=trade_size_override, limit_order_min_expiration=limit_order_min_expiration, cancel_order_threshold=cancel_order_threshold, active_order_canceling=active_order_canceling) elif strategy_name == "arbitrage": primary_market = strategy_cm.get("primary_market").value.lower() secondary_market = strategy_cm.get( "secondary_market").value.lower() raw_primary_symbol = strategy_cm.get( "primary_market_symbol").value.upper() raw_secondary_symbol = strategy_cm.get( "secondary_market_symbol").value.upper() min_profitability = strategy_cm.get("min_profitability").value try: primary_assets: Tuple[str, str] = SymbolSplitter.split( primary_market, raw_primary_symbol) secondary_assets: Tuple[str, str] = SymbolSplitter.split( secondary_market, raw_secondary_symbol) except ValueError as e: self.app.log(str(e)) return market_names: List[Tuple[str, str]] = [ (primary_market, raw_primary_symbol), (secondary_market, raw_secondary_symbol) ] self._initialize_wallet( token_symbols=list(set(primary_assets + secondary_assets))) self._initialize_markets(market_names) self.market_pair = ArbitrageMarketPair( *([self.markets[primary_market], raw_primary_symbol] + list(primary_assets) + [self.markets[secondary_market], raw_secondary_symbol] + list(secondary_assets))) strategy_logging_options = ArbitrageStrategy.OPTION_LOG_ALL self.strategy = ArbitrageStrategy( market_pairs=[self.market_pair], min_profitability=min_profitability, logging_options=strategy_logging_options) else: raise NotImplementedError try: self.clock = Clock(ClockMode.REALTIME) if self.wallet is not None: self.clock.add_iterator(self.wallet) for market in self.markets.values(): if market is not None: self.clock.add_iterator(market) self.clock.add_iterator(self.strategy) self.strategy_task: asyncio.Task = asyncio.ensure_future( self.clock.run()) self.app.log( f"\n'{strategy_name}' strategy started.\n" f"You can use the `status` command to query the progress.") except Exception as e: self.logger().error(str(e), exc_info=True)
async def start_market_making(self, strategy_name: str): strategy_cm = get_strategy_config_map(strategy_name) if strategy_name == "cross_exchange_market_making": maker_market = strategy_cm.get("maker_market").value.lower() taker_market = strategy_cm.get("taker_market").value.lower() raw_maker_symbol = strategy_cm.get( "maker_market_symbol").value.upper() raw_taker_symbol = strategy_cm.get( "taker_market_symbol").value.upper() min_profitability = strategy_cm.get("min_profitability").value trade_size_override = strategy_cm.get("trade_size_override").value strategy_report_interval = global_config_map.get( "strategy_report_interval").value limit_order_min_expiration = strategy_cm.get( "limit_order_min_expiration").value cancel_order_threshold = strategy_cm.get( "cancel_order_threshold").value active_order_canceling = strategy_cm.get( "active_order_canceling").value top_depth_tolerance_rules = [(re.compile(re_str), value) for re_str, value in strategy_cm.get( "top_depth_tolerance").value] top_depth_tolerance = 0.0 for regex, tolerance_value in top_depth_tolerance_rules: if regex.match(raw_maker_symbol) is not None: top_depth_tolerance = tolerance_value try: maker_assets: Tuple[str, str] = SymbolSplitter.split( maker_market, raw_maker_symbol) taker_assets: Tuple[str, str] = SymbolSplitter.split( taker_market, raw_taker_symbol) except ValueError as e: self.app.log(str(e)) return market_names: List[Tuple[str, List[str]]] = [ (maker_market, [raw_maker_symbol]), (taker_market, [raw_taker_symbol]) ] self._initialize_wallet( token_symbols=list(set(maker_assets + taker_assets))) self._initialize_markets(market_names) self.assets = set(maker_assets + taker_assets) self.market_pair = CrossExchangeMarketPair( *([self.markets[maker_market], raw_maker_symbol] + list(maker_assets) + [self.markets[taker_market], raw_taker_symbol] + list(taker_assets) + [top_depth_tolerance])) strategy_logging_options = ( CrossExchangeMarketMakingStrategy.OPTION_LOG_CREATE_ORDER | CrossExchangeMarketMakingStrategy.OPTION_LOG_ADJUST_ORDER | CrossExchangeMarketMakingStrategy.OPTION_LOG_MAKER_ORDER_FILLED | CrossExchangeMarketMakingStrategy.OPTION_LOG_REMOVING_ORDER | CrossExchangeMarketMakingStrategy.OPTION_LOG_STATUS_REPORT | CrossExchangeMarketMakingStrategy.OPTION_LOG_MAKER_ORDER_HEDGED ) self.strategy = CrossExchangeMarketMakingStrategy( market_pairs=[self.market_pair], min_profitability=min_profitability, status_report_interval=strategy_report_interval, logging_options=strategy_logging_options, trade_size_override=trade_size_override, limit_order_min_expiration=limit_order_min_expiration, cancel_order_threshold=cancel_order_threshold, active_order_canceling=active_order_canceling) elif strategy_name == "arbitrage": primary_market = strategy_cm.get("primary_market").value.lower() secondary_market = strategy_cm.get( "secondary_market").value.lower() raw_primary_symbol = strategy_cm.get( "primary_market_symbol").value.upper() raw_secondary_symbol = strategy_cm.get( "secondary_market_symbol").value.upper() min_profitability = strategy_cm.get("min_profitability").value try: primary_assets: Tuple[str, str] = SymbolSplitter.split( primary_market, raw_primary_symbol) secondary_assets: Tuple[str, str] = SymbolSplitter.split( secondary_market, raw_secondary_symbol) except ValueError as e: self.app.log(str(e)) return market_names: List[Tuple[str, List[str]]] = [ (primary_market, [raw_primary_symbol]), (secondary_market, [raw_secondary_symbol]) ] self._initialize_wallet( token_symbols=list(set(primary_assets + secondary_assets))) self._initialize_markets(market_names) self.assets = set(primary_assets + secondary_assets) self.market_pair = ArbitrageMarketPair( *([self.markets[primary_market], raw_primary_symbol] + list(primary_assets) + [self.markets[secondary_market], raw_secondary_symbol] + list(secondary_assets))) strategy_logging_options = ArbitrageStrategy.OPTION_LOG_ALL self.strategy = ArbitrageStrategy( market_pairs=[self.market_pair], min_profitability=min_profitability, logging_options=strategy_logging_options) elif strategy_name == "pure_market_making": order_size = strategy_cm.get("order_amount").value cancel_order_wait_time = strategy_cm.get( "cancel_order_wait_time").value bid_place_threshold = strategy_cm.get("bid_place_threshold").value ask_place_threshold = strategy_cm.get("ask_place_threshold").value maker_market = strategy_cm.get("maker_market").value.lower() raw_maker_symbol = strategy_cm.get( "maker_market_symbol").value.upper() try: primary_assets: Tuple[str, str] = SymbolSplitter.split( maker_market, raw_maker_symbol) except ValueError as e: self.app.log(str(e)) return market_names: List[Tuple[str, List[str]]] = [(maker_market, [raw_maker_symbol])] self._initialize_wallet(token_symbols=list(set(primary_assets))) self._initialize_markets(market_names) self.assets = set(primary_assets) self.market_pair = PureMarketPair( *([self.markets[maker_market], raw_maker_symbol] + list(primary_assets))) strategy_logging_options = PureMarketMakingStrategy.OPTION_LOG_ALL self.strategy = PureMarketMakingStrategy( market_pairs=[self.market_pair], order_size=order_size, bid_place_threshold=bid_place_threshold, ask_place_threshold=ask_place_threshold, cancel_order_wait_time=cancel_order_wait_time, logging_options=strategy_logging_options) elif strategy_name == "discovery": try: market_1 = strategy_cm.get("primary_market").value.lower() market_2 = strategy_cm.get("secondary_market").value.lower() target_symbol_1 = list( strategy_cm.get("target_symbol_1").value) target_symbol_2 = list( strategy_cm.get("target_symbol_2").value) target_profitability = float( strategy_cm.get("target_profitability").value) target_amount = float(strategy_cm.get("target_amount").value) equivalent_token: List[List[str]] = list( strategy_cm.get("equivalent_tokens").value) if not target_symbol_2: target_symbol_2 = SymbolFetcher.get_instance().symbols.get( market_2, []) if not target_symbol_1: target_symbol_1 = SymbolFetcher.get_instance().symbols.get( market_1, []) market_names: List[Tuple[str, List[str]]] = [ (market_1, target_symbol_1), (market_2, target_symbol_2) ] target_base_quote_1: List[Tuple[str, str]] = [ SymbolSplitter.split(market_1, symbol) for symbol in target_symbol_1 ] target_base_quote_2: List[Tuple[str, str]] = [ SymbolSplitter.split(market_2, symbol) for symbol in target_symbol_2 ] self._trading_required = False self._initialize_wallet( token_symbols=[] ) # wallet required only for dex hard dependency self._initialize_markets(market_names) self.market_pair = DiscoveryMarketPair(*([ self.markets[market_1], self.markets[market_1].get_active_exchange_markets ] + [ self.markets[market_2], self.markets[market_2].get_active_exchange_markets ])) self.strategy = DiscoveryStrategy( market_pairs=[self.market_pair], target_symbols=target_base_quote_1 + target_base_quote_2, equivalent_token=equivalent_token, target_profitability=target_profitability, target_amount=target_amount) except Exception as e: self.app.log(str(e)) self.logger().error("Error initializing strategy.", exc_info=True) else: raise NotImplementedError try: self.clock = Clock(ClockMode.REALTIME) if self.wallet is not None: self.clock.add_iterator(self.wallet) for market in self.markets.values(): if market is not None: self.clock.add_iterator(market) if self.strategy: self.clock.add_iterator(self.strategy) self.strategy_task: asyncio.Task = asyncio.ensure_future( self._run_clock()) self.app.log( f"\n '{strategy_name}' strategy started.\n" f" You can use the `status` command to query the progress.") self.starting_balances = await self.wait_till_ready( self.balance_snapshot) self.stop_loss_tracker = StopLossTracker( self.data_feed, list(self.assets), list(self.markets.values()), lambda *args, **kwargs: asyncio.ensure_future( self.stop(*args, **kwargs))) await self.wait_till_ready(self.stop_loss_tracker.start) except Exception as e: self.logger().error(str(e), exc_info=True)
def test_check_if_sufficient_balance_adjusts_including_slippage(self): self.taker_market.set_balance("COINALPHA", 4) self.taker_market.set_balance("ETH", 3) self.taker_market.set_balanced_order_book( self.taker_trading_pairs[0], mid_price=Decimal("1.0"), min_price=Decimal("0.5"), max_price=Decimal("1.5"), price_step_size=Decimal("0.1"), volume_step_size=Decimal("1"), ) strategy_with_slippage_buffer: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( ) strategy_with_slippage_buffer.init_params( [self.market_pair], order_size_taker_volume_factor=Decimal("1"), order_size_taker_balance_factor=Decimal("1"), order_size_portfolio_ratio_limit=Decimal("1"), min_profitability=Decimal("0.25"), logging_options=self.logging_options, slippage_buffer=Decimal("0.25"), order_amount=Decimal("4"), ) self.clock.remove_iterator(self.strategy) self.clock.add_iterator(strategy_with_slippage_buffer) self.clock.backtest_til(self.start_timestamp + 1) active_bids = strategy_with_slippage_buffer.active_bids active_asks = strategy_with_slippage_buffer.active_asks self.assertEqual(1, len(active_bids)) self.assertEqual(1, len(active_asks)) active_bid = active_bids[0][1] active_ask = active_asks[0][1] self.emit_order_created_event(self.maker_market, active_bid) self.emit_order_created_event(self.maker_market, active_ask) self.clock.backtest_til(self.start_timestamp + 2) active_bids = strategy_with_slippage_buffer.active_bids active_asks = strategy_with_slippage_buffer.active_asks self.assertEqual(1, len(active_bids)) self.assertEqual(1, len(active_asks)) active_bid = active_bids[0][1] active_ask = active_asks[0][1] bids_quantum = self.taker_market.get_order_size_quantum( self.taker_trading_pairs[0], active_bid.quantity) asks_quantum = self.taker_market.get_order_size_quantum( self.taker_trading_pairs[0], active_ask.quantity) self.taker_market.set_balance("COINALPHA", Decimal("4") - bids_quantum) self.taker_market.set_balance("ETH", Decimal("3") - asks_quantum * 1) self.clock.backtest_til(self.start_timestamp + 3) active_bids = strategy_with_slippage_buffer.active_bids active_asks = strategy_with_slippage_buffer.active_asks self.assertEqual(0, len(active_bids)) # cancelled self.assertEqual(0, len(active_asks)) # cancelled self.clock.backtest_til(self.start_timestamp + 4) new_active_bids = strategy_with_slippage_buffer.active_bids new_active_asks = strategy_with_slippage_buffer.active_asks self.assertEqual(1, len(new_active_bids)) self.assertEqual(1, len(new_active_asks)) new_active_bid = new_active_bids[0][1] new_active_ask = new_active_asks[0][1] self.assertEqual(Decimal(str(active_bid.quantity - bids_quantum)), new_active_bid.quantity) self.assertEqual(Decimal(str(active_ask.quantity - asks_quantum)), new_active_ask.quantity)
def setUp(self): self.clock: Clock = Clock(ClockMode.BACKTEST, 1.0, self.start_timestamp, self.end_timestamp) self.min_profitability = Decimal("0.005") self.maker_market: MockPaperExchange = MockPaperExchange( client_config_map=ClientConfigAdapter(ClientConfigMap())) self.taker_market: MockPaperExchange = MockPaperExchange( client_config_map=ClientConfigAdapter(ClientConfigMap())) self.maker_market.set_balanced_order_book(self.trading_pairs_maker[0], 1.0, 0.5, 1.5, 0.01, 10) self.taker_market.set_balanced_order_book(self.trading_pairs_taker[0], 1.0, 0.5, 1.5, 0.001, 4) self.maker_market.set_balance("COINALPHA", 5) self.maker_market.set_balance("WETH", 5) self.maker_market.set_balance("QETH", 5) self.taker_market.set_balance("COINALPHA", 5) self.taker_market.set_balance("ETH", 5) self.maker_market.set_quantization_param( QuantizationParams(self.trading_pairs_maker[0], 5, 5, 5, 5)) self.taker_market.set_quantization_param( QuantizationParams(self.trading_pairs_taker[0], 5, 5, 5, 5)) self.market_pair: CrossExchangeMarketPair = CrossExchangeMarketPair( MarketTradingPairTuple(self.maker_market, *self.trading_pairs_maker), MarketTradingPairTuple(self.taker_market, *self.trading_pairs_taker), ) self.config_map_raw = CrossExchangeMarketMakingConfigMap( maker_market=self.exchange_name_maker, taker_market=self.exchange_name_taker, maker_market_trading_pair=self.trading_pairs_maker[0], taker_market_trading_pair=self.trading_pairs_taker[0], min_profitability=Decimal(self.min_profitability), slippage_buffer=Decimal("0"), order_amount=Decimal("0"), # Default values folllow order_size_taker_volume_factor=Decimal("25"), order_size_taker_balance_factor=Decimal("99.5"), order_size_portfolio_ratio_limit=Decimal("30"), adjust_order_enabled=True, anti_hysteresis_duration=60.0, order_refresh_mode=ActiveOrderRefreshMode(), top_depth_tolerance=Decimal(0), conversion_rate_mode=TakerToMakerConversionRateMode(), ) self.config_map_raw.conversion_rate_mode.taker_to_maker_base_conversion_rate = Decimal( "1.0") self.config_map_raw.conversion_rate_mode.taker_to_maker_quote_conversion_rate = Decimal( "1.0") self.config_map = ClientConfigAdapter(self.config_map_raw) config_map_with_top_depth_tolerance_raw = deepcopy(self.config_map_raw) config_map_with_top_depth_tolerance_raw.top_depth_tolerance = Decimal( "1") config_map_with_top_depth_tolerance = ClientConfigAdapter( config_map_with_top_depth_tolerance_raw) logging_options: int = ( CrossExchangeMarketMakingStrategy.OPTION_LOG_ALL & (~CrossExchangeMarketMakingStrategy.OPTION_LOG_NULL_ORDER_SIZE)) self.strategy: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( ) self.strategy.init_params( config_map=self.config_map, market_pairs=[self.market_pair], logging_options=logging_options, ) self.strategy_with_top_depth_tolerance: CrossExchangeMarketMakingStrategy = CrossExchangeMarketMakingStrategy( ) self.strategy_with_top_depth_tolerance.init_params( config_map=config_map_with_top_depth_tolerance, market_pairs=[self.market_pair], logging_options=logging_options, ) self.logging_options = logging_options self.clock.add_iterator(self.maker_market) self.clock.add_iterator(self.taker_market) self.clock.add_iterator(self.strategy) self.maker_order_fill_logger: EventLogger = EventLogger() self.taker_order_fill_logger: EventLogger = EventLogger() self.cancel_order_logger: EventLogger = EventLogger() self.maker_order_created_logger: EventLogger = EventLogger() self.taker_order_created_logger: EventLogger = EventLogger() self.maker_market.add_listener(MarketEvent.OrderFilled, self.maker_order_fill_logger) self.taker_market.add_listener(MarketEvent.OrderFilled, self.taker_order_fill_logger) self.maker_market.add_listener(MarketEvent.OrderCancelled, self.cancel_order_logger) self.maker_market.add_listener(MarketEvent.BuyOrderCreated, self.maker_order_created_logger) self.maker_market.add_listener(MarketEvent.SellOrderCreated, self.maker_order_created_logger) self.taker_market.add_listener(MarketEvent.BuyOrderCreated, self.taker_order_created_logger) self.taker_market.add_listener(MarketEvent.SellOrderCreated, self.taker_order_created_logger)