class AccountTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = TestClock() logger = TestLogger(self.clock) self.order_factory = OrderFactory( trader_id=TraderId("TESTER", "000"), strategy_id=StrategyId("S", "001"), clock=TestClock(), ) self.portfolio = Portfolio(self.clock, logger) self.portfolio.register_cache(DataCache(logger)) def test_instantiate_single_asset_account(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money(1_000_000, USD)], [Money(1_000_000, USD)], [Money(0, USD)], info={"default_currency": "USD"}, # Set the default currency event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Assert self.assertEqual(AccountId("SIM", "001"), account.id) self.assertEqual(USD, account.default_currency) self.assertEqual(event, account.last_event) self.assertEqual([event], account.events) self.assertEqual(1, account.event_count) self.assertEqual(Money(1_000_000, USD), account.balance()) self.assertEqual(Money(1_000_000, USD), account.balance_free()) self.assertEqual(Money(0, USD), account.balance_locked()) self.assertEqual({USD: Money(1_000_000, USD)}, account.balances()) self.assertEqual({USD: Money(1_000_000, USD)}, account.balances_free()) self.assertEqual({USD: Money(0, USD)}, account.balances_locked()) self.assertEqual(Money(0, USD), account.unrealized_pnl()) self.assertEqual(Money(1_000_000, USD), account.equity()) self.assertEqual({}, account.init_margins()) self.assertEqual({}, account.maint_margins()) self.assertEqual(None, account.init_margin()) self.assertEqual(None, account.maint_margin()) def test_instantiate_multi_asset_account(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Assert self.assertEqual(AccountId("SIM", "001"), account.id) self.assertEqual(None, account.default_currency) self.assertEqual(event, account.last_event) self.assertEqual([event], account.events) self.assertEqual(1, account.event_count) self.assertEqual(Money("10.00000000", BTC), account.balance(BTC)) self.assertEqual(Money("20.00000000", ETH), account.balance(ETH)) self.assertEqual(Money("10.00000000", BTC), account.balance_free(BTC)) self.assertEqual(Money("20.00000000", ETH), account.balance_free(ETH)) self.assertEqual(Money("0.00000000", BTC), account.balance_locked(BTC)) self.assertEqual(Money("0.00000000", ETH), account.balance_locked(ETH)) self.assertEqual( { BTC: Money("10.00000000", BTC), ETH: Money("20.00000000", ETH) }, account.balances()) self.assertEqual( { BTC: Money("10.00000000", BTC), ETH: Money("20.00000000", ETH) }, account.balances_free()) self.assertEqual( { BTC: Money("0.00000000", BTC), ETH: Money("0.00000000", ETH) }, account.balances_locked()) self.assertEqual(Money("0.00000000", BTC), account.unrealized_pnl(BTC)) self.assertEqual(Money("0.00000000", ETH), account.unrealized_pnl(ETH)) self.assertEqual(Money("10.00000000", BTC), account.equity(BTC)) self.assertEqual(Money("20.00000000", ETH), account.equity(ETH)) self.assertEqual({}, account.init_margins()) self.assertEqual({}, account.maint_margins()) self.assertEqual(None, account.init_margin(BTC)) self.assertEqual(None, account.init_margin(ETH)) self.assertEqual(None, account.maint_margin(BTC)) self.assertEqual(None, account.maint_margin(ETH)) def test_apply_given_new_state_event_updates_correctly(self): # Arrange event1 = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) # Act account = Account(event1) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) event2 = AccountState( AccountId("SIM", "001"), [Money("9.00000000", BTC), Money("20.00000000", ETH)], [Money("8.50000000", BTC), Money("20.00000000", ETH)], [Money("0.50000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) # Act account.apply(event2) # Assert self.assertEqual(event2, account.last_event) self.assertEqual([event1, event2], account.events) self.assertEqual(2, account.event_count) self.assertEqual(Money("9.00000000", BTC), account.balance(BTC)) self.assertEqual(Money("8.50000000", BTC), account.balance_free(BTC)) self.assertEqual(Money("0.50000000", BTC), account.balance_locked(BTC)) self.assertEqual(Money("20.00000000", ETH), account.balance(ETH)) self.assertEqual(Money("20.00000000", ETH), account.balance_free(ETH)) self.assertEqual(Money("0.00000000", ETH), account.balance_locked(ETH)) def test_update_init_margin(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) margin = Money("0.00100000", BTC) # Act account.update_init_margin(margin) # Assert self.assertEqual(margin, account.init_margin(BTC)) self.assertEqual({BTC: margin}, account.init_margins()) def test_update_maint_margin(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) margin = Money("0.00050000", BTC) # Act account.update_maint_margin(margin) # Assert self.assertEqual(margin, account.maint_margin(BTC)) self.assertEqual({BTC: margin}, account.maint_margins()) def test_unrealized_pnl_with_single_asset_account_when_no_open_positions_returns_zero( self): # Arrange event = AccountState( AccountId("SIM", "001"), balances=[Money(1_000_000, USD)], balances_free=[Money(1_000_000, USD)], balances_locked=[Money(0, USD)], info={"default_currency": "USD"}, # No default currency set event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.unrealized_pnl() # Assert self.assertEqual(result, Money(0, USD)) def test_unrealized_pnl_with_multi_asset_account_when_no_open_positions_returns_zero( self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.unrealized_pnl(BTC) # Assert self.assertEqual(result, Money("0.00000000", BTC))
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())
class AccountTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = TestClock() logger = Logger(self.clock) self.order_factory = OrderFactory( trader_id=TraderId("TESTER", "000"), strategy_id=StrategyId("S", "001"), clock=TestClock(), ) self.portfolio = Portfolio(self.clock, logger) self.portfolio.register_cache(DataCache(logger)) def test_instantiated_accounts_basic_properties(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money(1_000_000, USD)], [Money(1_000_000, USD)], [Money(0, USD)], info={"default_currency": "USD"}, # Set the default currency event_id=uuid4(), timestamp_ns=0, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Assert self.assertEqual(AccountId("SIM", "001"), account.id) self.assertEqual("Account(id=SIM-001)", str(account)) self.assertEqual("Account(id=SIM-001)", repr(account)) self.assertEqual(int, type(hash(account))) self.assertTrue(account == account) self.assertFalse(account != account) def test_instantiate_single_asset_account(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money(1_000_000, USD)], [Money(1_000_000, USD)], [Money(0, USD)], info={"default_currency": "USD"}, # Set the default currency event_id=uuid4(), timestamp_ns=0, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Assert self.assertEqual(USD, account.default_currency) self.assertEqual(event, account.last_event) self.assertEqual([event], account.events) self.assertEqual(1, account.event_count) self.assertEqual(Money(1_000_000, USD), account.balance()) self.assertEqual(Money(1_000_000, USD), account.balance_free()) self.assertEqual(Money(0, USD), account.balance_locked()) self.assertEqual({USD: Money(1_000_000, USD)}, account.balances()) self.assertEqual({USD: Money(1_000_000, USD)}, account.balances_free()) self.assertEqual({USD: Money(0, USD)}, account.balances_locked()) self.assertEqual(Money(0, USD), account.unrealized_pnl()) self.assertEqual(Money(1_000_000, USD), account.equity()) self.assertEqual({}, account.initial_margins()) self.assertEqual({}, account.maint_margins()) self.assertEqual(None, account.initial_margin()) self.assertEqual(None, account.maint_margin()) def test_instantiate_multi_asset_account(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Assert self.assertEqual(AccountId("SIM", "001"), account.id) self.assertEqual(None, account.default_currency) self.assertEqual(event, account.last_event) self.assertEqual([event], account.events) self.assertEqual(1, account.event_count) self.assertEqual(Money("10.00000000", BTC), account.balance(BTC)) self.assertEqual(Money("20.00000000", ETH), account.balance(ETH)) self.assertEqual(Money("10.00000000", BTC), account.balance_free(BTC)) self.assertEqual(Money("20.00000000", ETH), account.balance_free(ETH)) self.assertEqual(Money("0.00000000", BTC), account.balance_locked(BTC)) self.assertEqual(Money("0.00000000", ETH), account.balance_locked(ETH)) self.assertEqual( { BTC: Money("10.00000000", BTC), ETH: Money("20.00000000", ETH) }, account.balances(), ) self.assertEqual( { BTC: Money("10.00000000", BTC), ETH: Money("20.00000000", ETH) }, account.balances_free(), ) self.assertEqual( { BTC: Money("0.00000000", BTC), ETH: Money("0.00000000", ETH) }, account.balances_locked(), ) self.assertEqual(Money("0.00000000", BTC), account.unrealized_pnl(BTC)) self.assertEqual(Money("0.00000000", ETH), account.unrealized_pnl(ETH)) self.assertEqual(Money("10.00000000", BTC), account.equity(BTC)) self.assertEqual(Money("20.00000000", ETH), account.equity(ETH)) self.assertEqual({}, account.initial_margins()) self.assertEqual({}, account.maint_margins()) self.assertEqual(None, account.initial_margin(BTC)) self.assertEqual(None, account.initial_margin(ETH)) self.assertEqual(None, account.maint_margin(BTC)) self.assertEqual(None, account.maint_margin(ETH)) def test_apply_given_new_state_event_updates_correctly(self): # Arrange event1 = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) # Act account = Account(event1) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) event2 = AccountState( AccountId("SIM", "001"), [Money("9.00000000", BTC), Money("20.00000000", ETH)], [Money("8.50000000", BTC), Money("20.00000000", ETH)], [Money("0.50000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) # Act account.apply(event=event2) # Assert self.assertEqual(event2, account.last_event) self.assertEqual([event1, event2], account.events) self.assertEqual(2, account.event_count) self.assertEqual(Money("9.00000000", BTC), account.balance(BTC)) self.assertEqual(Money("8.50000000", BTC), account.balance_free(BTC)) self.assertEqual(Money("0.50000000", BTC), account.balance_locked(BTC)) self.assertEqual(Money("20.00000000", ETH), account.balance(ETH)) self.assertEqual(Money("20.00000000", ETH), account.balance_free(ETH)) self.assertEqual(Money("0.00000000", ETH), account.balance_locked(ETH)) def test_update_initial_margin(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) margin = Money("0.00100000", BTC) # Act account.update_initial_margin(margin) # Assert self.assertEqual(margin, account.initial_margin(BTC)) self.assertEqual({BTC: margin}, account.initial_margins()) def test_update_maint_margin(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) margin = Money("0.00050000", BTC) # Act account.update_maint_margin(margin) # Assert self.assertEqual(margin, account.maint_margin(BTC)) self.assertEqual({BTC: margin}, account.maint_margins()) def test_unrealized_pnl_with_single_asset_account_when_no_open_positions_returns_zero( self, ): # Arrange event = AccountState( AccountId("SIM", "001"), balances=[Money(1_000_000, USD)], balances_free=[Money(1_000_000, USD)], balances_locked=[Money(0, USD)], info={"default_currency": "USD"}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.unrealized_pnl() # Assert self.assertEqual(Money(0, USD), result) def test_unrealized_pnl_with_multi_asset_account_when_no_open_positions_returns_zero( self, ): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.unrealized_pnl(BTC) # Assert self.assertEqual(Money("0.00000000", BTC), result) def test_equity_with_single_asset_account_no_default_returns_none(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("100000.00", USD)], [Money("0.00", USD)], [Money("0.00", USD)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.equity(BTC) # Assert self.assertIsNone(result) def test_equity_with_single_asset_account_returns_expected_money(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("100000.00", USD)], [Money("0.00", USD)], [Money("0.00", USD)], info={"default_currency": "USD"}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.equity() # Assert self.assertEqual(Money("100000.00", USD), result) def test_equity_with_multi_asset_account_returns_expected_money(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.equity(BTC) # Assert self.assertEqual(Money("10.00000000", BTC), result) def test_margin_available_for_single_asset_account(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("100000.00", USD)], [Money("0.00", USD)], [Money("0.00", USD)], info={"default_currency": "USD"}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result1 = account.margin_available() account.update_initial_margin(Money("500.00", USD)) result2 = account.margin_available() account.update_maint_margin(Money("1000.00", USD)) result3 = account.margin_available() # Assert self.assertEqual(Money("100000.00", USD), result1) self.assertEqual(Money("99500.00", USD), result2) self.assertEqual(Money("98500.00", USD), result3) def test_margin_available_for_multi_asset_account(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result1 = account.margin_available(BTC) account.update_initial_margin(Money("0.00010000", BTC)) result2 = account.margin_available(BTC) account.update_maint_margin(Money("0.00020000", BTC)) result3 = account.margin_available(BTC) result4 = account.margin_available(ETH) # Assert self.assertEqual(Money("10.00000000", BTC), result1) self.assertEqual(Money("9.99990000", BTC), result2) self.assertEqual(Money("9.99970000", BTC), result3) self.assertEqual(Money("20.00000000", ETH), result4)
class AccountTests(unittest.TestCase): def setUp(self): # Fixture Setup clock = TestClock() logger = Logger(clock) trader_id = TraderId("TESTER-000") self.order_factory = OrderFactory( trader_id=trader_id, strategy_id=StrategyId("S-001"), clock=TestClock(), ) cache_db = BypassCacheDatabase( trader_id=trader_id, logger=logger, ) cache = Cache( database=cache_db, logger=logger, ) self.portfolio = Portfolio( cache=cache, clock=clock, logger=logger, ) self.exec_engine = ExecutionEngine( portfolio=self.portfolio, cache=cache, clock=clock, logger=logger, ) def test_instantiated_accounts_basic_properties(self): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, 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(), ts_updated_ns=0, timestamp_ns=0, ) # Act account = Account(event) # Prepare components account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Assert self.assertEqual(AccountId("SIM", "001"), account.id) self.assertEqual("Account(id=SIM-001)", str(account)) self.assertEqual("Account(id=SIM-001)", repr(account)) self.assertEqual(int, type(hash(account))) self.assertTrue(account == account) self.assertFalse(account != account) def test_instantiate_single_asset_account(self): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, 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(), ts_updated_ns=0, timestamp_ns=0, ) # Act account = Account(event) # Prepare components account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Assert self.assertEqual(USD, account.base_currency) self.assertEqual(event, account.last_event) self.assertEqual([event], account.events) self.assertEqual(1, account.event_count) self.assertEqual(Money(1_000_000, USD), account.balance_total()) self.assertEqual(Money(1_000_000, USD), account.balance_free()) self.assertEqual(Money(0, USD), account.balance_locked()) self.assertEqual({USD: Money(1_000_000, USD)}, account.balances_total()) self.assertEqual({USD: Money(1_000_000, USD)}, account.balances_free()) self.assertEqual({USD: Money(0, USD)}, account.balances_locked()) self.assertEqual(Money(0, USD), account.unrealized_pnl()) self.assertEqual(Money(1_000_000, USD), account.equity()) self.assertEqual({}, account.initial_margins()) self.assertEqual({}, account.maint_margins()) self.assertEqual(None, account.initial_margin()) self.assertEqual(None, account.maint_margin()) def test_instantiate_multi_asset_account(self): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, base_currency=None, # Multi-currency 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={}, # No default currency set event_id=uuid4(), ts_updated_ns=0, timestamp_ns=0, ) # Act account = Account(event) # Prepare components account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Assert self.assertEqual(AccountId("SIM", "001"), account.id) self.assertEqual(None, account.base_currency) self.assertEqual(event, account.last_event) self.assertEqual([event], account.events) self.assertEqual(1, account.event_count) self.assertEqual(Money(10.00000000, BTC), account.balance_total(BTC)) self.assertEqual(Money(20.00000000, ETH), account.balance_total(ETH)) self.assertEqual(Money(10.00000000, BTC), account.balance_free(BTC)) self.assertEqual(Money(20.00000000, ETH), account.balance_free(ETH)) self.assertEqual(Money(0.00000000, BTC), account.balance_locked(BTC)) self.assertEqual(Money(0.00000000, ETH), account.balance_locked(ETH)) self.assertEqual( { BTC: Money(10.00000000, BTC), ETH: Money(20.00000000, ETH) }, account.balances_total(), ) self.assertEqual( { BTC: Money(10.00000000, BTC), ETH: Money(20.00000000, ETH) }, account.balances_free(), ) self.assertEqual( { BTC: Money(0.00000000, BTC), ETH: Money(0.00000000, ETH) }, account.balances_locked(), ) self.assertEqual(Money(0.00000000, BTC), account.unrealized_pnl(BTC)) self.assertEqual(Money(0.00000000, ETH), account.unrealized_pnl(ETH)) self.assertEqual(Money(10.00000000, BTC), account.equity(BTC)) self.assertEqual(Money(20.00000000, ETH), account.equity(ETH)) self.assertEqual({}, account.initial_margins()) self.assertEqual({}, account.maint_margins()) self.assertEqual(None, account.initial_margin(BTC)) self.assertEqual(None, account.initial_margin(ETH)) self.assertEqual(None, account.maint_margin(BTC)) self.assertEqual(None, account.maint_margin(ETH)) def test_apply_given_new_state_event_updates_correctly(self): # Arrange event1 = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, base_currency=None, # Multi-currency 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={}, # No default currency set event_id=uuid4(), ts_updated_ns=0, timestamp_ns=0, ) # Act account = Account(event1) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) event2 = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, base_currency=None, # Multi-currency reported=True, balances=[ AccountBalance( BTC, Money(9.00000000, BTC), Money(0.50000000, BTC), Money(8.50000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, # No default currency set event_id=uuid4(), ts_updated_ns=0, timestamp_ns=0, ) # Act account.apply(event=event2) # Assert self.assertEqual(event2, account.last_event) self.assertEqual([event1, event2], account.events) self.assertEqual(2, account.event_count) self.assertEqual(Money(9.00000000, BTC), account.balance_total(BTC)) self.assertEqual(Money(8.50000000, BTC), account.balance_free(BTC)) self.assertEqual(Money(0.50000000, BTC), account.balance_locked(BTC)) self.assertEqual(Money(20.00000000, ETH), account.balance_total(ETH)) self.assertEqual(Money(20.00000000, ETH), account.balance_free(ETH)) self.assertEqual(Money(0.00000000, ETH), account.balance_locked(ETH)) def test_update_initial_margin(self): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, base_currency=None, # Multi-currency 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={}, # No default currency set event_id=uuid4(), ts_updated_ns=0, timestamp_ns=0, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) margin = Money(0.00100000, BTC) # Act account.update_initial_margin(margin) # Assert self.assertEqual(margin, account.initial_margin(BTC)) self.assertEqual({BTC: margin}, account.initial_margins()) def test_update_maint_margin(self): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, base_currency=None, # Multi-currency 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={}, # No default currency set event_id=uuid4(), ts_updated_ns=0, timestamp_ns=0, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) margin = Money(0.00050000, BTC) # Act account.update_maint_margin(margin) # Assert self.assertEqual(margin, account.maint_margin(BTC)) self.assertEqual({BTC: margin}, account.maint_margins()) def test_unrealized_pnl_with_single_asset_account_when_no_open_positions_returns_zero( self, ): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, 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(), ts_updated_ns=0, timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.unrealized_pnl() # Assert self.assertEqual(Money(0, USD), result) def test_unrealized_pnl_with_multi_asset_account_when_no_open_positions_returns_zero( self, ): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, base_currency=None, # Multi-currency 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={}, # No default currency set event_id=uuid4(), ts_updated_ns=0, timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.unrealized_pnl(BTC) # Assert self.assertEqual(Money(0.00000000, BTC), result) def test_equity_with_single_asset_account_no_default_returns_none(self): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(100000.00, USD), Money(0.00, USD), Money(100000.00, USD), ), ], info={}, # No default currency set event_id=uuid4(), ts_updated_ns=0, timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.equity(BTC) # Assert self.assertIsNone(result) def test_equity_with_single_asset_account_returns_expected_money(self): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(100000.00, USD), Money(0.00, USD), Money(100000.00, USD), ), ], info={}, event_id=uuid4(), ts_updated_ns=0, timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.equity() # Assert self.assertEqual(Money(100000.00, USD), result) def test_equity_with_multi_asset_account_returns_expected_money(self): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, base_currency=None, # Multi-currency 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={}, # No default currency set event_id=uuid4(), ts_updated_ns=0, timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.equity(BTC) # Assert self.assertEqual(Money(10.00000000, BTC), result) def test_margin_available_for_single_asset_account(self): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(100000.00, USD), Money(0.00, USD), Money(100000.00, USD), ), ], info={}, event_id=uuid4(), ts_updated_ns=0, timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result1 = account.margin_available() account.update_initial_margin(Money(500.00, USD)) result2 = account.margin_available() account.update_maint_margin(Money(1000.00, USD)) result3 = account.margin_available() # Assert self.assertEqual(Money(100000.00, USD), result1) self.assertEqual(Money(99500.00, USD), result2) self.assertEqual(Money(98500.00, USD), result3) def test_margin_available_for_multi_asset_account(self): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, base_currency=None, # Multi-currency 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={}, # No default currency set event_id=uuid4(), ts_updated_ns=0, timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result1 = account.margin_available(BTC) account.update_initial_margin(Money(0.00010000, BTC)) result2 = account.margin_available(BTC) account.update_maint_margin(Money(0.00020000, BTC)) result3 = account.margin_available(BTC) result4 = account.margin_available(ETH) # Assert self.assertEqual(Money(10.00000000, BTC), result1) self.assertEqual(Money(9.99990000, BTC), result2) self.assertEqual(Money(9.99970000, BTC), result3) self.assertEqual(Money(20.00000000, ETH), result4)
class PortfolioTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = TestClock() uuid_factor = TestUUIDFactory() logger = TestLogger(self.clock) self.order_factory = OrderFactory( strategy_id=StrategyId("S", "001"), id_tag_trader=IdTag("001"), id_tag_strategy=IdTag("001"), clock=TestClock(), ) state = AccountState( AccountId.from_string("BITMEX-1513111-SIMULATED"), BTC, Money(10., BTC), Money(0., BTC), Money(0., BTC), uuid4(), UNIX_EPOCH ) self.account = Account(state) self.portfolio = Portfolio(self.clock, uuid_factor, logger) self.portfolio.register_account(self.account) def test_account_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertIsNone(self.portfolio.account(FXCM)) def test_account_when_account_returns_read_only_facade(self): # Arrange # Act result = self.portfolio.account(BITMEX) # Assert self.assertEqual(self.account, result) def test_unrealized_pnl_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertIsNone(self.portfolio.unrealized_pnl(FXCM)) def test_order_margin_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertIsNone(self.portfolio.order_margin(FXCM)) def test_position_margin_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertIsNone(self.portfolio.position_margin(FXCM)) def test_open_value_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertIsNone(self.portfolio.open_value(FXCM))