0.025, logging_options=ArbitrageStrategy.OPTION_LOG_CREATE_ORDER) 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", 100.0) binance_market.set_balance("USDT", 10000.0) ddex_market.set_balance("WETH", 100.0) ddex_market.set_balance("DAI", 10000.0) clock.backtest_til(start.timestamp() + 1) ddex_weth_price = ddex_market.get_price("WETH-DAI", False) binance_eth_price = binance_market.get_price("ETHUSDT", False) start_ddex_portfolio_value = ddex_market.get_balance( "DAI") + ddex_market.get_balance("WETH") * ddex_weth_price start_binance_portfolio_value = binance_market.get_balance( "USDT") + binance_market.get_balance("ETH") * binance_eth_price print(f"start DDEX portfolio value: {start_ddex_portfolio_value}\n" f"start Binance portfolio value: {start_binance_portfolio_value}") clock.backtest_til(end.timestamp()) ddex_weth_price = ddex_market.get_price("WETH-DAI", False) binance_eth_price = binance_market.get_price("ETHUSDT", False) ddex_portfolio_value = ddex_market.get_balance(
class OrderExpirationTest(unittest.TestCase): start: pd.Timestamp = pd.Timestamp("2019-01-24", tz="UTC") end: pd.Timestamp = pd.Timestamp("2019-01-26", tz="UTC") market_name = "ETHUSDT" quote = "ETH" base = "USDT" def setUp(self): #self.weth_dai_data = DDEXOrderBookLoader("WETH-DAI", "WETH", "DAI") self.pair_data = BinanceOrderBookLoaderV2(self.market_name, "ETH", "USDT") #self.pair_data = HuobiOrderBookLoader(self.market_name, "", "") self.clock = Clock(ClockMode.BACKTEST, 1.0, self.start.timestamp(), self.end.timestamp()) self.market = BacktestMarket() #self.market.add_data(self.weth_dai_data) self.market.add_data(self.pair_data) self.market.set_balance(self.quote, 200.0) self.market.set_balance(self.base, 20000.0) self.clock.add_iterator(self.market) def tearDown(self): #self.weth_dai_data.close() #self.eth_usd_data.close() self.pair_data.close() def verify_expired_order_cleanup(self, order_expired_events, limit_orders): """ Recorded order expired event should indicate that these orders are no longer in the limit orders """ limit_order_dict = {o.client_order_id: o for o in limit_orders} for index, order_expired_event in order_expired_events.iterrows(): self.assertTrue( order_expired_event.order_id not in limit_order_dict) def test_ask_order_expiration_clean_up(self): ts_1 = pd.Timestamp("2019-01-24 00:02:15+00:00").timestamp() ts_2 = pd.Timestamp("2019-01-24 00:02:20+00:00").timestamp() trades = { ts_1: [(self.market_name, "sell", 1302, 255, { "expiration_ts": ts_1 + 9 })], ts_2: [(self.market_name, "sell", 1302, 250, { "expiration_ts": ts_2 + 9 })] } strategy: OrderExpirationTestStrategy = OrderExpirationTestStrategy( self.market, trades) self.clock.add_iterator(strategy) # first limit order made self.clock.backtest_til(self.start.timestamp() + 60 * 2 + 15) first_order_id = self.market.limit_orders[0].client_order_id self.assertTrue(len(self.market.limit_orders) == 1) self.assertTrue(first_order_id in {o.order_id: o for o in self.market.order_expirations}) # second limit order made self.clock.backtest_til(self.start.timestamp() + 60 * 2 + 20) self.assertTrue(len(self.market.limit_orders) == 2) # first limit order expired self.clock.backtest_til(self.start.timestamp() + 60 * 2 + 25) # check if order expired event is fired self.assertTrue(first_order_id in [ evt.order_id for i, evt in strategy.order_expired_events.iterrows() ]) # check if the expired limit order is cleaned up self.verify_expired_order_cleanup(strategy.order_expired_events, self.market.limit_orders) self.assertTrue(len(self.market.limit_orders) == 1) second_order_id = self.market.limit_orders[0].client_order_id self.assertTrue(second_order_id in {o.order_id: o for o in self.market.order_expirations}) # second limit order expired self.clock.backtest_til(self.start.timestamp() + 60 * 2 + 30) # check if order expired event is fired self.assertTrue(second_order_id in [ evt.order_id for i, evt in strategy.order_expired_events.iterrows() ]) # check if the expired limit order is cleaned up self.verify_expired_order_cleanup(strategy.order_expired_events, self.market.limit_orders) def test_bid_order_expiration_clean_up(self): ts_1 = pd.Timestamp("2019-01-24 00:12:15+00:00").timestamp() ts_2 = pd.Timestamp("2019-01-24 00:12:20+00:00").timestamp() trades = { ts_1: [(self.market_name, "buy", 100, 55, { "expiration_ts": ts_1 + 9 })], ts_2: [(self.market_name, "buy", 100, 50, { "expiration_ts": ts_2 + 9 }), (self.market_name, "buy", 100, 55, { "expiration_ts": ts_2 + 9 })] } strategy: OrderExpirationTestStrategy = OrderExpirationTestStrategy( self.market, trades) self.clock.add_iterator(strategy) # first limit order made self.clock.backtest_til(self.start.timestamp() + 60 * 12 + 15) first_order_id = self.market.limit_orders[0].client_order_id self.assertTrue(len(self.market.limit_orders) == 1) self.assertTrue(first_order_id in {o.order_id: o for o in self.market.order_expirations}) # second limit order made self.clock.backtest_til(self.start.timestamp() + 60 * 12 + 20) self.assertTrue(len(self.market.limit_orders) == 3) # first limit order expired self.clock.backtest_til(self.start.timestamp() + 60 * 12 + 25) # check if order expired event is fired self.assertTrue(first_order_id in [ evt.order_id for i, evt in strategy.order_expired_events.iterrows() ]) # check if the expired limit order is cleaned up self.verify_expired_order_cleanup(strategy.order_expired_events, self.market.limit_orders) self.assertTrue(len(self.market.limit_orders) == 2) second_order_id_1 = self.market.limit_orders[0].client_order_id second_order_id_2 = self.market.limit_orders[1].client_order_id self.assertTrue(second_order_id_1 in {o.order_id: o for o in self.market.order_expirations}) self.assertTrue(second_order_id_2 in {o.order_id: o for o in self.market.order_expirations}) # second limit order expired self.clock.backtest_til(self.start.timestamp() + 60 * 12 + 30) # check if order expired event is fired self.assertTrue(second_order_id_1 in [ evt.order_id for i, evt in strategy.order_expired_events.iterrows() ]) self.assertTrue(second_order_id_2 in [ evt.order_id for i, evt in strategy.order_expired_events.iterrows() ]) # check if the expired limit order is cleaned up self.verify_expired_order_cleanup(strategy.order_expired_events, self.market.limit_orders)
class CompositeOrderBookTest(unittest.TestCase): start: pd.Timestamp = pd.Timestamp("2019-01-25", tz="UTC") end: pd.Timestamp = pd.Timestamp("2019-01-26", tz="UTC") def setUp(self): self.weth_dai_data = DDEXOrderBookLoader("WETH-DAI", "WETH", "DAI") self.clock = Clock(ClockMode.BACKTEST, 1.0, self.start.timestamp(), self.end.timestamp()) self.market = BacktestMarket() self.market.add_data(self.weth_dai_data) self.market.set_balance("WETH", 200.0) self.market.set_balance("DAI", 20000.0) self.clock.add_iterator(self.market) def tearDown(self): self.weth_dai_data.close() def verify_filled_order_recorded(self, recorded_filled_events, composite_order_book): bid_dict = {entry.price: (entry.amount, entry.update_id) for entry in composite_order_book.traded_order_book.bid_entries()} ask_dict = {entry.price: (entry.amount, entry.update_id) for entry in composite_order_book.traded_order_book.ask_entries()} for index, fill_event in recorded_filled_events.iterrows(): if fill_event.trade_type is TradeType.SELL: self.assertTrue(fill_event.price in bid_dict) self.assertTrue(bid_dict[fill_event.price][0] == fill_event.amount) self.assertTrue(bid_dict[fill_event.price][1] == fill_event.timestamp) elif fill_event.trade_type is TradeType.BUY: self.assertTrue(fill_event.price in ask_dict) self.assertTrue(ask_dict[fill_event.price][0] == fill_event.amount) self.assertTrue(ask_dict[fill_event.price][1] == fill_event.timestamp) def verify_composite_order_book_correctness(self, composite_order_book): filled_bid_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.traded_order_book.bid_entries()} filled_ask_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.traded_order_book.ask_entries()} composite_bid_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.bid_entries()} composite_ask_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.ask_entries()} original_bid_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.original_bid_entries()} original_ask_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.original_ask_entries()} for filled_bid_price, filled_bid_amount in filled_bid_dict.items(): if filled_bid_price in original_bid_dict: if (original_bid_dict[filled_bid_price] - filled_bid_amount) <= 0: self.assertTrue(filled_bid_price not in composite_bid_dict) else: self.assertTrue(composite_bid_dict[filled_bid_price] == original_bid_dict[filled_bid_price] - filled_bid_amount) for filled_ask_price, filled_ask_amount in filled_ask_dict.items(): if filled_ask_price in original_ask_dict: if (original_bid_dict[filled_ask_price] - filled_ask_amount) <= 0: self.assertTrue(filled_ask_price not in composite_ask_dict) else: self.assertTrue(composite_bid_dict[filled_ask_price] == original_bid_dict[filled_ask_price] - filled_ask_amount) def verify_composite_order_book_cleanup(self, recorded_filled_events, composite_order_book): """ Recorded fill order should be cleaned up when the original order book no longer contain that price entry """ filled_bid_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.traded_order_book.bid_entries()} filled_ask_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.traded_order_book.ask_entries()} original_bid_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.original_bid_entries()} original_ask_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.original_ask_entries()} for index, fill_event in recorded_filled_events.iterrows(): if fill_event.trade_type is TradeType.SELL: if fill_event.price not in original_bid_dict: self.assertTrue(fill_event.price not in filled_bid_dict) elif fill_event.trade_type is TradeType.BUY: if fill_event.price not in original_ask_dict: self.assertTrue(fill_event.price not in filled_ask_dict) def verify_composite_order_book_adjustment(self, composite_order_book): """ Recorded fill order sohuld adjust it's amount to no larger than the original price entries' amount """ filled_bid_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.traded_order_book.bid_entries()} filled_ask_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.traded_order_book.ask_entries()} original_bid_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.original_bid_entries()} original_ask_dict = {o.price: (o.amount, o.update_id) for o in composite_order_book.original_ask_entries()} for filled_bid_price, filled_bid_entry in filled_bid_dict.items(): if filled_bid_price in original_bid_dict: self.assertTrue(original_bid_dict[filled_bid_price][0] >= filled_bid_entry[0]) for filled_ask_price, filled_ask_entry in filled_ask_dict.items(): if filled_ask_price in original_ask_dict: self.assertTrue(original_ask_dict[filled_ask_price][0] >= filled_ask_entry[0]) def test_market_order(self): trades = { pd.Timestamp("2019-01-25 00:00:10+00:00").timestamp(): [ ("WETH-DAI", "buy", 5.0), ("WETH-DAI", "sell", 5.0) ] } strategy: CompositeOrderBookTestStrategy = CompositeOrderBookTestStrategy(self.market, trades) self.clock.add_iterator(strategy) self.clock.backtest_til(self.start.timestamp()+10) self.verify_filled_order_recorded(strategy.order_filled_events, self.market.get_order_book("WETH-DAI")) self.verify_composite_order_book_correctness(self.market.get_order_book("WETH-DAI")) self.clock.backtest_til(self.start.timestamp() + 70) self.verify_composite_order_book_cleanup(strategy.order_filled_events, self.market.get_order_book("WETH-DAI")) def test_composite_order_book_adjustment(self): trades = { pd.Timestamp("2019-01-25 00:02:15+00:00").timestamp(): [ ("WETH-DAI", "sell", 93.53 + 23.65) ] } strategy: CompositeOrderBookTestStrategy = CompositeOrderBookTestStrategy(self.market, trades) self.clock.add_iterator(strategy) self.clock.backtest_til(self.start.timestamp() + 60*2 + 15) self.clock.backtest_til(self.start.timestamp() + 60*2 + 25) self.verify_composite_order_book_adjustment(self.market.get_order_book("WETH-DAI"))