class TestRules(TestCase): def setUp(self): self.timer = SettableTimer() def test_market_open_rule(self): self.timer.set_current_time( str_to_date("2018-01-01 00:00:00.000000", DateFormat.FULL_ISO)) now = self.timer.now() next_trigger_time = MarketOpenEvent.next_trigger_time(now) self.assertEqual( str_to_date("2018-01-01 09:30:00.000000", DateFormat.FULL_ISO), next_trigger_time) self.timer.set_current_time( str_to_date("2018-01-01 09:29:59.999999", DateFormat.FULL_ISO)) now = self.timer.now() next_trigger_time = MarketOpenEvent.next_trigger_time(now) self.assertEqual( str_to_date("2018-01-01 09:30:00.000000", DateFormat.FULL_ISO), next_trigger_time) self.timer.set_current_time( str_to_date("2018-01-01 09:30:00.000000", DateFormat.FULL_ISO)) now = self.timer.now() next_trigger_time = MarketOpenEvent.next_trigger_time(now) self.assertEqual( str_to_date("2018-01-02 09:30:00.000000", DateFormat.FULL_ISO), next_trigger_time)
class TestDataHandler(TestCase): @classmethod def setUpClass(cls): cls.spx_index_ticker = BloombergTicker("SPX Index") cls.google_ticker = BloombergTicker("GOOGL US Equity") cls.microsoft_ticker = BloombergTicker("MSFT US Equity") cls.start_date = str_to_date("2018-01-02") cls.end_date = str_to_date("2018-01-31") cls.end_date_trimmed = str_to_date("2018-01-30") cls.get_history_field = "PX_TO_BOOK_RATIO" def setUp(self): try: self.price_data_provider = get_data_provider() except Exception as e: raise self.skipTest(e) self.timer = SettableTimer() self.data_handler = DailyDataHandler(self.price_data_provider, self.timer) MarketOpenEvent.set_trigger_time({ "hour": 13, "minute": 30, "second": 0, "microsecond": 0 }) MarketCloseEvent.set_trigger_time({ "hour": 20, "minute": 0, "second": 0, "microsecond": 0 }) def test_get_price_when_end_date_is_in_the_past(self): self.timer.set_current_time( str_to_date("2018-02-12 00:00:00.000000", DateFormat.FULL_ISO)) prices_tms = self.data_handler.get_price(self.spx_index_ticker, PriceField.Close, self.start_date, self.end_date) self.assertEqual(self.start_date, prices_tms.index[0].to_pydatetime()) self.assertEqual(self.end_date, prices_tms.index[-1].to_pydatetime()) def test_get_price_when_end_date_is_today_after_market_close(self): self.timer.set_current_time( str_to_date("2018-01-31") + MarketCloseEvent.trigger_time() + RelativeDelta(hours=1)) prices_tms = self.data_handler.get_price(self.spx_index_ticker, PriceField.Close, self.start_date, self.end_date) self.assertEqual(self.start_date, prices_tms.index[0].to_pydatetime()) self.assertEqual(self.end_date, prices_tms.index[-1].to_pydatetime()) def test_get_price_when_end_date_is_today_before_market_close(self): self.timer.set_current_time( str_to_date("2018-01-31") + MarketOpenEvent.trigger_time() + RelativeDelta(hours=1)) close_prices_tms = self.data_handler.get_price(self.spx_index_ticker, PriceField.Close, self.start_date, self.end_date) self.assertEqual(self.start_date, close_prices_tms.index[0].to_pydatetime()) self.assertEqual(self.end_date_trimmed, close_prices_tms.index[-1].to_pydatetime()) def test_get_open_price_when_end_date_is_today_before_market_close__single_ticker( self): self.timer.set_current_time( str_to_date("2018-01-31") + MarketOpenEvent.trigger_time() + RelativeDelta(hours=1)) current_date = datetime(self.timer.now().year, self.timer.now().month, self.timer.now().day) open_prices_tms = self.data_handler.get_price(self.spx_index_ticker, PriceField.Open, self.start_date, self.timer.now()) self.assertEqual(self.start_date, open_prices_tms.index[0].to_pydatetime()) self.assertEqual(current_date, open_prices_tms.index[-1].to_pydatetime()) prices_df = self.data_handler.get_price( self.spx_index_ticker, [PriceField.Open, PriceField.Close], self.start_date, self.timer.now()) self.assertEqual(self.start_date, prices_df.index[0].to_pydatetime()) self.assertEqual(current_date, prices_df.index[-1].to_pydatetime()) last_bar = prices_df.iloc[-1] self.assertTrue(isnan(last_bar.loc[PriceField.Close])) self.assertIsNotNone(last_bar.loc[PriceField.Open]) def test_get_open_price_when_end_date_is_today_before_market_close__multiple_tickers( self): self.timer.set_current_time( str_to_date("2018-01-31") + MarketOpenEvent.trigger_time() + RelativeDelta(hours=1)) current_date = datetime(self.timer.now().year, self.timer.now().month, self.timer.now().day) tickers = [self.spx_index_ticker, self.microsoft_ticker] open_prices_tms = self.data_handler.get_price(tickers, PriceField.Open, self.start_date, self.timer.now()) self.assertEqual(self.start_date, open_prices_tms.index[0].to_pydatetime()) self.assertEqual(current_date, open_prices_tms.index[-1].to_pydatetime()) prices_data_array = self.data_handler.get_price( tickers, [PriceField.Open, PriceField.Close], self.start_date, self.timer.now()) last_bar_df = prices_data_array.loc[current_date, :, :].to_pandas() self.assertTrue(last_bar_df[PriceField.Close].isna().all()) self.assertTrue(last_bar_df[PriceField.Open].notna().all()) def test_get_price_when_end_date_is_tomorrow(self): self.timer.set_current_time( str_to_date("2018-01-30") + MarketCloseEvent.trigger_time() + RelativeDelta(hours=1)) prices_tms = self.data_handler.get_price(self.spx_index_ticker, PriceField.Close, self.start_date, self.end_date_trimmed) self.assertEqual(self.start_date, prices_tms.index[0].to_pydatetime()) self.assertEqual(self.end_date_trimmed, prices_tms.index[-1].to_pydatetime()) def test_get_last_price_single_ticker(self): # just test if getting single ticker value works, when a single ticker (not wrapped in a list) is passed self.timer.set_current_time( str_to_date("2018-01-31") + MarketOpenEvent.trigger_time() + RelativeDelta(hours=1)) single_price = self.data_handler.get_last_available_price( self.spx_index_ticker) self.assertTrue(isinstance(single_price, float)) # at market open self.timer.set_current_time( str_to_date("2018-01-31") + MarketOpenEvent.trigger_time()) at_market_open = self.data_handler.get_last_available_price( [self.spx_index_ticker]) self.assertEqual(self.spx_index_ticker, at_market_open.index[0]) self.assertEqual(single_price, at_market_open[0]) # during the trading session self.timer.set_current_time( str_to_date("2018-01-31") + MarketOpenEvent.trigger_time() + RelativeDelta(hours=1)) during_the_day_last_prices = self.data_handler.get_last_available_price( [self.spx_index_ticker]) self.assertEqual(self.spx_index_ticker, during_the_day_last_prices.index[0]) self.assertEqual(single_price, during_the_day_last_prices[0]) # after the trading session self.timer.set_current_time( str_to_date("2018-01-31") + MarketCloseEvent.trigger_time() + RelativeDelta(hours=1)) after_close_last_prices = self.data_handler.get_last_available_price( [self.spx_index_ticker]) self.assertEqual(self.spx_index_ticker, after_close_last_prices.index[0]) self.assertNotEqual(during_the_day_last_prices[0], after_close_last_prices[0]) # before the trading session self.timer.set_current_time( str_to_date("2018-01-31") + MarketOpenEvent.trigger_time() - RelativeDelta(hours=1)) before_trading_session_prices = self.data_handler.get_last_available_price( [self.spx_index_ticker]) self.assertEqual(self.spx_index_ticker, before_trading_session_prices.index[0]) self.assertNotEqual(during_the_day_last_prices[0], before_trading_session_prices[0]) self.assertNotEqual(after_close_last_prices[0], before_trading_session_prices[0]) def test_get_last_price_with_multiple_tickers_when_current_data_is_unavailable( self): self.timer.set_current_time( str_to_date("2018-01-01") + MarketOpenEvent.trigger_time() + RelativeDelta(hours=1)) last_prices = self.data_handler.get_last_available_price( [self.spx_index_ticker, self.google_ticker]) self.assertEqual(self.spx_index_ticker, last_prices.index[0]) self.assertEqual(self.google_ticker, last_prices.index[1]) def test_get_last_price_with_empty_tickers_list(self): self.timer.set_current_time( str_to_date("2018-01-31") + MarketOpenEvent.trigger_time() + RelativeDelta(hours=1)) last_prices = self.data_handler.get_last_available_price([]) assert_series_equal(QFSeries(), last_prices) def test_get_history_when_end_date_is_in_the_past(self): self.timer.set_current_time( str_to_date("2018-02-12 00:00:00.000000", DateFormat.FULL_ISO)) prices_tms = self.data_handler.get_history(self.spx_index_ticker, self.get_history_field, self.start_date, self.end_date) self.assertEqual(self.start_date, prices_tms.index[0].to_pydatetime()) self.assertEqual(self.end_date, prices_tms.index[-1].to_pydatetime()) def test_get_history_when_end_date_is_today_after_market_close(self): self.timer.set_current_time( str_to_date("2018-01-31") + MarketCloseEvent.trigger_time() + RelativeDelta(hours=1)) prices_tms = self.data_handler.get_history(self.spx_index_ticker, self.get_history_field, self.start_date, self.end_date) self.assertEqual(self.start_date, prices_tms.index[0].to_pydatetime()) self.assertEqual(self.end_date, prices_tms.index[-1].to_pydatetime()) def test_get_history_when_end_date_is_today_before_market_close(self): self.timer.set_current_time( str_to_date("2018-01-31") + MarketOpenEvent.trigger_time() + RelativeDelta(hours=1)) prices_tms = self.data_handler.get_history(self.spx_index_ticker, self.get_history_field, self.start_date, self.end_date) self.assertEqual(self.start_date, prices_tms.index[0].to_pydatetime()) self.assertEqual(self.end_date_trimmed, prices_tms.index[-1].to_pydatetime()) def test_get_history_when_end_date_is_tomorrow(self): self.timer.set_current_time( str_to_date("2018-01-30") + MarketCloseEvent.trigger_time() + RelativeDelta(hours=1)) prices_tms = self.data_handler.get_history(self.spx_index_ticker, self.get_history_field, self.start_date, self.end_date_trimmed) self.assertEqual(self.start_date, prices_tms.index[0].to_pydatetime()) self.assertEqual(self.end_date_trimmed, prices_tms.index[-1].to_pydatetime()) def test_get_history_with_multiple_tickers(self): self.timer.set_current_time( str_to_date("2018-01-31") + MarketOpenEvent.trigger_time() + RelativeDelta(hours=1)) resilt_df = self.data_handler.get_history( [self.microsoft_ticker, self.google_ticker], self.get_history_field, self.start_date, self.end_date_trimmed) self.assertEqual(self.microsoft_ticker, resilt_df.columns[0]) self.assertEqual(self.google_ticker, resilt_df.columns[1]) self.assertEqual(self.start_date, resilt_df.index[0].to_pydatetime()) self.assertEqual(self.end_date_trimmed, resilt_df.index[-1].to_pydatetime()) self.assertEqual(resilt_df.shape, (20, 2)) def test_historical_price_many_tickers_many_fields(self): self.timer.set_current_time( str_to_date("2018-01-31") + MarketOpenEvent.trigger_time() + RelativeDelta(hours=1)) result_array = self.data_handler.historical_price( [self.microsoft_ticker], [PriceField.Open, PriceField.Close], nr_of_bars=5) self.assertEqual(QFDataArray, type(result_array)) self.assertEqual((5, 1, 2), result_array.shape) expected_dates_str = [ "2018-01-24", "2018-01-25", "2018-01-26", "2018-01-29", "2018-01-30" ] expected_dates = [ str_to_date(date_str) for date_str in expected_dates_str ] assert_same_index(pd.DatetimeIndex(expected_dates, name=DATES), result_array.dates.to_index(), check_index_type=True, check_names=True) def test_historical_price_many_tickers_one_field(self): self.timer.set_current_time( str_to_date("2018-01-04") + MarketOpenEvent.trigger_time() + RelativeDelta(hours=1)) result_df = self.data_handler.historical_price([self.microsoft_ticker], PriceField.Open, nr_of_bars=5) self.assertEqual(PricesDataFrame, type(result_df)) expected_dates_idx = pd.DatetimeIndex([ '2017-12-27', '2017-12-28', '2017-12-29', '2018-01-02', '2018-01-03' ], name=DATES) assert_same_index(expected_dates_idx, result_df.index, check_index_type=True, check_names=True) expected_tickers_idx = pd.Index([self.microsoft_ticker], name=TICKERS) assert_same_index(expected_tickers_idx, result_df.columns, check_index_type=True, check_names=True)
class TestMarketOnOpenExecutionStyle(TestCase): MarketOpenEvent.set_trigger_time({ "hour": 13, "minute": 30, "second": 0, "microsecond": 0 }) MarketCloseEvent.set_trigger_time({ "hour": 20, "minute": 0, "second": 0, "microsecond": 0 }) def setUp(self): self.scheduling_time_delay = 1 start_date = str_to_date("2018-02-04") before_close = start_date + MarketCloseEvent.trigger_time( ) - RelativeDelta(minutes=self.scheduling_time_delay) self.timer = SettableTimer(initial_time=before_close) contracts_to_tickers_mapper = SimulatedBloombergContractTickerMapper() msft_contract = Contract("MSFT US Equity", security_type='STK', exchange='TEST') self.msft_ticker = contracts_to_tickers_mapper.contract_to_ticker( msft_contract) self.data_handler = Mock(spec=DataHandler) self.scheduler = Mock(spec=Scheduler) self.commission_model = FixedCommissionModel(commission=0.0) self.monitor = Mock(spec=AbstractMonitor) self.portfolio = Mock(spec=Portfolio) slippage_model = PriceBasedSlippage(0.0, self.data_handler, contracts_to_tickers_mapper) self.exec_handler = SimulatedExecutionHandler( self.data_handler, self.timer, self.scheduler, self.monitor, self.commission_model, contracts_to_tickers_mapper, self.portfolio, slippage_model, RelativeDelta(minutes=self.scheduling_time_delay)) self.order_1 = Order(msft_contract, quantity=10, execution_style=MarketOrder(), time_in_force=TimeInForce.OPG) self.order_2 = Order(msft_contract, quantity=-5, execution_style=MarketOrder(), time_in_force=TimeInForce.OPG) self.order_3 = Order(msft_contract, quantity=-7, execution_style=MarketOrder(), time_in_force=TimeInForce.OPG) self.order_4 = Order(msft_contract, quantity=4, execution_style=MarketOnCloseOrder(), time_in_force=TimeInForce.DAY) def _trigger_single_time_event(self): self.timer.set_current_time(self.timer.now() + RelativeDelta( minutes=self.scheduling_time_delay)) event = ScheduleOrderExecutionEvent() self.exec_handler.on_orders_accept(event) def test_1_order_fill(self): self.exec_handler.assign_order_ids([self.order_1]) self._set_current_price(101) self._trigger_single_time_event() self.exec_handler.on_market_open(...) self.monitor.record_transaction.assert_called_once() self.portfolio.transact_transaction.assert_called_once() actual_orders = self.exec_handler.get_open_orders() expected_orders = [] assert_lists_equal(expected_orders, actual_orders) def test_3_orders_fill(self): self.exec_handler.assign_order_ids( [self.order_1, self.order_2, self.order_3]) self._set_current_price(101) self._trigger_single_time_event() self.exec_handler.on_market_open(...) self.assertEqual(self.monitor.record_transaction.call_count, 3) self.assertEqual(self.portfolio.transact_transaction.call_count, 3) actual_orders = self.exec_handler.get_open_orders() expected_orders = [] assert_lists_equal(expected_orders, actual_orders) def test_3_orders_fill_only_at_open(self): self.exec_handler.assign_order_ids( [self.order_1, self.order_2, self.order_3]) self._set_current_price(101) self._trigger_single_time_event() self.exec_handler.on_market_close(...) self.portfolio.transact_transaction.assert_not_called() self.monitor.record_transaction.assert_not_called() actual_orders = self.exec_handler.get_open_orders() expected_orders = [self.order_1, self.order_2, self.order_3] self.assertCountEqual(expected_orders, actual_orders) def test_fill_open_and_close(self): self.exec_handler.assign_order_ids([self.order_1, self.order_2]) self.exec_handler.assign_order_ids([self.order_2, self.order_3]) self.exec_handler.assign_order_ids([self.order_3, self.order_4]) self.exec_handler.assign_order_ids([self.order_4, self.order_4]) self._set_current_price(101) self._trigger_single_time_event() self.exec_handler.on_market_open(...) self.assertEqual(self.monitor.record_transaction.call_count, 3) self.assertEqual(self.portfolio.transact_transaction.call_count, 3) actual_orders = self.exec_handler.get_open_orders() expected_orders = [self.order_4] assert_lists_equal(expected_orders, actual_orders) self.exec_handler.on_market_close(...) self.assertEqual(self.monitor.record_transaction.call_count, 4) self.assertEqual(self.portfolio.transact_transaction.call_count, 4) actual_orders = self.exec_handler.get_open_orders() expected_orders = [] assert_lists_equal(expected_orders, actual_orders) def test_fill_close_and_open(self): self.exec_handler.assign_order_ids([self.order_1, self.order_2]) self.exec_handler.assign_order_ids([self.order_2, self.order_3]) self.exec_handler.assign_order_ids([self.order_3, self.order_4]) self.exec_handler.assign_order_ids([self.order_4, self.order_4]) self._set_current_price(101) self._trigger_single_time_event() self.exec_handler.on_market_close(...) # Transaction related to order 4 will be executed only once, as only one Order object was passed self.monitor.record_transaction.assert_called_once() self.portfolio.transact_transaction.assert_called_once() actual_orders = self.exec_handler.get_open_orders() expected_orders = [self.order_1, self.order_2, self.order_3] self.assertCountEqual(expected_orders, actual_orders) self.exec_handler.on_market_open(...) self.assertEqual(self.monitor.record_transaction.call_count, 4) self.assertEqual(self.portfolio.transact_transaction.call_count, 4) actual_orders = self.exec_handler.get_open_orders() expected_orders = [] self.assertCountEqual(expected_orders, actual_orders) def test_market_open_transaction(self): order = self.order_1 price = 102 self.exec_handler.assign_order_ids([order]) self._set_current_price(price) self._trigger_single_time_event() self.exec_handler.on_market_open(...) timestamp = self.timer.now() contract = order.contract quantity = order.quantity commission = self.commission_model.calculate_commission(order, price) expected_transaction = Transaction(timestamp, contract, quantity, price, commission) self.monitor.record_transaction.assert_called_once_with( expected_transaction) def test_market_close_transaction(self): order = self.order_4 price = 102 self.exec_handler.assign_order_ids([self.order_1, order]) self._set_current_price(price) self._trigger_single_time_event() self.exec_handler.on_market_close(...) timestamp = self.timer.now() contract = order.contract quantity = order.quantity commission = self.commission_model.calculate_commission(order, price) expected_transaction = Transaction(timestamp, contract, quantity, price, commission) self.monitor.record_transaction.assert_called_once_with( expected_transaction) def test_market_close_does_not_trade(self): price = None self.exec_handler.assign_order_ids([self.order_1, self.order_4]) self._set_current_price(price) self._trigger_single_time_event() self.exec_handler.on_market_close(...) self.portfolio.transact_transaction.assert_not_called() self.monitor.record_transaction.assert_not_called() def test_market_open_does_not_trade(self): price = None self.exec_handler.assign_order_ids([self.order_1, self.order_4]) self._set_current_price(price) self._trigger_single_time_event() self.exec_handler.on_market_open(...) self.portfolio.transact_transaction.assert_not_called() self.monitor.record_transaction.assert_not_called() def _set_last_available_price(self, price): self.data_handler.get_last_available_price.side_effect = lambda t: QFSeries( data=[price], index=pd.Index([self.msft_ticker])) def _set_current_price(self, price): self.data_handler.get_current_price.side_effect = lambda t: QFSeries( data=[price], index=pd.Index([self.msft_ticker]))
class TestStopLossExecutionStyle(TestCase): MSFT_TICKER_STR = "MSFT US Equity" def setUp(self): MarketOpenEvent.set_trigger_time({ "hour": 13, "minute": 30, "second": 0, "microsecond": 0 }) MarketCloseEvent.set_trigger_time({ "hour": 20, "minute": 0, "second": 0, "microsecond": 0 }) self.start_date = str_to_date("2018-02-04") self.number_of_minutes = 5 before_close = self.start_date + MarketCloseEvent.trigger_time( ) - RelativeDelta(minutes=self.number_of_minutes) self.msft_contract = Contract(self.MSFT_TICKER_STR, security_type='STK', exchange='TEST') self.msft_ticker = BloombergTicker(self.MSFT_TICKER_STR) contracts_to_tickers_mapper = SimulatedBloombergContractTickerMapper() self.timer = SettableTimer(initial_time=before_close) self.data_handler = Mock(spec=DataHandler) scheduler = Mock(spec=Scheduler) ScheduleOrderExecutionEvent.clear() # Set the periodic bar events to intraday trading IntradayBarEvent.frequency = Frequency.MIN_1 commission_model = FixedCommissionModel(commission=0.0) self.monitor = Mock(spec=AbstractMonitor) self.portfolio = Mock(spec=Portfolio) slippage_model = PriceBasedSlippage(0.0, self.data_handler, contracts_to_tickers_mapper) self.exec_handler = SimulatedExecutionHandler( self.data_handler, self.timer, scheduler, self.monitor, commission_model, contracts_to_tickers_mapper, self.portfolio, slippage_model, RelativeDelta(minutes=self.number_of_minutes)) self._set_last_available_price(100.0) self.stop_loss_order_1 = Order(self.msft_contract, quantity=-1, execution_style=StopOrder(95.0), time_in_force=TimeInForce.GTC) self.stop_loss_order_2 = Order(self.msft_contract, quantity=-1, execution_style=StopOrder(90.0), time_in_force=TimeInForce.GTC) self.stop_loss_order_3 = Order(self.msft_contract, quantity=-1, execution_style=StopOrder(50.0), time_in_force=TimeInForce.DAY) self.exec_handler.assign_order_ids([ self.stop_loss_order_1, self.stop_loss_order_2, self.stop_loss_order_3 ]) def _trigger_single_time_event(self): self.timer.set_current_time(self.timer.now() + RelativeDelta( minutes=self.number_of_minutes)) event = ScheduleOrderExecutionEvent() self.exec_handler.on_orders_accept(event) def test_day_order_disappears_after_a_day(self): self._set_bar_for_today(open_price=105.0, high_price=110.0, low_price=100.0, close_price=105.0, volume=100000000) self._trigger_single_time_event() expected_orders = [ self.stop_loss_order_1, self.stop_loss_order_2, self.stop_loss_order_3 ] actual_orders = self.exec_handler.get_open_orders() assert_lists_equal(expected_orders, actual_orders) self.exec_handler.on_market_close(...) self.portfolio.transact_transaction.assert_not_called() self.monitor.record_transaction.assert_not_called() actual_orders = self.exec_handler.get_open_orders() expected_orders = [self.stop_loss_order_1, self.stop_loss_order_2] assert_lists_equal(expected_orders, actual_orders) def test_no_orders_executed_on_market_open(self): # Move before the market open event self._set_bar_for_today(open_price=105.0, high_price=110.0, low_price=100.0, close_price=105.0, volume=100000000.0) self.timer.set_current_time(self.timer.now() + MarketOpenEvent.trigger_time() - RelativeDelta( minutes=self.number_of_minutes)) self.exec_handler.assign_order_ids([ self.stop_loss_order_1, self.stop_loss_order_2, self.stop_loss_order_3 ]) # Trigger the order execution event (the function also forwards time into the future) self._trigger_single_time_event() self.exec_handler.on_market_open(...) self.portfolio.transact_transaction.assert_not_called() self.monitor.record_transaction.assert_not_called() actual_orders = self.exec_handler.get_open_orders() expected_orders = [ self.stop_loss_order_1, self.stop_loss_order_2, self.stop_loss_order_3 ] assert_lists_equal(expected_orders, actual_orders) def test_order_not_executed_when_stop_price_not_hit(self): self._set_bar_for_today(open_price=105.0, high_price=110.0, low_price=100.0, close_price=105.0, volume=100000000.0) self._trigger_single_time_event() self.exec_handler.on_market_close(...) self.portfolio.transact_transaction.assert_not_called() self.monitor.record_transaction.assert_not_called() actual_orders = self.exec_handler.get_open_orders() expected_orders = [self.stop_loss_order_1, self.stop_loss_order_2] assert_lists_equal(expected_orders, actual_orders) def test_order_not_executed_when_bar_for_today_is_incomplete(self): self._set_bar_for_today(open_price=None, high_price=110.0, low_price=100.0, close_price=105.0, volume=100000000.0) self._trigger_single_time_event() self.exec_handler.on_market_close(...) self.portfolio.transact_transaction.assert_not_called() self.monitor.record_transaction.assert_not_called() actual_orders = self.exec_handler.get_open_orders() expected_orders = [self.stop_loss_order_1, self.stop_loss_order_2] assert_lists_equal(expected_orders, actual_orders) def test_one_order_executed_when_one_stop_price_hit(self): self._set_bar_for_today(open_price=100.0, high_price=110.0, low_price=94.0, close_price=105.0, volume=100000000.0) self._trigger_single_time_event() self.exec_handler.on_market_close(...) assert_lists_equal([self.stop_loss_order_2], self.exec_handler.get_open_orders()) expected_transaction = Transaction( self.timer.now(), self.msft_contract, -1, self.stop_loss_order_1.execution_style.stop_price, 0.0) self.monitor.record_transaction.assert_called_once_with( expected_transaction) self.portfolio.transact_transaction.assert_called_once_with( expected_transaction) def test_both_orders_executed_when_both_stop_prices_hit(self): self._set_bar_for_today(open_price=100.0, high_price=110.0, low_price=90.0, close_price=105.0, volume=100000000.0) self._trigger_single_time_event() self.exec_handler.on_market_close(...) assert_lists_equal([], self.exec_handler.get_open_orders()) expected_transactions = [ Transaction(self.timer.now(), self.msft_contract, -1, self.stop_loss_order_1.execution_style.stop_price, 0), Transaction(self.timer.now(), self.msft_contract, -1, self.stop_loss_order_2.execution_style.stop_price, 0), ] self.monitor.record_transaction.assert_has_calls( call(t) for t in expected_transactions) self.portfolio.transact_transaction.assert_has_calls( call(t) for t in expected_transactions) self.assertEqual(self.monitor.record_transaction.call_count, 2) self.assertEqual(self.portfolio.transact_transaction.call_count, 2) def test_market_opens_at_much_lower_price_than_it_closed_at_yesterday( self): self._set_bar_for_today(open_price=70.0, high_price=100.0, low_price=68.0, close_price=90.0, volume=100000000.0) self._trigger_single_time_event() self.exec_handler.on_market_close(...) assert_lists_equal([], self.exec_handler.get_open_orders()) expected_transactions = [ Transaction(self.timer.now(), self.msft_contract, -1, 70.0, 0), Transaction(self.timer.now(), self.msft_contract, -1, 70.0, 0), ] self.monitor.record_transaction.assert_has_calls( call(t) for t in expected_transactions) self.portfolio.transact_transaction.assert_has_calls( call(t) for t in expected_transactions) self.assertEqual(self.monitor.record_transaction.call_count, 2) self.assertEqual(self.portfolio.transact_transaction.call_count, 2) def test_market_opens_at_much_higher_price_than_it_closed_at_yesterday( self): self.buy_stop_loss_order = Order(self.msft_contract, quantity=1, execution_style=StopOrder(120.0), time_in_force=TimeInForce.GTC) self.exec_handler.assign_order_ids([self.buy_stop_loss_order]) self._set_bar_for_today(open_price=120.0, high_price=130.0, low_price=68.0, close_price=90.0, volume=100000000.0) self._trigger_single_time_event() self.exec_handler.on_market_close(...) assert_lists_equal([], self.exec_handler.get_open_orders()) expected_transactions = [ Transaction(self.timer.now(), self.msft_contract, -1, self.stop_loss_order_1.execution_style.stop_price, 0), Transaction(self.timer.now(), self.msft_contract, -1, self.stop_loss_order_2.execution_style.stop_price, 0), Transaction(self.timer.now(), self.msft_contract, 1, 120, 0), ] self.monitor.record_transaction.assert_has_calls( call(t) for t in expected_transactions) self.portfolio.transact_transaction.assert_has_calls( call(t) for t in expected_transactions) self.assertEqual(self.monitor.record_transaction.call_count, 3) self.assertEqual(self.portfolio.transact_transaction.call_count, 3) def _set_last_available_price(self, price): self.data_handler.get_last_available_price.side_effect = lambda tickers: QFSeries( data=[price], index=pd.Index(tickers), name=self.start_date) def _set_bar_for_today(self, open_price, high_price, low_price, close_price, volume): self.data_handler.get_current_bar.side_effect = lambda tickers: pd.DataFrame( index=pd.Index(tickers), columns=PriceField.ohlcv(), data=[[open_price, high_price, low_price, close_price, volume]])
class TestRules(TestCase): def setUp(self): self.timer = SettableTimer() MarketOpenEvent.set_trigger_time({"hour": 13, "minute": 30, "second": 0, "microsecond": 0}) MarketCloseEvent.set_trigger_time({"hour": 20, "minute": 0, "second": 0, "microsecond": 0}) def test_market_open_rule(self): market_open_event = MarketOpenEvent() self.timer.set_current_time(str_to_date("2018-01-01 00:00:00.000000", DateFormat.FULL_ISO)) now = self.timer.now() next_trigger_time = market_open_event.next_trigger_time(now) self.assertEqual(str_to_date("2018-01-01 13:30:00.000000", DateFormat.FULL_ISO), next_trigger_time) self.timer.set_current_time(str_to_date("2018-01-01 09:29:59.999999", DateFormat.FULL_ISO)) now = self.timer.now() next_trigger_time = market_open_event.next_trigger_time(now) self.assertEqual(str_to_date("2018-01-01 13:30:00.000000", DateFormat.FULL_ISO), next_trigger_time) self.timer.set_current_time(str_to_date("2018-01-01 13:30:00.000000", DateFormat.FULL_ISO)) now = self.timer.now() next_trigger_time = market_open_event.next_trigger_time(now) self.assertEqual(str_to_date("2018-01-02 13:30:00.000000", DateFormat.FULL_ISO), next_trigger_time) def test_periodic_events(self): self.timer.set_current_time(str_to_date("2018-01-01 13:23:00.000000", DateFormat.FULL_ISO)) now = self.timer.now() class MinuteBarEvent(PeriodicEvent): frequency = Frequency.MIN_1 start_time = {"hour": 8, "minute": 0, "second": 0} end_time = {"hour": 16, "minute": 0, "second": 0} def notify(self, listener): pass class Minutes15BarEvent(PeriodicEvent): frequency = Frequency.MIN_15 start_time = {"hour": 8, "minute": 0, "second": 0} end_time = {"hour": 16, "minute": 0, "second": 0} def notify(self, listener): pass self.assertEqual(now + RelativeDelta(minutes=1), MinuteBarEvent().next_trigger_time(now)) self.assertEqual(now + RelativeDelta(minute=30), Minutes15BarEvent().next_trigger_time(now)) self.timer.set_current_time(str_to_date("2018-01-01 23:23:00.000000", DateFormat.FULL_ISO)) now = self.timer.now() start_time = str_to_date("2018-01-02 08:00:00.000000", DateFormat.FULL_ISO) self.assertEqual(start_time, MinuteBarEvent().next_trigger_time(now)) self.assertEqual(start_time, Minutes15BarEvent().next_trigger_time(now)) def test_periodic_events_short_time_range(self): self.timer.set_current_time(str_to_date("2018-01-01 9:30:00.000000", DateFormat.FULL_ISO)) now = self.timer.now() class Periodic15MinutesEvent(PeriodicEvent): frequency = Frequency.MIN_15 start_time = {"hour": 9, "minute": 45, "second": 0} end_time = {"hour": 10, "minute": 10, "second": 0} def notify(self, listener): pass periodic_15_minutes_event = Periodic15MinutesEvent() now = periodic_15_minutes_event.next_trigger_time(now) self.assertEqual(str_to_date("2018-01-01 9:45:00.000000", DateFormat.FULL_ISO), now) now = periodic_15_minutes_event.next_trigger_time(now) self.assertEqual(str_to_date("2018-01-01 10:00:00.000000", DateFormat.FULL_ISO), now) now = periodic_15_minutes_event.next_trigger_time(now) self.assertEqual(str_to_date("2018-01-02 9:45:00.000000", DateFormat.FULL_ISO), now) def test_single_time_events(self): self.timer.set_current_time(str_to_date("2018-01-01 13:00:00.000000", DateFormat.FULL_ISO)) now = self.timer.now() # Schedule a few new events date_times = ( str_to_date("2018-01-01 13:15:00.000000", DateFormat.FULL_ISO), str_to_date("2018-01-01 13:30:00.000000", DateFormat.FULL_ISO), str_to_date("2018-01-01 13:45:00.000000", DateFormat.FULL_ISO), str_to_date("2018-01-01 12:45:00.000000", DateFormat.FULL_ISO) ) for date_time in date_times: SingleTimeEvent.schedule_new_event(date_time, None) self.assertEqual(str_to_date("2018-01-01 13:15:00.000000", DateFormat.FULL_ISO), SingleTimeEvent().next_trigger_time(now)) self.timer.set_current_time(SingleTimeEvent().next_trigger_time(now)) now = self.timer.now() self.assertEqual(str_to_date("2018-01-01 13:30:00.000000", DateFormat.FULL_ISO), SingleTimeEvent().next_trigger_time(now)) self.timer.set_current_time(str_to_date("2018-01-01 13:44:50.000000", DateFormat.FULL_ISO)) now = self.timer.now() self.assertEqual(str_to_date("2018-01-01 13:45:00.000000", DateFormat.FULL_ISO), SingleTimeEvent().next_trigger_time(now)) self.timer.set_current_time(SingleTimeEvent().next_trigger_time(now)) now = self.timer.now() self.assertEqual(None, SingleTimeEvent().next_trigger_time(now)) def test_simultaneous_time_event(self): def schedule_events(): SingleTimeEvent.schedule_new_event(str_to_date("2017-04-10 14:00:00.000000", DateFormat.FULL_ISO), 1) SingleTimeEvent.schedule_new_event(str_to_date("2017-04-10 14:00:00.000000", DateFormat.FULL_ISO), 2) self.assertRaises(ValueError, schedule_events)
class TestMarketOnOpenExecutionStyle(TestCase): MSFT_TICKER_STR = "MSFT US Equity" def setUp(self): self.start_date = str_to_date("2018-02-04") self.msft_contract = Contract(self.MSFT_TICKER_STR, security_type='SEK', exchange='TEST') self.msft_ticker = BloombergTicker(self.MSFT_TICKER_STR) self.contracts_to_tickers_mapper = DummyBloombergContractTickerMapper() self.timer = SettableTimer(initial_time=self.start_date) self.data_handler = mock(strict=True) self.scheduler = mock() self.commission_model = FixedCommissionModel(commission=0.0) self.monitor = _MonitorMock() self.spied_monitor = spy(self.monitor) self.portfolio = mock() slippage_model = PriceBasedSlippage(0.0) self.exec_hanlder = SimulatedExecutionHandler( self.data_handler, self.timer, self.scheduler, self.spied_monitor, self.commission_model, self.contracts_to_tickers_mapper, self.portfolio, slippage_model) self._set_last_msft_price(100.0) self.order_1 = Order(self.msft_contract, quantity=10, execution_style=MarketOrder(), time_in_force=TimeInForce.OPG) self.order_2 = Order(self.msft_contract, quantity=-5, execution_style=MarketOrder(), time_in_force=TimeInForce.OPG) self.order_3 = Order(self.msft_contract, quantity=-7, execution_style=MarketOrder(), time_in_force=TimeInForce.OPG) self.order_4 = Order(self.msft_contract, quantity=4, execution_style=MarketOnCloseOrder(), time_in_force=TimeInForce.DAY) def test_1_order_fill(self): self.exec_hanlder.accept_orders([self.order_1]) self._set_price_for_now(101) self.exec_hanlder.on_market_open(...) verify(self.spied_monitor, times=1).record_transaction(...) verify(self.portfolio, times=1).transact_transaction(...) actual_orders = self.exec_hanlder.get_open_orders() expected_orders = [] assert_lists_equal(expected_orders, actual_orders) def test_3_orders_fill(self): self.exec_hanlder.accept_orders( [self.order_1, self.order_2, self.order_3]) self._set_price_for_now(101) self.exec_hanlder.on_market_open(...) verify(self.spied_monitor, times=3).record_transaction(...) verify(self.portfolio, times=3).transact_transaction(...) actual_orders = self.exec_hanlder.get_open_orders() expected_orders = [] assert_lists_equal(expected_orders, actual_orders) def test_3_orders_fill_only_at_open(self): self.exec_hanlder.accept_orders( [self.order_1, self.order_2, self.order_3]) self._set_price_for_now(101) self.exec_hanlder.on_market_close(...) verifyZeroInteractions(self.portfolio, self.spied_monitor) actual_orders = self.exec_hanlder.get_open_orders() expected_orders = [self.order_1, self.order_2, self.order_3] assert_lists_equal(expected_orders, actual_orders) def test_fill_open_and_close(self): self.exec_hanlder.accept_orders([self.order_1, self.order_2]) self.exec_hanlder.accept_orders([self.order_2, self.order_3]) self.exec_hanlder.accept_orders([self.order_3, self.order_4]) self.exec_hanlder.accept_orders([self.order_4, self.order_4]) self._set_price_for_now(101) self.exec_hanlder.on_market_open(...) verify(self.spied_monitor, times=5).record_transaction(...) verify(self.portfolio, times=5).transact_transaction(...) actual_orders = self.exec_hanlder.get_open_orders() expected_orders = [self.order_4, self.order_4, self.order_4] assert_lists_equal(expected_orders, actual_orders) self.exec_hanlder.on_market_close(...) verify(self.spied_monitor, times=8).record_transaction(...) verify(self.portfolio, times=8).transact_transaction(...) actual_orders = self.exec_hanlder.get_open_orders() expected_orders = [] assert_lists_equal(expected_orders, actual_orders) def test_fill_close_and_open(self): self.exec_hanlder.accept_orders([self.order_1, self.order_2]) self.exec_hanlder.accept_orders([self.order_2, self.order_3]) self.exec_hanlder.accept_orders([self.order_3, self.order_4]) self.exec_hanlder.accept_orders([self.order_4, self.order_4]) self._set_price_for_now(101) self.exec_hanlder.on_market_close(...) verify(self.spied_monitor, times=3).record_transaction(...) verify(self.portfolio, times=3).transact_transaction(...) actual_orders = self.exec_hanlder.get_open_orders() expected_orders = [ self.order_1, self.order_2, self.order_2, self.order_3, self.order_3 ] assert_lists_equal(expected_orders, actual_orders) self.exec_hanlder.on_market_open(...) verify(self.spied_monitor, times=8).record_transaction(...) verify(self.portfolio, times=8).transact_transaction(...) actual_orders = self.exec_hanlder.get_open_orders() expected_orders = [] assert_lists_equal(expected_orders, actual_orders) def test_market_open_transaction(self): order = self.order_1 price = 102 self.exec_hanlder.accept_orders([order]) self._set_price_for_now(price) self.exec_hanlder.on_market_open(...) timestamp = self.timer.now() contract = order.contract quantity = order.quantity commission = self.commission_model.calculate_commission(order, price) expected_transaction = Transaction(timestamp, contract, quantity, price, commission) captor = _ArgCaptor() verify(self.spied_monitor, times=1).record_transaction(captor) self.assertEqual(expected_transaction, captor.get_value()) def test_market_close_transaction(self): order = self.order_4 price = 102 self.exec_hanlder.accept_orders([self.order_1, order]) self._set_price_for_now(price) self.exec_hanlder.on_market_close(...) timestamp = self.timer.now() contract = order.contract quantity = order.quantity commission = self.commission_model.calculate_commission(order, price) expected_transaction = Transaction(timestamp, contract, quantity, price, commission) captor = _ArgCaptor() verify(self.spied_monitor, times=1).record_transaction(captor) self.assertEqual(expected_transaction, captor.get_value()) def test_market_close_does_not_trade(self): price = None self.exec_hanlder.accept_orders([self.order_1, self.order_4]) self._set_price_for_now(price) self.exec_hanlder.on_market_close(...) verifyZeroInteractions(self.portfolio, self.spied_monitor) def test_market_open_does_not_trade(self): price = None self.exec_hanlder.accept_orders([self.order_1, self.order_4]) self._set_price_for_now(price) self.exec_hanlder.on_market_open(...) verifyZeroInteractions(self.portfolio, self.spied_monitor) def _set_last_msft_price(self, price): when(self.data_handler).get_last_available_price( [self.msft_ticker]).thenReturn( pd.Series(data=[price], index=pd.Index([self.msft_ticker]), name=self.start_date)) def _set_price_for_now(self, price): when(self.data_handler).get_current_price( [self.msft_ticker]).thenReturn( pd.Series(data=[price], index=pd.Index([self.msft_ticker])))