nautilus_trader icon indicating copy to clipboard operation
nautilus_trader copied to clipboard

Introduce fixed margin model to simulate a futures exchange

Open stefansimik opened this issue 10 months ago • 3 comments

Issue Description

The current margin calculation implementation incorrectly reduces margin requirements based on leverage, which doesn't match real-world broker behavior.

Current Implementation

In margin.pyx#L646, margin amounts are divided by leverage:

adjusted_notional = notional / leverage        # division by leverage is problem
margin = adjusted_notional * instrument.margin_init

Real-World Behavior

For example Interactive Brokers require fixed margin amounts regardless of leverage:

  • CME 6E Futures via Interactive Brokers: $3,000 per contract

These margin requirements:

  • Are fixed per contract (meaning fixed percentage of notional value)
  • Don't change based on account leverage
  • Scale linearly with position size (2 contracts = 2× margin)

Calculation of margin vs. leverage

Let's take 6E (EUR/USD futures) as an example:

  • Contract value: €125,000
  • Required margin: $3,000
  • EUR/USD rate: 1.10
  • Effective leverage: (€125,000 × 1.10 eurusd rate) / $3,000 ≈ 46:1

This means with just a $4,000 account, we can control a €125,000 contract, as long as we maintain the fixed $3,000 margin requirement. The leverage is a result of the relationship between contract value and margin requirement, but it doesn't affect the margin amount itself.

Proposed Change

Margin calculations should ignore leverage and use only the fixed margin rates specified by instrument.margin_init and instrument.margin_maint. Like this

margin = notional * instrument.margin_init   # see there is no leverage in calculation

The same is valid for calculation of both: margin_init and margin_maint

Questions

  1. Is there a specific reason why margins are currently divided by leverage?
  2. Should we update the implementation to match standard broker behavior?
  3. Do cryptocurrency exchanges handle margin/leverage differently? Input from those with crypto exchange experience would be valuable to ensure our solution works across all venue types.

References

stefansimik avatar Feb 02 '25 16:02 stefansimik

Adding MRE - Minimalistic reproducible example (self-contained - contains few artificial bars).

run_backtest.py.zip

How to reproduce:

  1. Just run file run_backtest.py
  2. Put debug breakpoint at line: 95 to see incorrectly value of margin_maint

Another clear sign this is real issue:

Current implementation of Nautilus with $1,000 balance and 10x leverage allows trading a full 6E contract because it incorrectly divides margin by leverage:

  • Required margin: $3,000 ÷ 10 (leverage) = $300 (Nautilus calculates $300, realistically it is full $3000)
  • Strategy sees $1,000 > $300 --> and allows trade

This is wrong - broker requires full $3,000 margin regardless of leverage. With $1,000 balance, it should fail immediately and do not allow to open 1 cts position with 6E contract

Note: This is deliberately also demonstrated in the MRE above, which is set to:

  • account balance: $1,000
  • leverage: 10

stefansimik avatar Feb 02 '25 17:02 stefansimik

Hi @stefansimik

Thanks for the detailed explanation.

This issue is similar to our fee model situation, where we introduced a FixedFee model specifically for futures. Currently, the margin calculation assumes a model where margin scales inversely with leverage—i.e., higher leverage means a lower collateral requirement—similar to many crypto venues.

I think the best solution is to offer multiple margin models, much like we handle fees?

In the meantime, are you able to set margin_init and margin_maint to values that align with your actual expected margin requirements?

cjdsellers avatar Feb 02 '25 21:02 cjdsellers

Thank you Chris, in the meantime this in not blocker for my backtesting.

I can fabricate artificial values (despite clearly incorrect), that are not blocking me right now:

  • leverage=1
  • account balance = 100_000 (set to huge value)

This settings compensates for current inability to setup custom Margin calculation model.


In the future, we should how make calculation of margins more flexible - initial idea could be to extract calculation of initial + maintenance margin into model like this - example:

class MarginModel():
   def calculate_margin_maint(
        self,
        Instrument instrument,
        PositionSide side,
        Quantity quantity,
        Price price,
        bint use_quote_for_inverse=False,
    ) -> Money:
       # code here

   def calculate_margin_init(
        self,
        Instrument instrument,
        PositionSide side,
        Quantity quantity,
        Price price,
        bint use_quote_for_inverse=False,
    ) -> Money:
       # code here

stefansimik avatar Feb 02 '25 22:02 stefansimik