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 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)
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 PortfolioTests(unittest.TestCase): def setUp(self): # Fixture Setup clock = TestClock() logger = TestLogger(clock) self.order_factory = OrderFactory( trader_id=TraderId("TESTER", "000"), strategy_id=StrategyId("S", "001"), clock=TestClock(), ) state = AccountState( account_id=AccountId("BINANCE", "1513111"), balances=[Money("10.00000000", BTC)], balances_free=[Money("0.00000000", BTC)], balances_locked=[Money("0.00000000", BTC)], info={}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) self.data_cache = DataCache(logger) self.account = Account(state) self.portfolio = Portfolio(clock, logger) self.portfolio.register_account(self.account) self.portfolio.register_cache(self.data_cache) self.data_cache.add_instrument(AUDUSD_SIM) self.data_cache.add_instrument(GBPUSD_SIM) self.data_cache.add_instrument(BTCUSDT_BINANCE) self.data_cache.add_instrument(BTCUSD_BITMEX) self.data_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 # Act result = self.portfolio.account(BINANCE) # Assert self.assertEqual(self.account, result) def test_net_position_when_no_positions_returns_zero(self): # Arrange # Act # Assert self.assertEqual(Decimal(0), self.portfolio.net_position(AUDUSD_SIM.symbol)) def test_is_net_long_when_no_positions_returns_false(self): # Arrange # Act # Assert self.assertEqual(False, self.portfolio.is_net_long(AUDUSD_SIM.symbol)) def test_is_net_short_when_no_positions_returns_false(self): # Arrange # Act # Assert self.assertEqual(False, self.portfolio.is_net_short(AUDUSD_SIM.symbol)) def test_is_flat_when_no_positions_returns_true(self): # Arrange # Act # Assert self.assertEqual(True, self.portfolio.is_flat(AUDUSD_SIM.symbol)) def test_is_completely_flat_when_no_positions_returns_true(self): # Arrange # Act # Assert self.assertEqual(True, self.portfolio.is_flat(AUDUSD_SIM.symbol)) def test_unrealized_pnl_for_symbol_when_no_instrument_returns_none(self): # Arrange # Act # Assert self.assertIsNone(self.portfolio.unrealized_pnl(USDJPY_SIM.symbol)) 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.market_values(SIM)) def test_update_tick(self): # Arrange tick = TestStubs.quote_tick_5decimal(GBPUSD_SIM.symbol) # Act self.portfolio.update_tick(tick) # Assert self.assertIsNone(self.portfolio.unrealized_pnl(GBPUSD_SIM.symbol)) def test_update_orders_working(self): # Arrange self.portfolio.register_account(self.account) # Create two working orders order1 = self.order_factory.stop_market( BTCUSDT_BINANCE.symbol, OrderSide.BUY, Quantity("10.5"), Price("25000.00"), ) order2 = self.order_factory.stop_market( BTCUSDT_BINANCE.symbol, OrderSide.BUY, Quantity("10.5"), Price("25000.00"), ) # Push state to FILLED order1.apply(TestStubs.event_order_submitted(order1)) order1.apply(TestStubs.event_order_accepted(order1)) filled1 = TestStubs.event_order_filled( order1, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S", "1"), fill_price=Price("25000.00"), ) order1.apply(filled1) # Push state to ACCEPTED order2.apply(TestStubs.event_order_submitted(order2)) order2.apply(TestStubs.event_order_accepted(order2)) # Update the last quote last = QuoteTick( BTCUSDT_BINANCE.symbol, Price("25001.00"), Price("25002.00"), Quantity(1), Quantity(1), UNIX_EPOCH, ) # Act self.portfolio.update_tick(last) self.portfolio.initialize_orders({order1, order2}) # Assert self.assertEqual({}, self.portfolio.initial_margins(BINANCE)) def test_update_positions(self): # Arrange self.portfolio.register_account(self.account) # Create a closed position order1 = self.order_factory.market( BTCUSDT_BINANCE.symbol, OrderSide.BUY, Quantity("10.50000000"), ) order2 = self.order_factory.market( BTCUSDT_BINANCE.symbol, OrderSide.SELL, Quantity("10.50000000"), ) filled1 = TestStubs.event_order_filled( order1, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S", "1"), fill_price=Price("25000.00"), ) filled2 = TestStubs.event_order_filled( order2, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S", "1"), fill_price=Price("25000.00"), ) position1 = Position(filled1) position1.apply(filled2) order3 = self.order_factory.market( BTCUSDT_BINANCE.symbol, OrderSide.BUY, Quantity("10.00000000"), ) filled3 = TestStubs.event_order_filled( order3, instrument=BTCUSDT_BINANCE, strategy_id=StrategyId("S", "1"), fill_price=Price("25000.00"), ) position2 = Position(filled3) # Update the last quote last = QuoteTick( BTCUSDT_BINANCE.symbol, Price("25001.00"), Price("25002.00"), Quantity(1), Quantity(1), UNIX_EPOCH, ) # Act self.portfolio.initialize_positions({position1, position2}) self.portfolio.update_tick(last) # Assert self.assertTrue(self.portfolio.is_net_long(BTCUSDT_BINANCE.symbol)) def test_opening_one_long_position_updates_portfolio(self): # Arrange order = self.order_factory.market( BTCUSDT_BINANCE.symbol, OrderSide.BUY, Quantity("10.000000"), ) fill = TestStubs.event_order_filled( order=order, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "001"), fill_price=Price("10500.00"), ) last = QuoteTick( BTCUSDT_BINANCE.symbol, Price("10510.00"), Price("10511.00"), Quantity("1.000000"), Quantity("1.000000"), UNIX_EPOCH, ) self.data_cache.add_quote_tick(last) self.portfolio.update_tick(last) position = Position(fill) # Act self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert self.assertEqual({USDT: Money("105100.00000000", USDT)}, self.portfolio.market_values(BINANCE)) self.assertEqual({USDT: Money("100.00000000", USDT)}, self.portfolio.unrealized_pnls(BINANCE)) self.assertEqual({}, self.portfolio.maint_margins(BINANCE)) self.assertEqual(Money("105100.00000000", USDT), self.portfolio.market_value(BTCUSDT_BINANCE.symbol)) self.assertEqual(Money("100.00000000", USDT), self.portfolio.unrealized_pnl(BTCUSDT_BINANCE.symbol)) self.assertEqual(Decimal("10.00000000"), self.portfolio.net_position(order.symbol)) self.assertTrue(self.portfolio.is_net_long(order.symbol)) self.assertFalse(self.portfolio.is_net_short(order.symbol)) self.assertFalse(self.portfolio.is_flat(order.symbol)) self.assertFalse(self.portfolio.is_completely_flat()) def test_opening_one_short_position_updates_portfolio(self): # Arrange order = self.order_factory.market( BTCUSDT_BINANCE.symbol, OrderSide.SELL, Quantity("0.515"), ) fill = TestStubs.event_order_filled(order=order, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "001"), fill_price=Price("15000.00")) last = QuoteTick( BTCUSDT_BINANCE.symbol, Price("15510.15"), Price("15510.25"), Quantity("12.62"), Quantity("3.1"), UNIX_EPOCH, ) self.data_cache.add_quote_tick(last) self.portfolio.update_tick(last) position = Position(fill) # Act self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert self.assertEqual({USDT: Money("7987.77875000", USDT)}, self.portfolio.market_values(BINANCE)) self.assertEqual({USDT: Money("-262.77875000", USDT)}, self.portfolio.unrealized_pnls(BINANCE)) self.assertEqual({}, self.portfolio.maint_margins(BINANCE)) self.assertEqual(Money("7987.77875000", USDT), self.portfolio.market_value(BTCUSDT_BINANCE.symbol)) self.assertEqual(Money("-262.77875000", USDT), self.portfolio.unrealized_pnl(BTCUSDT_BINANCE.symbol)) self.assertEqual(Decimal("-0.515"), self.portfolio.net_position(order.symbol)) self.assertFalse(self.portfolio.is_net_long(order.symbol)) self.assertTrue(self.portfolio.is_net_short(order.symbol)) self.assertFalse(self.portfolio.is_flat(order.symbol)) self.assertFalse(self.portfolio.is_completely_flat()) def test_opening_positions_with_multi_asset_account(self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), balances=[Money("10.00000000", BTC), Money("10.00000000", ETH)], balances_free=[ Money("0.00000000", BTC), Money("10.00000000", ETH) ], balances_locked=[ Money("0.00000000", BTC), Money("0.00000000", ETH) ], info={}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) last_ethusd = QuoteTick( ETHUSD_BITMEX.symbol, Price("376.05"), Price("377.10"), Quantity("16"), Quantity("25"), UNIX_EPOCH, ) last_btcusd = QuoteTick( BTCUSD_BITMEX.symbol, Price("10500.05"), Price("10501.51"), Quantity("2.54"), Quantity("0.91"), UNIX_EPOCH, ) self.data_cache.add_quote_tick(last_ethusd) self.data_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.symbol, OrderSide.BUY, Quantity(10000), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "001"), fill_price=Price("376.05"), ) position = Position(fill) # Act self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert self.assertEqual({ETH: Money("2.65922085", ETH)}, self.portfolio.market_values(BITMEX)) self.assertEqual({ETH: Money("0.03855870", ETH)}, self.portfolio.maint_margins(BITMEX)) self.assertEqual(Money("2.65922085", ETH), self.portfolio.market_value(ETHUSD_BITMEX.symbol)) self.assertEqual(Money("0.00000000", ETH), self.portfolio.unrealized_pnl(ETHUSD_BITMEX.symbol)) def test_unrealized_pnl_when_insufficient_data_for_xrate_returns_none( self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), balances=[Money("10.00000000", BTC), Money("10.00000000", ETH)], balances_free=[ Money("10.00000000", BTC), Money("10.00000000", ETH) ], balances_locked=[ Money("0.00000000", BTC), Money("0.00000000", ETH) ], info={}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) order = self.order_factory.market( ETHUSD_BITMEX.symbol, OrderSide.BUY, Quantity(100), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "001"), fill_price=Price("376.05"), ) position = Position(fill) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Act result = self.portfolio.unrealized_pnls(BITMEX) # # Assert self.assertIsNone(result) def test_market_value_when_insufficient_data_for_xrate_returns_none(self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), balances=[Money("10.00000000", BTC), Money("10.00000000", ETH)], balances_free=[ Money("10.00000000", BTC), Money("10.00000000", ETH) ], balances_locked=[ Money("0.00000000", BTC), Money("0.00000000", ETH) ], info={}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) order = self.order_factory.market( ETHUSD_BITMEX.symbol, OrderSide.BUY, Quantity(100), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "001"), fill_price=Price("376.05"), ) last_ethusd = QuoteTick( ETHUSD_BITMEX.symbol, Price("376.05"), Price("377.10"), Quantity("16"), Quantity("25"), UNIX_EPOCH, ) position = Position(fill) self.portfolio.update_position( TestStubs.event_position_opened(position)) self.data_cache.add_quote_tick(last_ethusd) self.portfolio.update_tick(last_ethusd) # Act result = self.portfolio.market_values(BITMEX) # Assert # TODO: Currently no Quanto thus no xrate required self.assertEqual({ETH: Money('0.02659221', ETH)}, result) def test_opening_several_positions_updates_portfolio(self): # Arrange state = AccountState( AccountId("SIM", "01234"), balances=[Money(1_000_000.00, USD)], balances_free=[Money(1_000_000.00, USD)], balances_locked=[Money(0.00, USD)], info={"default_currency": "USD"}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) last_audusd = QuoteTick( AUDUSD_SIM.symbol, Price("0.80501"), Price("0.80505"), Quantity(1), Quantity(1), UNIX_EPOCH, ) last_gbpusd = QuoteTick( GBPUSD_SIM.symbol, Price("1.30315"), Price("1.30317"), Quantity(1), Quantity(1), UNIX_EPOCH, ) self.data_cache.add_quote_tick(last_audusd) self.data_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.symbol, OrderSide.BUY, Quantity(100000), ) order2 = self.order_factory.market( GBPUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) order1_filled = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-1"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) order2_filled = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, position_id=PositionId("P-2"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) position1 = Position(order1_filled) position2 = Position(order2_filled) position_opened1 = TestStubs.event_position_opened(position1) position_opened2 = TestStubs.event_position_opened(position2) # Act self.portfolio.update_position(position_opened1) self.portfolio.update_position(position_opened2) # Assert self.assertEqual({USD: Money("4216.32", USD)}, self.portfolio.market_values(SIM)) self.assertEqual({USD: Money("10816.00", USD)}, self.portfolio.unrealized_pnls(SIM)) self.assertEqual({USD: Money("130.71", USD)}, self.portfolio.maint_margins(SIM)) self.assertEqual(Money("1610.02", USD), self.portfolio.market_value(AUDUSD_SIM.symbol)) self.assertEqual(Money("2606.30", USD), self.portfolio.market_value(GBPUSD_SIM.symbol)) self.assertEqual(Money("-19499.00", USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.symbol)) self.assertEqual(Money("30315.00", USD), self.portfolio.unrealized_pnl(GBPUSD_SIM.symbol)) self.assertEqual(Decimal(100000), self.portfolio.net_position(AUDUSD_SIM.symbol)) self.assertEqual(Decimal(100000), self.portfolio.net_position(GBPUSD_SIM.symbol)) self.assertTrue(self.portfolio.is_net_long(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_net_short(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_flat(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_completely_flat()) def test_modifying_position_updates_portfolio(self): # Arrange state = AccountState( AccountId("SIM", "01234"), balances=[Money(1_000_000.00, USD)], balances_free=[Money(1_000_000.00, USD)], balances_locked=[Money(0.00, USD)], info={"default_currency": "USD"}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) last_audusd = QuoteTick( AUDUSD_SIM.symbol, Price("0.80501"), Price("0.80505"), Quantity(1), Quantity(1), UNIX_EPOCH, ) self.data_cache.add_quote_tick(last_audusd) self.portfolio.update_tick(last_audusd) order1 = self.order_factory.market( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) order1_filled = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) position = Position(order1_filled) self.portfolio.update_position( TestStubs.event_position_opened(position)) order2 = self.order_factory.market( AUDUSD_SIM.symbol, OrderSide.SELL, Quantity(50000), ) order2_filled = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) position.apply(order2_filled) # Act self.portfolio.update_position( TestStubs.event_position_changed(position)) # Assert self.assertEqual({USD: Money("805.01", USD)}, self.portfolio.market_values(SIM)) self.assertEqual({USD: Money("-9749.50", USD)}, self.portfolio.unrealized_pnls(SIM)) self.assertEqual({USD: Money("24.96", USD)}, self.portfolio.maint_margins(SIM)) self.assertEqual(Money("805.01", USD), self.portfolio.market_value(AUDUSD_SIM.symbol)) self.assertEqual(Money("-9749.50", USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.symbol)) self.assertEqual(Decimal(50000), self.portfolio.net_position(AUDUSD_SIM.symbol)) self.assertTrue(self.portfolio.is_net_long(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_net_short(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_flat(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_completely_flat()) self.assertEqual({}, self.portfolio.unrealized_pnls(BINANCE)) self.assertEqual({}, self.portfolio.market_values(BINANCE)) def test_closing_position_updates_portfolio(self): # Arrange state = AccountState( AccountId("SIM", "01234"), balances=[Money(1_000_000.00, USD)], balances_free=[Money(1_000_000.00, USD)], balances_locked=[Money(0.00, USD)], info={"default_currency": "USD"}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) order1 = self.order_factory.market( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) order1_filled = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) position = Position(order1_filled) self.portfolio.update_position( TestStubs.event_position_opened(position)) order2 = self.order_factory.market( AUDUSD_SIM.symbol, OrderSide.SELL, Quantity(100000), ) order2_filled = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00010"), ) position.apply(order2_filled) # Act self.portfolio.update_position( TestStubs.event_position_closed(position)) # Assert self.assertEqual({}, self.portfolio.market_values(SIM)) self.assertEqual({}, self.portfolio.unrealized_pnls(SIM)) self.assertEqual({}, self.portfolio.maint_margins(SIM)) self.assertEqual(Money("0", USD), self.portfolio.market_value(AUDUSD_SIM.symbol)) self.assertEqual(Money("0", USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.symbol)) self.assertEqual(Decimal(0), self.portfolio.net_position(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_net_long(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_net_short(AUDUSD_SIM.symbol)) self.assertTrue(self.portfolio.is_flat(AUDUSD_SIM.symbol)) self.assertTrue(self.portfolio.is_completely_flat()) def test_several_positions_with_different_symbols_updates_portfolio(self): # Arrange state = AccountState( AccountId("SIM", "01234"), balances=[Money(1_000_000.00, USD)], balances_free=[Money(1_000_000.00, USD)], balances_locked=[Money(0.00, USD)], info={"default_currency": "USD"}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) order1 = self.order_factory.market( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) order2 = self.order_factory.market( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) order3 = self.order_factory.market( GBPUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) order4 = self.order_factory.market( GBPUSD_SIM.symbol, OrderSide.SELL, Quantity(100000), ) order1_filled = TestStubs.event_order_filled( order1, instrument=GBPUSD_SIM, position_id=PositionId("P-1"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) order2_filled = TestStubs.event_order_filled( order2, instrument=GBPUSD_SIM, position_id=PositionId("P-2"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) order3_filled = TestStubs.event_order_filled( order3, instrument=GBPUSD_SIM, position_id=PositionId("P-3"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) order4_filled = TestStubs.event_order_filled( order4, instrument=GBPUSD_SIM, position_id=PositionId("P-3"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00100"), ) position1 = Position(order1_filled) position2 = Position(order2_filled) position3 = Position(order3_filled) last_audusd = QuoteTick( AUDUSD_SIM.symbol, Price("0.80501"), Price("0.80505"), Quantity(1), Quantity(1), UNIX_EPOCH, ) last_gbpusd = QuoteTick( GBPUSD_SIM.symbol, Price("1.30315"), Price("1.30317"), Quantity(1), Quantity(1), UNIX_EPOCH, ) self.data_cache.add_quote_tick(last_audusd) self.data_cache.add_quote_tick(last_gbpusd) self.portfolio.update_tick(last_audusd) self.portfolio.update_tick(last_gbpusd) # 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(order4_filled) 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("3220.04", USD)}, self.portfolio.market_values(SIM)) self.assertEqual({USD: Money("99.82", USD)}, self.portfolio.maint_margins(SIM)) self.assertEqual(Money("3220.04", USD), self.portfolio.market_value(AUDUSD_SIM.symbol)) self.assertEqual(Money("-38998.00", USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.symbol)) self.assertEqual(Money("0", USD), self.portfolio.unrealized_pnl(GBPUSD_SIM.symbol)) self.assertEqual(Decimal(200000), self.portfolio.net_position(AUDUSD_SIM.symbol)) self.assertEqual(Decimal(0), self.portfolio.net_position(GBPUSD_SIM.symbol)) self.assertTrue(self.portfolio.is_net_long(AUDUSD_SIM.symbol)) self.assertTrue(self.portfolio.is_flat(GBPUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_completely_flat())