Introduce fixed margin model to simulate a futures exchange
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
- Is there a specific reason why margins are currently divided by leverage?
- Should we update the implementation to match standard broker behavior?
- 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
Adding MRE - Minimalistic reproducible example (self-contained - contains few artificial bars).
How to reproduce:
- Just run file
run_backtest.py - Put debug breakpoint at
line: 95to see incorrectly value ofmargin_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
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?
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=1account 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