Exemplo n.º 1
0
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())
Exemplo n.º 2
0
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)
Exemplo n.º 3
0
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())