def test_expire_items(self): # Arrange instance = WindowedMinMaxPrices(timedelta(minutes=5)) # Act instance.add_price( datetime(2020, 1, 1, 0, 0, 0, tzinfo=pytz.utc), Price.from_str("1.0"), ) # 5 min later (still in the window) instance.add_price( datetime(2020, 1, 1, 0, 5, 0, tzinfo=pytz.utc), Price.from_str("0.9"), ) # Allow the first item to expire out # This also tests that the new tick is the new min/max instance.add_price( datetime(2020, 1, 1, 0, 5, 1, tzinfo=pytz.utc), Price.from_str("0.95"), ) # Assert assert instance.min_price == Price.from_str("0.95") assert instance.max_price == Price.from_str("0.95")
def test_calculate_margin_init_with_no_leverage_for_inverse( self, inverse_as_quote, expected): # Arrange account = TestStubs.margin_account() instrument = TestInstrumentProvider.xbtusd_bitmex() result = account.calculate_margin_init( instrument=instrument, quantity=Quantity.from_int(100000), price=Price.from_str("11493.60"), inverse_as_quote=inverse_as_quote, ) # Assert assert result == expected
def ethusd_bitmex() -> CryptoPerpetual: """ Return the BitMEX ETH/USD perpetual swap contract for backtesting. Returns ------- CryptoPerpetual """ return CryptoPerpetual( instrument_id=InstrumentId( symbol=Symbol("ETH/USD"), venue=Venue("BITMEX"), ), native_symbol=Symbol("ETHUSD"), base_currency=ETH, quote_currency=USD, settlement_currency=BTC, is_inverse=True, price_precision=2, size_precision=0, price_increment=Price.from_str("0.05"), size_increment=Quantity.from_int(1), max_quantity=Quantity.from_int(10000000), min_quantity=Quantity.from_int(1), max_notional=None, min_notional=None, max_price=Price.from_str("1000000.00"), min_price=Price.from_str("0.05"), margin_init=Decimal("0.02"), margin_maint=Decimal("0.007"), maker_fee=Decimal("-0.00025"), taker_fee=Decimal("0.00075"), ts_event=0, ts_init=0, )
def test_apply_order_updated_event_to_stop_order(self): # Arrange order = self.order_factory.stop_market( 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)) order.apply(TestStubs.event_order_pending_update(order)) updated = OrderUpdated( self.account_id, order.client_order_id, VenueOrderId("1"), Quantity.from_int(120000), Price.from_str("1.00001"), None, 0, uuid4(), 0, ) # Act order.apply(updated) # Assert self.assertEqual(OrderState.ACCEPTED, order.state) self.assertEqual(VenueOrderId("1"), order.venue_order_id) self.assertEqual(Quantity.from_int(120000), order.quantity) self.assertEqual(Price.from_str("1.00001"), order.price) self.assertTrue(order.is_working) self.assertFalse(order.is_completed) self.assertEqual(5, order.event_count)
def xbtusd_bitmex() -> CryptoPerpetual: """ Return the BitMEX XBT/USD perpetual contract for backtesting. Returns ------- CryptoPerpetual """ return CryptoPerpetual( instrument_id=InstrumentId( symbol=Symbol("BTC/USD"), venue=Venue("BITMEX"), ), native_symbol=Symbol("XBTUSD"), base_currency=BTC, quote_currency=USD, settlement_currency=BTC, is_inverse=True, price_precision=1, size_precision=0, price_increment=Price.from_str("0.5"), size_increment=Quantity.from_int(1), max_quantity=None, min_quantity=None, max_notional=Money(10_000_000.00, USD), min_notional=Money(1.00, USD), max_price=Price.from_str("1000000.0"), min_price=Price(0.5, precision=1), margin_init=Decimal("0.01"), margin_maint=Decimal("0.0035"), maker_fee=Decimal("-0.00025"), taker_fee=Decimal("0.00075"), ts_event=0, ts_init=0, )
def ethusdt_perp_binance() -> CryptoPerpetual: """ Return the Binance ETHUSDT-PERP instrument for backtesting. Returns ------- CryptoPerpetual """ return CryptoPerpetual( instrument_id=InstrumentId( symbol=Symbol("ETHUSDT-PERP"), venue=Venue("BINANCE"), ), native_symbol=Symbol("ETHUSDT"), base_currency=ETH, quote_currency=USDT, settlement_currency=USDT, is_inverse=False, price_precision=2, size_precision=3, price_increment=Price.from_str("0.01"), size_increment=Quantity.from_str("0.001"), max_quantity=Quantity.from_str("10000.000"), min_quantity=Quantity.from_str("0.001"), max_notional=None, min_notional=Money(10.00, USDT), max_price=Price.from_str("152588.43"), min_price=Price.from_str("29.91"), margin_init=Decimal("1.00"), margin_maint=Decimal("0.35"), maker_fee=Decimal("0.0002"), taker_fee=Decimal("0.0004"), ts_event=1646199312128000000, ts_init=1646199342953849862, )
def trade_tick_5decimal( instrument_id=None, price=None, aggressor_side=None, quantity=None, ) -> TradeTick: return TradeTick( instrument_id=instrument_id or TestStubs.audusd_id(), price=price or Price.from_str("1.00001"), size=quantity or Quantity.from_int(100000), aggressor_side=aggressor_side or AggressorSide.BUY, trade_id="123456", ts_event=0, ts_init=0, )
def test_submit_bracket_order_when_instrument_not_in_cache_then_denies( self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bracket = strategy.order_factory.bracket_market( GBPUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), stop_loss=Price.from_str("1.00000"), take_profit=Price.from_str("1.00010"), ) submit_bracket = SubmitOrderList( self.trader_id, strategy.id, bracket, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_bracket) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine
def price_to_probability(price: str) -> Price: PyCondition.type(price, str, "price", "str") PyCondition.positive(float(price), "price") price = Price.from_str(price) if price in BETFAIR_PRICE_TO_PROBABILITY_MAP: return BETFAIR_PRICE_TO_PROBABILITY_MAP[price] else: # This is likely a trade tick that has been currency adjusted, simply return the nearest price value = Price.from_int(1) / price bid = BETFAIR_TICK_SCHEME.next_bid_price(value=value) ask = BETFAIR_TICK_SCHEME.next_ask_price(value=value) if abs(bid - value) < abs(ask - value): return bid else: return ask
def test_submit_bracket_with_default_settings_sends_to_client(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bracket = strategy.order_factory.bracket_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), stop_loss=Price.from_str("1.00000"), take_profit=Price.from_str("1.00010"), ) submit_bracket = SubmitOrderList( self.trader_id, strategy.id, bracket, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_bracket) # Assert assert self.exec_engine.command_count == 1 assert self.exec_client.calls == ["_start", "submit_order_list"]
def from_dict(values: Dict[str, Any]) -> "FTXTicker": """ Return an `FTX` ticker parsed from the given values. Parameters ---------- values : dict[str, Any] The values for initialization. Returns ------- FTXTicker """ return FTXTicker( instrument_id=InstrumentId.from_str(values["instrument_id"]), bid=Price.from_str(values["bid"]), ask=Price.from_str(values["ask"]), bid_size=Quantity.from_str(values["bid_size"]), ask_size=Quantity.from_str(values["ask_size"]), last=Price.from_str(values["last"]), ts_event=values["ts_event"], ts_init=values["ts_init"], )
def test_apply_order_triggered_event(self): # Arrange order = self.order_factory.stop_limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), Price.from_str("0.99990"), TimeInForce.GTD, expire_time=UNIX_EPOCH, ) order.apply(TestStubs.event_order_submitted(order)) order.apply(TestStubs.event_order_accepted(order)) # Act order.apply(TestStubs.event_order_triggered(order)) # Assert assert order.status == OrderStatus.TRIGGERED assert order.is_active assert not order.is_inflight assert order.is_working assert not order.is_completed
def test_calculate_unrealized_pnl_for_short(self): # Arrange order = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.SELL, Quantity.from_str("5.912000"), ) fill = TestStubs.event_order_filled( order, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("10505.60"), ) position = Position(instrument=BTCUSDT_BINANCE, fill=fill) pnl = position.unrealized_pnl(Price.from_str("10407.15")) # Assert self.assertEqual(Money(582.03640000, USDT), pnl) self.assertEqual(Money(-62.10910720, USDT), position.realized_pnl) self.assertEqual([Money(62.10910720, USDT)], position.commissions())
def test_handle_quote_tick_when_count_below_threshold_updates(self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_spec = BarSpecification(3, BarAggregation.TICK, PriceType.MID) bar_type = BarType(instrument_id, bar_spec) aggregator = TickBarAggregator(bar_type, handler, Logger(TestClock())) tick1 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00001"), ask=Price.from_str("1.00004"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), timestamp_origin_ns=0, timestamp_ns=0, ) # Act aggregator.handle_quote_tick(tick1) # Assert self.assertEqual(0, len(bar_store.get_store()))
def test_calculate_margin_maint_with_no_leverage(self): # Arrange account = TestExecStubs.margin_account() instrument = TestInstrumentProvider.xbtusd_bitmex() # Act result = account.calculate_margin_maint( instrument=instrument, side=PositionSide.LONG, quantity=Quantity.from_int(100000), avg_open_px=Price.from_str("11493.60"), ) # Assert assert result == Money(0.03697710, BTC)
def test_submit_order_when_invalid_trigger_price_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.stop_limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), Price.from_str("0.999999999999999"), # <- invalid trigger ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine
def test_process_trade_tick_when_subscribers_then_sends_to_registered_handlers( self, ): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler1 = [] subscribe1 = Subscribe( client_id=ClientId(BINANCE.value), data_type=DataType( TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler1.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) handler2 = [] subscribe2 = Subscribe( client_id=ClientId(BINANCE.value), data_type=DataType( TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler2.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) tick = TradeTick( ETHUSDT_BINANCE.id, Price.from_str("1050.00000"), Quantity.from_int(100), AggressorSide.BUY, TradeMatchId("123456789"), 0, 0, ) # Act self.data_engine.process(tick) # Assert self.assertEqual([tick], handler1) self.assertEqual([tick], handler2)
async def test_trailing_stop_market_order(self, mocker): # Arrange mock_send_request = mocker.patch( target= "nautilus_trader.adapters.binance.http.client.BinanceHttpClient.send_request" ) order = self.strategy.order_factory.trailing_stop_market( instrument_id=ETHUSDT_PERP_BINANCE.id, order_side=OrderSide.SELL, quantity=Quantity.from_int(10), trailing_offset=Decimal(100), offset_type=TrailingOffsetType.BASIS_POINTS, trigger_price=Price.from_str("10000.00"), trigger_type=TriggerType.MARK, reduce_only=True, ) self.cache.add_order(order, None) submit_order = SubmitOrder( trader_id=self.trader_id, strategy_id=self.strategy.id, position_id=None, order=order, command_id=UUID4(), ts_init=0, ) # Act self.exec_client.submit_order(submit_order) await asyncio.sleep(0.3) # Assert request = mock_send_request.call_args[0] assert request[0] == "POST" assert request[1] == "/fapi/v1/order" assert request[2]["symbol"] == "ETHUSDT" # -PERP was stripped assert request[2]["side"] == "SELL" assert request[2]["type"] == "TRAILING_STOP_MARKET" assert request[2]["timeInForce"] == "GTC" assert request[2]["quantity"] == "10" assert request[2]["reduceOnly"] == "true" assert request[2]["newClientOrderId"] is not None assert request[2]["activationPrice"] == "10000.00" assert request[2]["callbackRate"] == "1" assert request[2]["workingType"] == "MARK_PRICE" assert request[2]["recvWindow"] == "5000" assert request[2]["signature"] is not None
def test_calculate_pnls_for_single_currency_cash_account(self): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, base_currency=USD, reported=True, balances=[ AccountBalance( Money(1_000_000.00, USD), Money(0.00, USD), Money(1_000_000.00, USD), ), ], margins=[], info={}, # No default currency set event_id=UUID4(), ts_event=0, ts_init=0, ) account = CashAccount(event) order = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(1_000_000), ) fill = TestEventStubs.order_filled( order, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("0.80000"), ) position = Position(AUDUSD_SIM, fill) # Act result = account.calculate_pnls( instrument=AUDUSD_SIM, position=position, fill=fill, ) # Assert assert result == [Money(-800016.00, USD)]
def test_hash_str_and_repr(self): # Arrange tick = TradeTick( instrument_id=AUDUSD_SIM.id, price=Price.from_str("1.00000"), size=Quantity.from_int(50000), aggressor_side=AggressorSide.BUY, trade_id=TradeId("123456789"), ts_event=0, ts_init=0, ) # Act, Assert assert isinstance(hash(tick), int) assert str(tick) == "AUD/USD.SIM,1.00000,50000,BUY,123456789,0" assert repr(tick) == "TradeTick(AUD/USD.SIM,1.00000,50000,BUY,123456789,0)"
def test_calculate_margin_maint_with_leverage_inverse_instrument(self): # Arrange account = TestExecStubs.margin_account() instrument = TestInstrumentProvider.xbtusd_bitmex() account.set_default_leverage(Decimal(10)) # Act result = account.calculate_margin_maint( instrument=instrument, side=PositionSide.LONG, quantity=Quantity.from_int(100000), avg_open_px=Price.from_str("100000.00"), ) # Assert assert result == Money(0.00042500, BTC)
def test_calculate_margin_maint_with_leverage_fx_instrument(self): # Arrange account = TestExecStubs.margin_account() instrument = TestInstrumentProvider.default_fx_ccy("AUD/USD") account.set_default_leverage(Decimal(50)) # Act result = account.calculate_margin_maint( instrument=instrument, side=PositionSide.LONG, quantity=Quantity.from_int(1_000_000), avg_open_px=Price.from_str("1.00000"), ) # Assert assert result == Money(600.40, USD)
def test_hash_str_and_repr(self): # Arrange tick = TradeTick( AUDUSD_SIM.id, Price.from_str("1.00000"), Quantity.from_int(50000), AggressorSide.BUY, "123456789", 0, 0, ) # Act, Assert assert isinstance(hash(tick), int) assert str(tick) == "AUD/USD.SIM,1.00000,50000,BUY,123456789,0" assert repr(tick) == "TradeTick(AUD/USD.SIM,1.00000,50000,BUY,123456789,0)"
def test_load_order_when_stop_market_order_in_database_returns_order(self): # Arrange order = self.strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) self.database.add_order(order) # Act result = self.database.load_order(order.client_order_id) # Assert assert result == order
def es_future(): return Future( instrument_id=InstrumentId(symbol=Symbol("ESZ21"), venue=Venue("CME")), local_symbol=Symbol("ESZ21"), asset_class=AssetClass.INDEX, currency=USD, price_precision=2, price_increment=Price.from_str("0.01"), multiplier=Quantity.from_int(1), lot_size=Quantity.from_int(1), underlying="ES", expiry_date=datetime.date(2021, 12, 17), ts_event=0, ts_init=0, )
def test_update_order_for_submitted_order(self): # Arrange order = self.strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) position_id = PositionId("P-1") self.cache.add_order(order, position_id) order.apply(TestEventStubs.order_submitted(order)) # Act self.cache.update_order(order) # Assert assert self.cache.order_exists(order.client_order_id) assert order.client_order_id in self.cache.client_order_ids() assert order in self.cache.orders() assert order in self.cache.orders_inflight() assert order in self.cache.orders_inflight( instrument_id=order.instrument_id) assert order in self.cache.orders_inflight( strategy_id=self.strategy.id) assert order in self.cache.orders_inflight( instrument_id=order.instrument_id, strategy_id=self.strategy.id) assert order not in self.cache.orders_open() assert order not in self.cache.orders_open( instrument_id=order.instrument_id) assert order not in self.cache.orders_open( strategy_id=self.strategy.id) assert order not in self.cache.orders_open( instrument_id=order.instrument_id, strategy_id=self.strategy.id) assert order not in self.cache.orders_closed() assert order not in self.cache.orders_closed( instrument_id=order.instrument_id) assert order not in self.cache.orders_closed( strategy_id=self.strategy.id) assert order not in self.cache.orders_closed( instrument_id=order.instrument_id, strategy_id=self.strategy.id) assert self.cache.orders_open_count() == 0 assert self.cache.orders_closed_count() == 0 assert self.cache.orders_inflight_count() == 1 assert self.cache.orders_total_count() == 1
def test_update_position_for_open_position(self): # Arrange order1 = self.strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) position_id = PositionId("P-1") self.cache.add_order(order1, position_id) order1.apply(TestStubs.event_order_submitted(order1)) self.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.cache.update_order(order1) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-1"), last_px=Price.from_str("1.00001"), ) position = Position(instrument=AUDUSD_SIM, fill=fill1) # Act self.cache.add_position(position, OMSType.HEDGING) # Assert assert self.cache.position_exists(position.id) assert position.id in self.cache.position_ids() assert position in self.cache.positions() assert position in self.cache.positions_open() assert position in self.cache.positions_open(instrument_id=position.instrument_id) assert position in self.cache.positions_open(strategy_id=self.strategy.id) assert position in self.cache.positions_open( instrument_id=position.instrument_id, strategy_id=self.strategy.id ) assert position not in self.cache.positions_closed() assert position not in self.cache.positions_closed(instrument_id=position.instrument_id) assert position not in self.cache.positions_closed(strategy_id=self.strategy.id) assert position not in self.cache.positions_closed( instrument_id=position.instrument_id, strategy_id=self.strategy.id ) assert self.cache.position(position_id) == position assert self.cache.positions_open_count() == 1 assert self.cache.positions_closed_count() == 0 assert self.cache.positions_total_count() == 1
def make_order(engine: MockLiveExecutionEngine) -> LimitOrder: strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER-000"), BetfairTestStubs.clock(), BetfairTestStubs.logger(), ) engine.register_strategy(strategy) order = strategy.order_factory.limit( BetfairTestStubs.instrument_id(), OrderSide.BUY, Quantity.from_int(10), Price.from_str("0.50"), ) return order
def test_duplicate_trades(betfair_data_client): messages = [] for update in BetfairTestStubs.raw_market_updates(market="1.180305278", runner1="2696769", runner2="4297085"): messages.extend( on_market_update( instrument_provider=betfair_data_client.instrument_provider(), update=update, )) if update["pt"] >= 1615222877785: break trades = [ m for m in messages if isinstance(m, TradeTick) and m.price == Price.from_str("0.69930") ] assert len(trades) == 5
def test_limit_order_to_dict(self): # Arrange order = self.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), display_qty=Quantity.from_int(20000), ) # Act result = order.to_dict() # Assert assert result == { "trader_id": "TESTER-000", "strategy_id": "S-001", "instrument_id": "AUD/USD.SIM", "client_order_id": "O-19700101-000000-000-001-1", "venue_order_id": None, "position_id": None, "account_id": None, "execution_id": None, "type": "LIMIT", "side": "BUY", "quantity": "100000", "price": "1.00000", "liquidity_side": "NONE", "expire_time_ns": 0, "time_in_force": "GTC", "filled_qty": "0", "avg_px": None, "slippage": "0", "status": "INITIALIZED", "is_post_only": False, "is_reduce_only": False, "display_qty": "20000", "order_list_id": None, "parent_order_id": None, "child_order_ids": None, "contingency": "NONE", "contingency_ids": None, "tags": None, "ts_last": 0, "ts_init": 0, }