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): # Fixture Setup clock = TestClock() logger = Logger(clock, level_stdout=LogLevel.DEBUG) trader_id = TraderId("TESTER-000") self.order_factory = OrderFactory( trader_id=trader_id, strategy_id=StrategyId("S-001"), clock=TestClock(), ) cache_db = BypassCacheDatabase( trader_id=trader_id, logger=logger, ) self.cache = Cache( database=cache_db, logger=logger, ) self.portfolio = Portfolio( cache=self.cache, clock=clock, logger=logger, ) self.exec_engine = ExecutionEngine( portfolio=self.portfolio, cache=self.cache, 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.exec_engine.register_risk_engine(self.risk_engine) # Prepare components self.cache.add_instrument(AUDUSD_SIM) self.cache.add_instrument(GBPUSD_SIM) self.cache.add_instrument(BTCUSDT_BINANCE) self.cache.add_instrument(BTCUSD_BITMEX) self.cache.add_instrument(ETHUSD_BITMEX)
def setup(self): # Fixture Setup self.loop = asyncio.get_event_loop() self.clock = LiveClock() self.logger = LiveLogger( loop=self.loop, clock=self.clock, level_stdout=LogLevel.DEBUG, ) self.trader_id = TestStubs.trader_id() self.strategy_id = TestStubs.strategy_id() self.account_id = TestStubs.account_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache_db = MockCacheDatabase(logger=self.logger, ) self.cache = Cache( database=self.cache_db, logger=self.logger, )
def setUp(self): # Fixture Setup clock = TestClock() logger = Logger(clock) trader_id = TraderId("TESTER-000") self.order_factory = OrderFactory( trader_id=trader_id, strategy_id=StrategyId("S-001"), clock=TestClock(), ) cache_db = BypassCacheDatabase( trader_id=trader_id, logger=logger, ) cache = Cache( database=cache_db, logger=logger, ) self.portfolio = Portfolio( cache=cache, clock=clock, logger=logger, ) self.exec_engine = ExecutionEngine( portfolio=self.portfolio, cache=cache, clock=clock, logger=logger, )
def setup(self): os.environ.update( { "TWS_USERNAME": "******", "TWS_PASSWORD": "******", } ) # Fixture Setup self.loop = asyncio.get_event_loop() self.clock = LiveClock() self.logger = LiveLogger( loop=self.loop, clock=self.clock, level_stdout=LogLevel.DEBUG, ) self.trader_id = TestIdStubs.trader_id() self.strategy_id = TestIdStubs.strategy_id() self.account_id = TestIdStubs.account_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache_db = MockCacheDatabase( logger=self.logger, ) self.cache = Cache( database=self.cache_db, logger=self.logger, ) with patch("nautilus_trader.adapters.interactive_brokers.factories.get_cached_ib_client"): self.data_client = InteractiveBrokersLiveDataClientFactory.create( loop=self.loop, name="IB", config=InteractiveBrokersDataClientConfig( # noqa: S106 username="******", password="******" ), msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) with patch("nautilus_trader.adapters.interactive_brokers.factories.get_cached_ib_client"): self.exec_client = InteractiveBrokersLiveExecClientFactory.create( loop=self.loop, name="IB", config=InteractiveBrokersExecClientConfig( # noqa: S106 username="******", password="******" ), msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, )
class PortfolioTests(unittest.TestCase): def setUp(self): # Fixture Setup clock = TestClock() logger = Logger(clock, level_stdout=LogLevel.DEBUG) trader_id = TraderId("TESTER-000") self.order_factory = OrderFactory( trader_id=trader_id, strategy_id=StrategyId("S-001"), clock=TestClock(), ) cache_db = BypassCacheDatabase( trader_id=trader_id, logger=logger, ) self.cache = Cache( database=cache_db, logger=logger, ) self.portfolio = Portfolio( cache=self.cache, clock=clock, logger=logger, ) self.exec_engine = ExecutionEngine( portfolio=self.portfolio, cache=self.cache, 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.exec_engine.register_risk_engine(self.risk_engine) # Prepare components self.cache.add_instrument(AUDUSD_SIM) self.cache.add_instrument(GBPUSD_SIM) self.cache.add_instrument(BTCUSDT_BINANCE) self.cache.add_instrument(BTCUSD_BITMEX) self.cache.add_instrument(ETHUSD_BITMEX) def test_account_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertIsNone(self.portfolio.account(SIM)) def test_account_when_account_returns_the_account_facade(self): # Arrange account_state = AccountState( account_id=AccountId("BINANCE", "1513111"), account_type=AccountType.CASH, base_currency=None, reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ) ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(account_state) # Act result = self.portfolio.account(BINANCE) # Assert self.assertEqual("BINANCE", result.id.issuer) def test_net_position_when_no_positions_returns_zero(self): # Arrange # Act # Assert self.assertEqual(Decimal(0), self.portfolio.net_position(AUDUSD_SIM.id)) def test_is_net_long_when_no_positions_returns_false(self): # Arrange # Act # Assert self.assertEqual(False, self.portfolio.is_net_long(AUDUSD_SIM.id)) def test_is_net_short_when_no_positions_returns_false(self): # Arrange # Act # Assert self.assertEqual(False, self.portfolio.is_net_short(AUDUSD_SIM.id)) def test_is_flat_when_no_positions_returns_true(self): # Arrange # Act # Assert self.assertEqual(True, self.portfolio.is_flat(AUDUSD_SIM.id)) def test_is_completely_flat_when_no_positions_returns_true(self): # Arrange # Act # Assert self.assertEqual(True, self.portfolio.is_flat(AUDUSD_SIM.id)) def test_unrealized_pnl_for_instrument_when_no_instrument_returns_none( self): # Arrange # Act # Assert self.assertIsNone(self.portfolio.unrealized_pnl(USDJPY_SIM.id)) def test_unrealized_pnl_for_venue_when_no_account_returns_empty_dict(self): # Arrange # Act # Assert self.assertEqual({}, self.portfolio.unrealized_pnls(SIM)) def test_initial_margins_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertEqual(None, self.portfolio.initial_margins(SIM)) def test_maint_margins_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertEqual(None, self.portfolio.maint_margins(SIM)) def test_open_value_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertEqual(None, self.portfolio.net_exposures(SIM)) def test_update_tick(self): # Arrange tick = TestStubs.quote_tick_5decimal(GBPUSD_SIM.id) # Act self.portfolio.update_tick(tick) # Assert self.assertIsNone(self.portfolio.unrealized_pnl(GBPUSD_SIM.id)) def test_update_orders_working(self): # Arrange state = AccountState( account_id=AccountId("BINANCE", "01234"), account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) # Create two working orders order1 = self.order_factory.stop_market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.5"), Price.from_str("25000.00"), ) order2 = self.order_factory.stop_market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.5"), Price.from_str("25000.00"), ) self.exec_engine.cache.add_order(order1, PositionId.null()) self.exec_engine.cache.add_order(order2, PositionId.null()) # Push states to ACCEPTED order1.apply(TestStubs.event_order_submitted(order1)) self.exec_engine.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.exec_engine.cache.update_order(order1) filled1 = TestStubs.event_order_filled( order1, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("25000.00"), ) self.exec_engine.process(filled1) # Update the last quote last = QuoteTick( BTCUSDT_BINANCE.id, Price.from_str("25001.00"), Price.from_str("25002.00"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) # Act self.portfolio.update_tick(last) self.portfolio.initialize_orders() # Assert self.assertEqual({}, self.portfolio.initial_margins(BINANCE)) def test_update_positions(self): # Arrange state = AccountState( account_id=AccountId("BINANCE", "01234"), account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) # Create a closed position order1 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.50000000"), ) order2 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.SELL, Quantity.from_str("10.50000000"), ) self.exec_engine.cache.add_order(order1, PositionId.null()) self.exec_engine.cache.add_order(order2, PositionId.null()) # Push states to ACCEPTED order1.apply(TestStubs.event_order_submitted(order1)) self.exec_engine.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.exec_engine.cache.update_order(order1) fill1 = TestStubs.event_order_filled( order1, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("25000.00"), ) fill2 = TestStubs.event_order_filled( order2, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("25000.00"), ) position1 = Position(instrument=BTCUSDT_BINANCE, fill=fill1) position1.apply(fill2) order3 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.00000000"), ) fill3 = TestStubs.event_order_filled( order3, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-2"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("25000.00"), ) position2 = Position(instrument=BTCUSDT_BINANCE, fill=fill3) # Update the last quote last = QuoteTick( BTCUSDT_BINANCE.id, Price.from_str("25001.00"), Price.from_str("25002.00"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) # Act self.cache.add_position(position1) self.cache.add_position(position2) self.portfolio.initialize_positions() self.portfolio.update_tick(last) # Assert self.assertTrue(self.portfolio.is_net_long(BTCUSDT_BINANCE.id)) def test_opening_one_long_position_updates_portfolio(self): # Arrange state = AccountState( account_id=AccountId("BINANCE", "01234"), account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.000000"), ) fill = TestStubs.event_order_filled( order=order, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("10500.00"), ) last = QuoteTick( BTCUSDT_BINANCE.id, Price.from_str("10510.00"), Price.from_str("10511.00"), Quantity.from_str("1.000000"), Quantity.from_str("1.000000"), 0, 0, ) self.cache.add_quote_tick(last) self.portfolio.update_tick(last) position = Position(instrument=BTCUSDT_BINANCE, fill=fill) # Act self.cache.add_position(position) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert self.assertEqual( {USDT: Money(105100.00000000, USDT)}, self.portfolio.net_exposures(BINANCE), ) self.assertEqual( {USDT: Money(100.00000000, USDT)}, self.portfolio.unrealized_pnls(BINANCE), ) self.assertEqual( {USDT: Money(105.10000000, USDT)}, self.portfolio.maint_margins(BINANCE), ) self.assertEqual( Money(105100.00000000, USDT), self.portfolio.net_exposure(BTCUSDT_BINANCE.id), ) self.assertEqual( Money(100.00000000, USDT), self.portfolio.unrealized_pnl(BTCUSDT_BINANCE.id), ) self.assertEqual( Decimal("10.00000000"), self.portfolio.net_position(order.instrument_id), ) self.assertTrue(self.portfolio.is_net_long(order.instrument_id)) self.assertFalse(self.portfolio.is_net_short(order.instrument_id)) self.assertFalse(self.portfolio.is_flat(order.instrument_id)) self.assertFalse(self.portfolio.is_completely_flat()) def test_opening_one_short_position_updates_portfolio(self): # Arrange state = AccountState( account_id=AccountId("BINANCE", "01234"), account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.SELL, Quantity.from_str("0.515"), ) fill = TestStubs.event_order_filled( order=order, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("15000.00"), ) last = QuoteTick( BTCUSDT_BINANCE.id, Price.from_str("15510.15"), Price.from_str("15510.25"), Quantity.from_str("12.62"), Quantity.from_str("3.1"), 0, 0, ) self.cache.add_quote_tick(last) self.portfolio.update_tick(last) position = Position(instrument=BTCUSDT_BINANCE, fill=fill) # Act self.cache.add_position(position) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert self.assertEqual( {USDT: Money(7987.77875000, USDT)}, self.portfolio.net_exposures(BINANCE), ) self.assertEqual( {USDT: Money(-262.77875000, USDT)}, self.portfolio.unrealized_pnls(BINANCE), ) self.assertEqual( {USDT: Money(7.98777875, USDT)}, self.portfolio.maint_margins(BINANCE), ) self.assertEqual( Money(7987.77875000, USDT), self.portfolio.net_exposure(BTCUSDT_BINANCE.id), ) self.assertEqual( Money(-262.77875000, USDT), self.portfolio.unrealized_pnl(BTCUSDT_BINANCE.id), ) self.assertEqual( Decimal("-0.515"), self.portfolio.net_position(order.instrument_id), ) self.assertFalse(self.portfolio.is_net_long(order.instrument_id)) self.assertTrue(self.portfolio.is_net_short(order.instrument_id)) self.assertFalse(self.portfolio.is_flat(order.instrument_id)) self.assertFalse(self.portfolio.is_completely_flat()) def test_opening_positions_with_multi_asset_account(self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) last_ethusd = QuoteTick( ETHUSD_BITMEX.id, Price.from_str("376.05"), Price.from_str("377.10"), Quantity.from_str("16"), Quantity.from_str("25"), 0, 0, ) last_btcusd = QuoteTick( BTCUSD_BITMEX.id, Price.from_str("10500.05"), Price.from_str("10501.51"), Quantity.from_str("2.54"), Quantity.from_str("0.91"), 0, 0, ) self.cache.add_quote_tick(last_ethusd) self.cache.add_quote_tick(last_btcusd) self.portfolio.update_tick(last_ethusd) self.portfolio.update_tick(last_btcusd) order = self.order_factory.market( ETHUSD_BITMEX.id, OrderSide.BUY, Quantity.from_int(10000), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("376.05"), ) position = Position(instrument=ETHUSD_BITMEX, fill=fill) # Act self.cache.add_position(position) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert self.assertEqual( {ETH: Money(26.59220848, ETH)}, self.portfolio.net_exposures(BITMEX), ) self.assertEqual( {ETH: Money(0.20608962, ETH)}, self.portfolio.maint_margins(BITMEX), ) self.assertEqual( Money(26.59220848, ETH), self.portfolio.net_exposure(ETHUSD_BITMEX.id), ) self.assertEqual( Money(0.00000000, ETH), self.portfolio.unrealized_pnl(ETHUSD_BITMEX.id), ) def test_unrealized_pnl_when_insufficient_data_for_xrate_returns_none( self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), account_type=AccountType.MARGIN, base_currency=BTC, reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order = self.order_factory.market( ETHUSD_BITMEX.id, OrderSide.BUY, Quantity.from_int(100), ) self.exec_engine.cache.add_order(order, PositionId.null()) self.exec_engine.process(TestStubs.event_order_submitted(order)) self.exec_engine.process(TestStubs.event_order_accepted(order)) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("376.05"), ) self.exec_engine.process(fill) position = Position(instrument=ETHUSD_BITMEX, fill=fill) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Act result = self.portfolio.unrealized_pnls(BITMEX) # # Assert self.assertEqual({}, result) def test_market_value_when_insufficient_data_for_xrate_returns_none(self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), account_type=AccountType.MARGIN, base_currency=BTC, reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order = self.order_factory.market( ETHUSD_BITMEX.id, OrderSide.BUY, Quantity.from_int(100), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("376.05"), ) last_ethusd = QuoteTick( ETHUSD_BITMEX.id, Price.from_str("376.05"), Price.from_str("377.10"), Quantity.from_str("16"), Quantity.from_str("25"), 0, 0, ) last_xbtusd = QuoteTick( BTCUSD_BITMEX.id, Price.from_str("50000.00"), Price.from_str("50000.00"), Quantity.from_str("1"), Quantity.from_str("1"), 0, 0, ) position = Position(instrument=ETHUSD_BITMEX, fill=fill) self.portfolio.update_position( TestStubs.event_position_opened(position)) self.cache.add_position(position) self.cache.add_quote_tick(last_ethusd) self.cache.add_quote_tick(last_xbtusd) self.portfolio.update_tick(last_ethusd) self.portfolio.update_tick(last_xbtusd) # Act result = self.portfolio.net_exposures(BITMEX) # Assert self.assertEqual({BTC: Money(0.00200000, BTC)}, result) def test_opening_several_positions_updates_portfolio(self): # Arrange state = AccountState( account_id=AccountId("SIM", "01234"), account_type=AccountType.MARGIN, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(1_000_000, USD), Money(0, USD), Money(1_000_000, USD), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) last_audusd = QuoteTick( AUDUSD_SIM.id, Price.from_str("0.80501"), Price.from_str("0.80505"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) last_gbpusd = QuoteTick( GBPUSD_SIM.id, Price.from_str("1.30315"), Price.from_str("1.30317"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) self.cache.add_quote_tick(last_audusd) self.cache.add_quote_tick(last_gbpusd) self.portfolio.update_tick(last_audusd) self.portfolio.update_tick(last_gbpusd) order1 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2 = self.order_factory.market( GBPUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) self.exec_engine.cache.add_order(order1, PositionId.null()) self.exec_engine.cache.add_order(order2, PositionId.null()) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-1"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) fill2 = TestStubs.event_order_filled( order2, instrument=GBPUSD_SIM, position_id=PositionId("P-2"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) self.exec_engine.cache.update_order(order1) self.exec_engine.cache.update_order(order2) position1 = Position(instrument=AUDUSD_SIM, fill=fill1) position2 = Position(instrument=GBPUSD_SIM, fill=fill2) position_opened1 = TestStubs.event_position_opened(position1) position_opened2 = TestStubs.event_position_opened(position2) # Act self.cache.add_position(position1) self.cache.add_position(position2) self.portfolio.update_position(position_opened1) self.portfolio.update_position(position_opened2) # Assert self.assertEqual( {USD: Money(210816.00, USD)}, self.portfolio.net_exposures(SIM), ) self.assertEqual( {USD: Money(10816.00, USD)}, self.portfolio.unrealized_pnls(SIM), ) self.assertEqual({USD: Money(3912.06, USD)}, self.portfolio.maint_margins(SIM)), self.assertEqual( Money(80501.00, USD), self.portfolio.net_exposure(AUDUSD_SIM.id), ) self.assertEqual( Money(130315.00, USD), self.portfolio.net_exposure(GBPUSD_SIM.id), ) self.assertEqual( Money(-19499.00, USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.id), ) self.assertEqual( Money(30315.00, USD), self.portfolio.unrealized_pnl(GBPUSD_SIM.id), ) self.assertEqual(Decimal(100000), self.portfolio.net_position(AUDUSD_SIM.id)) self.assertEqual(Decimal(100000), self.portfolio.net_position(GBPUSD_SIM.id)) self.assertTrue(self.portfolio.is_net_long(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_net_short(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_flat(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_completely_flat()) def test_modifying_position_updates_portfolio(self): # Arrange state = AccountState( account_id=AccountId("SIM", "01234"), account_type=AccountType.MARGIN, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(1_000_000, USD), Money(0, USD), Money(1_000_000, USD), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) last_audusd = QuoteTick( AUDUSD_SIM.id, Price.from_str("0.80501"), Price.from_str("0.80505"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) self.cache.add_quote_tick(last_audusd) self.portfolio.update_tick(last_audusd) order1 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) position = Position(instrument=AUDUSD_SIM, fill=fill1) self.exec_engine.cache.add_position(position) self.portfolio.update_position( TestStubs.event_position_opened(position)) order2 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.SELL, Quantity.from_int(50000), ) order2_filled = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) position.apply(order2_filled) # Act self.portfolio.update_position( TestStubs.event_position_changed(position)) # Assert self.assertEqual( {USD: Money(40250.50, USD)}, self.portfolio.net_exposures(SIM), ) self.assertEqual( {USD: Money(-9749.50, USD)}, self.portfolio.unrealized_pnls(SIM), ) self.assertEqual( {USD: Money(1208.32, USD)}, self.portfolio.maint_margins(SIM), ) self.assertEqual( Money(40250.50, USD), self.portfolio.net_exposure(AUDUSD_SIM.id), ) self.assertEqual( Money(-9749.50, USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.id), ) self.assertEqual(Decimal(50000), self.portfolio.net_position(AUDUSD_SIM.id)) self.assertTrue(self.portfolio.is_net_long(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_net_short(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_flat(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_completely_flat()) self.assertEqual({}, self.portfolio.unrealized_pnls(BINANCE)) self.assertIsNone(self.portfolio.net_exposures(BINANCE)) def test_closing_position_updates_portfolio(self): # Arrange state = AccountState( account_id=AccountId("SIM", "01234"), account_type=AccountType.MARGIN, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(1_000_000, USD), Money(0, USD), Money(1_000_000, USD), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order1 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) position = Position(instrument=AUDUSD_SIM, fill=fill1) self.exec_engine.cache.add_position(position) self.portfolio.update_position( TestStubs.event_position_opened(position)) order2 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) order2_filled = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00010"), ) position.apply(order2_filled) self.exec_engine.cache.update_position(position) # Act self.portfolio.update_position( TestStubs.event_position_closed(position)) # Assert self.assertEqual({}, self.portfolio.net_exposures(SIM)) self.assertEqual({}, self.portfolio.unrealized_pnls(SIM)) self.assertEqual({}, self.portfolio.maint_margins(SIM)) self.assertEqual(Money(0, USD), self.portfolio.net_exposure(AUDUSD_SIM.id)) self.assertEqual(Money(0, USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.id)) self.assertEqual(Decimal(0), self.portfolio.net_position(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_net_long(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_net_short(AUDUSD_SIM.id)) self.assertTrue(self.portfolio.is_flat(AUDUSD_SIM.id)) self.assertTrue(self.portfolio.is_completely_flat()) def test_several_positions_with_different_instruments_updates_portfolio( self): # Arrange state = AccountState( account_id=AccountId("SIM", "01234"), account_type=AccountType.MARGIN, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(1_000_000, USD), Money(0, USD), Money(1_000_000, USD), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order1 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order3 = self.order_factory.market( GBPUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order4 = self.order_factory.market( GBPUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-1"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) fill2 = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, position_id=PositionId("P-2"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) fill3 = TestStubs.event_order_filled( order3, instrument=GBPUSD_SIM, position_id=PositionId("P-3"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) fill4 = TestStubs.event_order_filled( order4, instrument=GBPUSD_SIM, position_id=PositionId("P-3"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00100"), ) position1 = Position(instrument=AUDUSD_SIM, fill=fill1) position2 = Position(instrument=AUDUSD_SIM, fill=fill2) position3 = Position(instrument=GBPUSD_SIM, fill=fill3) last_audusd = QuoteTick( AUDUSD_SIM.id, Price.from_str("0.80501"), Price.from_str("0.80505"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) last_gbpusd = QuoteTick( GBPUSD_SIM.id, Price.from_str("1.30315"), Price.from_str("1.30317"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) self.cache.add_quote_tick(last_audusd) self.cache.add_quote_tick(last_gbpusd) self.portfolio.update_tick(last_audusd) self.portfolio.update_tick(last_gbpusd) self.cache.add_position(position1) self.cache.add_position(position2) self.cache.add_position(position3) # Act self.portfolio.update_position( TestStubs.event_position_opened(position1)) self.portfolio.update_position( TestStubs.event_position_opened(position2)) self.portfolio.update_position( TestStubs.event_position_opened(position3)) position3.apply(fill4) self.cache.update_position(position3) self.portfolio.update_position( TestStubs.event_position_closed(position3)) # Assert self.assertEqual( {USD: Money(-38998.00, USD)}, self.portfolio.unrealized_pnls(SIM), ) self.assertEqual( {USD: Money(161002.00, USD)}, self.portfolio.net_exposures(SIM), ) self.assertEqual({USD: Money(3912.06, USD)}, self.portfolio.maint_margins(SIM)), self.assertEqual( Money(161002.00, USD), self.portfolio.net_exposure(AUDUSD_SIM.id), ) self.assertEqual( Money(-38998.00, USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.id), ) self.assertEqual(Money(0, USD), self.portfolio.unrealized_pnl(GBPUSD_SIM.id)) self.assertEqual(Decimal(200000), self.portfolio.net_position(AUDUSD_SIM.id)) self.assertEqual(Decimal(0), self.portfolio.net_position(GBPUSD_SIM.id)) self.assertTrue(self.portfolio.is_net_long(AUDUSD_SIM.id)) self.assertTrue(self.portfolio.is_flat(GBPUSD_SIM.id)) self.assertFalse(self.portfolio.is_completely_flat())
class TestCache: 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 test_cache_currencies_with_no_currencies(self): # Arrange, Act self.cache.cache_currencies() # Assert assert True # No exception raised def test_cache_instruments_with_no_instruments(self): # Arrange, Act self.cache.cache_instruments() # Assert assert True # No exception raised def test_cache_accounts_with_no_accounts(self): # Arrange, Act self.cache.cache_accounts() # Assert assert True # No exception raised def test_cache_orders_with_no_orders(self): # Arrange, Act self.cache.cache_orders() # Assert assert True # No exception raised def test_orders_for_position_when_no_position_returns_empty_list(self): # Arrange, Act result = self.cache.orders_for_position(PositionId("1")) # Assert assert result == [] def test_cache_positions_with_no_positions(self): # Arrange, Act self.cache.cache_positions() # Assert assert True # No exception raised def test_build_index_with_no_objects(self): # Arrange, Act self.cache.build_index() # Assert assert True # No exception raised def test_add_currency(self): # Arrange currency = Currency( code="1INCH", precision=8, iso4217=0, name="1INCH", currency_type=CurrencyType.CRYPTO, ) # Act self.cache.add_currency(currency) # Assert assert Currency.from_str("1INCH") == currency def test_add_account(self): # Arrange account = TestStubs.cash_account() # Act self.cache.add_account(account) # Assert assert self.cache.load_account(account.id) == account def test_load_instrument(self): # Arrange self.cache.add_instrument(AUDUSD_SIM) # Act result = self.cache.load_instrument(AUDUSD_SIM.id) # Assert assert result == AUDUSD_SIM def test_load_account(self): # Arrange account = TestStubs.cash_account() self.cache.add_account(account) # Act result = self.cache.load_account(account.id) # Assert assert result == account def test_account_for_venue(self): # Arrange, Act result = self.cache.account_for_venue(Venue("SIM")) # Assert assert result is None def test_accounts_when_no_accounts_returns_empty_list(self): # Arrange, Act result = self.cache.accounts() # Assert assert result == [] def test_get_strategy_ids_with_no_ids_returns_empty_set(self): # Arrange, Act result = self.cache.strategy_ids() # Assert assert result == set() def test_get_order_ids_with_no_ids_returns_empty_set(self): # Arrange, Act result = self.cache.client_order_ids() # Assert assert result == set() def test_get_strategy_ids_with_id_returns_correct_set(self): # Arrange self.cache.update_strategy(self.strategy) # Act result = self.cache.strategy_ids() # Assert assert result == {self.strategy.id} def test_position_for_order_when_no_position_returns_none(self): # Arrange, Act, Assert assert self.cache.position_for_order(ClientOrderId("O-123456")) is None def test_position_exists_when_no_position_returns_false(self): # Arrange, Act, Assert assert not self.cache.position_exists(PositionId("P-123456")) def test_order_exists_when_no_order_returns_false(self): # Arrange, Act, Assert assert not self.cache.order_exists(ClientOrderId("O-123456")) def test_position_when_no_position_returns_none(self): # Arrange position_id = PositionId("P-123456") # Act result = self.cache.position(position_id) # Assert assert result is None def test_order_when_no_order_returns_none(self): # Arrange order_id = ClientOrderId("O-201908080101-000-001") # Act result = self.cache.order(order_id) # Assert assert result is None def test_strategy_id_for_position_when_no_strategy_registered_returns_none(self): # Arrange, Act, Assert assert self.cache.strategy_id_for_position(PositionId("P-123456")) is None def test_add_order(self): # Arrange order = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) position_id = PositionId("P-1") # Act self.cache.add_order(order, position_id) # Assert assert order.client_order_id in self.cache.client_order_ids() assert order.client_order_id in self.cache.client_order_ids( instrument_id=order.instrument_id ) assert order.client_order_id in self.cache.client_order_ids(strategy_id=self.strategy.id) assert order.client_order_id not in self.cache.client_order_ids( strategy_id=StrategyId("S-ZX1") ) assert order.client_order_id in self.cache.client_order_ids( instrument_id=order.instrument_id, strategy_id=self.strategy.id ) assert order in self.cache.orders() assert self.cache.venue_order_id(order.client_order_id) is None def test_load_order(self): # Arrange order = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) position_id = PositionId("P-1") self.cache.add_order(order, position_id) # Act result = self.cache.load_order(order.client_order_id) # Assert assert result == order def test_add_position(self): # Arrange order = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) position_id = PositionId("P-1") self.cache.add_order(order, position_id) fill = TestStubs.event_order_filled( order, instrument=AUDUSD_SIM, position_id=PositionId("P-1"), last_px=Price.from_str("1.00000"), ) position = Position(instrument=AUDUSD_SIM, fill=fill) # Act self.cache.add_position(position, OMSType.HEDGING) # Assert assert self.cache.position_exists(position.id) assert position.id in self.cache.position_ids() assert position in self.cache.positions() assert position in self.cache.positions_open() assert position in self.cache.positions_open(instrument_id=position.instrument_id) assert position in self.cache.positions_open(strategy_id=self.strategy.id) assert position in self.cache.positions_open( instrument_id=position.instrument_id, strategy_id=self.strategy.id ) assert position not in self.cache.positions_closed() assert position not in self.cache.positions_closed(instrument_id=position.instrument_id) assert position not in self.cache.positions_closed(strategy_id=self.strategy.id) assert position not in self.cache.positions_closed( instrument_id=position.instrument_id, strategy_id=self.strategy.id ) assert self.cache.position_for_order(order.client_order_id) == position assert self.cache.orders_for_position(position.id) == [order] def test_load_position(self): # Arrange order = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) position_id = PositionId("P-1") self.cache.add_order(order, position_id) fill = TestStubs.event_order_filled( order, instrument=AUDUSD_SIM, position_id=PositionId("P-1"), last_px=Price.from_str("1.00000"), ) position = Position(instrument=AUDUSD_SIM, fill=fill) self.cache.add_position(position, OMSType.HEDGING) # Act result = self.cache.load_position(position.id) # Assert assert result == position def test_update_order_for_submitted_order(self): # Arrange order = self.strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) position_id = PositionId("P-1") self.cache.add_order(order, position_id) order.apply(TestStubs.event_order_submitted(order)) # Act self.cache.update_order(order) # Assert assert self.cache.order_exists(order.client_order_id) assert order.client_order_id in self.cache.client_order_ids() assert order in self.cache.orders() assert order in self.cache.orders_active() assert order in self.cache.orders_active(instrument_id=order.instrument_id) assert order in self.cache.orders_active(strategy_id=self.strategy.id) assert order in self.cache.orders_active( instrument_id=order.instrument_id, strategy_id=self.strategy.id ) assert order in self.cache.orders_inflight() assert order in self.cache.orders_inflight(instrument_id=order.instrument_id) assert order in self.cache.orders_inflight(strategy_id=self.strategy.id) assert order in self.cache.orders_inflight( instrument_id=order.instrument_id, strategy_id=self.strategy.id ) assert order not in self.cache.orders_working() assert order not in self.cache.orders_working(instrument_id=order.instrument_id) assert order not in self.cache.orders_working(strategy_id=self.strategy.id) assert order not in self.cache.orders_working( instrument_id=order.instrument_id, strategy_id=self.strategy.id ) assert order not in self.cache.orders_completed() assert order not in self.cache.orders_completed(instrument_id=order.instrument_id) assert order not in self.cache.orders_completed(strategy_id=self.strategy.id) assert order not in self.cache.orders_completed( instrument_id=order.instrument_id, strategy_id=self.strategy.id ) assert self.cache.orders_active_count() == 1 assert self.cache.orders_inflight_count() == 1 assert self.cache.orders_working_count() == 0 assert self.cache.orders_completed_count() == 0 assert self.cache.orders_total_count() == 1 def test_update_order_for_accepted_order(self): # Arrange order = self.strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) position_id = PositionId("P-1") self.cache.add_order(order, position_id) order.apply(TestStubs.event_order_submitted(order)) self.cache.update_order(order) order.apply(TestStubs.event_order_accepted(order)) # Act self.cache.update_order(order) # Assert assert self.cache.order_exists(order.client_order_id) assert order.client_order_id in self.cache.client_order_ids() assert order in self.cache.orders() assert order in self.cache.orders_active() assert order in self.cache.orders_active(instrument_id=order.instrument_id) assert order in self.cache.orders_active(strategy_id=self.strategy.id) assert order in self.cache.orders_active( instrument_id=order.instrument_id, strategy_id=self.strategy.id ) assert order not in self.cache.orders_inflight() assert order not in self.cache.orders_inflight() assert order not in self.cache.orders_inflight(instrument_id=order.instrument_id) assert order not in self.cache.orders_inflight(strategy_id=self.strategy.id) assert order not in self.cache.orders_inflight( instrument_id=order.instrument_id, strategy_id=self.strategy.id ) assert order in self.cache.orders_working() assert order in self.cache.orders_working(instrument_id=order.instrument_id) assert order in self.cache.orders_working(strategy_id=self.strategy.id) assert order in self.cache.orders_working( instrument_id=order.instrument_id, strategy_id=self.strategy.id ) assert order not in self.cache.orders_completed() assert order not in self.cache.orders_completed(instrument_id=order.instrument_id) assert order not in self.cache.orders_completed(strategy_id=self.strategy.id) assert order not in self.cache.orders_completed( instrument_id=order.instrument_id, strategy_id=self.strategy.id ) assert self.cache.orders_active_count() == 1 assert self.cache.orders_inflight_count() == 0 assert self.cache.orders_working_count() == 1 assert self.cache.orders_completed_count() == 0 assert self.cache.orders_total_count() == 1 def test_update_order_for_completed_order(self): # Arrange order = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) position_id = PositionId("P-1") self.cache.add_order(order, position_id) order.apply(TestStubs.event_order_submitted(order)) self.cache.update_order(order) order.apply(TestStubs.event_order_accepted(order)) self.cache.update_order(order) fill = TestStubs.event_order_filled( order, instrument=AUDUSD_SIM, last_px=Price.from_str("1.00001") ) order.apply(fill) # Act self.cache.update_order(order) # Assert assert self.cache.order_exists(order.client_order_id) assert order.client_order_id in self.cache.client_order_ids() assert order in self.cache.orders() assert order not in self.cache.orders_active() assert order not in self.cache.orders_active(instrument_id=order.instrument_id) assert order not in self.cache.orders_active(strategy_id=self.strategy.id) assert order not in self.cache.orders_active( instrument_id=order.instrument_id, strategy_id=self.strategy.id ) assert order not in self.cache.orders_inflight() assert order not in self.cache.orders_inflight(instrument_id=order.instrument_id) assert order not in self.cache.orders_inflight(strategy_id=self.strategy.id) assert order not in self.cache.orders_inflight( instrument_id=order.instrument_id, strategy_id=self.strategy.id ) assert order not in self.cache.orders_working() assert order not in self.cache.orders_working(instrument_id=order.instrument_id) assert order not in self.cache.orders_working(strategy_id=self.strategy.id) assert order not in self.cache.orders_working( instrument_id=order.instrument_id, strategy_id=self.strategy.id ) assert order in self.cache.orders_completed() assert order in self.cache.orders_completed(instrument_id=order.instrument_id) assert order in self.cache.orders_completed(strategy_id=self.strategy.id) assert order in self.cache.orders_completed( instrument_id=order.instrument_id, strategy_id=self.strategy.id ) assert self.cache.venue_order_id(order.client_order_id) == order.venue_order_id assert self.cache.orders_active_count() == 0 assert self.cache.orders_inflight_count() == 0 assert self.cache.orders_working_count() == 0 assert self.cache.orders_completed_count() == 1 assert self.cache.orders_total_count() == 1 def test_update_position_for_open_position(self): # Arrange order1 = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) position_id = PositionId("P-1") self.cache.add_order(order1, position_id) order1.apply(TestStubs.event_order_submitted(order1)) self.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.cache.update_order(order1) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-1"), last_px=Price.from_str("1.00001"), ) position = Position(instrument=AUDUSD_SIM, fill=fill1) # Act self.cache.add_position(position, OMSType.HEDGING) # Assert assert self.cache.position_exists(position.id) assert position.id in self.cache.position_ids() assert position in self.cache.positions() assert position in self.cache.positions_open() assert position in self.cache.positions_open(instrument_id=position.instrument_id) assert position in self.cache.positions_open(strategy_id=self.strategy.id) assert position in self.cache.positions_open( instrument_id=position.instrument_id, strategy_id=self.strategy.id ) assert position not in self.cache.positions_closed() assert position not in self.cache.positions_closed(instrument_id=position.instrument_id) assert position not in self.cache.positions_closed(strategy_id=self.strategy.id) assert position not in self.cache.positions_closed( instrument_id=position.instrument_id, strategy_id=self.strategy.id ) assert self.cache.position(position_id) == position assert self.cache.positions_open_count() == 1 assert self.cache.positions_closed_count() == 0 assert self.cache.positions_total_count() == 1 def test_update_position_for_closed_position(self): # Arrange order1 = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) position_id = PositionId("P-1") self.cache.add_order(order1, position_id) order1.apply(TestStubs.event_order_submitted(order1)) self.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.cache.update_order(order1) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-1"), last_px=Price.from_str("1.00001"), ) position = Position(instrument=AUDUSD_SIM, fill=fill1) self.cache.add_position(position, OMSType.HEDGING) order2 = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) self.cache.add_order(order2, position_id) order2.apply(TestStubs.event_order_submitted(order2)) self.cache.update_order(order2) order2.apply(TestStubs.event_order_accepted(order2)) self.cache.update_order(order2) order2_filled = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, position_id=position_id, last_px=Price.from_str("1.00001"), ) position.apply(order2_filled) # Act self.cache.update_position(position) # Assert assert self.cache.position_exists(position.id) assert position.id in self.cache.position_ids() assert position in self.cache.positions() assert position in self.cache.positions_closed() assert position in self.cache.positions_closed(instrument_id=position.instrument_id) assert position in self.cache.positions_closed(strategy_id=self.strategy.id) assert position in self.cache.positions_closed( instrument_id=position.instrument_id, strategy_id=self.strategy.id ) assert position not in self.cache.positions_open() assert position not in self.cache.positions_open(instrument_id=position.instrument_id) assert position not in self.cache.positions_open(strategy_id=self.strategy.id) assert position not in self.cache.positions_open( instrument_id=position.instrument_id, strategy_id=self.strategy.id ) assert self.cache.position(position_id) == position assert self.cache.positions_open_count() == 0 assert self.cache.positions_closed_count() == 1 assert self.cache.positions_total_count() == 1 assert self.cache.position_for_order(order1.client_order_id) == position assert self.cache.position_for_order(order2.client_order_id) == position assert order1 in self.cache.orders_for_position(position.id) assert order2 in self.cache.orders_for_position(position.id) def test_positions_queries_with_multiple_open_returns_expected_positions(self): # Arrange # -- Position 1 -------------------------------------------------------- order1 = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) position_id = PositionId("P-1") self.cache.add_order(order1, position_id) order1.apply(TestStubs.event_order_submitted(order1)) self.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.cache.update_order(order1) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-1"), last_px=Price.from_str("1.00001"), ) position1 = Position(instrument=AUDUSD_SIM, fill=fill1) self.cache.add_position(position1, OMSType.HEDGING) # -- Position 2 -------------------------------------------------------- order2 = self.strategy.order_factory.market( GBPUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2.apply(TestStubs.event_order_submitted(order2)) self.cache.update_order(order2) order2.apply(TestStubs.event_order_accepted(order2)) self.cache.update_order(order2) fill2 = TestStubs.event_order_filled( order2, instrument=GBPUSD_SIM, position_id=PositionId("P-2"), last_px=Price.from_str("1.00001"), ) position2 = Position(instrument=GBPUSD_SIM, fill=fill2) self.cache.add_position(position2, OMSType.HEDGING) # Assert assert position1.is_open assert position2.is_open assert position1 in self.cache.positions() assert position2 in self.cache.positions() assert self.cache.positions(venue=AUDUSD_SIM.venue, instrument_id=AUDUSD_SIM.id) == [ position1 ] assert self.cache.positions(venue=GBPUSD_SIM.venue, instrument_id=GBPUSD_SIM.id) == [ position2 ] assert self.cache.positions(instrument_id=GBPUSD_SIM.id) == [position2] assert self.cache.positions(instrument_id=AUDUSD_SIM.id) == [position1] assert self.cache.positions(instrument_id=GBPUSD_SIM.id) == [position2] assert self.cache.positions_open(instrument_id=AUDUSD_SIM.id) == [position1] assert self.cache.positions_open(instrument_id=GBPUSD_SIM.id) == [position2] assert position1 in self.cache.positions_open() assert position2 in self.cache.positions_open() assert position1 not in self.cache.positions_closed() assert position2 not in self.cache.positions_closed() def test_positions_queries_with_one_closed_returns_expected_positions(self): # Arrange # -- Position 1 -------------------------------------------------------- order1 = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) position_id = PositionId("P-1") self.cache.add_order(order1, position_id) order1.apply(TestStubs.event_order_submitted(order1)) self.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.cache.update_order(order1) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-1"), last_px=Price.from_str("1.00001"), ) position1 = Position(instrument=AUDUSD_SIM, fill=fill1) self.cache.add_position(position1, OMSType.HEDGING) # -- Position 2 -------------------------------------------------------- order2 = self.strategy.order_factory.market( GBPUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2.apply(TestStubs.event_order_submitted(order2)) self.cache.update_order(order2) order2.apply(TestStubs.event_order_accepted(order2)) self.cache.update_order(order2) fill2 = TestStubs.event_order_filled( order2, instrument=GBPUSD_SIM, position_id=PositionId("P-2"), last_px=Price.from_str("1.00001"), ) position2 = Position(instrument=GBPUSD_SIM, fill=fill2) self.cache.add_position(position2, OMSType.HEDGING) order3 = self.strategy.order_factory.market( GBPUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) order3.apply(TestStubs.event_order_submitted(order3)) self.cache.update_order(order3) order3.apply(TestStubs.event_order_accepted(order3)) self.cache.update_order(order3) fill3 = TestStubs.event_order_filled( order3, instrument=GBPUSD_SIM, position_id=PositionId("P-2"), last_px=Price.from_str("1.00001"), ) position2.apply(fill3) self.cache.update_position(position2) # Assert assert position1.is_open assert position2.is_closed assert position1 in self.cache.positions() assert position1 in self.cache.positions(instrument_id=AUDUSD_SIM.id) assert position2 in self.cache.positions() assert position2 in self.cache.positions(instrument_id=GBPUSD_SIM.id) assert self.cache.positions_open(venue=BTCUSD_BINANCE.venue) == [] assert self.cache.positions_open(venue=AUDUSD_SIM.venue) == [position1] assert self.cache.positions_open(instrument_id=BTCUSD_BINANCE.id) == [] assert self.cache.positions_open(instrument_id=AUDUSD_SIM.id) == [position1] assert self.cache.positions_open(instrument_id=GBPUSD_SIM.id) == [] assert self.cache.positions_closed(instrument_id=AUDUSD_SIM.id) == [] assert self.cache.positions_closed(venue=GBPUSD_SIM.venue) == [position2] assert self.cache.positions_closed(instrument_id=GBPUSD_SIM.id) == [position2] def test_update_account(self): # Arrange account = TestStubs.cash_account() self.cache.add_account(account) # Act self.cache.update_account(account) # Assert assert True # No exceptions raised def test_delete_strategy(self): # Arrange self.cache.update_strategy(self.strategy) # Act self.cache.delete_strategy(self.strategy) # Assert assert self.strategy.id not in self.cache.strategy_ids() def test_check_residuals(self): # Arrange order1 = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) position1_id = PositionId("P-1") self.cache.add_order(order1, position1_id) order1.apply(TestStubs.event_order_submitted(order1)) self.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.cache.update_order(order1) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=position1_id, last_px=Price.from_str("1.00000"), ) position1 = Position(instrument=AUDUSD_SIM, fill=fill1) self.cache.update_order(order1) self.cache.add_position(position1, OMSType.HEDGING) order2 = self.strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) position2_id = PositionId("P-2") self.cache.add_order(order2, position2_id) order2.apply(TestStubs.event_order_submitted(order2)) self.cache.update_order(order2) order2.apply(TestStubs.event_order_accepted(order2)) self.cache.update_order(order2) # Act self.cache.check_residuals() # Assert assert True # No exception raised def test_reset(self): # Arrange order1 = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) position1_id = PositionId("P-1") self.cache.add_order(order1, position1_id) order1.apply(TestStubs.event_order_submitted(order1)) self.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.cache.update_order(order1) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=position1_id, last_px=Price.from_str("1.00000"), ) position1 = Position(instrument=AUDUSD_SIM, fill=fill1) self.cache.update_order(order1) self.cache.add_position(position1, OMSType.HEDGING) order2 = self.strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) position2_id = PositionId("P-2") self.cache.add_order(order2, position2_id) order2.apply(TestStubs.event_order_submitted(order2)) self.cache.update_order(order2) order2.apply(TestStubs.event_order_accepted(order2)) self.cache.update_order(order2) self.cache.update_order(order2) # Act self.cache.reset() # Assert assert len(self.cache.strategy_ids()) == 0 assert self.cache.orders_total_count() == 0 assert self.cache.positions_total_count() == 0 def test_flush_db(self): # Arrange order1 = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) position1_id = PositionId("P-1") self.cache.add_order(order1, position1_id) order1.apply(TestStubs.event_order_submitted(order1)) self.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.cache.update_order(order1) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=position1_id, last_px=Price.from_str("1.00000"), ) position1 = Position(instrument=AUDUSD_SIM, fill=fill1) self.cache.update_order(order1) self.cache.add_position(position1, OMSType.HEDGING) order2 = self.strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) position2_id = PositionId("P-2") self.cache.add_order(order2, position2_id) order2.apply(TestStubs.event_order_submitted(order2)) self.cache.update_order(order2) order2.apply(TestStubs.event_order_accepted(order2)) self.cache.update_order(order2) # Act self.cache.reset() self.cache.flush_db() # Assert assert True # No exception raised
def __init__(self, config: Optional[TradingNodeConfig] = None): if config is None: config = TradingNodeConfig() PyCondition.not_none(config, "config") PyCondition.type(config, TradingNodeConfig, "config") # Configuration self._config = config # Setup loop self._loop = asyncio.get_event_loop() self._executor = concurrent.futures.ThreadPoolExecutor() self._loop.set_default_executor(self._executor) self._loop.set_debug(config.loop_debug) # Components self._clock = LiveClock(loop=self._loop) self._uuid_factory = UUIDFactory() self.created_time = self._clock.utc_now() self._is_running = False # Identifiers self.trader_id = TraderId(config.trader_id) self.machine_id = socket.gethostname() self.instance_id = self._uuid_factory.generate() # Setup logging self._logger = LiveLogger( loop=self._loop, clock=self._clock, trader_id=self.trader_id, machine_id=self.machine_id, instance_id=self.instance_id, level_stdout=LogLevelParser.from_str_py(config.log_level.upper()), ) self._log = LoggerAdapter( component_name=type(self).__name__, logger=self._logger, ) self._log_header() self._log.info("Building...") if platform.system() != "Windows": # Windows does not support signal handling # https://stackoverflow.com/questions/45987985/asyncio-loops-add-signal-handler-in-windows self._setup_loop() ######################################################################## # Build platform ######################################################################## if config.cache_database is None or config.cache_database.type == "in-memory": cache_db = None elif config.cache_database.type == "redis": cache_db = RedisCacheDatabase( trader_id=self.trader_id, logger=self._logger, serializer=MsgPackSerializer(timestamps_as_str=True), config=config.cache_database, ) else: # pragma: no cover (design-time error) raise ValueError( "The cache_db_type in the configuration is unrecognized, " "can one of {{'in-memory', 'redis'}}.", ) self._msgbus = MessageBus( trader_id=self.trader_id, clock=self._clock, logger=self._logger, ) self._cache = Cache( database=cache_db, logger=self._logger, config=config.cache, ) self.portfolio = Portfolio( msgbus=self._msgbus, cache=self._cache, clock=self._clock, logger=self._logger, ) self._data_engine = LiveDataEngine( loop=self._loop, msgbus=self._msgbus, cache=self._cache, clock=self._clock, logger=self._logger, config=config.data_engine, ) self._exec_engine = LiveExecutionEngine( loop=self._loop, msgbus=self._msgbus, cache=self._cache, clock=self._clock, logger=self._logger, config=config.exec_engine, ) self._exec_engine.load_cache() self._risk_engine = LiveRiskEngine( loop=self._loop, portfolio=self.portfolio, msgbus=self._msgbus, cache=self._cache, clock=self._clock, logger=self._logger, config=config.risk_engine, ) 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, ) if config.load_strategy_state: self.trader.load() # Setup persistence (requires trader) self.persistence_writers: List[Any] = [] if config.persistence: self._setup_persistence(config=config.persistence) self._builder = TradingNodeBuilder( loop=self._loop, data_engine=self._data_engine, exec_engine=self._exec_engine, msgbus=self._msgbus, cache=self._cache, clock=self._clock, logger=self._logger, log=self._log, ) self._log.info("INITIALIZED.") self.time_to_initialize = self._clock.delta(self.created_time) self._log.info(f"Initialized in {int(self.time_to_initialize.total_seconds() * 1000)}ms.") self._is_built = False
def cache(live_logger, cache_db): return Cache( database=cache_db, logger=live_logger, )
def __init__( self, strategies: List[TradingStrategy], config: Dict[str, object], ): """ Initialize a new instance of the TradingNode class. Parameters ---------- strategies : list[TradingStrategy] The list of strategies to run on the trading node. config : dict[str, object] The configuration for the trading node. Raises ------ ValueError If strategies is None or empty. ValueError If config is None or empty. """ PyCondition.not_none(strategies, "strategies") PyCondition.not_none(config, "config") PyCondition.not_empty(strategies, "strategies") PyCondition.not_empty(config, "config") self._config = config # Extract configs config_trader = config.get("trader", {}) config_system = config.get("system", {}) config_log = config.get("logging", {}) config_cache_db = config.get("cache_database", {}) config_cache = config.get("cache", {}) config_data = config.get("data_engine", {}) config_risk = config.get("risk_engine", {}) config_exec = config.get("exec_engine", {}) config_strategy = config.get("strategy", {}) # System config self._timeout_connection = config_system.get("timeout_connection", 5.0) self._timeout_reconciliation = config_system.get( "timeout_reconciliation", 10.0) self._timeout_portfolio = config_system.get("timeout_portfolio", 10.0) self._timeout_disconnection = config_system.get( "timeout_disconnection", 5.0) self._check_residuals_delay = config_system.get( "check_residuals_delay", 5.0) self._load_strategy_state = config_strategy.get("load_state", True) self._save_strategy_state = config_strategy.get("save_state", True) # Setup loop self._loop = asyncio.get_event_loop() self._executor = concurrent.futures.ThreadPoolExecutor() self._loop.set_default_executor(self._executor) self._loop.set_debug(config_system.get("loop_debug", False)) # Components self._clock = LiveClock(loop=self._loop) self._uuid_factory = UUIDFactory() self.system_id = self._uuid_factory.generate() self.created_time = self._clock.utc_now() self._is_running = False # Setup identifiers self.trader_id = TraderId( f"{config_trader['name']}-{config_trader['id_tag']}", ) # Setup logging level_stdout = LogLevelParser.from_str_py( config_log.get("level_stdout")) self._logger = LiveLogger( loop=self._loop, clock=self._clock, trader_id=self.trader_id, system_id=self.system_id, level_stdout=level_stdout, ) self._log = LoggerAdapter( component=self.__class__.__name__, logger=self._logger, ) self._log_header() self._log.info("Building...") if platform.system() != "Windows": # Requires the logger to be initialized # Windows does not support signal handling # https://stackoverflow.com/questions/45987985/asyncio-loops-add-signal-handler-in-windows self._setup_loop() # Build platform # ---------------------------------------------------------------------- if config_cache_db["type"] == "redis": cache_db = RedisCacheDatabase( trader_id=self.trader_id, logger=self._logger, instrument_serializer=MsgPackInstrumentSerializer(), command_serializer=MsgPackCommandSerializer(), event_serializer=MsgPackEventSerializer(), config={ "host": config_cache_db["host"], "port": config_cache_db["port"], }, ) else: cache_db = BypassCacheDatabase( trader_id=self.trader_id, logger=self._logger, ) cache = Cache( database=cache_db, logger=self._logger, config=config_cache, ) self.portfolio = Portfolio( cache=cache, clock=self._clock, logger=self._logger, ) self._data_engine = LiveDataEngine( loop=self._loop, portfolio=self.portfolio, cache=cache, clock=self._clock, logger=self._logger, config=config_data, ) self._exec_engine = LiveExecutionEngine( loop=self._loop, portfolio=self.portfolio, cache=cache, clock=self._clock, logger=self._logger, config=config_exec, ) self._risk_engine = LiveRiskEngine( loop=self._loop, exec_engine=self._exec_engine, portfolio=self.portfolio, cache=cache, clock=self._clock, logger=self._logger, config=config_risk, ) # Wire up components self._exec_engine.register_risk_engine(self._risk_engine) self._exec_engine.load_cache() self.trader = Trader( trader_id=self.trader_id, strategies=strategies, portfolio=self.portfolio, data_engine=self._data_engine, risk_engine=self._risk_engine, exec_engine=self._exec_engine, clock=self._clock, logger=self._logger, ) if self._load_strategy_state: self.trader.load() self._builder = TradingNodeBuilder( data_engine=self._data_engine, exec_engine=self._exec_engine, clock=self._clock, logger=self._logger, log=self._log, ) self._log.info("state=INITIALIZED.") self.time_to_initialize = self._clock.delta(self.created_time) self._log.info( f"Initialized in {self.time_to_initialize.total_seconds():.3f}s.") self._is_built = False
def cache(): return Cache( database=TestStubs.cache_db(), logger=TestStubs.logger(), )
def cache(): return Cache( database=None, logger=TestComponentStubs.logger(), )