nautechsystems / nautilus_trader

A high-performance algorithmic trading platform and event-driven backtester
https://nautilustrader.io
GNU Lesser General Public License v3.0
2.18k stars 498 forks source link

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

Closed davidsblom closed 8 months ago

davidsblom commented 8 months ago

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

davidsblom commented 8 months ago

TRADER-000_2024-03-04_6a9e2929-a387-4605-87a9-e94ab59745c9.log

davidsblom commented 8 months ago

Closing issue since it is fixed in develop.