nautilus_trader icon indicating copy to clipboard operation
nautilus_trader copied to clipboard

Incorrect calculations for margin accounts

Open cjdsellers opened this issue 8 months ago • 9 comments

The platform currently provides three different account types:

  • Cash accounts
  • Margin accounts
  • Betting accounts

This issue flags that margin account calculations for the platform are incorrect and can't be relied on for backtesting, or when "calculated accounts" are used in live trading (account state updates based on arriving events).

Current investigation via several LLMs reveals no specific bugs, assuming we apply fees twice for initial margin (to account for the round trip of a potential trade) and once for maintenance margin (the required collateral needed to maintain the position until exited, where fees will be charged once).

The implementation locations are as follows:

  • https://github.com/nautechsystems/nautilus_trader/blob/develop/nautilus_trader/accounting/accounts/margin.pyx#L547
  • https://github.com/nautechsystems/nautilus_trader/blob/develop/nautilus_trader/accounting/accounts/margin.pyx#L600
  • https://github.com/nautechsystems/nautilus_trader/blob/develop/crates/model/src/accounts/margin.rs

The current calculations might be simplistic/incorrect and do not account for the differences between isolated and cross-margin, or every fee model -- along with other potential issues.

Expected behavior

Margin calculations should be correct for the margin styles which the platform supports, including:

  • Isolated margin
  • Cross-asset margin

Along with all fee models:

  • Maker-taker fee model
  • Fixed-fee model
  • Per-contract fee model

Next steps

It would be ideal to source some canonical examples of margin calculations for all of the above, to be used as a basis for unit testing the current margin calculations for all styles and asset classes. For example:

OKX: Introduction to options margin calculation.

It would also be helpful if users provide feedback through this issue on instances of incorrect margin calculations:

  • The result produced by the platform
  • The expected margin based on a simple formula

These will then be used to fix the current implementation and write regression tests so that margins stay correct based on the canonical examples (which can be linked from the tests).

cjdsellers avatar Apr 18 '25 02:04 cjdsellers

Currency / Index / Commodity Futures

The current margin calculation implementation: (adjusted_notional = notional / leverage) is overly simplistic and doesn't match how margins work for currency futures in real markets.

💹 Realistic futures margin model

Most brokers and exchanges providing futures use fixed margin requirements per contract rather than simple leverage ratios:

  • Initial margin: Fixed amount required to open a position (e.g., $2,000 per contract)
  • Maintenance margin: Lower fixed amount required to maintain the position (e.g., $1,600 per contract)
  • Intraday vs. overnight margins: Intraday margins are mostly lower, overnight margins are higher

Link to example: [InteractiveBrokers margins](https://www.interactivebrokers.com/en/trading/margin-futures-fops.php)

🧮 Example calculation

For 2x EUR/USD futures contracts:

Overnight position:

  • Initial margin = $2,000 × 2 contracts = $4,000
  • Maintenance margin = $1,600 × 2 contracts = $3,200

Intraday position:

  • Initial margin = $1,600 × 2 contracts = $3,200
  • Maintenance margin = $1,280 × 2 contracts = $2,560

🏗️ Architecture proposal

Priority 1: Create pluggable architecture First, we need to develop a flexible architecture that allows:

  • Customizable margin calculations via some sort of stable interface
  • Access to trader context when needed (portfolio, positions, time, etc.) - to allow for custom calculations based on more complex rules, that are quite common in real world:
    • Margins go higher during stress periods (important / planned news)
    • Market volatility adjustments
    • Exchange requirements
    • Portfolio-level considerations
    • Custom Broker Rules

Priority 2: Implement basic implementations Using the pluggable architecture, we'll provide:

  • A simple fixed margin implementation
  • (optional) Support for different intraday vs. overnight values

✅ Implementation Checklist

Before finalizing the implementation, verify that the architecture satisfies these requirements:

  • Can one create custom calculation of initial / maintenance margin (for any instrument)?
  • Can one calculate realistic intraday / overnight margins? (of course, perspective trader needs to add some logic about time/timezones to differentiate, what is intraday / overnight for various instruments. But architecture has to support such a custom calculation and have some way how trader gets contextual information like real clock time, instrument, etc.)
  • Can one potentially increase margin during high volatility events? (of course, perspective trader needs to add some logic about when these events happen...)

stefansimik avatar Apr 18 '25 06:04 stefansimik

Completed some further ground work by simplifying the current margin calculations (commit e2f242a995eacf838750adc764c7bb4674277ab7).

This should now align with what most users would expect of cryptocurrency exchanges for cash or isolated margin accounts.

Next step will be to design/define the interface for the MarginModel and implement.

cjdsellers avatar Apr 21 '25 07:04 cjdsellers

Now that this is objectively correct for at least one style of margin calculation (it now matches Binance), and given this may take several weeks to wrap up -- would it be more accurate to convert this into an enhancement ticket, e.g., "Implement pluggable MarginModels"?

[edit: the initial margin might not exactly match Binance, but it's much closer]

cjdsellers avatar Apr 21 '25 07:04 cjdsellers

I would agree, it could be clearer if we split it into two separate parts:

  • this old one addressing issue in old way of margin calculations (that was fixed)
  • and new dealing with the pluggable MarginModels (+ we should copy the useful related info / requirements related to the new issue).

stefansimik avatar Apr 21 '25 09:04 stefansimik

In addition to how the margin is calculated, what behaviour should we model when margin limits are exceeded? Currently the backtest throws a AccountMarginExceeded event and the backtest stops (!) when a fill pushes free margin < 0. This is not how trading on exchanges work. In this scenario, I would expect the order to be rejected (or partially filled up to the free margin limit), and a warning generated (for the logs and later analysis) and trading (backtest) to continue. I think the condition for an account stop out is when the unrealised pnl + account balance < 0. In this scenario I would expect the exchange to liquidate open positions, zeroing out the account, at which point the backtest should stop as there is no longer any account capital to trade. Margin calculations impact when this condition should occur. Maintaining margin requirements against open positions is more complex, because the rules around margin requirements vary a lot across asset classes and brokers. However, I would expect the that the backtest engine simulates the standard vendor behaviour when margin requirements are exceeded - liquidate open positions.

Probably need a new issue to track any changes for account / margin event behaviour.

sdk451 avatar Apr 23 '25 23:04 sdk451

Hi @sdk451

I agree that margin exceedance and auto-deleveraging simulation could be more accurately handled.

The platforms current approach is to just stop the backtest, simply because if you're reaching these margin limits - the strategy probably isn't optimal and parameters/logic likely need adjusting (it's still valuable to simulate what would happen in these scenarios though, to know the strategy/system can handle this).

This could be a natural enhancement after the new margin model architecture is introduced and all margin calculations are correct - in a separate enhancement ticket as you suggest.

cjdsellers avatar Apr 24 '25 01:04 cjdsellers

My suggestion: What should really happen on margin exceeeded event:

  1. This event should be simply implemented in on_margin_exceeded callback / handler. Probably in the Venue as exchange rules decide, what really happens in this situation.
  2. We should have default and reasonable implementation, that could be easily overriden and block / allow / reduce positions based on custom user requirements, like was mentioned in the first message in this thread.

stefansimik avatar Apr 24 '25 02:04 stefansimik

My suggestion: What should really happen on margin exceeeded event:

1. This event should be simply implemented in `on_margin_exceeded` callback / handler. Probably in the `Venue` as exchange rules decide, what really happens in this situation.

2. We should have default and reasonable implementation, that could be easily overriden and block / allow / reduce positions based on custom user requirements, like was mentioned in the first message in this thread.

Agreed, and with this approach we could achieve a high degree of simulation accuracy across venues - and there would be this hook point for users to implement their own behaviors for venues which aren't (yet) officially supported.

cjdsellers avatar Apr 24 '25 03:04 cjdsellers

A few links for reference

  • https://www.cmegroup.com/solutions/risk-management/performance-bonds-margins/span-methodology-overview.html
  • https://www.reddit.com/r/PMTraders/s/RdOV5y96BI
  • https://www.quantconnect.com/docs/v2/writing-algorithms/reality-modeling/buying-power

faysou avatar Apr 24 '25 06:04 faysou

@faysou has now introduced the concept, base models, config, and factory for this from #2794 great work! 👏

If someone is able to verify the StandardMarginModel is now correct per @stefansimik's example then we can close this bug ticket and raise more specific tickets when necessary to continue extending and documenting the feature. As well as adding some more detailed tutorials.

cjdsellers avatar Jul 16 '25 22:07 cjdsellers