def setup(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock) self.cache = TestStubs.cache() self.portfolio = Portfolio( cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine.process(USDJPY_SIM) self.client = BacktestMarketDataClient( client_id=ClientId("SIM"), engine=self.data_engine, clock=TestClock(), logger=self.logger, )
def setUp(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.portfolio.register_cache(self.data_engine.cache) self.binance_client = BacktestDataClient( instruments=[BTCUSDT_BINANCE, ETHUSDT_BINANCE], venue=BINANCE, engine=self.data_engine, clock=self.clock, logger=self.logger, ) self.bitmex_client = BacktestDataClient( instruments=[XBTUSD_BITMEX], venue=BITMEX, engine=self.data_engine, clock=self.clock, logger=self.logger, )
def setup(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger( clock=self.clock, level_stdout=LogLevel.DEBUG, ) self.trader_id = TestIdStubs.trader_id() self.account_id = TestIdStubs.account_id() self.component_id = "MyComponent-001" self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestComponentStubs.cache() self.data_engine = DataEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_client = BacktestMarketDataClient( client_id=ClientId("SIM"), msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine.register_client(self.data_client) # Add instruments self.data_engine.process(AUDUSD_SIM) self.data_engine.process(GBPUSD_SIM) self.data_engine.process(USDJPY_SIM) self.cache.add_instrument(AUDUSD_SIM) self.cache.add_instrument(GBPUSD_SIM) self.cache.add_instrument(USDJPY_SIM) self.data_engine.start() self.exec_engine.start()
def setUp(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.venue = Venue("SIM") self.client = DataClient( venue=self.venue, engine=self.data_engine, clock=self.clock, logger=self.logger, )
def setUp(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock) self.cache = TestStubs.cache() self.portfolio = Portfolio( cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, cache=self.cache, clock=self.clock, logger=self.logger, ) self.venue = Venue("SIM") self.client = DataClient( client_id=ClientId("TEST_PROVIDER"), engine=self.data_engine, clock=self.clock, logger=self.logger, )
def setUp(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock) self.cache = TestStubs.cache() self.portfolio = Portfolio( cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, cache=self.cache, clock=self.clock, logger=self.logger, ) self.binance_client = BacktestMarketDataClient( client_id=ClientId(BINANCE.value), engine=self.data_engine, clock=self.clock, logger=self.logger, ) self.bitmex_client = BacktestMarketDataClient( client_id=ClientId(BITMEX.value), engine=self.data_engine, clock=self.clock, logger=self.logger, ) self.quandl = MockMarketDataClient( client_id=ClientId("QUANDL"), engine=self.data_engine, clock=self.clock, logger=self.logger, ) self.data_engine.process(BTCUSDT_BINANCE) self.data_engine.process(ETHUSDT_BINANCE) self.data_engine.process(XBTUSD_BITMEX)
def setup(self): # Fixture Setup self.clock = TestClock() self.logger = Logger(self.clock) self.trader_id = TestStubs.trader_id() self.account_id = TestStubs.account_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = Cache( database=None, logger=self.logger, ) self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.risk_engine = RiskEngine( portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.strategy = TradingStrategy() self.strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, )
def setUp(self): self.clock = TestClock() self.uuid_factory = TestUUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.data_engine = DataEngine( tick_capacity=1000, bar_capacity=1000, portfolio=self.portfolio, clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, )
def setUp(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.portfolio.register_cache(self.data_engine.cache) self.binance_client = BacktestMarketDataClient( instruments=[BTCUSDT_BINANCE, ETHUSDT_BINANCE], client_id=ClientId(BINANCE.value), engine=self.data_engine, clock=self.clock, logger=self.logger, ) self.bitmex_client = BacktestMarketDataClient( instruments=[XBTUSD_BITMEX], client_id=ClientId(BITMEX.value), engine=self.data_engine, clock=self.clock, logger=self.logger, ) self.quandl = MockMarketDataClient( client_id=ClientId("QUANDL"), engine=self.data_engine, clock=self.clock, logger=self.logger, )
def setup(self): # Fixture Setup self.loop = asyncio.get_event_loop() self.loop.set_debug(True) self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.logger = Logger(clock=self.clock) self.trader_id = TestIdStubs.trader_id() self.venue = BINANCE_VENUE self.account_id = AccountId(self.venue.value, "001") self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestComponentStubs.cache() self.http_client = BinanceHttpClient( # noqa: S106 (no hardcoded password) loop=asyncio.get_event_loop(), clock=self.clock, logger=self.logger, key="SOME_BINANCE_API_KEY", secret="SOME_BINANCE_API_SECRET", ) self.provider = BinanceSpotInstrumentProvider( client=self.http_client, logger=self.logger, config=InstrumentProviderConfig(load_all=True), ) self.data_engine = DataEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_client = BinanceDataClient( loop=self.loop, client=self.http_client, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, instrument_provider=self.provider, )
class DataEngineTests(unittest.TestCase): def setUp(self): self.clock = TestClock() self.uuid_factory = TestUUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.data_engine = DataEngine( tick_capacity=1000, bar_capacity=1000, portfolio=self.portfolio, clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) def test_get_exchange_rate_returns_correct_rate(self): # Arrange tick = QuoteTick( USDJPY_FXCM, Price("110.80000"), Price("110.80010"), Quantity(1), Quantity(1), datetime(2018, 1, 1, 19, 59, 1, 0, pytz.utc), ) self.data_engine.handle_quote_tick(tick) # Act result = self.data_engine.get_xrate(JPY, USD) # Assert self.assertEqual(0.009025266685348969, result) def test_get_exchange_rate_with_no_conversion(self): # Arrange tick = QuoteTick( AUDUSD_FXCM, Price("0.80000"), Price("0.80010"), Quantity(1), Quantity(1), datetime(2018, 1, 1, 19, 59, 1, 0, pytz.utc), ) self.data_engine.handle_quote_tick(tick) # Act result = self.data_engine.get_xrate(AUD, USD) # Assert self.assertEqual(0.80005, result)
def setup(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock) self.trader_id = TestIdStubs.trader_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestComponentStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.venue = Venue("SIM") self.client = DataClient( client_id=ClientId("TEST_PROVIDER"), venue=self.venue, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, )
def setup(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.client = BacktestMarketDataClient( instruments=[USDJPY_SIM], client_id=ClientId("SIM"), engine=self.data_engine, clock=TestClock(), logger=self.logger, )
def setUp(self): # Fixture Setup clock = TestClock() logger = Logger(clock) trader_id = TraderId("TESTER", "000") account_id = TestStubs.account_id() self.portfolio = Portfolio( clock=clock, logger=logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=clock, logger=logger, config={"use_previous_close": False}, ) self.portfolio.register_cache(self.data_engine.cache) self.analyzer = PerformanceAnalyzer() self.exec_db = BypassExecutionDatabase( trader_id=trader_id, logger=logger, ) self.exec_engine = ExecutionEngine( database=self.exec_db, portfolio=self.portfolio, clock=clock, logger=logger, ) self.exchange = SimulatedExchange( venue=Venue("SIM"), oms_type=OMSType.HEDGING, is_frozen_account=False, starting_balances=[Money(1_000_000, USD)], exec_cache=self.exec_engine.cache, instruments=[USDJPY_SIM], modules=[], fill_model=FillModel(), clock=clock, logger=logger, ) self.data_client = BacktestMarketDataClient( instruments=[USDJPY_SIM], client_id=ClientId("SIM"), engine=self.data_engine, clock=clock, logger=logger, ) self.data_engine.register_client(self.data_client) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=account_id, engine=self.exec_engine, clock=clock, logger=logger, ) self.risk_engine = RiskEngine( exec_engine=self.exec_engine, portfolio=self.portfolio, clock=clock, logger=logger, ) self.exec_engine.register_risk_engine(self.risk_engine) self.exec_engine.register_client(self.exec_client) strategies = [ TradingStrategy("001"), TradingStrategy("002"), ] self.trader = Trader( trader_id=trader_id, strategies=strategies, portfolio=self.portfolio, data_engine=self.data_engine, exec_engine=self.exec_engine, risk_engine=self.risk_engine, clock=clock, logger=logger, )
class DataEngineTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.portfolio.register_cache(self.data_engine.cache) self.binance_client = BacktestMarketDataClient( instruments=[BTCUSDT_BINANCE, ETHUSDT_BINANCE], name=BINANCE.value, engine=self.data_engine, clock=self.clock, logger=self.logger, ) self.bitmex_client = BacktestMarketDataClient( instruments=[XBTUSD_BITMEX], name=BITMEX.value, engine=self.data_engine, clock=self.clock, logger=self.logger, ) self.quandl = MockMarketDataClient( name="QUANDL", engine=self.data_engine, clock=self.clock, logger=self.logger, ) def test_registered_venues(self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.registered_clients) def test_subscribed_instruments_when_nothing_subscribed_returns_empty_list(self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.subscribed_instruments) def test_subscribed_quote_ticks_when_nothing_subscribed_returns_empty_list(self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.subscribed_quote_ticks) def test_subscribed_trade_ticks_when_nothing_subscribed_returns_empty_list(self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.subscribed_trade_ticks) def test_subscribed_bars_when_nothing_subscribed_returns_empty_list(self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.subscribed_bars) def test_register_client_successfully_adds_client(self): # Arrange # Act self.data_engine.register_client(self.binance_client) # Assert self.assertIn(BINANCE.value, self.data_engine.registered_clients) def test_deregister_client_successfully_removes_client(self): # Arrange self.data_engine.register_client(self.binance_client) # Act self.data_engine.deregister_client(self.binance_client) # Assert self.assertNotIn(BINANCE.value, self.data_engine.registered_clients) def test_register_strategy_successfully_registered_with_strategy(self): # Arrange strategy = TradingStrategy("000") # Act strategy.register_data_engine(self.data_engine) # Assert self.assertEqual(self.data_engine.cache, strategy.data) def test_reset(self): # Arrange # Act self.data_engine.reset() # Assert self.assertEqual(0, self.data_engine.command_count) self.assertEqual(0, self.data_engine.data_count) self.assertEqual(0, self.data_engine.request_count) self.assertEqual(0, self.data_engine.response_count) def test_stop_and_resume(self): # Arrange self.data_engine.start() # Act self.data_engine.stop() self.data_engine.resume() self.data_engine.stop() self.data_engine.reset() # Assert self.assertEqual(0, self.data_engine.command_count) self.assertEqual(0, self.data_engine.data_count) self.assertEqual(0, self.data_engine.request_count) self.assertEqual(0, self.data_engine.response_count) def test_dispose(self): # Arrange self.data_engine.reset() # Act self.data_engine.dispose() # Assert self.assertEqual(0, self.data_engine.command_count) self.assertEqual(0, self.data_engine.data_count) self.assertEqual(0, self.data_engine.request_count) self.assertEqual(0, self.data_engine.response_count) def test_check_connected_when_client_disconnected_returns_false(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.bitmex_client) self.binance_client.disconnect() self.bitmex_client.disconnect() # Act result = self.data_engine.check_connected() # Assert self.assertFalse(result) def test_check_connected_when_client_connected_returns_true(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.bitmex_client) self.binance_client.connect() self.bitmex_client.connect() # Act result = self.data_engine.check_connected() # Assert self.assertTrue(result) def test_check_disconnected_when_client_disconnected_returns_true(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.bitmex_client) # Act result = self.data_engine.check_disconnected() # Assert self.assertTrue(result) def test_check_disconnected_when_client_connected_returns_false(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.bitmex_client) self.binance_client.connect() self.bitmex_client.connect() # Act result = self.data_engine.check_disconnected() # Assert self.assertFalse(result) def test_reset_when_already_disposed_raises_invalid_state_trigger(self): # Arrange self.data_engine.dispose() # Act # Assert self.assertRaises(InvalidStateTrigger, self.data_engine.reset) def test_dispose_when_already_disposed_raises_invalid_state_trigger(self): # Arrange self.data_engine.dispose() # Act # Assert self.assertRaises(InvalidStateTrigger, self.data_engine.dispose) def test_execute_unrecognized_message_logs_and_does_nothing(self): # Arrange self.data_engine.register_client(self.binance_client) # Bogus message command = DataCommand( provider=BINANCE.value, data_type=DataType(str), handler=[].append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(command) # Assert self.assertEqual(1, self.data_engine.command_count) def test_send_request_when_no_data_clients_registered_does_nothing(self): # Arrange handler = [] request = DataRequest( provider="RANDOM", data_type=DataType(QuoteTick, metadata={ "InstrumentId": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")), "FromDateTime": None, "ToDateTime": None, "Limit": 1000, }), callback=handler.append, request_id=self.uuid_factory.generate(), request_timestamp=self.clock.utc_now(), ) # Act self.data_engine.send(request) # Assert self.assertEqual(1, self.data_engine.request_count) def test_send_data_request_when_data_type_unrecognized_logs_and_does_nothing(self): # Arrange self.data_engine.register_client(self.binance_client) handler = [] request = DataRequest( provider=BINANCE.value, data_type=DataType(str, metadata={ # str data type is invalid "InstrumentId": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")), "FromDateTime": None, "ToDateTime": None, "Limit": 1000, }), callback=handler.append, request_id=self.uuid_factory.generate(), request_timestamp=self.clock.utc_now(), ) # Act self.data_engine.send(request) # Assert self.assertEqual(1, self.data_engine.request_count) def test_send_data_request_with_duplicate_ids_logs_and_does_not_handle_second(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.start() handler = [] uuid = self.uuid_factory.generate() # We'll use this as a duplicate request1 = DataRequest( provider=BINANCE.value, data_type=DataType(QuoteTick, metadata={ # str data type is invalid "InstrumentId": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")), "FromDateTime": None, "ToDateTime": None, "Limit": 1000, }), callback=handler.append, request_id=uuid, # Duplicate request_timestamp=self.clock.utc_now(), ) request2 = DataRequest( provider=BINANCE.value, data_type=DataType(QuoteTick, metadata={ # str data type is invalid "InstrumentId": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")), "FromDateTime": None, "ToDateTime": None, "Limit": 1000, }), callback=handler.append, request_id=uuid, # Duplicate request_timestamp=self.clock.utc_now(), ) # Act self.data_engine.send(request1) self.data_engine.send(request2) # Assert self.assertEqual(2, self.data_engine.request_count) def test_execute_subscribe_when_data_type_unrecognized_logs_and_does_nothing(self): # Arrange self.data_engine.register_client(self.binance_client) subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(str), # str data type is invalid handler=[].append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual(1, self.data_engine.command_count) def test_execute_subscribe_when_already_subscribed_does_not_add_and_logs(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=[].append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) self.data_engine.execute(subscribe) # Assert self.assertEqual(2, self.data_engine.command_count) def test_execute_subscribe_custom_data(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.quandl) self.binance_client.connect() subscribe = Subscribe( provider="QUANDL", data_type=DataType(str, metadata={"Type": "news"}), handler=[].append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual(1, self.data_engine.command_count) self.assertEqual(["subscribe"], self.quandl.calls) def test_execute_unsubscribe_custom_data(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.quandl) self.binance_client.connect() handler = [] subscribe = Subscribe( provider="QUANDL", data_type=DataType(str, metadata={"Type": "news"}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( provider="QUANDL", data_type=DataType(str, metadata={"Type": "news"}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual(2, self.data_engine.command_count) self.assertEqual(["subscribe", "unsubscribe"], self.quandl.calls) def test_execute_unsubscribe_when_data_type_unrecognized_logs_and_does_nothing(self): # Arrange self.data_engine.register_client(self.binance_client) handler = [] unsubscribe = Unsubscribe( provider=BINANCE.value, data_type=DataType(type(str)), # str data type is invalid handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual(1, self.data_engine.command_count) def test_execute_unsubscribe_when_not_subscribed_logs_and_does_nothing(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] unsubscribe = Unsubscribe( provider=BINANCE.value, data_type=DataType(type(QuoteTick), metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual(1, self.data_engine.command_count) def test_receive_response_when_no_data_clients_registered_does_nothing(self): # Arrange response = DataResponse( provider=BINANCE.value, data_type=DataType(QuoteTick), data=[], correlation_id=self.uuid_factory.generate(), response_id=self.uuid_factory.generate(), response_timestamp=self.clock.utc_now(), ) # Act self.data_engine.receive(response) # Assert self.assertEqual(1, self.data_engine.response_count) def test_process_unrecognized_data_type_logs_and_does_nothing(self): # Arrange # Act self.data_engine.process("DATA!") # Invalid # Assert self.assertEqual(1, self.data_engine.data_count) def test_process_data_places_data_on_queue(self): # Arrange tick = TestStubs.trade_tick_5decimal() # Act self.data_engine.process(tick) # Assert self.assertEqual(1, self.data_engine.data_count) def test_execute_subscribe_instrument_then_adds_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=[].append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_instruments) def test_execute_unsubscribe_instrument_then_removes_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( provider=BINANCE.value, data_type=DataType(Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_instruments) def test_process_instrument_when_subscriber_then_sends_to_registered_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) # Act self.data_engine.process(ETHUSDT_BINANCE) # Assert self.assertEqual([ETHUSDT_BINANCE], handler) def test_process_instrument_when_subscribers_then_sends_to_registered_handlers(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler1 = [] subscribe1 = Subscribe( provider=BINANCE.value, data_type=DataType(Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler1.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) handler2 = [] subscribe2 = Subscribe( provider=BINANCE.value, data_type=DataType(Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler2.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) # Act self.data_engine.process(ETHUSDT_BINANCE) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_instruments) self.assertEqual([ETHUSDT_BINANCE], handler1) self.assertEqual([ETHUSDT_BINANCE], handler2) def test_execute_subscribe_order_book_stream_then_adds_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(OrderBook, metadata={ "InstrumentId": ETHUSDT_BINANCE.id, "Level": 2, "Depth": 10, "Interval": 0, }), handler=[].append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_order_books) def test_execute_subscribe_order_book_intervals_then_adds_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(OrderBook, metadata={ "InstrumentId": ETHUSDT_BINANCE.id, "Level": 2, "Depth": 25, "Interval": 10, }), handler=[].append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_order_books) def test_execute_unsubscribe_order_book_stream_then_removes_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(OrderBook, metadata={ "InstrumentId": ETHUSDT_BINANCE.id, "Level": 2, "Depth": 25, "Interval": 0, }), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( provider=BINANCE.value, data_type=DataType(OrderBook, metadata={ "InstrumentId": ETHUSDT_BINANCE.id, "Interval": 0, }), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_order_books) def test_execute_unsubscribe_order_book_interval_then_removes_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(OrderBook, metadata={ "InstrumentId": ETHUSDT_BINANCE.id, "Level": 2, "Depth": 25, "Interval": 10, }), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( provider=BINANCE.value, data_type=DataType(OrderBook, metadata={ "InstrumentId": ETHUSDT_BINANCE.id, "Interval": 10, }), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_order_books) def test_process_order_book_when_one_subscriber_then_sends_to_registered_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() order_book = OrderBook( instrument_id=ETHUSDT_BINANCE.id, level=2, depth=25, price_precision=2, size_precision=5, bids=[], asks=[], update_id=0, timestamp=0, ) handler = [] subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(OrderBook, { "InstrumentId": ETHUSDT_BINANCE.id, "Level": 2, "Depth": 25, "Interval": 0, # Streaming }), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) # Act self.data_engine.process(order_book) # Assert self.assertEqual(order_book, handler[0]) def test_process_order_book_when_multiple_subscribers_then_sends_to_registered_handlers(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() order_book = OrderBook( instrument_id=ETHUSDT_BINANCE.id, level=2, depth=25, price_precision=2, size_precision=5, bids=[], asks=[], update_id=0, timestamp=0, ) handler1 = [] subscribe1 = Subscribe( provider=BINANCE.value, data_type=DataType(OrderBook, { "InstrumentId": ETHUSDT_BINANCE.id, "Level": 2, "Depth": 25, "Interval": 0, # Streaming }), handler=handler1.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) handler2 = [] subscribe2 = Subscribe( provider=BINANCE.value, data_type=DataType(OrderBook, { "InstrumentId": ETHUSDT_BINANCE.id, "Level": 2, "Depth": 25, "Interval": 0, # Streaming }), handler=handler2.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) # Act self.data_engine.process(order_book) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_order_books) self.assertEqual(order_book, handler1[0]) self.assertEqual(order_book, handler2[0]) def test_execute_subscribe_for_quote_ticks(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_quote_ticks) def test_execute_unsubscribe_for_quote_ticks(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( provider=BINANCE.value, data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_quote_ticks) def test_process_quote_tick_when_subscriber_then_sends_to_registered_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) tick = QuoteTick( ETHUSDT_BINANCE.id, Price("100.003"), Price("100.003"), Quantity(1), Quantity(1), UNIX_EPOCH, ) # Act self.data_engine.process(tick) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_quote_ticks) self.assertEqual([tick], handler) def test_process_quote_tick_when_subscribers_then_sends_to_registered_handlers(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler1 = [] subscribe1 = Subscribe( provider=BINANCE.value, data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler1.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) handler2 = [] subscribe2 = Subscribe( provider=BINANCE.value, data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler2.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) tick = QuoteTick( ETHUSDT_BINANCE.id, Price("100.003"), Price("100.003"), Quantity(1), Quantity(1), UNIX_EPOCH, ) # Act self.data_engine.process(tick) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_quote_ticks) self.assertEqual([tick], handler1) self.assertEqual([tick], handler2) def test_subscribe_trade_tick_then_subscribes(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_trade_ticks) def test_unsubscribe_trade_tick_then_unsubscribes(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( provider=BINANCE.value, data_type=DataType(TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_trade_ticks) def test_process_trade_tick_when_subscriber_then_sends_to_registered_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) tick = TradeTick( ETHUSDT_BINANCE.id, Price("1050.00000"), Quantity(100), OrderSide.BUY, TradeMatchId("123456789"), UNIX_EPOCH, ) # Act self.data_engine.process(tick) # Assert self.assertEqual([tick], handler) def test_process_trade_tick_when_subscribers_then_sends_to_registered_handlers(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler1 = [] subscribe1 = Subscribe( provider=BINANCE.value, data_type=DataType(TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler1.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) handler2 = [] subscribe2 = Subscribe( provider=BINANCE.value, data_type=DataType(TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}), handler=handler2.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) tick = TradeTick( ETHUSDT_BINANCE.id, Price("1050.00000"), Quantity(100), OrderSide.BUY, TradeMatchId("123456789"), UNIX_EPOCH, ) # Act self.data_engine.process(tick) # Assert self.assertEqual([tick], handler1) self.assertEqual([tick], handler2) def test_subscribe_bar_type_then_subscribes(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID) bar_type = BarType(ETHUSDT_BINANCE.id, bar_spec, internal_aggregation=True) handler = ObjectStorer() subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(Bar, metadata={"BarType": bar_type}), handler=handler.store_2, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([bar_type], self.data_engine.subscribed_bars) def test_unsubscribe_bar_type_then_unsubscribes(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID) bar_type = BarType(ETHUSDT_BINANCE.id, bar_spec, internal_aggregation=True) handler = ObjectStorer() subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(Bar, metadata={"BarType": bar_type}), handler=handler.store_2, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( provider=BINANCE.value, data_type=DataType(Bar, metadata={"BarType": bar_type}), handler=handler.store_2, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_bars) def test_process_bar_when_subscriber_then_sends_to_registered_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID) bar_type = BarType(ETHUSDT_BINANCE.id, bar_spec, internal_aggregation=True) handler = ObjectStorer() subscribe = Subscribe( provider=BINANCE.value, data_type=DataType(Bar, metadata={"BarType": bar_type}), handler=handler.store_2, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) bar = Bar( Price("1051.00000"), Price("1055.00000"), Price("1050.00000"), Price("1052.00000"), Quantity(100), UNIX_EPOCH, ) data = BarData(bar_type, bar) # Act self.data_engine.process(data) # Assert self.assertEqual([(bar_type, bar)], handler.get_store()) def test_process_bar_when_subscribers_then_sends_to_registered_handlers(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID) bar_type = BarType(ETHUSDT_BINANCE.id, bar_spec, internal_aggregation=True) handler1 = ObjectStorer() subscribe1 = Subscribe( provider=BINANCE.value, data_type=DataType(Bar, metadata={"BarType": bar_type}), handler=handler1.store_2, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) handler2 = ObjectStorer() subscribe2 = Subscribe( provider=BINANCE.value, data_type=DataType(Bar, metadata={"BarType": bar_type}), handler=handler2.store_2, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) bar = Bar( Price("1051.00000"), Price("1055.00000"), Price("1050.00000"), Price("1052.00000"), Quantity(100), UNIX_EPOCH, ) data = BarData(bar_type, bar) # Act self.data_engine.process(data) # Assert self.assertEqual([(bar_type, bar)], handler1.get_store()) self.assertEqual([(bar_type, bar)], handler2.get_store())
def setUp(self): # Fixture Setup clock = TestClock() logger = Logger(clock) trader_id = TraderId("TESTER-000") account_id = TestStubs.account_id() self.cache = TestStubs.cache() self.portfolio = Portfolio( cache=self.cache, clock=clock, logger=logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, cache=self.cache, clock=clock, logger=logger, config={"use_previous_close": False}, ) self.data_engine.process(USDJPY_SIM) self.exec_engine = ExecutionEngine( portfolio=self.portfolio, cache=self.cache, clock=clock, logger=logger, ) self.exchange = SimulatedExchange( venue=Venue("SIM"), venue_type=VenueType.ECN, oms_type=OMSType.HEDGING, account_type=AccountType.MARGIN, base_currency=USD, starting_balances=[Money(1_000_000, USD)], is_frozen_account=False, cache=self.exec_engine.cache, instruments=[USDJPY_SIM], modules=[], fill_model=FillModel(), clock=clock, logger=logger, ) self.data_client = BacktestMarketDataClient( client_id=ClientId("SIM"), engine=self.data_engine, clock=clock, logger=logger, ) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=account_id, account_type=AccountType.MARGIN, base_currency=USD, engine=self.exec_engine, clock=clock, logger=logger, ) self.risk_engine = RiskEngine( exec_engine=self.exec_engine, portfolio=self.portfolio, cache=self.cache, clock=clock, logger=logger, ) # Wire up components self.data_engine.register_client(self.data_client) self.exec_engine.register_risk_engine(self.risk_engine) self.exec_engine.register_client(self.exec_client) strategies = [ TradingStrategy("001"), TradingStrategy("002"), ] self.trader = Trader( trader_id=trader_id, strategies=strategies, portfolio=self.portfolio, data_engine=self.data_engine, risk_engine=self.risk_engine, exec_engine=self.exec_engine, clock=clock, logger=logger, )
class TraderTests(unittest.TestCase): def setUp(self): # Fixture Setup clock = TestClock() logger = Logger(clock) trader_id = TraderId("TESTER", "000") account_id = TestStubs.account_id() self.portfolio = Portfolio( clock=clock, logger=logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=clock, logger=logger, config={"use_previous_close": False}, ) self.portfolio.register_cache(self.data_engine.cache) self.analyzer = PerformanceAnalyzer() self.exec_db = BypassExecutionDatabase( trader_id=trader_id, logger=logger, ) self.exec_engine = ExecutionEngine( database=self.exec_db, portfolio=self.portfolio, clock=clock, logger=logger, ) self.exchange = SimulatedExchange( venue=Venue("SIM"), oms_type=OMSType.HEDGING, is_frozen_account=False, starting_balances=[Money(1_000_000, USD)], exec_cache=self.exec_engine.cache, instruments=[USDJPY_SIM], modules=[], fill_model=FillModel(), clock=clock, logger=logger, ) self.data_client = BacktestMarketDataClient( instruments=[USDJPY_SIM], client_id=ClientId("SIM"), engine=self.data_engine, clock=clock, logger=logger, ) self.data_engine.register_client(self.data_client) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=account_id, engine=self.exec_engine, clock=clock, logger=logger, ) self.risk_engine = RiskEngine( exec_engine=self.exec_engine, portfolio=self.portfolio, clock=clock, logger=logger, ) self.exec_engine.register_risk_engine(self.risk_engine) self.exec_engine.register_client(self.exec_client) strategies = [ TradingStrategy("001"), TradingStrategy("002"), ] self.trader = Trader( trader_id=trader_id, strategies=strategies, portfolio=self.portfolio, data_engine=self.data_engine, exec_engine=self.exec_engine, risk_engine=self.risk_engine, clock=clock, logger=logger, ) def test_initialize_trader(self): # Arrange # Act trader_id = self.trader.id # Assert self.assertEqual(TraderId("TESTER", "000"), trader_id) self.assertEqual(IdTag("000"), trader_id.tag) self.assertEqual(ComponentState.INITIALIZED, self.trader.state) self.assertEqual(2, len(self.trader.strategy_states())) def test_get_strategy_states(self): # Arrange # Act status = self.trader.strategy_states() # Assert self.assertTrue(StrategyId("TradingStrategy", "001") in status) self.assertTrue(StrategyId("TradingStrategy", "002") in status) self.assertEqual("INITIALIZED", status[StrategyId("TradingStrategy", "001")]) self.assertEqual("INITIALIZED", status[StrategyId("TradingStrategy", "002")]) self.assertEqual(2, len(status)) def test_change_strategies(self): # Arrange strategies = [ TradingStrategy("003"), TradingStrategy("004"), ] # Act self.trader.initialize_strategies(strategies, warn_no_strategies=True) # Assert self.assertTrue(strategies[0].id in self.trader.strategy_states()) self.assertTrue(strategies[1].id in self.trader.strategy_states()) self.assertEqual(2, len(self.trader.strategy_states())) def test_trader_detects_duplicate_identifiers(self): # Arrange strategies = [ TradingStrategy("000"), TradingStrategy("000"), ] # Act self.assertRaises( ValueError, self.trader.initialize_strategies, strategies, True, ) def test_start_a_trader(self): # Arrange # Act self.trader.start() strategy_states = self.trader.strategy_states() # Assert self.assertEqual(ComponentState.RUNNING, self.trader.state) self.assertEqual("RUNNING", strategy_states[StrategyId("TradingStrategy", "001")]) self.assertEqual("RUNNING", strategy_states[StrategyId("TradingStrategy", "002")]) def test_stop_a_running_trader(self): # Arrange self.trader.start() # Act self.trader.stop() strategy_states = self.trader.strategy_states() # Assert self.assertEqual(ComponentState.STOPPED, self.trader.state) self.assertEqual("STOPPED", strategy_states[StrategyId("TradingStrategy", "001")]) self.assertEqual("STOPPED", strategy_states[StrategyId("TradingStrategy", "002")])
class TradingStrategyTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = TestUUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.data_engine = DataEngine( tick_capacity=1000, bar_capacity=1000, portfolio=self.portfolio, clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.data_engine.set_use_previous_close(False) self.analyzer = PerformanceAnalyzer() trader_id = TraderId('TESTER', '000') account_id = TestStubs.account_id() self.exec_db = BypassExecutionDatabase( trader_id=trader_id, logger=self.logger, ) self.exec_engine = ExecutionEngine( database=self.exec_db, portfolio=self.portfolio, clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) usdjpy = InstrumentLoader.default_fx_ccy(TestStubs.symbol_usdjpy_fxcm()) self.market = SimulatedMarket( venue=Venue("FXCM"), oms_type=OMSType.HEDGING, generate_position_ids=True, exec_cache=self.exec_engine.cache, instruments={usdjpy.symbol: usdjpy}, config=BacktestConfig(), fill_model=FillModel(), commission_model=GenericCommissionModel(), clock=self.clock, uuid_factory=TestUUIDFactory(), logger=self.logger, ) self.exec_client = BacktestExecClient( market=self.market, account_id=account_id, engine=self.exec_engine, logger=self.logger, ) self.exec_engine.register_client(self.exec_client) self.market.register_client(self.exec_client) self.exec_engine.process(TestStubs.event_account_state()) self.market.process_tick(TestStubs.quote_tick_3decimal(usdjpy.symbol)) # Prepare market self.strategy = TradingStrategy(order_id_tag="001") self.strategy.register_trader( trader_id=TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.strategy.register_data_engine(self.data_engine) self.strategy.register_execution_engine(self.exec_engine) print("\n") def test_strategy_equality(self): # Arrange strategy1 = TradingStrategy(order_id_tag="001") strategy2 = TradingStrategy(order_id_tag="AUD/USD-001") strategy3 = TradingStrategy(order_id_tag="AUD/USD-002") # Act result1 = strategy1 == strategy1 result2 = strategy1 == strategy2 result3 = strategy2 == strategy3 result4 = strategy1 != strategy1 result5 = strategy1 != strategy2 result6 = strategy2 != strategy3 # Assert self.assertTrue(result1) self.assertFalse(result2) self.assertFalse(result3) self.assertFalse(result4) self.assertTrue(result5) self.assertTrue(result6) def test_strategy_is_hashable(self): # Arrange # Act result = self.strategy.__hash__() # Assert # If this passes then result must be an int self.assertTrue(result != 0) def test_strategy_str_and_repr(self): # Arrange strategy = TradingStrategy(order_id_tag="GBP/USD-MM") # Act result1 = str(strategy) result2 = repr(strategy) # Assert self.assertEqual("TradingStrategy(TradingStrategy-GBP/USD-MM)", result1) self.assertTrue(result2.startswith("<TradingStrategy(TradingStrategy-GBP/USD-MM) object at")) self.assertTrue(result2.endswith(">")) def test_get_strategy_id(self): # Arrange # Act # Assert self.assertEqual(StrategyId("TradingStrategy", "001"), self.strategy.id) def test_get_current_time(self): # Arrange # Act result = self.strategy.clock.utc_now() # Assert self.assertEqual(pytz.utc, result.tzinfo) def test_initialization(self): # Arrange bar_type = TestStubs.bartype_gbpusd_1sec_mid() strategy = TestStrategy1(bar_type) # Act # Assert self.assertFalse(strategy.indicators_initialized()) def test_get_tick_count_for_unknown_symbol_returns_zero(self): # Arrange # Act result = self.strategy.quote_tick_count(AUDUSD_FXCM) # Assert self.assertEqual(0, result) def test_get_ticks_for_unknown_symbol_raises_exception(self): # Arrange # Act # Assert self.assertRaises(KeyError, self.strategy.quote_ticks, AUDUSD_FXCM) def test_get_bar_count_for_unknown_bar_type_returns_zero(self): # Arrange bar_type = TestStubs.bartype_gbpusd_1sec_mid() # Act result = self.strategy.bar_count(bar_type) # Assert self.assertEqual(0, result) def test_get_bars_for_unknown_bar_type_raises_exception(self): # Arrange bar_type = TestStubs.bartype_gbpusd_1sec_mid() # Act # Assert self.assertRaises(KeyError, self.strategy.bars, bar_type) def test_bars(self): # Arrange bar_type = TestStubs.bartype_gbpusd_1sec_mid() bar = Bar( Price("1.00001"), Price("1.00004"), Price("1.00002"), Price("1.00003"), Quantity(100000), datetime(1970, 1, 1, 00, 00, 0, 0, pytz.utc), ) self.data_engine.handle_bar(bar_type, bar) # Act result = self.strategy.bars(bar_type) # Assert self.assertTrue(bar, result[0]) def test_getting_bar_for_unknown_bar_type_raises_exception(self): # Arrange unknown_bar_type = TestStubs.bartype_gbpusd_1sec_mid() # Act # Assert self.assertRaises(KeyError, self.strategy.bar, unknown_bar_type, 0) def test_getting_bar_at_out_of_range_index_raises_exception(self): # Arrange bar_type = TestStubs.bartype_gbpusd_1sec_mid() bar = Bar( Price("1.00001"), Price("1.00004"), Price("1.00002"), Price("1.00003"), Quantity(100000), datetime(1970, 1, 1, 00, 00, 0, 0, pytz.utc), ) self.data_engine.handle_bar(bar_type, bar) # Act # Assert self.assertRaises(IndexError, self.strategy.bar, bar_type, -2) def test_get_bar(self): bar_type = TestStubs.bartype_gbpusd_1sec_mid() bar = Bar( Price("1.00001"), Price("1.00004"), Price("1.00002"), Price("1.00003"), Quantity(100000), datetime(1970, 1, 1, 00, 00, 0, 0, pytz.utc), ) self.data_engine.handle_bar(bar_type, bar) # Act result = self.strategy.bar(bar_type, 0) # Assert self.assertEqual(bar, result) def test_getting_tick_with_unknown_tick_type_raises_exception(self): # Act # Assert self.assertRaises(KeyError, self.strategy.quote_tick, AUDUSD_FXCM, 0) def test_get_quote_tick(self): tick = QuoteTick( AUDUSD_FXCM, Price("1.00000"), Price("1.00001"), Quantity(1), Quantity(1), datetime(2018, 1, 1, 19, 59, 1, 0, pytz.utc), ) self.data_engine.handle_quote_tick(tick) # Act result = self.strategy.quote_tick(tick.symbol, 0) # Assert self.assertEqual(tick, result) def test_get_trade_tick(self): tick = TradeTick( AUDUSD_FXCM, Price("1.00000"), Quantity(10000), Maker.BUYER, MatchId("123456789"), datetime(2018, 1, 1, 19, 59, 1, 0, pytz.utc), ) self.data_engine.handle_trade_tick(tick) # Act result = self.strategy.trade_tick(tick.symbol, 0) # Assert self.assertEqual(tick, result) def test_start_strategy(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = TestStrategy1(bar_type) strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.data_engine.register_strategy(strategy) self.exec_engine.register_strategy(strategy) result1 = strategy.state() # Act strategy.start() result2 = strategy.state() # Assert self.assertEqual(ComponentState.INITIALIZED, result1) self.assertEqual(ComponentState.RUNNING, result2) self.assertTrue("custom start logic" in strategy.object_storer.get_store()) def test_stop_strategy(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = TestStrategy1(bar_type) strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.data_engine.register_strategy(strategy) self.exec_engine.register_strategy(strategy) # Act strategy.start() strategy.stop() # Assert self.assertEqual(ComponentState.STOPPED, strategy.state()) self.assertTrue("custom stop logic" in strategy.object_storer.get_store()) def test_reset_strategy(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = TestStrategy1(bar_type) strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) bar_type = TestStubs.bartype_gbpusd_1sec_mid() bar = Bar( Price("1.00001"), Price("1.00004"), Price("1.00002"), Price("1.00003"), Quantity(100000), datetime(1970, 1, 1, 00, 00, 0, 0, pytz.utc), ) strategy.handle_bar(bar_type, bar) # Act strategy.reset() # Assert self.assertEqual(ComponentState.INITIALIZED, strategy.state()) self.assertEqual(0, strategy.ema1.count) self.assertEqual(0, strategy.ema2.count) self.assertTrue("custom reset logic" in strategy.object_storer.get_store()) def test_register_indicator_with_strategy(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = TestStrategy1(bar_type) strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) # Act result = strategy.registered_indicators() # Assert self.assertEqual([strategy.ema1, strategy.ema2], result) def test_register_strategy_with_exec_client(self): # Arrange strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) # Act self.exec_engine.register_strategy(strategy) # Assert self.assertIsNotNone(strategy.execution) def test_stopping_a_strategy_cancels_a_running_time_alert(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = TestStrategy1(bar_type) strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.data_engine.register_strategy(strategy) self.exec_engine.register_strategy(strategy) alert_time = datetime.now(pytz.utc) + timedelta(milliseconds=200) strategy.clock.set_time_alert("test_alert1", alert_time) # Act strategy.start() time.sleep(0.1) strategy.stop() # Assert self.assertEqual(2, strategy.object_storer.count) def test_stopping_a_strategy_cancels_a_running_timer(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = TestStrategy1(bar_type) strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.data_engine.register_strategy(strategy) self.exec_engine.register_strategy(strategy) start_time = datetime.now(pytz.utc) + timedelta(milliseconds=100) strategy.clock.set_timer("test_timer3", timedelta(milliseconds=100), start_time, stop_time=None) # Act strategy.start() time.sleep(0.1) strategy.stop() # Assert self.assertEqual(2, strategy.object_storer.count) def test_strategy_can_submit_order(self): # Arrange strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.exec_engine.register_strategy(strategy) order = strategy.order_factory.market( USDJPY_FXCM, OrderSide.BUY, Quantity(100000), ) # Act strategy.submit_order(order) # Assert self.assertTrue(order in strategy.execution.orders()) self.assertEqual(OrderState.FILLED, strategy.execution.orders()[0].state()) self.assertTrue(order.cl_ord_id not in strategy.execution.orders_working()) self.assertFalse(strategy.execution.is_order_working(order.cl_ord_id)) self.assertTrue(strategy.execution.is_order_completed(order.cl_ord_id)) def test_cancel_order(self): # Arrange strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.exec_engine.register_strategy(strategy) order = strategy.order_factory.stop( USDJPY_FXCM, OrderSide.BUY, Quantity(100000), Price("90.005"), ) strategy.submit_order(order) # Act strategy.cancel_order(order) # Assert self.assertTrue(order in strategy.execution.orders()) self.assertEqual(OrderState.CANCELLED, strategy.execution.orders()[0].state()) self.assertEqual(order.cl_ord_id, strategy.execution.orders_completed()[0].cl_ord_id) self.assertTrue(order.cl_ord_id not in strategy.execution.orders_working()) self.assertTrue(strategy.execution.order_exists(order.cl_ord_id)) self.assertFalse(strategy.execution.is_order_working(order.cl_ord_id)) self.assertTrue(strategy.execution.is_order_completed(order.cl_ord_id)) def test_modify_order(self): # Arrange strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger) self.exec_engine.register_strategy(strategy) order = strategy.order_factory.limit( USDJPY_FXCM, OrderSide.BUY, Quantity(100000), Price("90.001"), ) strategy.submit_order(order) # Act strategy.modify_order(order, Quantity(110000), Price("90.002")) # Assert self.assertEqual(order, strategy.execution.orders()[0]) self.assertEqual(OrderState.WORKING, strategy.execution.orders()[0].state()) self.assertEqual(Quantity(110000), strategy.execution.orders()[0].quantity) self.assertEqual(Price("90.002"), strategy.execution.orders()[0].price) self.assertTrue(strategy.execution.is_flat()) self.assertTrue(strategy.execution.order_exists(order.cl_ord_id)) self.assertTrue(strategy.execution.is_order_working(order.cl_ord_id)) self.assertFalse(strategy.execution.is_order_completed(order.cl_ord_id)) def test_cancel_all_orders(self): # Arrange strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger) self.exec_engine.register_strategy(strategy) order1 = strategy.order_factory.stop( USDJPY_FXCM, OrderSide.BUY, Quantity(100000), Price("90.003"), ) order2 = strategy.order_factory.stop( USDJPY_FXCM, OrderSide.BUY, Quantity(100000), Price("90.005"), ) strategy.submit_order(order1) strategy.submit_order(order2) # Act strategy.cancel_all_orders(USDJPY_FXCM) # Assert self.assertTrue(order1 in strategy.execution.orders()) self.assertTrue(order2 in strategy.execution.orders()) self.assertEqual(OrderState.CANCELLED, strategy.execution.orders()[0].state()) self.assertEqual(OrderState.CANCELLED, strategy.execution.orders()[1].state()) self.assertTrue(order1 in strategy.execution.orders_completed()) self.assertTrue(order2 in strategy.execution.orders_completed()) def test_flatten_position(self): # Arrange strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.exec_engine.register_strategy(strategy) order = strategy.order_factory.market( USDJPY_FXCM, OrderSide.BUY, Quantity(100000), ) strategy.submit_order(order) filled = TestStubs.event_order_filled( order, position_id=PositionId("B-USD/JPY-1"), strategy_id=strategy.id, ) position = Position(filled) # Act strategy.flatten_position(position) # Assert self.assertTrue(order in strategy.execution.orders()) self.assertEqual(OrderState.FILLED, strategy.execution.orders()[0].state()) self.assertEqual(PositionSide.FLAT, strategy.execution.positions()[0].side) self.assertTrue(strategy.execution.positions()[0].is_closed()) self.assertTrue(PositionId("B-USD/JPY-1") in strategy.execution.position_closed_ids()) self.assertTrue(strategy.execution.is_completely_flat()) def test_flatten_all_positions(self): # Arrange strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.exec_engine.register_strategy(strategy) order1 = strategy.order_factory.market( USDJPY_FXCM, OrderSide.BUY, Quantity(100000), ) order2 = strategy.order_factory.market( USDJPY_FXCM, OrderSide.BUY, Quantity(100000), ) strategy.submit_order(order1) strategy.submit_order(order2) filled1 = TestStubs.event_order_filled( order1, position_id=PositionId("B-USD/JPY-1"), strategy_id=strategy.id, ) filled2 = TestStubs.event_order_filled( order2, position_id=PositionId("B-USD/JPY-2"), strategy_id=strategy.id, ) position1 = Position(filled1) position2 = Position(filled2) # Act strategy.flatten_all_positions(USDJPY_FXCM) # Assert self.assertTrue(order1 in strategy.execution.orders()) self.assertTrue(order2 in strategy.execution.orders()) self.assertEqual(OrderState.FILLED, strategy.execution.orders()[0].state()) self.assertEqual(OrderState.FILLED, strategy.execution.orders()[1].state()) self.assertEqual(PositionSide.FLAT, strategy.execution.positions()[0].side) self.assertEqual(PositionSide.FLAT, strategy.execution.positions()[1].side) self.assertTrue(position1.id in strategy.execution.position_closed_ids()) self.assertTrue(position2.id in strategy.execution.position_closed_ids()) self.assertTrue(strategy.execution.is_completely_flat()) def test_update_indicators(self): # Arrange bar_type = TestStubs.bartype_gbpusd_1sec_mid() strategy = TestStrategy1(bar_type) strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) bar = Bar( Price("1.00001"), Price("1.00004"), Price("1.00002"), Price("1.00003"), Quantity(100000), datetime(1970, 1, 1, 00, 00, 0, 0, pytz.utc), ) # Act strategy.handle_bar(bar_type, bar) # Assert self.assertEqual(1, strategy.ema1.count) self.assertEqual(1, strategy.ema2.count) def test_can_track_orders_for_an_opened_position(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = TestStrategy1(bar_type) strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.exec_engine.register_strategy(strategy) order = strategy.order_factory.market( USDJPY_FXCM, OrderSide.BUY, Quantity(100000), ) strategy.submit_order(order) # Act # Assert self.assertTrue(order in strategy.execution.orders()) self.assertTrue(PositionId("B-USD/JPY-1") in strategy.execution.position_ids()) self.assertEqual(0, len(strategy.execution.orders_working())) self.assertTrue(order in strategy.execution.orders_completed()) self.assertEqual(0, len(strategy.execution.positions_closed())) self.assertTrue(order in strategy.execution.orders_completed()) self.assertTrue(PositionId("B-USD/JPY-1") in strategy.execution.position_open_ids()) self.assertFalse(strategy.execution.is_completely_flat()) def test_can_track_orders_for_a_closing_position(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = TestStrategy1(bar_type) strategy.register_trader( TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.exec_engine.register_strategy(strategy) order1 = strategy.order_factory.market( USDJPY_FXCM, OrderSide.BUY, Quantity(100000), ) order2 = strategy.order_factory.market( USDJPY_FXCM, OrderSide.SELL, Quantity(100000), ) strategy.submit_order(order1) strategy.submit_order(order2, PositionId("B-USD/JPY-1")) # Position identifier generated by exchange # Act print(self.exec_engine.cache.orders()) # Assert self.assertEqual(0, len(self.exec_engine.cache.orders_working())) self.assertTrue(order1 in self.exec_engine.cache.orders_completed()) self.assertTrue(order2 in self.exec_engine.cache.orders_completed()) self.assertEqual(1, len(self.exec_engine.cache.positions_closed())) self.assertEqual(0, len(self.exec_engine.cache.positions_open())) self.assertTrue(self.exec_engine.cache.is_completely_flat())
def setUp(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = TestUUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.data_engine = DataEngine( tick_capacity=1000, bar_capacity=1000, portfolio=self.portfolio, clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.data_engine.set_use_previous_close(False) self.analyzer = PerformanceAnalyzer() trader_id = TraderId('TESTER', '000') account_id = TestStubs.account_id() self.exec_db = BypassExecutionDatabase( trader_id=trader_id, logger=self.logger, ) self.exec_engine = ExecutionEngine( database=self.exec_db, portfolio=self.portfolio, clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) usdjpy = InstrumentLoader.default_fx_ccy(TestStubs.symbol_usdjpy_fxcm()) self.market = SimulatedMarket( venue=Venue("FXCM"), oms_type=OMSType.HEDGING, generate_position_ids=True, exec_cache=self.exec_engine.cache, instruments={usdjpy.symbol: usdjpy}, config=BacktestConfig(), fill_model=FillModel(), commission_model=GenericCommissionModel(), clock=self.clock, uuid_factory=TestUUIDFactory(), logger=self.logger, ) self.exec_client = BacktestExecClient( market=self.market, account_id=account_id, engine=self.exec_engine, logger=self.logger, ) self.exec_engine.register_client(self.exec_client) self.market.register_client(self.exec_client) self.exec_engine.process(TestStubs.event_account_state()) self.market.process_tick(TestStubs.quote_tick_3decimal(usdjpy.symbol)) # Prepare market self.strategy = TradingStrategy(order_id_tag="001") self.strategy.register_trader( trader_id=TraderId("TESTER", "000"), clock=self.clock, uuid_factory=self.uuid_factory, logger=self.logger, ) self.strategy.register_data_engine(self.data_engine) self.strategy.register_execution_engine(self.exec_engine) print("\n")
class TestSimulatedExchangeContingencyAdvancedOrders: def setup(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger( clock=self.clock, level_stdout=LogLevel.INFO, ) self.trader_id = TestIdStubs.trader_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestComponentStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( msgbus=self.msgbus, clock=self.clock, cache=self.cache, logger=self.logger, ) self.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.risk_engine = RiskEngine( portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=FTX, oms_type=OMSType.NETTING, account_type=AccountType.MARGIN, base_currency=None, # Multi-asset wallet starting_balances=[Money(200, ETH), Money(1_000_000, USD)], default_leverage=Decimal(100), leverages={}, is_frozen_account=False, instruments=[ETHUSD_FTX], modules=[], fill_model=FillModel(), cache=self.cache, clock=self.clock, logger=self.logger, latency_model=LatencyModel(0), ) self.exec_client = BacktestExecClient( exchange=self.exchange, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Wire up components self.exec_engine.register_client(self.exec_client) self.exchange.register_client(self.exec_client) self.cache.add_instrument(ETHUSD_FTX) # Create mock strategy self.strategy = MockStrategy( bar_type=TestDataStubs.bartype_usdjpy_1min_bid()) self.strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Start components self.exchange.reset() self.data_engine.start() self.exec_engine.start() self.strategy.start() def test_submit_bracket_market_buy_accepts_sl_and_tp(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_market( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # Assert assert bracket.orders[0].status == OrderStatus.FILLED assert bracket.orders[1].status == OrderStatus.ACCEPTED assert bracket.orders[2].status == OrderStatus.ACCEPTED def test_submit_bracket_market_sell_accepts_sl_and_tp(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_market( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.SELL, quantity=ETHUSD_FTX.make_qty(10.000), stop_loss=ETHUSD_FTX.make_price(3150.0), take_profit=ETHUSD_FTX.make_price(3050.0), ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # Assert assert bracket.orders[0].status == OrderStatus.FILLED assert bracket.orders[1].status == OrderStatus.ACCEPTED assert bracket.orders[2].status == OrderStatus.ACCEPTED def test_submit_bracket_limit_buy_has_sl_tp_pending(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3090.0), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # # # Assert assert bracket.orders[0].status == OrderStatus.ACCEPTED assert bracket.orders[1].status == OrderStatus.SUBMITTED assert bracket.orders[2].status == OrderStatus.SUBMITTED def test_submit_bracket_limit_sell_has_sl_tp_pending(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.SELL, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3100.0), stop_loss=ETHUSD_FTX.make_price(3150.0), take_profit=ETHUSD_FTX.make_price(3050.0), ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # # # Assert assert bracket.orders[0].status == OrderStatus.ACCEPTED assert bracket.orders[1].status == OrderStatus.SUBMITTED assert bracket.orders[2].status == OrderStatus.SUBMITTED def test_submit_bracket_limit_buy_fills_then_triggers_sl_and_tp(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3100.0), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # Assert assert bracket.orders[0].status == OrderStatus.FILLED assert bracket.orders[1].status == OrderStatus.ACCEPTED assert bracket.orders[2].status == OrderStatus.ACCEPTED assert len(self.exchange.get_open_orders()) == 2 assert bracket.orders[1] in self.exchange.get_open_orders() assert bracket.orders[2] in self.exchange.get_open_orders() def test_submit_bracket_limit_sell_fills_then_triggers_sl_and_tp(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.SELL, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3050.0), stop_loss=ETHUSD_FTX.make_price(3150.0), take_profit=ETHUSD_FTX.make_price(3000.0), ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # Assert assert bracket.orders[0].status == OrderStatus.FILLED assert bracket.orders[1].status == OrderStatus.ACCEPTED assert bracket.orders[2].status == OrderStatus.ACCEPTED assert len(self.exchange.get_open_orders()) == 2 assert bracket.orders[1] in self.exchange.get_open_orders() assert bracket.orders[2] in self.exchange.get_open_orders() def test_reject_bracket_entry_then_rejects_sl_and_tp(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.SELL, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3050.0), # <-- in the market stop_loss=ETHUSD_FTX.make_price(3150.0), take_profit=ETHUSD_FTX.make_price(3000.0), post_only=True, # <-- will reject placed into the market ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # Assert assert bracket.orders[0].status == OrderStatus.REJECTED assert bracket.orders[1].status == OrderStatus.REJECTED assert bracket.orders[2].status == OrderStatus.REJECTED assert len(self.exchange.get_open_orders()) == 0 assert bracket.orders[1] not in self.exchange.get_open_orders() assert bracket.orders[2] not in self.exchange.get_open_orders() def test_filling_bracket_sl_cancels_tp_order(self): # Arrange: Prepare market tick1 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick1) self.exchange.process_tick(tick1) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3100.0), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) self.strategy.submit_order_list(bracket) self.exchange.process(0) tick2 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3150.0), ask=ETHUSD_FTX.make_price(3151.0), bid_size=ETHUSD_FTX.make_qty(10.000), ask_size=ETHUSD_FTX.make_qty(10.000), ts_event=0, ts_init=0, ) # Act self.exchange.process_tick(tick2) # Assert assert bracket.orders[0].status == OrderStatus.FILLED assert bracket.orders[1].status == OrderStatus.CANCELED assert bracket.orders[2].status == OrderStatus.FILLED assert len(self.exchange.get_open_orders()) == 0 assert len(self.exchange.cache.positions_open()) == 0 def test_filling_bracket_tp_cancels_sl_order(self): # Arrange: Prepare market tick1 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick1) self.exchange.process_tick(tick1) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3100.0), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) self.strategy.submit_order_list(bracket) self.exchange.process(0) # Act tick2 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3150.0), ask=ETHUSD_FTX.make_price(3151.0), bid_size=ETHUSD_FTX.make_qty(10.000), ask_size=ETHUSD_FTX.make_qty(10.000), ts_event=0, ts_init=0, ) self.exchange.process_tick(tick2) # Assert assert bracket.orders[0].status == OrderStatus.FILLED assert bracket.orders[1].status == OrderStatus.CANCELED assert bracket.orders[2].status == OrderStatus.FILLED assert len(self.exchange.get_open_orders()) == 0 assert len(self.exchange.cache.positions_open()) == 0 def test_partial_fill_bracket_tp_updates_sl_order(self): # Arrange: Prepare market tick1 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick1) self.exchange.process_tick(tick1) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3100.0), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) en = bracket.orders[0] sl = bracket.orders[1] tp = bracket.orders[2] self.strategy.submit_order_list(bracket) self.exchange.process(0) # Act tick2 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3150.0), ask=ETHUSD_FTX.make_price(3151.0), bid_size=ETHUSD_FTX.make_qty(5.000), ask_size=ETHUSD_FTX.make_qty(5.1000), ts_event=0, ts_init=0, ) self.exchange.process_tick(tick2) # Assert assert en.status == OrderStatus.FILLED assert sl.status == OrderStatus.ACCEPTED assert tp.status == OrderStatus.PARTIALLY_FILLED assert sl.quantity == Quantity.from_int(5) assert tp.leaves_qty == Quantity.from_int(5) assert tp.quantity == Quantity.from_int(10) assert len(self.exchange.get_open_orders()) == 2 assert len(self.exchange.cache.positions_open()) == 1 def test_modifying_bracket_tp_updates_sl_order(self): # Arrange: Prepare market tick1 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick1) self.exchange.process_tick(tick1) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3100.0), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) en = bracket.orders[0] sl = bracket.orders[1] tp = bracket.orders[2] self.strategy.submit_order_list(bracket) self.exchange.process(0) # Act self.strategy.modify_order( order=sl, quantity=Quantity.from_int(5), trigger_price=sl.trigger_price, ) self.exchange.process(0) # Assert assert en.status == OrderStatus.FILLED assert sl.status == OrderStatus.ACCEPTED assert tp.status == OrderStatus.ACCEPTED assert sl.quantity == Quantity.from_int(5) assert tp.quantity == Quantity.from_int(5) assert len(self.exchange.get_open_orders()) == 2 assert len(self.exchange.cache.positions_open()) == 1 def test_closing_position_cancels_bracket_ocos(self): # Arrange: Prepare market tick1 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick1) self.exchange.process_tick(tick1) bracket = self.strategy.order_factory.bracket_market( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) en = bracket.orders[0] sl = bracket.orders[1] tp = bracket.orders[2] self.strategy.submit_order_list(bracket) self.exchange.process(0) # Act self.strategy.flatten_position( self.strategy.cache.position(en.position_id)) self.exchange.process(0) # Assert assert en.status == OrderStatus.FILLED assert sl.status == OrderStatus.CANCELED assert tp.status == OrderStatus.CANCELED assert len(self.exchange.get_open_orders()) == 0 assert len(self.exchange.cache.positions_open()) == 0 def test_partially_filling_position_updates_bracket_ocos(self): # Arrange: Prepare market tick1 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick1) self.exchange.process_tick(tick1) bracket = self.strategy.order_factory.bracket_market( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) en = bracket.orders[0] sl = bracket.orders[1] tp = bracket.orders[2] self.strategy.submit_order_list(bracket) self.exchange.process(0) # Act reduce_order = self.strategy.order_factory.market( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.SELL, quantity=ETHUSD_FTX.make_qty(5.000), ) self.strategy.submit_order( reduce_order, position_id=self.cache.position_for_order(en.client_order_id).id, ) self.exchange.process(0) # Assert assert en.status == OrderStatus.FILLED assert sl.status == OrderStatus.ACCEPTED assert tp.status == OrderStatus.ACCEPTED assert sl.quantity == ETHUSD_FTX.make_qty(5.000) assert tp.quantity == ETHUSD_FTX.make_qty(5.000) assert len(self.exchange.get_open_orders()) == 2 assert len(self.exchange.cache.positions_open()) == 1
class SimulatedExchangeTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={'use_previous_close': False}, # To correctly reproduce historical data bars ) self.data_engine.cache.add_instrument(AUDUSD_SIM) self.data_engine.cache.add_instrument(USDJPY_SIM) self.portfolio.register_cache(self.data_engine.cache) self.analyzer = PerformanceAnalyzer() self.trader_id = TraderId("TESTER", "000") self.account_id = AccountId("SIM", "001") exec_db = BypassExecutionDatabase( trader_id=self.trader_id, logger=self.logger, ) self.exec_engine = ExecutionEngine( database=exec_db, portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=SIM, oms_type=OMSType.HEDGING, generate_position_ids=False, # Will force execution engine to generate ids is_frozen_account=False, starting_balances=[Money(1_000_000, USD)], instruments=[AUDUSD_SIM, USDJPY_SIM], modules=[], fill_model=FillModel(), exec_cache=self.exec_engine.cache, clock=self.clock, logger=self.logger, ) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=self.account_id, engine=self.exec_engine, clock=self.clock, logger=self.logger, ) self.exec_engine.register_client(self.exec_client) self.exchange.register_client(self.exec_client) self.strategy = MockStrategy(bar_type=TestStubs.bartype_usdjpy_1min_bid()) self.strategy.register_trader( self.trader_id, self.clock, self.logger, ) self.data_engine.register_strategy(self.strategy) self.exec_engine.register_strategy(self.strategy) self.data_engine.start() self.exec_engine.start() self.strategy.start() def test_repr(self): # Arrange # Act # Assert self.assertEqual("SimulatedExchange(SIM)", repr(self.exchange)) def test_check_residuals(self): # Arrange # Act self.exchange.check_residuals() # Assert self.assertTrue(True) # No exceptions raised def test_check_residuals_with_working_and_oco_orders(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry1 = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("90.000"), ) entry2 = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("89.900"), ) bracket1 = self.strategy.order_factory.bracket( entry_order=entry1, stop_loss=Price("89.900"), take_profit=Price("91.000"), ) bracket2 = self.strategy.order_factory.bracket( entry_order=entry2, stop_loss=Price("89.800"), ) self.strategy.submit_bracket_order(bracket1) self.strategy.submit_bracket_order(bracket2) tick2 = QuoteTick( USDJPY_SIM.symbol, Price("89.998"), Price("89.999"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) # Act self.exchange.check_residuals() # Assert self.assertEqual(3, len(self.exchange.get_working_orders())) self.assertIn(bracket1.stop_loss, self.exchange.get_working_orders().values()) self.assertIn(bracket1.take_profit, self.exchange.get_working_orders().values()) self.assertIn(entry2, self.exchange.get_working_orders().values()) def test_get_working_orders_when_no_orders_returns_empty_dict(self): # Arrange # Act orders = self.exchange.get_working_orders() self.assertEqual({}, orders) def test_submit_order_with_no_market_rejects_order(self): # Arrange order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("80.000"), ) # Act self.strategy.submit_order(order) # Assert self.assertEqual(2, self.strategy.object_storer.count) self.assertTrue(isinstance(self.strategy.object_storer.get_store()[1], OrderRejected)) def test_submit_order_with_invalid_price_gets_rejected(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.exchange.process_tick(tick) self.portfolio.update_tick(tick) order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("80.000"), ) # Act self.strategy.submit_order(order) # Assert self.assertEqual(OrderState.REJECTED, order.state) def test_submit_market_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) # Create order order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) # Act self.strategy.submit_order(order) # Assert self.assertEqual(OrderState.FILLED, order.state) self.assertEqual(Decimal("90.003"), order.avg_price) def test_submit_limit_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("80.000"), ) # Act self.strategy.submit_order(order) # Assert self.assertEqual(1, len(self.exchange.get_working_orders())) self.assertIn(order.cl_ord_id, self.exchange.get_working_orders()) def test_submit_bracket_market_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry_order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) bracket_order = self.strategy.order_factory.bracket( entry_order, Price("80.000"), ) # Act self.strategy.submit_bracket_order(bracket_order) # Assert self.assertEqual(OrderState.FILLED, entry_order.state) def test_submit_bracket_stop_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry_order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("96.710"), ) bracket_order = self.strategy.order_factory.bracket( entry_order, Price("86.000"), Price("97.000"), ) # Act self.strategy.submit_bracket_order(bracket_order) # Assert self.assertEqual(1, len(self.exchange.get_working_orders())) self.assertIn(entry_order.cl_ord_id, self.exchange.get_working_orders()) def test_cancel_stop_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("96.711"), ) self.strategy.submit_order(order) # Act self.strategy.cancel_order(order) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) def test_cancel_stop_order_when_order_does_not_exist_generates_cancel_reject(self): # Arrange command = CancelOrder( venue=SIM, trader_id=self.trader_id, account_id=self.account_id, cl_ord_id=ClientOrderId("O-123456"), order_id=OrderId("001"), command_id=self.uuid_factory.generate(), command_timestamp=UNIX_EPOCH, ) # Act self.exchange.handle_cancel_order(command) # Assert self.assertEqual(2, self.exec_engine.event_count) def test_modify_stop_order_when_order_does_not_exist(self): # Arrange command = AmendOrder( venue=SIM, trader_id=self.trader_id, account_id=self.account_id, cl_ord_id=ClientOrderId("O-123456"), quantity=Quantity(100000), price=Price("1.00000"), command_id=self.uuid_factory.generate(), command_timestamp=UNIX_EPOCH, ) # Act self.exchange.handle_amend_order(command) # Assert self.assertEqual(2, self.exec_engine.event_count) def test_modify_stop_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("96.711"), ) self.strategy.submit_order(order) # Act self.strategy.amend_order(order, order.quantity, Price("96.714")) # Assert self.assertEqual(1, len(self.exchange.get_working_orders())) self.assertEqual(Price("96.714"), order.price) def test_expire_order(self): # Arrange # Prepare market tick1 = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick1) self.exchange.process_tick(tick1) order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("96.711"), time_in_force=TimeInForce.GTD, expire_time=UNIX_EPOCH + timedelta(minutes=1), ) self.strategy.submit_order(order) tick2 = QuoteTick( USDJPY_SIM.symbol, Price("96.709"), Price("96.710"), Quantity(100000), Quantity(100000), UNIX_EPOCH + timedelta(minutes=1), ) # Act self.exchange.process_tick(tick2) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) def test_modify_bracket_order_working_stop_loss(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry_order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) bracket_order = self.strategy.order_factory.bracket( entry_order, stop_loss=Price("85.000"), ) self.strategy.submit_bracket_order(bracket_order) # Act self.strategy.amend_order(bracket_order.stop_loss, bracket_order.entry.quantity, Price("85.100")) # Assert self.assertEqual(Price("85.100"), bracket_order.stop_loss.price) def test_submit_market_order_with_slippage_fill_model_slips_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) fill_model = FillModel( prob_fill_at_limit=0.0, prob_fill_at_stop=1.0, prob_slippage=1.0, random_seed=None, ) self.exchange.set_fill_model(fill_model) order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) # Act self.strategy.submit_order(order) # Assert self.assertEqual(Decimal("90.004"), order.avg_price) def test_order_fills_gets_commissioned(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) top_up_order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) reduce_order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(50000), ) # Act self.strategy.submit_order(order) position_id = PositionId("P-19700101-000000-000-001-1") # Generated by platform self.strategy.submit_order(top_up_order, position_id) self.strategy.submit_order(reduce_order, position_id) account_event1 = self.strategy.object_storer.get_store()[2] account_event2 = self.strategy.object_storer.get_store()[6] account_event3 = self.strategy.object_storer.get_store()[10] account = self.exec_engine.cache.account_for_venue(Venue("SIM")) # Assert self.assertEqual(Money(180.01, JPY), account_event1.commission) self.assertEqual(Money(180.01, JPY), account_event2.commission) self.assertEqual(Money(90.00, JPY), account_event3.commission) self.assertTrue(Money(999995.00, USD), account.balance()) def test_process_quote_tick_fills_buy_stop_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("96.711"), ) self.strategy.submit_order(order) # Act tick2 = QuoteTick( AUDUSD_SIM.symbol, # Different market Price("80.010"), Price("80.011"), Quantity(200000), Quantity(200000), UNIX_EPOCH, ) tick3 = QuoteTick( USDJPY_SIM.symbol, Price("96.710"), Price("96.712"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) self.exchange.process_tick(tick3) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) self.assertEqual(OrderState.FILLED, order.state) self.assertEqual(Price("96.711"), order.avg_price) def test_process_quote_tick_fills_buy_limit_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("90.001"), ) self.strategy.submit_order(order) # Act tick2 = QuoteTick( AUDUSD_SIM.symbol, # Different market Price("80.010"), Price("80.011"), Quantity(200000), Quantity(200000), UNIX_EPOCH, ) tick3 = QuoteTick( USDJPY_SIM.symbol, Price("89.998"), Price("89.999"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) self.exchange.process_tick(tick3) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) self.assertEqual(OrderState.FILLED, order.state) self.assertEqual(Price("90.001"), order.avg_price) def test_process_quote_tick_fills_sell_stop_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.SELL, Quantity(100000), Price("90.000"), ) self.strategy.submit_order(order) # Act tick2 = QuoteTick( USDJPY_SIM.symbol, Price("89.997"), Price("89.999"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) self.assertEqual(OrderState.FILLED, order.state) self.assertEqual(Price("90.000"), order.avg_price) def test_process_quote_tick_fills_sell_limit_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.SELL, Quantity(100000), Price("90.100"), ) self.strategy.submit_order(order) # Act tick2 = QuoteTick( USDJPY_SIM.symbol, Price("90.101"), Price("90.102"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) self.assertEqual(OrderState.FILLED, order.state) self.assertEqual(Price("90.100"), order.avg_price) def test_process_quote_tick_fills_buy_limit_entry_with_bracket(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("90.000"), ) bracket = self.strategy.order_factory.bracket( entry_order=entry, stop_loss=Price("89.900"), ) self.strategy.submit_bracket_order(bracket) # Act tick2 = QuoteTick( USDJPY_SIM.symbol, Price("89.998"), Price("89.999"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) # Assert self.assertEqual(1, len(self.exchange.get_working_orders())) self.assertIn(bracket.stop_loss, self.exchange.get_working_orders().values()) def test_process_quote_tick_fills_sell_limit_entry_with_bracket(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.SELL, Quantity(100000), Price("91.100"), ) bracket = self.strategy.order_factory.bracket( entry_order=entry, stop_loss=Price("91.200"), take_profit=Price("90.000"), ) self.strategy.submit_bracket_order(bracket) # Act tick2 = QuoteTick( USDJPY_SIM.symbol, Price("91.101"), Price("91.102"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) # Assert self.assertEqual(2, len(self.exchange.get_working_orders())) # SL and TP self.assertIn(bracket.stop_loss, self.exchange.get_working_orders().values()) self.assertIn(bracket.take_profit, self.exchange.get_working_orders().values()) def test_process_trade_tick_fills_buy_limit_entry_with_bracket(self): # Arrange # Prepare market tick1 = TradeTick( AUDUSD_SIM.symbol, Price("1.00000"), Quantity(100000), OrderSide.SELL, TradeMatchId("123456789"), UNIX_EPOCH, ) tick2 = TradeTick( AUDUSD_SIM.symbol, Price("1.00001"), Quantity(100000), OrderSide.BUY, TradeMatchId("123456790"), UNIX_EPOCH, ) self.data_engine.process(tick1) self.data_engine.process(tick2) self.exchange.process_tick(tick1) self.exchange.process_tick(tick2) entry = self.strategy.order_factory.limit( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("0.99900"), ) bracket = self.strategy.order_factory.bracket( entry_order=entry, stop_loss=Price("0.99800"), take_profit=Price("1.100"), ) self.strategy.submit_bracket_order(bracket) # Act tick3 = TradeTick( AUDUSD_SIM.symbol, Price("0.99899"), Quantity(100000), OrderSide.BUY, # Lowers ask price TradeMatchId("123456789"), UNIX_EPOCH, ) self.exchange.process_tick(tick3) # Assert self.assertEqual(2, len(self.exchange.get_working_orders())) # SL and TP only self.assertIn(bracket.stop_loss, self.exchange.get_working_orders().values()) self.assertIn(bracket.take_profit, self.exchange.get_working_orders().values()) def test_filling_oco_sell_cancels_other_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.SELL, Quantity(100000), Price("91.100"), ) bracket = self.strategy.order_factory.bracket( entry_order=entry, stop_loss=Price("91.200"), take_profit=Price("90.000"), ) self.strategy.submit_bracket_order(bracket) # Act tick2 = QuoteTick( USDJPY_SIM.symbol, Price("91.101"), Price("91.102"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) tick3 = QuoteTick( USDJPY_SIM.symbol, Price("91.201"), Price("91.203"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) self.exchange.process_tick(tick3) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) def test_realized_pnl_contains_commission(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) # Act self.strategy.submit_order(order) position = self.exec_engine.cache.positions_open()[0] # Assert self.assertEqual(Money(-180.01, JPY), position.realized_pnl) self.assertEqual(Money(180.01, JPY), position.commission) self.assertEqual([Money(180.01, JPY)], position.commissions()) def test_unrealized_pnl(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order_open = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) # Act 1 self.strategy.submit_order(order_open) reduce_quote = QuoteTick( USDJPY_SIM.symbol, Price("100.003"), Price("100.003"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(reduce_quote) self.portfolio.update_tick(reduce_quote) order_reduce = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.SELL, Quantity(50000), ) position_id = PositionId("P-19700101-000000-000-001-1") # Generated by platform # Act 2 self.strategy.submit_order(order_reduce, position_id) # Assert position = self.exec_engine.cache.positions_open()[0] self.assertEqual(Money(500000.00, JPY), position.unrealized_pnl(Price("100.003"))) def test_position_flipped_when_reduce_order_exceeds_original_quantity(self): # Arrange # Prepare market open_quote = QuoteTick( USDJPY_SIM.symbol, Price("90.002"), Price("90.003"), Quantity(1), Quantity(1), UNIX_EPOCH, ) self.data_engine.process(open_quote) self.exchange.process_tick(open_quote) order_open = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) # Act 1 self.strategy.submit_order(order_open) reduce_quote = QuoteTick( USDJPY_SIM.symbol, Price("100.003"), Price("100.003"), Quantity(1), Quantity(1), UNIX_EPOCH, ) self.exchange.process_tick(reduce_quote) self.portfolio.update_tick(reduce_quote) order_reduce = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.SELL, Quantity(150000), ) # Act 2 self.strategy.submit_order(order_reduce, PositionId("P-19700101-000000-000-001-1")) # Generated by platform # Assert print(self.exec_engine.cache.positions()) position_open = self.exec_engine.cache.positions_open()[0] position_closed = self.exec_engine.cache.positions_closed()[0] self.assertEqual(PositionSide.SHORT, position_open.side) self.assertEqual(Quantity(50000), position_open.quantity) self.assertEqual(Money(999619.98, JPY), position_closed.realized_pnl) self.assertEqual([Money(380.02, JPY)], position_closed.commissions())
class TestTradingStrategy: def setup(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger( clock=self.clock, level_stdout=LogLevel.DEBUG, ) self.trader_id = TestStubs.trader_id() self.account_id = TestStubs.account_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.risk_engine = RiskEngine( portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=Venue("SIM"), venue_type=VenueType.ECN, oms_type=OMSType.HEDGING, account_type=AccountType.MARGIN, base_currency=USD, starting_balances=[Money(1_000_000, USD)], default_leverage=Decimal(50), leverages={}, is_frozen_account=False, cache=self.cache, instruments=[USDJPY_SIM], modules=[], fill_model=FillModel(), clock=self.clock, logger=self.logger, latency_model=LatencyModel(0), ) self.data_client = BacktestMarketDataClient( client_id=ClientId("SIM"), msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=self.account_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Wire up components self.exchange.register_client(self.exec_client) self.data_engine.register_client(self.data_client) self.exec_engine.register_client(self.exec_client) self.exchange.reset() # Add instruments self.data_engine.process(AUDUSD_SIM) self.data_engine.process(GBPUSD_SIM) self.data_engine.process(USDJPY_SIM) self.cache.add_instrument(AUDUSD_SIM) self.cache.add_instrument(GBPUSD_SIM) self.cache.add_instrument(USDJPY_SIM) self.exchange.process_tick(TestStubs.quote_tick_3decimal( USDJPY_SIM.id)) # Prepare market self.data_engine.start() self.exec_engine.start() def test_strategy_equality(self): # Arrange strategy1 = TradingStrategy(config=TradingStrategyConfig( order_id_tag="AUD/USD-001")) strategy2 = TradingStrategy(config=TradingStrategyConfig( order_id_tag="AUD/USD-001")) strategy3 = TradingStrategy(config=TradingStrategyConfig( order_id_tag="AUD/USD-002")) # Act, Assert assert strategy1 == strategy1 assert strategy1 == strategy2 assert strategy2 != strategy3 def test_str_and_repr(self): # Arrange strategy = TradingStrategy(config=TradingStrategyConfig( order_id_tag="GBP/USD-MM")) # Act, Assert assert str(strategy) == "TradingStrategy-GBP/USD-MM" assert repr(strategy) == "TradingStrategy(TradingStrategy-GBP/USD-MM)" def test_id(self): # Arrange strategy = TradingStrategy() # Act, Assert assert strategy.id == StrategyId("TradingStrategy-000") def test_initialization(self): # Arrange strategy = TradingStrategy(config=TradingStrategyConfig( order_id_tag="001")) # Act, Assert assert strategy.state == ComponentState.PRE_INITIALIZED assert not strategy.indicators_initialized() def test_on_save_when_not_overridden_does_nothing(self): # Arrange strategy = TradingStrategy() # Act strategy.on_save() # Assert assert True # Exception not raised def test_on_load_when_not_overridden_does_nothing(self): # Arrange strategy = TradingStrategy() # Act strategy.on_load({}) # Assert assert True # Exception not raised def test_save_when_not_registered_logs_error(self): # Arrange config = TradingStrategyConfig() strategy = TradingStrategy(config) strategy.save() # Assert assert True # Exception not raised def test_save_when_user_code_raises_error_logs_and_reraises(self): # Arrange strategy = KaboomStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act, Assert with pytest.raises(RuntimeError): strategy.save() def test_load_when_user_code_raises_error_logs_and_reraises(self): # Arrange strategy = KaboomStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act, Assert with pytest.raises(RuntimeError): strategy.load({"something": b"123456"}) def test_load(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) state = {} # Act strategy.load(state) # Assert # TODO: Write a users custom save method assert True def test_reset(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = MockStrategy(bar_type) strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bar = Bar( bar_type, Price.from_str("1.00001"), Price.from_str("1.00004"), Price.from_str("1.00002"), Price.from_str("1.00003"), Quantity.from_int(100000), 0, 0, ) strategy.handle_bar(bar) # Act strategy.reset() # Assert assert "on_reset" in strategy.calls assert strategy.is_initialized assert strategy.ema1.count == 0 assert strategy.ema2.count == 0 def test_dispose(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = MockStrategy(bar_type) strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) strategy.reset() # Act strategy.dispose() # Assert assert "on_dispose" in strategy.calls assert strategy.is_disposed def test_save_load(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = MockStrategy(bar_type) strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act state = strategy.save() strategy.load(state) # Assert assert state == {"UserState": b"1"} assert "on_save" in strategy.calls assert strategy.is_initialized def test_register_indicator_for_quote_ticks_when_already_registered(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema1 = ExponentialMovingAverage(10, price_type=PriceType.MID) ema2 = ExponentialMovingAverage(10, price_type=PriceType.MID) # Act strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema1) strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema2) strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema2) assert len(strategy.registered_indicators) == 2 assert ema1 in strategy.registered_indicators assert ema2 in strategy.registered_indicators def test_register_indicator_for_trade_ticks_when_already_registered(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema1 = ExponentialMovingAverage(10) ema2 = ExponentialMovingAverage(10) # Act strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema1) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema2) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema2) assert len(strategy.registered_indicators) == 2 assert ema1 in strategy.registered_indicators assert ema2 in strategy.registered_indicators def test_register_indicator_for_bars_when_already_registered(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema1 = ExponentialMovingAverage(10) ema2 = ExponentialMovingAverage(10) bar_type = TestStubs.bartype_audusd_1min_bid() # Act strategy.register_indicator_for_bars(bar_type, ema1) strategy.register_indicator_for_bars(bar_type, ema2) strategy.register_indicator_for_bars(bar_type, ema2) assert len(strategy.registered_indicators) == 2 assert ema1 in strategy.registered_indicators assert ema2 in strategy.registered_indicators def test_register_indicator_for_multiple_data_sources(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) bar_type = TestStubs.bartype_audusd_1min_bid() # Act strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema) strategy.register_indicator_for_quote_ticks(GBPUSD_SIM.id, ema) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema) strategy.register_indicator_for_bars(bar_type, ema) assert len(strategy.registered_indicators) == 1 assert ema in strategy.registered_indicators def test_handle_quote_tick_updates_indicator_registered_for_quote_ticks( self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10, price_type=PriceType.MID) strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema) tick = TestStubs.quote_tick_5decimal(AUDUSD_SIM.id) # Act strategy.handle_quote_tick(tick) strategy.handle_quote_tick(tick, True) # Assert assert ema.count == 2 def test_handle_quote_ticks_with_no_ticks_logs_and_continues(self): # Arrange strategy = KaboomStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10, price_type=PriceType.MID) strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema) # Act strategy.handle_quote_ticks([]) # Assert assert ema.count == 0 def test_handle_quote_ticks_updates_indicator_registered_for_quote_ticks( self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10, price_type=PriceType.MID) strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema) tick = TestStubs.quote_tick_5decimal(AUDUSD_SIM.id) # Act strategy.handle_quote_ticks([tick]) # Assert assert ema.count == 1 def test_handle_trade_tick_updates_indicator_registered_for_trade_ticks( self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema) tick = TestStubs.trade_tick_5decimal(AUDUSD_SIM.id) # Act strategy.handle_trade_tick(tick) strategy.handle_trade_tick(tick, True) # Assert assert ema.count == 2 def test_handle_trade_ticks_updates_indicator_registered_for_trade_ticks( self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema) tick = TestStubs.trade_tick_5decimal(AUDUSD_SIM.id) # Act strategy.handle_trade_ticks([tick]) # Assert assert ema.count == 1 def test_handle_trade_ticks_with_no_ticks_logs_and_continues(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema) # Act strategy.handle_trade_ticks([]) # Assert assert ema.count == 0 def test_handle_bar_updates_indicator_registered_for_bars(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_bars(bar_type, ema) bar = TestStubs.bar_5decimal() # Act strategy.handle_bar(bar) strategy.handle_bar(bar, True) # Assert assert ema.count == 2 def test_handle_bars_updates_indicator_registered_for_bars(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_bars(bar_type, ema) bar = TestStubs.bar_5decimal() # Act strategy.handle_bars([bar]) # Assert assert ema.count == 1 def test_handle_bars_with_no_bars_logs_and_continues(self): # Arrange bar_type = TestStubs.bartype_gbpusd_1sec_mid() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_bars(bar_type, ema) # Act strategy.handle_bars([]) # Assert assert ema.count == 0 def test_stop_cancels_a_running_time_alert(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = MockStrategy(bar_type) strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) alert_time = datetime.now(pytz.utc) + timedelta(milliseconds=200) strategy.clock.set_time_alert("test_alert1", alert_time) # Act strategy.start() strategy.stop() # Assert assert len(strategy.clock.timer_names()) == 0 def test_stop_cancels_a_running_timer(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = MockStrategy(bar_type) strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) start_time = datetime.now(pytz.utc) + timedelta(milliseconds=100) strategy.clock.set_timer("test_timer", timedelta(milliseconds=100), start_time, stop_time=None) # Act strategy.start() strategy.stop() # Assert assert len(strategy.clock.timer_names()) == 0 def test_submit_order_with_valid_order_successfully_submits(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) # Act strategy.submit_order(order) self.exchange.process(0) # Assert assert order in strategy.cache.orders() assert strategy.cache.orders()[0].status == OrderStatus.FILLED assert order.client_order_id not in strategy.cache.orders_working() assert not strategy.cache.is_order_working(order.client_order_id) assert strategy.cache.is_order_completed(order.client_order_id) def test_submit_order_list_with_valid_order_successfully_submits(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bracket = strategy.order_factory.bracket_market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), stop_loss=Price.from_str("90.000"), take_profit=Price.from_str("90.500"), ) # Act strategy.submit_order_list(bracket) # Assert assert bracket.orders[0] in strategy.cache.orders() assert bracket.orders[1] in strategy.cache.orders() assert bracket.orders[2] in strategy.cache.orders() # TODO: Implement # assert bracket.orders[0].status == OrderStatus.ACCEPTED # assert entry in strategy.cache.orders_working() # assert strategy.cache.is_order_working(entry.client_order_id) # assert not strategy.cache.is_order_completed(entry.client_order_id) def test_cancel_order(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.stop_market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.006"), ) strategy.submit_order(order) self.exchange.process(0) # Act strategy.cancel_order(order) self.exchange.process(0) # Assert assert order in strategy.cache.orders() assert strategy.cache.orders()[0].status == OrderStatus.CANCELED assert order.client_order_id == strategy.cache.orders_completed( )[0].client_order_id assert order not in strategy.cache.orders_working() assert strategy.cache.order_exists(order.client_order_id) assert not strategy.cache.is_order_working(order.client_order_id) assert strategy.cache.is_order_completed(order.client_order_id) def test_cancel_order_when_pending_cancel_does_not_submit_command(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.stop_market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.006"), ) strategy.submit_order(order) self.exchange.process(0) self.exec_engine.process(TestStubs.event_order_pending_cancel(order)) # Act strategy.cancel_order(order) self.exchange.process(0) # Assert assert strategy.cache.orders()[0].status == OrderStatus.PENDING_CANCEL assert order in strategy.cache.orders_working() assert strategy.cache.order_exists(order.client_order_id) assert strategy.cache.is_order_working(order.client_order_id) assert not strategy.cache.is_order_completed(order.client_order_id) def test_cancel_order_when_completed_does_not_submit_command(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.stop_market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.006"), ) strategy.submit_order(order) self.exchange.process(0) self.exec_engine.process(TestStubs.event_order_expired(order)) # Act strategy.cancel_order(order) self.exchange.process(0) # Assert assert strategy.cache.orders()[0].status == OrderStatus.EXPIRED assert order not in strategy.cache.orders_working() assert strategy.cache.order_exists(order.client_order_id) assert not strategy.cache.is_order_working(order.client_order_id) assert strategy.cache.is_order_completed(order.client_order_id) def test_modify_order_when_pending_update_does_not_submit_command(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.001"), ) strategy.submit_order(order) self.exchange.process(0) self.exec_engine.process(TestStubs.event_order_pending_update(order)) # Act strategy.modify_order( order=order, quantity=Quantity.from_int(100000), price=Price.from_str("90.000"), ) self.exchange.process(0) # Assert assert self.exec_engine.command_count == 1 def test_modify_order_when_pending_cancel_does_not_submit_command(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.001"), ) strategy.submit_order(order) self.exchange.process(0) self.exec_engine.process(TestStubs.event_order_pending_cancel(order)) # Act strategy.modify_order( order=order, quantity=Quantity.from_int(100000), price=Price.from_str("90.000"), ) self.exchange.process(0) # Assert assert self.exec_engine.command_count == 1 def test_modify_order_when_completed_does_not_submit_command(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.001"), ) strategy.submit_order(order) self.exchange.process(0) self.exec_engine.process(TestStubs.event_order_expired(order)) # Act strategy.modify_order( order=order, quantity=Quantity.from_int(100000), price=Price.from_str("90.000"), ) self.exchange.process(0) # Assert assert self.exec_engine.command_count == 1 def test_modify_order_when_no_changes_does_not_submit_command(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.001"), ) strategy.submit_order(order) # Act strategy.modify_order( order=order, quantity=Quantity.from_int(100000), price=Price.from_str("90.001"), ) # Assert assert self.exec_engine.command_count == 1 def test_modify_order(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.000"), ) strategy.submit_order(order) self.exchange.process(0) # Act strategy.modify_order( order=order, quantity=Quantity.from_int(110000), price=Price.from_str("90.001"), ) self.exchange.process(0) # Assert assert strategy.cache.orders()[0] == order assert strategy.cache.orders()[0].status == OrderStatus.ACCEPTED assert strategy.cache.orders()[0].quantity == Quantity.from_int(110000) assert strategy.cache.orders()[0].price == Price.from_str("90.001") assert strategy.cache.order_exists(order.client_order_id) assert strategy.cache.is_order_working(order.client_order_id) assert not strategy.cache.is_order_completed(order.client_order_id) assert strategy.portfolio.is_flat(order.instrument_id) def test_cancel_all_orders(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order1 = strategy.order_factory.stop_market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.007"), ) order2 = strategy.order_factory.stop_market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.006"), ) strategy.submit_order(order1) self.exchange.process(0) strategy.submit_order(order2) self.exchange.process(0) # Act strategy.cancel_all_orders(USDJPY_SIM.id) self.exchange.process(0) # Assert assert order1 in self.cache.orders() assert order2 in self.cache.orders() assert self.cache.orders()[0].status == OrderStatus.CANCELED assert self.cache.orders()[1].status == OrderStatus.CANCELED assert order1 in self.cache.orders_completed() assert order2 in strategy.cache.orders_completed() def test_flatten_position_when_position_already_flat_does_nothing(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order1 = strategy.order_factory.market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2 = strategy.order_factory.market( USDJPY_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) strategy.submit_order(order1) self.exchange.process(0) strategy.submit_order(order2, PositionId("1-001")) # Generated by exchange self.exchange.process(0) position = strategy.cache.positions_closed()[0] # Act strategy.flatten_position(position) self.exchange.process(0) # Assert assert strategy.portfolio.is_completely_flat() def test_flatten_position(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) strategy.submit_order(order) self.exchange.process(0) position = self.cache.positions_open()[0] # Act strategy.flatten_position(position) self.exchange.process(0) # Assert assert order.status == OrderStatus.FILLED assert strategy.portfolio.is_completely_flat() def test_flatten_all_positions(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Start strategy and submit orders to open positions strategy.start() order1 = strategy.order_factory.market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2 = strategy.order_factory.market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) strategy.submit_order(order1) self.exchange.process(0) strategy.submit_order(order2) self.exchange.process(0) # Act strategy.flatten_all_positions(USDJPY_SIM.id) self.exchange.process(0) # Assert assert order1.status == OrderStatus.FILLED assert order2.status == OrderStatus.FILLED assert strategy.portfolio.is_completely_flat()
def setup(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger( clock=self.clock, level_stdout=LogLevel.DEBUG, ) self.trader_id = TestStubs.trader_id() self.account_id = TestStubs.account_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.risk_engine = RiskEngine( portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=Venue("SIM"), venue_type=VenueType.ECN, oms_type=OMSType.HEDGING, account_type=AccountType.MARGIN, base_currency=USD, starting_balances=[Money(1_000_000, USD)], default_leverage=Decimal(50), leverages={}, is_frozen_account=False, cache=self.cache, instruments=[USDJPY_SIM], modules=[], fill_model=FillModel(), clock=self.clock, logger=self.logger, latency_model=LatencyModel(0), ) self.data_client = BacktestMarketDataClient( client_id=ClientId("SIM"), msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=self.account_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Wire up components self.exchange.register_client(self.exec_client) self.data_engine.register_client(self.data_client) self.exec_engine.register_client(self.exec_client) self.exchange.reset() # Add instruments self.data_engine.process(AUDUSD_SIM) self.data_engine.process(GBPUSD_SIM) self.data_engine.process(USDJPY_SIM) self.cache.add_instrument(AUDUSD_SIM) self.cache.add_instrument(GBPUSD_SIM) self.cache.add_instrument(USDJPY_SIM) self.exchange.process_tick(TestStubs.quote_tick_3decimal( USDJPY_SIM.id)) # Prepare market self.data_engine.start() self.exec_engine.start()
def setup(self): # Fixture Setup self.clock = TestClock() self.logger = Logger(self.clock) self.trader_id = TestStubs.trader_id() self.account_id = TestStubs.account_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine.process(USDJPY_SIM) self.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=Venue("SIM"), venue_type=VenueType.ECN, oms_type=OMSType.HEDGING, account_type=AccountType.MARGIN, base_currency=USD, starting_balances=[Money(1_000_000, USD)], default_leverage=Decimal(50), leverages={}, is_frozen_account=False, cache=self.cache, instruments=[USDJPY_SIM], modules=[], fill_model=FillModel(), clock=self.clock, logger=self.logger, ) self.data_client = BacktestMarketDataClient( client_id=ClientId("SIM"), msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=self.account_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.risk_engine = RiskEngine( portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Wire up components self.data_engine.register_client(self.data_client) self.exec_engine.register_client(self.exec_client) self.trader = Trader( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, portfolio=self.portfolio, data_engine=self.data_engine, risk_engine=self.risk_engine, exec_engine=self.exec_engine, clock=self.clock, logger=self.logger, )
class TestTrader: def setup(self): # Fixture Setup self.clock = TestClock() self.logger = Logger(self.clock) self.trader_id = TestStubs.trader_id() self.account_id = TestStubs.account_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine.process(USDJPY_SIM) self.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=Venue("SIM"), venue_type=VenueType.ECN, oms_type=OMSType.HEDGING, account_type=AccountType.MARGIN, base_currency=USD, starting_balances=[Money(1_000_000, USD)], default_leverage=Decimal(50), leverages={}, is_frozen_account=False, cache=self.cache, instruments=[USDJPY_SIM], modules=[], fill_model=FillModel(), clock=self.clock, logger=self.logger, ) self.data_client = BacktestMarketDataClient( client_id=ClientId("SIM"), msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=self.account_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.risk_engine = RiskEngine( portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Wire up components self.data_engine.register_client(self.data_client) self.exec_engine.register_client(self.exec_client) self.trader = Trader( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, portfolio=self.portfolio, data_engine=self.data_engine, risk_engine=self.risk_engine, exec_engine=self.exec_engine, clock=self.clock, logger=self.logger, ) def test_initialize_trader(self): # Arrange, Act, Assert assert self.trader.id == TraderId("TESTER-000") assert self.trader.is_initialized assert len(self.trader.strategy_states()) == 0 def test_add_strategy(self): # Arrange, Act self.trader.add_strategy(TradingStrategy()) # Assert assert self.trader.strategy_states() == { StrategyId("TradingStrategy-000"): "INITIALIZED" } def test_add_strategies(self): # Arrange strategies = [ TradingStrategy(TradingStrategyConfig(order_id_tag="001")), TradingStrategy(TradingStrategyConfig(order_id_tag="002")), ] # Act self.trader.add_strategies(strategies) # Assert assert self.trader.strategy_states() == { StrategyId("TradingStrategy-001"): "INITIALIZED", StrategyId("TradingStrategy-002"): "INITIALIZED", } def test_clear_strategies(self): # Arrange strategies = [ TradingStrategy(TradingStrategyConfig(order_id_tag="001")), TradingStrategy(TradingStrategyConfig(order_id_tag="002")), ] self.trader.add_strategies(strategies) # Act self.trader.clear_strategies() # Assert assert self.trader.strategy_states() == {} def test_add_actor(self): # Arrange config = ActorConfig(component_id="MyPlugin-01") actor = Actor(config) # Act self.trader.add_actor(actor) # Assert assert self.trader.actor_ids() == [ComponentId("MyPlugin-01")] def test_add_actors(self): # Arrange actors = [ Actor(ActorConfig(component_id="MyPlugin-01")), Actor(ActorConfig(component_id="MyPlugin-02")), ] # Act self.trader.add_actors(actors) # Assert assert self.trader.actor_ids() == [ ComponentId("MyPlugin-01"), ComponentId("MyPlugin-02"), ] def test_clear_actors(self): # Arrange actors = [ Actor(ActorConfig(component_id="MyPlugin-01")), Actor(ActorConfig(component_id="MyPlugin-02")), ] self.trader.add_actors(actors) # Act self.trader.clear_actors() # Assert assert self.trader.actor_ids() == [] def test_get_strategy_states(self): # Arrange strategies = [ TradingStrategy(TradingStrategyConfig(order_id_tag="001")), TradingStrategy(TradingStrategyConfig(order_id_tag="002")), ] self.trader.add_strategies(strategies) # Act status = self.trader.strategy_states() # Assert assert StrategyId("TradingStrategy-001") in status assert StrategyId("TradingStrategy-002") in status assert status[StrategyId("TradingStrategy-001")] == "INITIALIZED" assert status[StrategyId("TradingStrategy-002")] == "INITIALIZED" assert len(status) == 2 def test_change_strategies(self): # Arrange strategies = [ TradingStrategy(TradingStrategyConfig(order_id_tag="003")), TradingStrategy(TradingStrategyConfig(order_id_tag="004")), ] # Act self.trader.add_strategies(strategies) # Assert assert strategies[0].id in self.trader.strategy_states() assert strategies[1].id in self.trader.strategy_states() assert len(self.trader.strategy_states()) == 2 def test_start_a_trader(self): # Arrange strategies = [ TradingStrategy(TradingStrategyConfig(order_id_tag="001")), TradingStrategy(TradingStrategyConfig(order_id_tag="002")), ] self.trader.add_strategies(strategies) # Act self.trader.start() strategy_states = self.trader.strategy_states() # Assert assert self.trader.is_running assert strategy_states[StrategyId("TradingStrategy-001")] == "RUNNING" assert strategy_states[StrategyId("TradingStrategy-002")] == "RUNNING" def test_stop_a_running_trader(self): # Arrange strategies = [ TradingStrategy(TradingStrategyConfig(order_id_tag="001")), TradingStrategy(TradingStrategyConfig(order_id_tag="002")), ] self.trader.add_strategies(strategies) self.trader.start() # Act self.trader.stop() strategy_states = self.trader.strategy_states() # Assert assert self.trader.is_stopped assert strategy_states[StrategyId("TradingStrategy-001")] == "STOPPED" assert strategy_states[StrategyId("TradingStrategy-002")] == "STOPPED" def test_subscribe_to_msgbus_topic_adds_subscription(self): # Arrange consumer = [] # Act self.trader.subscribe("events*", consumer.append) # Assert assert len(self.msgbus.subscriptions("events*")) == 6 assert "events*" in self.msgbus.topics() assert self.msgbus.subscriptions( "events*")[-1].handler == consumer.append def test_unsubscribe_from_msgbus_topic_removes_subscription(self): # Arrange consumer = [] self.trader.subscribe("events*", consumer.append) # Act self.trader.unsubscribe("events*", consumer.append) # Assert assert len(self.msgbus.subscriptions("events*")) == 5
def setup(self): # Fixture Setup self.loop = asyncio.get_event_loop() self.loop.set_debug(True) self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.logger = Logger(clock=self.clock) self.trader_id = TestIdStubs.trader_id() self.venue = BINANCE_VENUE self.account_id = AccountId(self.venue.value, "001") self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestComponentStubs.cache() self.http_client = BinanceHttpClient( # noqa: S106 (no hardcoded password) loop=asyncio.get_event_loop(), clock=self.clock, logger=self.logger, key="SOME_BINANCE_API_KEY", secret="SOME_BINANCE_API_SECRET", ) self.provider = BinanceFuturesInstrumentProvider( client=self.http_client, logger=self.logger, config=InstrumentProviderConfig(load_all=True), ) self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.risk_engine = RiskEngine( portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_client = BinanceFuturesExecutionClient( loop=self.loop, client=self.http_client, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, instrument_provider=self.provider, account_type=BinanceAccountType.FUTURES_USDT, ) self.strategy = TradingStrategy() self.strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, )
class TestBacktestDataClient: def setup(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock) self.cache = TestStubs.cache() self.portfolio = Portfolio( cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine.process(USDJPY_SIM) self.client = BacktestMarketDataClient( client_id=ClientId("SIM"), engine=self.data_engine, clock=TestClock(), logger=self.logger, ) def test_connect(self): # Arrange # Act self.client.connect() # Assert assert self.client.is_connected def test_disconnect(self): # Arrange self.client.connect() # Act self.client.disconnect() # Assert assert not self.client.is_connected def test_reset(self): # Arrange # Act self.client.reset() # Assert assert True # No exceptions raised def test_dispose(self): # Arrange # Act self.client.dispose() # Assert assert True # No exceptions raised def test_subscribe_instrument(self): # Arrange # Act self.client.subscribe_instrument(USDJPY_SIM.id) self.client.connect() self.client.subscribe_instrument(USDJPY_SIM.id) # Assert assert True # No exceptions raised def test_subscribe_quote_ticks(self): # Arrange # Act self.client.subscribe_quote_ticks(USDJPY_SIM.id) self.client.connect() self.client.subscribe_quote_ticks(USDJPY_SIM.id) # Assert assert True # No exceptions raised def test_subscribe_trade_ticks(self): # Arrange # Act self.client.subscribe_trade_ticks(USDJPY_SIM.id) self.client.connect() self.client.subscribe_trade_ticks(USDJPY_SIM.id) # Assert assert True # No exceptions raised def test_subscribe_bars(self): # Arrange # Act self.client.subscribe_bars(TestStubs.bartype_gbpusd_1sec_mid()) self.client.connect() self.client.subscribe_bars(TestStubs.bartype_gbpusd_1sec_mid()) # Assert assert True # No exceptions raised def test_unsubscribe_instrument(self): # Arrange # Act self.client.unsubscribe_instrument(USDJPY_SIM.id) self.client.connect() self.client.unsubscribe_instrument(USDJPY_SIM.id) # Assert assert True # No exceptions raised def test_unsubscribe_quote_ticks(self): # Arrange # Act self.client.unsubscribe_quote_ticks(USDJPY_SIM.id) self.client.connect() self.client.unsubscribe_quote_ticks(USDJPY_SIM.id) # Assert assert True # No exceptions raised def test_unsubscribe_trade_ticks(self): # Arrange # Act self.client.unsubscribe_trade_ticks(USDJPY_SIM.id) self.client.connect() self.client.unsubscribe_trade_ticks(USDJPY_SIM.id) # Assert assert True # No exceptions raised def test_unsubscribe_bars(self): # Arrange # Act self.client.unsubscribe_bars(TestStubs.bartype_usdjpy_1min_bid()) self.client.connect() self.client.unsubscribe_bars(TestStubs.bartype_usdjpy_1min_bid()) # Assert assert True # No exceptions raised def test_request_instrument(self): # Arrange # Act self.client.request_instrument(USDJPY_SIM.id, uuid4()) self.client.connect() self.client.request_instrument(USDJPY_SIM.id, uuid4()) # Assert assert True # No exceptions raised def test_request_instruments(self): # Arrange # Act self.client.request_instruments(uuid4()) self.client.connect() self.client.request_instruments(uuid4()) # Assert assert True # No exceptions raised def test_request_quote_ticks(self): # Arrange # Act self.client.request_quote_ticks(USDJPY_SIM.id, None, None, 0, uuid4()) self.client.connect() self.client.request_quote_ticks(USDJPY_SIM.id, None, None, 0, uuid4()) # Assert assert True # No exceptions raised def test_request_trade_ticks(self): # Arrange # Act self.client.request_trade_ticks(USDJPY_SIM.id, None, None, 0, uuid4()) self.client.connect() self.client.request_trade_ticks(USDJPY_SIM.id, None, None, 0, uuid4()) # Assert assert True # No exceptions raised def test_request_bars(self): # Arrange # Act self.client.request_bars( TestStubs.bartype_usdjpy_1min_bid(), None, None, 0, uuid4() ) self.client.connect() self.client.request_bars( TestStubs.bartype_usdjpy_1min_bid(), None, None, 0, uuid4() ) # Assert assert True # No exceptions raised
def setup(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger( clock=self.clock, level_stdout=LogLevel.INFO, ) self.trader_id = TestIdStubs.trader_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestComponentStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( msgbus=self.msgbus, clock=self.clock, cache=self.cache, logger=self.logger, ) self.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.risk_engine = RiskEngine( portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=FTX, oms_type=OMSType.NETTING, account_type=AccountType.MARGIN, base_currency=None, # Multi-asset wallet starting_balances=[Money(200, ETH), Money(1_000_000, USD)], default_leverage=Decimal(100), leverages={}, is_frozen_account=False, instruments=[ETHUSD_FTX], modules=[], fill_model=FillModel(), cache=self.cache, clock=self.clock, logger=self.logger, latency_model=LatencyModel(0), ) self.exec_client = BacktestExecClient( exchange=self.exchange, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Wire up components self.exec_engine.register_client(self.exec_client) self.exchange.register_client(self.exec_client) self.cache.add_instrument(ETHUSD_FTX) # Create mock strategy self.strategy = MockStrategy( bar_type=TestDataStubs.bartype_usdjpy_1min_bid()) self.strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Start components self.exchange.reset() self.data_engine.start() self.exec_engine.start() self.strategy.start()
def setUp(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={'use_previous_close': False}, # To correctly reproduce historical data bars ) self.data_engine.cache.add_instrument(AUDUSD_SIM) self.data_engine.cache.add_instrument(USDJPY_SIM) self.portfolio.register_cache(self.data_engine.cache) self.analyzer = PerformanceAnalyzer() self.trader_id = TraderId("TESTER", "000") self.account_id = AccountId("SIM", "001") exec_db = BypassExecutionDatabase( trader_id=self.trader_id, logger=self.logger, ) self.exec_engine = ExecutionEngine( database=exec_db, portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=SIM, oms_type=OMSType.HEDGING, generate_position_ids=False, # Will force execution engine to generate ids is_frozen_account=False, starting_balances=[Money(1_000_000, USD)], instruments=[AUDUSD_SIM, USDJPY_SIM], modules=[], fill_model=FillModel(), exec_cache=self.exec_engine.cache, clock=self.clock, logger=self.logger, ) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=self.account_id, engine=self.exec_engine, clock=self.clock, logger=self.logger, ) self.exec_engine.register_client(self.exec_client) self.exchange.register_client(self.exec_client) self.strategy = MockStrategy(bar_type=TestStubs.bartype_usdjpy_1min_bid()) self.strategy.register_trader( self.trader_id, self.clock, self.logger, ) self.data_engine.register_strategy(self.strategy) self.exec_engine.register_strategy(self.strategy) self.data_engine.start() self.exec_engine.start() self.strategy.start()
class BitmexExchangeTests(unittest.TestCase): def setUp(self): # Fixture Setup self.strategies = [MockStrategy(TestStubs.bartype_btcusdt_binance_1min_bid())] self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={'use_previous_close': False}, # To correctly reproduce historical data bars ) self.data_engine.cache.add_instrument(XBTUSD_BITMEX) self.portfolio.register_cache(self.data_engine.cache) self.analyzer = PerformanceAnalyzer() self.trader_id = TraderId("TESTER", "000") self.account_id = AccountId("BITMEX", "001") exec_db = BypassExecutionDatabase( trader_id=self.trader_id, logger=self.logger, ) self.exec_engine = ExecutionEngine( database=exec_db, portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=Venue("BITMEX"), oms_type=OMSType.HEDGING, generate_position_ids=True, is_frozen_account=False, starting_balances=[Money(1_000_000, USD)], exec_cache=self.exec_engine.cache, instruments=[XBTUSD_BITMEX], modules=[], fill_model=FillModel(), clock=self.clock, logger=self.logger, ) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=self.account_id, engine=self.exec_engine, clock=self.clock, logger=self.logger, ) self.exec_engine.register_client(self.exec_client) self.exchange.register_client(self.exec_client) self.strategy = MockStrategy(bar_type=TestStubs.bartype_btcusdt_binance_1min_bid()) self.strategy.register_trader( self.trader_id, self.clock, self.logger, ) self.data_engine.register_strategy(self.strategy) self.exec_engine.register_strategy(self.strategy) self.data_engine.start() self.exec_engine.start() self.strategy.start() def test_commission_maker_taker_order(self): # Arrange # Prepare market quote1 = QuoteTick( XBTUSD_BITMEX.symbol, Price("11493.70"), Price("11493.75"), Quantity(1500000), Quantity(1500000), UNIX_EPOCH, ) self.data_engine.process(quote1) self.exchange.process_tick(quote1) order_market = self.strategy.order_factory.market( XBTUSD_BITMEX.symbol, OrderSide.BUY, Quantity(100000), ) order_limit = self.strategy.order_factory.limit( XBTUSD_BITMEX.symbol, OrderSide.BUY, Quantity(100000), Price("11493.65"), ) # Act self.strategy.submit_order(order_market) self.strategy.submit_order(order_limit) quote2 = QuoteTick( XBTUSD_BITMEX.symbol, Price("11493.60"), Price("11493.64"), Quantity(1500000), Quantity(1500000), UNIX_EPOCH, ) self.exchange.process_tick(quote2) # Fill the limit order self.portfolio.update_tick(quote2) # Assert self.assertEqual(LiquiditySide.TAKER, self.strategy.object_storer.get_store()[2].liquidity_side) self.assertEqual(LiquiditySide.MAKER, self.strategy.object_storer.get_store()[6].liquidity_side) self.assertEqual(Money("0.00652529", BTC), self.strategy.object_storer.get_store()[2].commission) self.assertEqual(Money("-0.00217511", BTC), self.strategy.object_storer.get_store()[6].commission)