nautilus_trader icon indicating copy to clipboard operation
nautilus_trader copied to clipboard

Order throttler is too aggressive: it drops messages after slowing down sending messages

Open davidsblom opened this issue 11 months ago • 1 comments

Bug Report

The order throttle mechanism drops messages if they exceed a predetermined threshold as expected. When the message rate becomes too high, it starts to drop messages. When the order rate goes down, the throttler keeps dropping orders.

Expected Behavior

The throttler should not drop messages if nothing has happened for x seconds.

Actual Behavior

Steps to Reproduce the Problem

from decimal import Decimal

from nautilus_trader.backtest.engine import BacktestEngine, BacktestEngineConfig
from nautilus_trader.common.component import init_logging
from nautilus_trader.common.enums import LogLevel
from nautilus_trader.model.currencies import BTC, USDT
from nautilus_trader.model.data import QuoteTick
from nautilus_trader.model.enums import (
    AccountType,
    OmsType,
    OrderSide,
    TimeInForce,
)
from nautilus_trader.model.identifiers import InstrumentId, Symbol, TraderId, Venue
from nautilus_trader.model.instruments import CurrencyPair
from nautilus_trader.model.objects import Money, Price, Quantity
from nautilus_trader.trading.strategy import Strategy


class TestStrategy(Strategy):

    def __init__(self, instrument):
        super().__init__()
        self.instrument = instrument

    def on_start(self) -> None:
        self.subscribe_quote_ticks(instrument_id=self.instrument.id)

    def on_stop(self):
        self.cancel_all_orders(self.instrument.id)
        self.close_all_positions(self.instrument.id)
        self.unsubscribe_quote_ticks(self.instrument.id)

    def on_quote_tick(self, tick):
        self.cancel_all_orders(self.instrument.id)
        order = self.order_factory.limit(
            instrument_id=self.instrument.id,
            order_side=OrderSide.BUY,
            price=self.instrument.make_price(1000),
            quantity=self.instrument.make_qty(1),
            post_only=True,
            time_in_force=TimeInForce.GTC,
        )
        self.submit_order(order)


def main():
    """Run the test."""
    init_logging(level_stdout=LogLevel.DEBUG, level_file=LogLevel.DEBUG)

    instrument = CurrencyPair(
        instrument_id=InstrumentId(
            symbol=Symbol("BTCUSDT"),
            venue=Venue("BINANCE"),
        ),
        raw_symbol=Symbol("BTCUSDT"),
        base_currency=BTC,
        quote_currency=USDT,
        price_precision=2,
        size_precision=6,
        price_increment=Price(1e-02, precision=2),
        size_increment=Quantity(1e-06, precision=6),
        lot_size=None,
        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(1),
        margin_maint=Decimal(1),
        maker_fee=Decimal("0.00075"),
        taker_fee=Decimal("0.00075"),
        ts_event=0,
        ts_init=0,
    )

    config = BacktestEngineConfig(trader_id=TraderId("BACKTESTER-001"))
    engine = BacktestEngine(config=config)

    engine.add_venue(
        venue=Venue("BINANCE"),
        oms_type=OmsType.NETTING,
        account_type=AccountType.MARGIN,  # Spot CASH account (not for perpetuals or futures)
        base_currency=None,  # Multi-currency account
        starting_balances=[Money(1_000_000, USDT)],
    )

    # Add instrument(s)
    engine.add_instrument(instrument)

    # Add data
    quote_ticks = []
    for i in range(105):
        quote_tick = QuoteTick(
            instrument_id=instrument.id,
            bid_price=instrument.make_price(1000),
            ask_price=instrument.make_price(1001),
            bid_size=instrument.make_qty(1),
            ask_size=instrument.make_qty(1),
            ts_event=1695604690000000312 + i * 1_000_000,
            ts_init=1695604690000000312 + i * 1_000_000,
        )
        quote_ticks.append(quote_tick)
    for i in range(105):
        quote_tick = QuoteTick(
            instrument_id=instrument.id,
            bid_price=instrument.make_price(1000),
            ask_price=instrument.make_price(1001),
            bid_size=instrument.make_qty(1),
            ask_size=instrument.make_qty(1),
            ts_event=1695604690000000312 + 5604690000000312 + i * 1_000_000,
            ts_init=1695604690000000312 + 5604690000000312 + i * 1_000_000,
        )
        quote_ticks.append(quote_tick)
    for i in range(20):
        quote_tick = QuoteTick(
            instrument_id=instrument.id,
            bid_price=instrument.make_price(1000),
            ask_price=instrument.make_price(1001),
            bid_size=instrument.make_qty(1),
            ask_size=instrument.make_qty(1),
            ts_event=1695604690000000312 + 2 * 5604690000000312 + i * 1_000_000,
            ts_init=1695604690000000312 + 2 * 5604690000000312 + i * 1_000_000,
        )
        quote_ticks.append(quote_tick)
    engine.add_data(quote_ticks)

    strategy = TestStrategy(instrument=instrument)
    engine.add_strategy(strategy=strategy)

    engine.run()


if __name__ == "__main__":
    main()

Specifications

  • OS platform: OS X
  • Python version: 3.10.12
  • nautilus_trader version: 1.188.0

davidsblom avatar Mar 04 '24 11:03 davidsblom

Closing issue since it is fixed in develop.

davidsblom avatar Mar 07 '24 07:03 davidsblom