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("2019-01-01", tz="UTC")
    end = pd.Timestamp("2019-01-02", tz="UTC")
    binance_trading_pair = ("ETHUSDT", "ETH", "USDT")
    # ddex_trading_pair = ("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_trading_pair)
    #ddex_loader = DDEXOrderBookLoader(*ddex_trading_pair)

    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 = PureMarketPair(*([binance_market] +
                                   list(binance_trading_pair)))
    strategy = PureMarketMakingStrategy(
        [market_pair],
        order_size=50000,
        bid_place_threshold=0.003,
        ask_place_threshold=0.003,
        logging_options=PureMarketMakingStrategy.OPTION_LOG_ALL)

    clock = Clock(ClockMode.BACKTEST,
                  tick_size=60,
                  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", 100000.0)
    binance_market.set_balance("USDT", 100000000.0)
    ddex_market.set_balance("WETH", 100000.0)
    ddex_market.set_balance("DAI", 1000.0)

    current = start.timestamp()
    step = 60

    while current <= end.timestamp():

        current += step
        clock.backtest_til(current)
        print("clock ticked")

    binance_loader.close()
class PyTimeIteratorUnitTest(unittest.TestCase):

    start_timestamp: float = pd.Timestamp("2021-01-01", tz="UTC").timestamp()
    end_timestamp: float = pd.Timestamp("2022-01-01 01:00:00", tz="UTC").timestamp()
    tick_size: int = 10

    def setUp(self):
        self.py_time_iterator = MockPyTimeIterator()
        self.clock = Clock(ClockMode.BACKTEST, self.tick_size, self.start_timestamp, self.end_timestamp)
        self.clock.add_iterator(self.py_time_iterator)

    def test_current_timestamp(self):
        # On initialization, current_timestamp should be NaN
        self.assertTrue(math.isnan(self.py_time_iterator.current_timestamp))

        self.py_time_iterator.start(self.clock)
        self.clock.backtest_til(self.start_timestamp)
        self.assertEqual(self.start_timestamp, self.py_time_iterator.current_timestamp)

    def test_clock(self):
        # On initialization, clock should be None
        self.assertTrue(self.py_time_iterator.clock is None)

        self.py_time_iterator.start(self.clock)
        self.assertEqual(self.clock, self.py_time_iterator.clock)

    def test_start(self):
        self.py_time_iterator.start(self.clock)
        self.assertEqual(self.clock, self.py_time_iterator.clock)
        self.assertEqual(self.start_timestamp, self.py_time_iterator.current_timestamp)

    def test_stop(self):
        self.py_time_iterator.start(self.clock)
        self.assertEqual(self.clock, self.py_time_iterator.clock)
        self.assertEqual(self.start_timestamp, self.py_time_iterator.current_timestamp)

        self.py_time_iterator.stop(self.clock)
        self.assertTrue(math.isnan(self.py_time_iterator.current_timestamp))
        self.assertTrue(self.py_time_iterator.clock is None)

    def test_tick(self):
        self.py_time_iterator.start(self.clock)
        self.assertEqual(self.start_timestamp, self.py_time_iterator.current_timestamp)

        # c_tick is called within Clock
        self.clock.backtest_til(self.start_timestamp + self.tick_size)
        self.assertEqual(self.start_timestamp + self.tick_size, self.py_time_iterator.current_timestamp)

        self.assertEqual(self.start_timestamp + self.tick_size, self.py_time_iterator.mock_variable)
    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(
Beispiel #4
0
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"))
Beispiel #5
0
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)
Beispiel #6
0
class ClockUnitTest(unittest.TestCase):

    backtest_start_timestamp: float = pd.Timestamp("2021-01-01",
                                                   tz="UTC").timestamp()
    backtest_end_timestamp: float = pd.Timestamp("2021-01-01 01:00:00",
                                                 tz="UTC").timestamp()
    tick_size: int = 1

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()

    def setUp(self):
        super().setUp()
        self.realtime_start_timestamp = time.time()
        self.realtime_end_timestamp = self.realtime_start_timestamp + 2.0  #
        self.clock_realtime = Clock(ClockMode.REALTIME, self.tick_size,
                                    self.realtime_start_timestamp,
                                    self.realtime_end_timestamp)
        self.clock_backtest = Clock(ClockMode.BACKTEST, self.tick_size,
                                    self.backtest_start_timestamp,
                                    self.backtest_end_timestamp)

    def test_clock_mode(self):
        self.assertEqual(ClockMode.REALTIME, self.clock_realtime.clock_mode)
        self.assertEqual(ClockMode.BACKTEST, self.clock_backtest.clock_mode)

    def test_start_time(self):
        self.assertEqual(self.realtime_start_timestamp,
                         self.clock_realtime.start_time)
        self.assertEqual(self.backtest_start_timestamp,
                         self.clock_backtest.start_time)

    def test_tick_time(self):
        self.assertEqual(self.tick_size, self.clock_realtime.tick_size)
        self.assertEqual(self.tick_size, self.clock_backtest.tick_size)

    def test_child_iterators(self):
        # Tests child_iterators property after initialization. See also test_add_iterator
        self.assertEqual(0, len(self.clock_realtime.child_iterators))
        self.assertEqual(0, len(self.clock_backtest.child_iterators))

    def test_current_timestamp(self):
        self.assertEqual(self.backtest_start_timestamp,
                         self.clock_backtest.current_timestamp)
        self.assertAlmostEqual(
            (self.realtime_start_timestamp // self.tick_size) * self.tick_size,
            self.clock_realtime.current_timestamp)

        self.clock_backtest.backtest()
        self.clock_realtime.backtest()

        self.assertEqual(self.backtest_end_timestamp,
                         self.clock_backtest.current_timestamp)
        self.assertLessEqual(self.realtime_end_timestamp,
                             self.clock_realtime.current_timestamp)

    def test_add_iterator(self):
        self.assertEqual(0, len(self.clock_realtime.child_iterators))
        self.assertEqual(0, len(self.clock_backtest.child_iterators))

        time_iterator: TimeIterator = TimeIterator()
        self.clock_realtime.add_iterator(time_iterator)
        self.clock_backtest.add_iterator(time_iterator)

        self.assertEqual(1, len(self.clock_realtime.child_iterators))
        self.assertEqual(time_iterator, self.clock_realtime.child_iterators[0])
        self.assertEqual(1, len(self.clock_backtest.child_iterators))
        self.assertEqual(time_iterator, self.clock_backtest.child_iterators[0])

    def test_remove_iterator(self):
        self.assertEqual(0, len(self.clock_realtime.child_iterators))
        self.assertEqual(0, len(self.clock_backtest.child_iterators))

        time_iterator: TimeIterator = TimeIterator()
        self.clock_realtime.add_iterator(time_iterator)
        self.clock_backtest.add_iterator(time_iterator)

        self.assertEqual(1, len(self.clock_realtime.child_iterators))
        self.assertEqual(time_iterator, self.clock_realtime.child_iterators[0])
        self.assertEqual(1, len(self.clock_backtest.child_iterators))
        self.assertEqual(time_iterator, self.clock_backtest.child_iterators[0])

        self.clock_realtime.remove_iterator(time_iterator)
        self.clock_backtest.remove_iterator(time_iterator)

        self.assertEqual(0, len(self.clock_realtime.child_iterators))
        self.assertEqual(0, len(self.clock_backtest.child_iterators))

    def test_run(self):
        # Note: Technically you do not execute `run()` when in BACKTEST mode

        # Tests EnvironmentError raised when not runnning within a context
        with self.assertRaises(EnvironmentError):
            self.ev_loop.run_until_complete(self.clock_realtime.run())

        # Note: run() will essentially run indefinitely hence the enforced timeout.
        with self.assertRaises(asyncio.TimeoutError), self.clock_realtime:
            self.ev_loop.run_until_complete(
                asyncio.wait_for(self.clock_realtime.run(), 1))

        self.assertLess(self.realtime_start_timestamp,
                        self.clock_realtime.current_timestamp)

    def test_run_til(self):
        # Note: Technically you do not execute `run_til()` when in BACKTEST mode

        # Tests EnvironmentError raised when not runnning within a context
        with self.assertRaises(EnvironmentError):
            self.ev_loop.run_until_complete(
                self.clock_realtime.run_til(self.realtime_end_timestamp))

        with self.clock_realtime:
            self.ev_loop.run_until_complete(
                self.clock_realtime.run_til(self.realtime_end_timestamp))

        self.assertGreaterEqual(self.clock_realtime.current_timestamp,
                                self.realtime_end_timestamp)

    def test_backtest(self):
        # Note: Technically you do not execute `backtest()` when in REALTIME mode

        self.clock_backtest.backtest()
        self.assertGreaterEqual(self.clock_backtest.current_timestamp,
                                self.backtest_end_timestamp)

    def test_backtest_til(self):
        # Note: Technically you do not execute `backtest_til()` when in REALTIME mode

        self.clock_backtest.backtest_til(self.backtest_start_timestamp +
                                         self.tick_size)
        self.assertGreater(self.clock_backtest.current_timestamp,
                           self.clock_backtest.start_time)
        self.assertLess(self.clock_backtest.current_timestamp,
                        self.backtest_end_timestamp)