Closed
Description
Confirmation
Before opening a bug report, please confirm:
- I’ve re-read the relevant sections of the documentation.
- I’ve searched existing issues and discussions to avoid duplicates.
- I’ve reviewed or skimmed the source code (or examples) to confirm the behavior is not by design.
- I’ve confirmed the issue is reproducible with the latest development version of
nautilus_trader
(it may have been fixed already).
Expected Behavior
The final account balance should be 1000245 USDT in the example below.
Actual Behavior
The final account balance is 999535 USDT in the example below.
Steps to Reproduce the Problem
Run the code below on commit 9b5cf5d (Mon May 12 15:06:07 2025 +0530) and get the final account balance 999535 USDT.
Run the code below on commit c882a9f (Tue Mar 11 10:34:31 2025 +1100) and get the final account balance 1000245 USDT.
Code Snippets or Logs
from datetime import UTC
from datetime import datetime
from decimal import Decimal
import pandas as pd
from fsspec.implementations.local import LocalFileSystem
from nautilus_trader.accounting.accounts.margin import MarginAccount
from nautilus_trader.backtest.engine import BacktestEngine
from nautilus_trader.backtest.engine import BacktestEngineConfig
from nautilus_trader.config import LoggingConfig
from nautilus_trader.model import Bar
from nautilus_trader.model import InstrumentId
from nautilus_trader.model import Price
from nautilus_trader.model import Quantity
from nautilus_trader.model.currencies import BTC
from nautilus_trader.model.currencies import USDT
from nautilus_trader.model.data import BarType
from nautilus_trader.model.enums import AccountType
from nautilus_trader.model.enums import OmsType
from nautilus_trader.model.enums import OrderSide
from nautilus_trader.model.events import PositionClosed
from nautilus_trader.model.events import PositionEvent
from nautilus_trader.model.events import PositionOpened
from nautilus_trader.model.identifiers import TraderId
from nautilus_trader.model.identifiers import Venue
from nautilus_trader.model.instruments import CryptoPerpetual
from nautilus_trader.model.instruments import Instrument
from nautilus_trader.model.objects import Money
from nautilus_trader.persistence.wranglers import QuoteTickDataWrangler
from nautilus_trader.persistence.wranglers import TradeTickDataWrangler
from nautilus_trader.test_kit.providers import TestDataProvider
from nautilus_trader.trading import Strategy
from nautilus_trader.trading.config import StrategyConfig
class StratTestConfig(StrategyConfig):
instrument: Instrument
bar_type: BarType
class StratTest(Strategy):
def __init__(self, config: StratTestConfig | None = None) -> None:
super().__init__(config)
self._account: MarginAccount = None
self._bar_count = 0
def on_start(self) -> None:
self._account: MarginAccount = self.cache.accounts()[0]
self.subscribe_bars(self.config.bar_type)
def on_stop(self):
self.unsubscribe_bars(self.config.bar_type)
def on_bar(self, bar: Bar) -> None:
if self._bar_count == 0:
self.submit_order(
self.order_factory.market(
instrument_id=self.config.instrument.id,
order_side=OrderSide.BUY,
quantity=self.config.instrument.make_qty(10),
),
)
elif self._bar_count == 10:
self.submit_order(
self.order_factory.market(
instrument_id=self.config.instrument.id,
order_side=OrderSide.SELL,
quantity=self.config.instrument.make_qty(10),
),
)
self._bar_count += 1
def on_position_event(self, event: PositionEvent):
super().on_position_event(event)
if isinstance(event, PositionOpened):
self.log.warning("> position opened")
elif isinstance(event, PositionClosed):
self.log.warning("> position closed")
else:
self.log.warning("> position changed")
self.log.warning(
f"> account balance: total {round(self.total_balance(), 2)}"
)
def total_balance(self) -> Decimal:
return self._account.balance(USDT).total.as_decimal()
def main():
config = BacktestEngineConfig(
trader_id=TraderId("BACKTESTER-001"),
logging=LoggingConfig(
log_level="INFO",
log_colors=True,
use_pyo3=False,
),
)
engine = BacktestEngine(config=config)
binance = Venue("BINANCE")
engine.add_venue(
venue=binance,
oms_type=OmsType.NETTING,
account_type=AccountType.MARGIN,
base_currency=USDT,
starting_balances=[Money(1000000.0, USDT)],
)
instrument_id = InstrumentId.from_str("BTCUSDT-PERP.BINANCE")
instrument = CryptoPerpetual(
instrument_id=instrument_id,
raw_symbol=instrument_id.symbol,
base_currency=BTC,
quote_currency=USDT,
settlement_currency=USDT,
is_inverse=False,
price_precision=2,
size_precision=3,
price_increment=Price(0.10, 2),
size_increment=Quantity(0.001, 3),
ts_event=datetime.now(UTC).timestamp() * 10**9,
ts_init=datetime.now(UTC).timestamp() * 10**9,
margin_init=Decimal('0.0500'),
margin_maint=Decimal('0.0250'),
maker_fee=Decimal('0.000200'),
taker_fee=Decimal('0.000500'),
)
engine.add_instrument(instrument)
data_provider = TestDataProvider()
data_provider.fs = LocalFileSystem()
bars = data_provider.read_csv_bars("btc-perp-20211231-20220201_1m.csv")
quote_tick_wrangler = QuoteTickDataWrangler(instrument=instrument)
ticks = quote_tick_wrangler.process_bar_data(
bid_data=bars,
ask_data=bars,
)
engine.add_data(ticks[:60])
trade_tick_wrangler = TradeTickDataWrangler(instrument=instrument)
ticks = trade_tick_wrangler.process_bar_data(data=bars)
engine.add_data(ticks[:60])
strategy = StratTest(
StratTestConfig(
instrument=instrument,
bar_type=BarType.from_str("BTCUSDT-PERP.BINANCE-1-MINUTE-BID-INTERNAL")
)
)
engine.add_strategy(strategy=strategy)
engine.run()
with pd.option_context(
"display.max_rows",
100,
"display.max_columns",
None,
"display.width",
300,
):
print(engine.trader.generate_account_report(binance))
print(engine.trader.generate_positions_report())
engine.reset()
engine.dispose()
if __name__ == "__main__":
main()
Specifications
- OS platform: Debian
- Python version: 3.12
nautilus_trader
version: develop branch
Metadata
Metadata
Assignees
Labels
Type
Projects
Status
Done