def test_run_ema_cross_with_tick_bar_spec(self): # Arrange strategy = EMACross( symbol=self.instrument.symbol, bar_spec=BarSpecification(250, BarAggregation.TICK, PriceType.LAST), trade_size=Decimal(100), fast_ema=10, slow_ema=20, ) # Act self.engine.run(strategies=[strategy]) # Assert self.assertEqual(39, strategy.fast_ema.count) self.assertEqual(19998, self.engine.iteration) self.assertEqual(Money('995991.41500000', USDT), self.engine.portfolio.account(self.venue).balance())
def test_calculate_for_usdjpy(self): # Arrange sizer = FixedRiskSizer( InstrumentLoader.default_fx_ccy(TestStubs.symbol_usdjpy_fxcm())) equity = Money(1000000, USD) # Act result = sizer.calculate( equity, 10, # 0.1% Price("107.703"), Price("107.403"), exchange_rate=0.0093, units=1, unit_batch_size=1000, ) # Assert self.assertEqual(Quantity(358000), result)
def test_comparisons_with_different_currencies_returns_false( self, value1, value2, expected1, expected2, expected3, expected4, ): # Arrange # Act result1 = Money(value1, USD) > Money(value2, BTC) result2 = Money(value1, USD) >= Money(value2, BTC) result3 = Money(value1, USD) <= Money(value2, BTC) result4 = Money(value1, USD) < Money(value2, BTC) # Assert assert expected1 == result1 assert expected2 == result2 assert expected3 == result3 assert expected4 == result4
def test_comparisons_with_various_values_returns_expected_result( self, value1, value2, expected1, expected2, expected3, expected4, ): # Arrange # Act result1 = Money(value1, USD) > Money(value2, USD) result2 = Money(value1, USD) >= Money(value2, USD) result3 = Money(value1, USD) <= Money(value2, USD) result4 = Money(value1, USD) < Money(value2, USD) # Assert self.assertEqual(expected1, result1) self.assertEqual(expected2, result2) self.assertEqual(expected3, result3) self.assertEqual(expected4, result4)
def test_comparisons_with_different_currencies_returns_false( self, value1, value2, expected1, expected2, expected3, expected4, ): # Arrange # Act result1 = Money(value1, USD) > Money(value2, BTC) result2 = Money(value1, USD) >= Money(value2, BTC) result3 = Money(value1, USD) <= Money(value2, BTC) result4 = Money(value1, USD) < Money(value2, BTC) # Assert self.assertEqual(expected1, result1) self.assertEqual(expected2, result2) self.assertEqual(expected3, result3) self.assertEqual(expected4, result4)
def test_apply_order_partially_filled_event_to_buy_limit_order(self): # Arrange order = self.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) order.apply(TestStubs.event_order_submitted(order)) order.apply(TestStubs.event_order_accepted(order)) partially = OrderFilled( self.account_id, order.client_order_id, VenueOrderId("1"), ExecutionId("E-1"), PositionId("P-1"), StrategyId.null(), order.instrument_id, order.side, Quantity.from_int(50000), Price.from_str("0.999999"), AUDUSD_SIM.quote_currency, Money(0, USD), LiquiditySide.MAKER, 1_000_000_000, uuid4(), 1_000_000_000, ) # Act order.apply(partially) # Assert self.assertEqual(OrderState.PARTIALLY_FILLED, order.state) self.assertEqual(Quantity.from_int(50000), order.filled_qty) self.assertEqual(Price.from_str("1.00000"), order.price) self.assertEqual(Decimal("0.999999"), order.avg_px) self.assertEqual(Decimal("-0.000001"), order.slippage) self.assertTrue(order.is_working) self.assertFalse(order.is_completed) self.assertEqual(1_000_000_000, order.ts_filled_ns)
def test_run_ema_cross_with_tick_bar_spec(self): # Arrange strategy = EMACross( instrument_id=self.ethusdt.id, bar_spec=BarSpecification(250, BarAggregation.TICK, PriceType.LAST), trade_size=Decimal(100), fast_ema=10, slow_ema=20, ) # Act self.engine.run(strategies=[strategy]) # Assert self.assertEqual(279, strategy.fast_ema.count) self.assertEqual(69806, self.engine.iteration) self.assertEqual(Money("998873.43110000", USDT), self.engine.portfolio.account(self.venue).balance())
def btcusdt_future_binance(expiry: date = None) -> CryptoFuture: """ Return the Binance BTCUSDT instrument for backtesting. Parameters ---------- expiry : date, optional The expiry date for the contract. Returns ------- CryptoFuture """ if expiry is None: expiry = date(2022, 3, 25) return CryptoFuture( instrument_id=InstrumentId( symbol=Symbol(f"BTCUSDT_{expiry.strftime('%y%m%d')}"), venue=Venue("BINANCE"), ), native_symbol=Symbol("BTCUSDT"), underlying=BTC, quote_currency=USDT, settlement_currency=USDT, expiry_date=expiry, price_precision=2, size_precision=6, price_increment=Price(1e-02, precision=2), size_increment=Quantity(1e-06, precision=6), max_quantity=Quantity(9000, precision=6), min_quantity=Quantity(1e-06, precision=6), max_notional=None, min_notional=Money(10.00000000, USDT), max_price=Price(1000000, precision=2), min_price=Price(0.01, precision=2), margin_init=Decimal(0), margin_maint=Decimal(0), maker_fee=Decimal("0.001"), taker_fee=Decimal("0.001"), ts_event=0, ts_init=0, )
def event_betting_account_state(account_id=None) -> AccountState: return AccountState( account_id=account_id or TestStubs.account_id(), account_type=AccountType.BETTING, base_currency=GBP, reported=False, # reported balances=[ AccountBalance( GBP, Money(1_000, GBP), Money(0, GBP), Money(1_000, GBP), ) ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, )
def event_margin_account_state(account_id=None) -> AccountState: return AccountState( account_id=account_id or TestStubs.account_id(), account_type=AccountType.MARGIN, base_currency=USD, reported=True, # reported balances=[ AccountBalance( USD, Money(1_000_000, USD), Money(0, USD), Money(1_000_000, USD), ) ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, )
def test_run_ema_cross_strategy(self): # Arrange strategy = EMACross( instrument_id=self.usdjpy.id, bar_spec=BarSpecification(15, BarAggregation.MINUTE, PriceType.BID), trade_size=Decimal(1_000_000), fast_ema=10, slow_ema=20, ) # Act self.engine.run(strategies=[strategy]) # Assert - Should return expected PnL self.assertEqual(2689, strategy.fast_ema.count) self.assertEqual(115043, self.engine.iteration) self.assertEqual(Money(997731.23, USD), self.engine.portfolio.account(self.venue).balance())
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("B-USD/JPY-1") # Generated by exchange # 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_update_orders_open_cash_account(self): # Arrange AccountFactory.register_calculated_account("BINANCE") account_id = AccountId("BINANCE", "000") state = AccountState( account_id=account_id, account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( Money(100000.00000000, USDT), Money(0.00000000, USDT), Money(100000.00000000, USDT), ), ], margins=[], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) # Create open order order = self.order_factory.limit( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("1.0"), Price.from_str("50000.00"), ) self.cache.add_order(order, position_id=None) # Act: push order state to ACCEPTED self.exec_engine.process( TestEventStubs.order_submitted(order, account_id=account_id)) self.exec_engine.process( TestEventStubs.order_accepted(order, account_id=account_id)) # Assert assert self.portfolio.balances_locked( BINANCE)[USDT].as_decimal() == 50100
def event_order_filled( order, position_id=None, strategy_id=None, fill_price=None, filled_qty=None, leaves_qty=None, base_currency=USD, quote_currency=JPY, commission=0, ) -> OrderFilled: if position_id is None: position_id = PositionId(order.cl_ord_id.value.replace("P", "T")) if strategy_id is None: strategy_id = StrategyId("S", "NULL") if fill_price is None: fill_price = Price("1.00000") if filled_qty is None: filled_qty = order.quantity if leaves_qty is None: leaves_qty = Quantity() return OrderFilled( TestStubs.account_id(), order.cl_ord_id, OrderId("1"), ExecutionId(order.cl_ord_id.value.replace("O", "E")), position_id, strategy_id, order.symbol, order.side, filled_qty, leaves_qty, order.price if fill_price is None else fill_price, Money(commission, base_currency), LiquiditySide.TAKER, base_currency, # Stub event quote_currency, # Stub event UNIX_EPOCH, uuid4(), UNIX_EPOCH, )
def test_run_ema_cross_with_minute_bar_spec(self): # Arrange strategy = EMACross( instrument_id=self.gbpusd.id, bar_spec=BarSpecification(5, BarAggregation.MINUTE, PriceType.MID), trade_size=Decimal(1_000_000), fast_ema=10, slow_ema=20, ) # Act self.engine.run(strategies=[strategy]) # Assert self.assertEqual(8353, strategy.fast_ema.count) self.assertEqual(120467, self.engine.iteration) self.assertEqual( Money(947226.84, GBP), self.engine.portfolio.account(self.venue).balance_total(GBP), )
def test_run_ema_cross_with_tick_bar_spec(self): # Arrange strategy = EMACross( instrument_id=self.audusd.id, bar_spec=BarSpecification(100, BarAggregation.TICK, PriceType.MID), trade_size=Decimal(1_000_000), fast_ema=10, slow_ema=20, ) # Act self.engine.run(strategies=[strategy]) # Assert self.assertEqual(999, strategy.fast_ema.count) self.assertEqual(99999, self.engine.iteration) self.assertEqual( Money(995431.92, AUD), self.engine.portfolio.account(self.venue).balance_total(AUD), )
def test_data_catalog_backtest_data_filtered(catalog): instruments = catalog.instruments(as_nautilus=True) engine = BacktestEngine(bypass_logging=True) engine = catalog.setup_engine( engine=engine, instruments=[instruments[1]], start_timestamp=1576869877788000000, ) engine.add_venue( venue=BETFAIR_VENUE, venue_type=VenueType.EXCHANGE, account_type=AccountType.CASH, base_currency=GBP, oms_type=OMSType.NETTING, starting_balances=[Money(10000, GBP)], order_book_level=BookLevel.L2, ) engine.run() # Total events 1045 assert engine.iteration == 530
def test_run_ema_cross_strategy(self): # Arrange config = EMACrossConfig( instrument_id=str(self.usdjpy.id), bar_type="USD/JPY.SIM-15-MINUTE-BID-INTERNAL", trade_size=Decimal(1_000_000), fast_ema=10, slow_ema=20, ) strategy = EMACross(config=config) self.engine.add_strategy(strategy) # Act self.engine.run() # Assert - Should return expected PnL assert strategy.fast_ema.count == 2689 assert self.engine.iteration == 115044 assert self.engine.portfolio.account( self.venue).balance_total(USD) == Money(992811.26, USD)
def test_run_ema_cross_with_minute_bar_spec(self): # Arrange config = EMACrossConfig( instrument_id="AUD/USD.SIM", bar_type="AUD/USD.SIM-1-MINUTE-MID-INTERNAL", trade_size=Decimal(1_000_000), fast_ema=10, slow_ema=20, ) strategy = EMACross(config=config) self.engine.add_strategy(strategy) # Act self.engine.run() # Assert assert strategy.fast_ema.count == 1771 assert self.engine.iteration == 100000 assert self.engine.portfolio.account( self.venue).balance_total(AUD) == Money(987920.04, AUD)
def test_run_ema_cross_with_tick_bar_spec(self): # Arrange config = EMACrossConfig( instrument_id=str(self.audusd.id), bar_type="AUD/USD.SIM-100-TICK-MID-INTERNAL", trade_size=Decimal(1_000_000), fast_ema=10, slow_ema=20, ) strategy = EMACross(config=config) self.engine.add_strategy(strategy) # Act self.engine.run() # Assert assert strategy.fast_ema.count == 1000 assert self.engine.iteration == 100000 assert self.engine.portfolio.account( self.venue).balance_total(AUD) == Money(994441.60, AUD)
def test_run_ema_cross_with_minute_bar_spec(self): # Arrange config = EMACrossConfig( instrument_id=str(self.gbpusd.id), bar_type="GBP/USD.SIM-5-MINUTE-MID-INTERNAL", trade_size=Decimal(1_000_000), fast_ema=10, slow_ema=20, ) strategy = EMACross(config=config) self.engine.add_strategy(strategy) # Act self.engine.run() # Assert assert strategy.fast_ema.count == 8353 assert self.engine.iteration == 120468 assert self.engine.portfolio.account( self.venue).balance_total(GBP) == Money(931346.81, GBP)
def test_run_ema_cross_with_tick_bar_spec(self): # Arrange config = EMACrossConfig( instrument_id=str(self.ethusdt.id), bar_type="ETH/USDT.BINANCE-250-TICK-LAST-INTERNAL", trade_size=Decimal(100), fast_ema=10, slow_ema=20, ) strategy = EMACross(config=config) self.engine.add_strategy(strategy) # Act self.engine.run() # Assert assert strategy.fast_ema.count == 279 assert self.engine.iteration == 69806 assert self.engine.portfolio.account( self.venue).balance_total(USDT) == Money(998450.62196820, USDT)
def ethusdt_binance() -> Instrument: """ Return the Binance ETH/USDT instrument for backtesting. Returns ------- Instrument """ instrument_id = InstrumentId( symbol=Symbol("ETH/USDT"), venue=Venue("BINANCE"), ) return Instrument( instrument_id=instrument_id, asset_class=AssetClass.CRYPTO, asset_type=AssetType.SPOT, base_currency=ETH, quote_currency=USDT, settlement_currency=USDT, is_inverse=False, price_precision=2, size_precision=5, tick_size=Decimal("0.01"), multiplier=Decimal("1"), leverage=Decimal("1"), lot_size=Quantity("1"), max_quantity=Quantity("9000"), min_quantity=Quantity("1e-05"), max_notional=None, min_notional=Money("10.00000000", USDT), max_price=Price("1000000.0"), min_price=Price("0.01"), margin_init=Decimal("1.00"), margin_maint=Decimal("0.35"), maker_fee=Decimal("0.0001"), taker_fee=Decimal("0.0001"), financing={}, timestamp=UNIX_EPOCH, )
def test_submit_limit_order_aggressive_multiple_levels(self): # Arrange: Prepare market self.cache.add_instrument(USDJPY_SIM) quote = QuoteTick( instrument_id=USDJPY_SIM.id, bid=Price.from_str("110.000"), ask=Price.from_str("110.010"), bid_size=Quantity.from_int(1500000), ask_size=Quantity.from_int(1500000), ts_event=0, ts_init=0, ) self.data_engine.process(quote) snapshot = TestDataStubs.order_book_snapshot( instrument_id=USDJPY_SIM.id, bid_volume=1000, ask_volume=1000, ) self.data_engine.process(snapshot) self.exchange.process_order_book(snapshot) # Create order order = self.strategy.order_factory.limit( instrument_id=USDJPY_SIM.id, order_side=OrderSide.BUY, quantity=Quantity.from_int(2000), price=Price.from_int(20), post_only=False, ) # Act self.strategy.submit_order(order) self.exchange.process(0) # Assert assert order.status == OrderStatus.FILLED assert order.filled_qty == Decimal("2000.0") # No slippage assert order.avg_px == Decimal("15.33333333333333333333333333") assert self.exchange.get_account().balance_total(USD) == Money( 999999.96, USD)
def setUp(self): # Fixture Setup self.engine = BacktestEngine(bypass_logging=True) self.venue = Venue("BINANCE") self.instrument = TestInstrumentProvider.btcusdt_binance() self.engine.add_instrument(self.instrument) self.engine.add_trade_ticks(self.instrument.id, TestDataProvider.tardis_trades()) self.engine.add_quote_ticks(self.instrument.id, TestDataProvider.tardis_quotes()) self.engine.add_venue( venue=self.venue, venue_type=VenueType.EXCHANGE, oms_type=OMSType.NETTING, account_type=AccountType.CASH, base_currency=None, # Multi-currency account starting_balances=[Money(1_000_000, USDT), Money(10, BTC)], )
def test_calculate_pnls_partially_closed(self): # Arrange event = self._make_account_state(starting_balance=1000.0) account = BettingAccount(event) fill1 = self._make_fill(price="0.50", volume=100, side="BUY") fill2 = self._make_fill(price="0.80", volume=100, side="SELL") position = Position(self.instrument, fill1) position.apply(fill2) # Act result = account.calculate_pnls( instrument=self.instrument, position=position, fill=fill2, ) # Assert # TODO - this should really be 75 GBP given the position (but we are currently not using position?) # assert result == [Money("75.00", GBP)] assert result == [Money("80.00", GBP)]
def test_run_ema_cross_with_minute_bar_spec(self): # Arrange config = EMACrossConfig( instrument_id=str(self.gbpusd.id), bar_type="GBP/USD.SIM-1-MINUTE-BID-EXTERNAL", trade_size=Decimal(1_000_000), fast_ema=10, slow_ema=20, ) strategy = EMACross(config=config) self.engine.add_strategy(strategy) # Act self.engine.run() # Assert assert strategy.fast_ema.count == 30117 assert self.engine.iteration == 60234 ending_balance = self.engine.portfolio.account( self.venue).balance_total(USD) assert ending_balance == Money(1016188.45, USD)
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_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_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())