Rajandran R Creator of OpenAlgo - OpenSource Algo Trading framework for Indian Traders. Building GenAI Applications. Telecom Engineer turned Full-time Derivative Trader. Mostly Trading Nifty, Banknifty, High Liquid Stock Derivatives. Trading the Markets Since 2006 onwards. Using Market Profile and Orderflow for more than a decade. Designed and published 100+ open source trading systems on various trading tools. Strongly believe that market understanding and robust trading frameworks are the key to the trading success. Building Algo Platforms, Writing about Markets, Trading System Design, Market Sentiment, Trading Softwares & Trading Nuances since 2007 onwards. Author of Marketcalls.in

Python Tutorial on Span Margin Calculator using Marginism

17 min read

If you trade options full time, margin is not a detail. It is the constraint that decides how many lots you can carry, whether an adjustment is affordable, and how much of your capital an iron condor actually locks up until expiry. Most of us check this on a broker’s margin calculator, one basket at a time, in a browser tab.

Marginism is a Python library that removes that dependency completely. You give it the exchange’s daily SPAN risk parameter file and it computes SPAN margin, exposure margin, total margin and the hedging benefit on your own machine. No login, no API key, no rate limits, no network call at all. It is pure standard library Python, so it drops into a backtest, a position sizing script or a risk dashboard without dragging in dependencies.

In this tutorial we will install Marginism, load the NSCCL SPAN file for 18-Sep-2026, and margin a full book of real strategies on the 29SEP26 monthly and 22SEP26 weekly contracts: naked shorts, credit and debit spreads, iron condors, butterflies, calendar spreads and hedged futures on NIFTY, BANKNIFTY, RELIANCE, SBIN and INFY. At the end we will validate every number against a live broker calculator.

Why SPAN margin is arithmetic, not option pricing

A lot of traders assume a margin calculator has to price options. It does not. The exchange does the hard work for you, once a day, and ships the answer inside the SPAN file.

Every contract in a .spn file carries a precomputed 16 scenario risk array: the profit or loss of one unit of that contract under 16 combinations of underlying move and volatility move. The underlying is shocked by one third, two thirds and the full price scan range in both directions, volatility is moved up and down at each step, and two extreme moves are added on top.

Once you have those arrays, margining a portfolio is addition:

  • Scan risk: add up the signed position size times the risk array for every leg, in each of the 16 scenarios, and take the worst one. That single number is why a hedge reduces margin: the long leg’s gain offsets the short leg’s loss inside the same scenario.
  • Calendar spread charge: the scan assumes all expiries move together, which is not true, so a flat charge is added back for long near month against short far month.
  • Short option minimum: a floor for short option books. NSCCL files ship this at zero.
  • Net option value: the market value of your option legs. CME SPAN subtracts it, which is exactly why a long option needs only its premium and no margin.

The formula Marginism implements per underlying is:

span_risk = max( scan_risk
                 + calendar spread charge
                 + spot / delivery charge      # 0 in NSCCL files
                 - inter-commodity credit      # 0 in NSCCL files
               , short_option_minimum )

span_margin = max(0, span_risk - net_option_value)

One thing is deliberately not in the SPAN file: exposure margin, also called ELM or extreme loss margin. That is an exchange percentage of notional published separately by circular. Marginism applies the NSE defaults (2 percent for index, about 3.5 percent for stocks) and lets you override them. Your broker’s total is:

total margin = SPAN margin + exposure margin (+ any additional / adhoc margin)

Installation

pip install marginism

That is the whole setup. Marginism is 100 percent standard library and needs Python 3.8 or newer. Nothing else gets installed.

Getting the SPAN file

NSE publishes the SPAN risk parameter file several times a day under the daily reports for the derivatives segment. Look for the SPAN file entry on the Daily Reports, Derivatives page, download it and unzip it, and you get an XML file named like nsccl.20260918.s.spn. The date in the name is the business date, and the file used throughout this tutorial is about 50 MB and covers 239 underlyings.

Point Marginism at the path and you are done. The same code works on Windows, macOS and Linux:

# Same folder as your script
SPN = "nsccl.20260918.s.spn"

# macOS / Linux
SPN = "/Users/you/Downloads/nsccl.20260918.s.spn"

# Windows: raw string or forward slashes, both work
SPN = r"D:\AI Bootcamp 2026\Day31\margin example\nsccl.20260918.s.spn"
SPN = "D:/AI Bootcamp 2026/Day31/margin example/nsccl.20260918.s.spn"

The same library reads currency (CDS) and commodity (MCX) SPAN files. The algorithm does not change, only the file does.

Your first margin calculation

The high level entry point is RiskEngine. Load the file once, then evaluate as many baskets as you like against it.

from marginism import RiskEngine

SPN = "nsccl.20260918.s.spn"

# Parse only the underlyings you need. The full file has 239 symbols.
eng = RiskEngine.from_file(SPN, symbols=["NIFTY"])

result = eng.basket([
    {"exchange": "NFO",
     "tradingsymbol": "NIFTY26SEP23400CE",
     "transaction_type": "SELL",
     "quantity": 65},          # quantity is in UNITS, 65 = 1 NIFTY lot
])

final = result["data"]["final"]
print("SPAN     :", final["span"])
print("Exposure :", final["exposure"])
print("Total    :", final["total"])

Output:

SPAN     : 133227.9
Exposure : 30350.32
Total    : 163578.22

One lot of a short NIFTY 23400 call expiring 29-Sep-2026 blocks about Rs 1,63,578. That is the number, computed offline, in well under a second.

Anatomy of the response

basket() returns a broker style dictionary, so it slots into tooling you may already have:

{
  "status": "success",
  "data": {
    "initial": { ... },       # same layout as "final"
    "final": {                # the consolidated basket, WITH hedge benefit
      "span": 133227.9,       # SPAN margin
      "exposure": 30350.32,   # exposure / ELM margin
      "option_premium": 0.0,  # premium payable on net long options
      "additional": 0.0,      # adhoc + expiry-day ELM
      "total": 163578.22      # span + exposure + additional
    },
    "orders": [ ... ],        # each leg priced STANDALONE, no netting
    "margin_benefit": 0.0     # capital saved by the hedge
  }
}

Two fields deserve attention because they are what makes this useful for a spread trader:

  • orders holds every leg priced on its own, as if you carried it naked. This is your reference point.
  • margin_benefit is the difference between the sum of those standalone legs and the consolidated basket. It is the capital your hedge just released.

Quantity is in units, not lots

This trips people up on the first run. Marginism works in underlying units because lot sizes are not in the SPAN file. Pass 65 for one NIFTY lot, 130 for two. Keep your own lot size map and multiply:

# NSE F&O lot sizes, September 2026. Verify against the current NSE contract file.
LOT = {
    "NIFTY": 65, "BANKNIFTY": 30, "FINNIFTY": 60, "MIDCPNIFTY": 120,
    "RELIANCE": 500, "SBIN": 750, "TCS": 225,
    "HDFCBANK": 650, "INFY": 400, "ICICIBANK": 700,
}

def leg(tradingsymbol, side, symbol, lots=1):
    """Build one order dict. Quantity = lots x lot size."""
    return {
        "exchange": "NFO",
        "tradingsymbol": tradingsymbol,
        "transaction_type": side,
        "quantity": lots * LOT[symbol],
    }

Two tradingsymbol formats, both resolve

Marginism generates a tradingsymbol for every contract actually present in the loaded file and builds a reverse index, so there is no fragile string parsing. Both NSE conventions work:

StyleFutureMonthly optionWeekly option
CompactNIFTY26SEPFUTNIFTY26SEP23400CENIFTY2692223700CE
Full dateNIFTY29SEP26FUTNIFTY29SEP2623400CENIFTY22SEP2623700CE

The weekly compact form is the familiar NSE pattern SYMBOL + YY + month code + DD + strike + CE/PE, where the month code is 1 to 9 for January to September and O, N, D for October, November and December. So the 22-Sep-2026 weekly 23700 call is NIFTY2692223700CE.

If you would rather not deal with symbol strings at all, pass the fields directly:

eng.basket([
    {"symbol": "NIFTY", "instrument": "CE", "expiry": "2026-09-29",
     "strike": 23400, "transaction_type": "SELL", "quantity": 65},
])

What is inside the file we are using

Before margining strategies, it helps to see what the file actually contains. Marginism ships a command line interface:

python -m marginism nsccl.20260918.s.spn --list          # all 239 symbols
python -m marginism nsccl.20260918.s.spn --info NIFTY    # contracts and expiries
NIFTY  (NIFTY)  currency=INR  som_rate=0.0
futures (3):
  20260929  price=   23,378.50  scan(maxloss/unit)=  2,182.70
  20261027  price=   23,480.30  scan(maxloss/unit)=  2,193.31
  20261123  price=   23,579.80  scan(maxloss/unit)=  2,203.59
option expiries (18): 20260922, 20260929, 20261006, 20261013, 20261019, 20261027,
20261123, 20261229, 20270330, 20270629, 20271228, 20280627, 20281226, 20290626,
20291224, 20300625, 20301231, 20310624

So on 18-Sep-2026 the reference levels are:

UnderlyingSpot in file29SEP26 futureLot sizeNearest weekly
NIFTY23,346.4023,378.506522-Sep-2026
BANKNIFTY56,358.7056,527.0030monthly only
RELIANCE1,226.471,241.40500monthly only
SBIN996.07994.30750monthly only
INFY1,051.401,049.60400monthly only

NIFTY carries weekly expiries (22SEP26, 06OCT26, 13OCT26 and so on). BANKNIFTY and the single stocks are monthly only, which matches the current NSE structure.

A note on speed, because this matters if you are calling it in a loop. Parsing the full 50 MB file takes about 2.8 seconds, and restricting it with symbols= brings that down to roughly 1.5 seconds. After that, each basket calculation takes well under a millisecond, so 100 two leg baskets run in about 47 milliseconds. Load once, reuse the engine.

The strategy cookbook

Everything below uses the same helper from earlier and a small reporting function:

from marginism import RiskEngine

SPN = "nsccl.20260918.s.spn"
eng = RiskEngine.from_file(
    SPN, symbols=["NIFTY", "BANKNIFTY", "RELIANCE", "SBIN", "INFY"])

def report(title, legs):
    data = eng.basket(legs)["data"]
    final = data["final"]
    print("=" * 72)
    print(title)
    for order, out in zip(legs, data["orders"]):
        print(f"  {out['tradingsymbol']:<26}{order['transaction_type']:<6}"
              f"{order['quantity']:>6}  standalone {out['total']:>12,.2f}")
    print(f"  SPAN margin      {final['span']:>14,.2f}")
    print(f"  Exposure (ELM)   {final['exposure']:>14,.2f}")
    print(f"  Option premium   {final['option_premium']:>14,.2f}")
    print(f"  TOTAL MARGIN     {final['total']:>14,.2f}")
    print(f"  Margin benefit   {data['margin_benefit']:>14,.2f}")

1. The baseline: naked short options

Start with what you are trying to beat. One lot, sold naked, 29SEP26 monthly.

report("NAKED SHORT CALL - NIFTY 29SEP26 23400 CE",
       [leg("NIFTY26SEP23400CE", "SELL", "NIFTY")])

report("NAKED SHORT PUT - NIFTY 29SEP26 23300 PE",
       [leg("NIFTY26SEP23300PE", "SELL", "NIFTY")])
PositionSPANExposureTotal
Short 23400 CE1,33,227.9030,350.321,63,578.22
Short 23300 PE1,30,364.6530,350.321,60,714.97

Now the other side. Buy the same call and the margin collapses to nothing:

report("LONG CALL - NIFTY 29SEP26 23400 CE",
       [leg("NIFTY26SEP23400CE", "BUY", "NIFTY")])
  SPAN margin                0.00
  Exposure (ELM)             0.00
  Option premium         9,548.50
  TOTAL MARGIN               0.00

A long option carries no margin at all. Your risk is capped at the premium, so the premium is all you pay. Marginism reports it under option_premium rather than total, because premium is a debit, not a margin block. Keep that distinction in mind when you read the debit spread numbers below.

2. Short straddle and short strangle

report("SHORT STRADDLE - NIFTY 29SEP26 23350 CE + PE",
       [leg("NIFTY26SEP23350CE", "SELL", "NIFTY"),
        leg("NIFTY26SEP23350PE", "SELL", "NIFTY")])

report("SHORT STRANGLE - NIFTY weekly 22SEP26 23700 CE / 23000 PE",
       [leg("NIFTY2692223700CE", "SELL", "NIFTY"),
        leg("NIFTY2692223000PE", "SELL", "NIFTY")])
SHORT STRADDLE - NIFTY 29SEP26 23350 CE + PE
  NIFTY26SEP23350CE         SELL      65  standalone   166,934.82
  NIFTY26SEP23350PE         SELL      65  standalone   163,739.42
  SPAN margin          136,584.50
  Exposure (ELM)        60,700.64
  TOTAL MARGIN         197,285.14
  Margin benefit       133,389.10

This is the first real lesson. Two naked shorts would cost 1,66,935 plus 1,63,739, that is Rs 3,30,674. As a straddle the exchange charges Rs 1,97,285, saving Rs 1,33,389.

The reason is the scan. A call and a put cannot both lose in the same scenario. SPAN takes the worst single scenario for the combined book, not the worst case of each leg separately. Notice SPAN barely moved (1,33,228 for one leg, 1,36,585 for both), while exposure margin exactly doubled, because ELM is a flat percentage of notional per short leg and gets no netting at all.

The weekly strangle on 22SEP26 shows the same shape with less time value:

StrategySum of legsBasket totalBenefit
Short straddle 29SEP263,30,6741,97,2851,33,389
Short strangle 22SEP262,93,5941,77,2791,16,314

3. Vertical spreads: where the capital actually gets freed

A defined risk spread is where offline margin maths starts paying for itself.

# Debit spread: buy the nearer strike, sell the further one
report("BULL CALL SPREAD - long 23400 CE, short 23700 CE",
       [leg("NIFTY26SEP23400CE", "BUY", "NIFTY"),
        leg("NIFTY26SEP23700CE", "SELL", "NIFTY")])

# Credit spread: sell the nearer strike, buy the further one
report("BULL PUT CREDIT SPREAD - short 23300 PE, long 23000 PE",
       [leg("NIFTY26SEP23300PE", "SELL", "NIFTY"),
        leg("NIFTY26SEP23000PE", "BUY", "NIFTY")])
BULL CALL SPREAD - long 23400 CE, short 23700 CE
  NIFTY26SEP23400CE         BUY       65  standalone     9,548.50
  NIFTY26SEP23700CE         SELL      65  standalone   145,036.32
  SPAN margin                0.00
  Exposure (ELM)        30,350.32
  Option premium         6,919.25
  TOTAL MARGIN          30,350.32
  Margin benefit       114,686.00

BULL PUT CREDIT SPREAD - short 23300 PE, long 23000 PE
  NIFTY26SEP23300PE         SELL      65  standalone   160,714.97
  NIFTY26SEP23000PE         BUY       65  standalone     3,295.50
  SPAN margin           17,611.75
  Exposure (ELM)        30,350.32
  TOTAL MARGIN          47,962.07
  Margin benefit       112,752.90

Read those two carefully, because they behave differently and traders regularly get this wrong:

  • The debit spread has zero SPAN. You are long the lower strike and short the higher one, so in every one of the 16 scenarios the long leg covers the short leg completely. All that remains is the flat 2 percent ELM on the short leg, Rs 30,350, plus the Rs 6,919 net premium you actually pay.
  • The credit spread keeps Rs 17,612 of SPAN, comfortably inside the Rs 19,500 maximum loss that a 300 point wide spread on 65 units implies. That is the exchange sizing your worst case correctly. ELM is still charged in full on the short leg.

In both cases roughly Rs 1.13 lakh of the naked short’s margin is released by adding the protective long leg. If you sell naked options because spreads feel like they cost too much, this is the number to look at.

4. Iron condors, weekly and monthly

The bread and butter of a lot of full time income traders. Four legs, two credit spreads, one underlying.

# Weekly: 22SEP26 expiry, 200 point wings
report("IRON CONDOR - NIFTY weekly 22SEP26",
       [leg("NIFTY2692223700CE", "SELL", "NIFTY"),
        leg("NIFTY2692223900CE", "BUY",  "NIFTY"),
        leg("NIFTY2692223000PE", "SELL", "NIFTY"),
        leg("NIFTY2692222800PE", "BUY",  "NIFTY")])

# Monthly: 29SEP26 expiry, 300 point wings
report("IRON CONDOR - NIFTY monthly 29SEP26",
       [leg("NIFTY26SEP23800CE", "SELL", "NIFTY"),
        leg("NIFTY26SEP24100CE", "BUY",  "NIFTY"),
        leg("NIFTY26SEP22900PE", "SELL", "NIFTY"),
        leg("NIFTY26SEP22600PE", "BUY",  "NIFTY")])
IRON CONDOR - NIFTY weekly 22SEP26
  NIFTY2692223700CE         SELL      65  standalone   146,664.57
  NIFTY2692223900CE         BUY       65  standalone       146.25
  NIFTY2692223000PE         SELL      65  standalone   146,929.12
  NIFTY2692222800PE         BUY       65  standalone       377.00
  SPAN margin           11,884.60
  Exposure (ELM)        60,700.64
  TOTAL MARGIN          72,585.24
  Margin benefit       221,008.45
Iron condorSPANExposureTotalBenefit
Weekly 22SEP26, 200 pt wings11,884.6060,700.6472,585.242,21,008
Monthly 29SEP26, 300 pt wings17,746.9560,700.6478,447.591,98,375

Two observations that change how you size these:

  • SPAN is tiny and wing width drives it. Widening the wings from 200 to 300 points raises SPAN by roughly 50 percent, from 11,885 to 17,747, because the wing defines your worst case loss. Everything else is unchanged.
  • Exposure margin dominates and does not care about your hedge at all. Rs 60,701 of the Rs 72,585 total, that is 84 percent, is a flat 2 percent of notional on the two short legs. Once you are hedged, the wings barely matter and the real capital cost is just the ELM on the short strikes.

5. Butterflies

# Long call butterfly: buy 1, sell 2, buy 1
report("LONG CALL BUTTERFLY - 23200 / 2x 23400 / 23600 CE",
       [leg("NIFTY26SEP23200CE", "BUY",  "NIFTY"),
        leg("NIFTY26SEP23400CE", "SELL", "NIFTY", lots=2),
        leg("NIFTY26SEP23600CE", "BUY",  "NIFTY")])

# Iron butterfly: short straddle with protective wings
report("IRON BUTTERFLY - short 23350 CE+PE, long 23650 CE / 23050 PE",
       [leg("NIFTY26SEP23350CE", "SELL", "NIFTY"),
        leg("NIFTY26SEP23350PE", "SELL", "NIFTY"),
        leg("NIFTY26SEP23650CE", "BUY",  "NIFTY"),
        leg("NIFTY26SEP23050PE", "BUY",  "NIFTY")])
StrategySPANExposureTotalBenefit
Long call butterfly0.0060,700.6460,700.642,66,456
Iron butterfly19,065.8060,700.6479,766.442,50,908

The long call butterfly is a pure debit structure and its SPAN is exactly zero. The two wings fully cover the two short bodies in every scenario, so the entire Rs 60,701 you post is exposure margin on the 130 short units, plus Rs 2,662 of net premium. A structure with a theoretical maximum loss of Rs 2,662 costs Rs 60,701 to carry, and every rupee of that is ELM.

The iron butterfly, being a short straddle with wings, keeps Rs 19,066 of SPAN. Compare it against the naked short straddle from earlier: Rs 79,766 against Rs 1,97,285. Adding two cheap wings cut the margin by 60 percent.

6. BANKNIFTY

Same structures, different lot size (30) and a much larger notional.

report("BANKNIFTY SHORT STRANGLE - 29SEP26 57500 CE / 55200 PE",
       [leg("BANKNIFTY26SEP57500CE", "SELL", "BANKNIFTY"),
        leg("BANKNIFTY26SEP55200PE", "SELL", "BANKNIFTY")])

report("BANKNIFTY IRON CONDOR - 29SEP26",
       [leg("BANKNIFTY26SEP57500CE", "SELL", "BANKNIFTY"),
        leg("BANKNIFTY26SEP58000CE", "BUY",  "BANKNIFTY"),
        leg("BANKNIFTY26SEP55200PE", "SELL", "BANKNIFTY"),
        leg("BANKNIFTY26SEP54700PE", "BUY",  "BANKNIFTY")])
StrategySPANExposureTotalBenefit
Short strangle1,19,486.1067,630.441,87,116.541,15,155
Iron condor, 500 pt wings13,257.6067,630.4480,888.042,21,383

Buying the 58000 call and the 54700 put, which together cost under Rs 3,700 of premium, takes SPAN from Rs 1,19,486 down to Rs 13,258. The strangle needs Rs 1.87 lakh, the condor needs Rs 0.81 lakh. On a Rs 10 lakh account that is the difference between carrying five strangles and carrying twelve condors.

7. Futures and hedged futures

Futures are where the hedging benefit is most dramatic, because a naked future has unlimited risk in one direction and a single cheap put truncates it.

# Naked long future
report("LONG FUTURE - NIFTY 29SEP26",
       [leg("NIFTY26SEPFUT", "BUY", "NIFTY")])

# Protective put
report("HEDGED FUTURE - long future + long 23000 PE",
       [leg("NIFTY26SEPFUT", "BUY", "NIFTY"),
        leg("NIFTY26SEP23000PE", "BUY", "NIFTY")])

# Collar: protective put financed by a short call
report("COLLAR - long future, long 23000 PE, short 23700 CE",
       [leg("NIFTY26SEPFUT", "BUY", "NIFTY"),
        leg("NIFTY26SEP23000PE", "BUY", "NIFTY"),
        leg("NIFTY26SEP23700CE", "SELL", "NIFTY")])
HEDGED FUTURE - long future + long 23000 PE
  NIFTY26SEPFUT             BUY       65  standalone   172,267.55
  NIFTY26SEP23000PE         BUY       65  standalone     3,295.50
  SPAN margin           29,138.20
  Exposure (ELM)        30,392.05
  Option premium         3,295.50
  TOTAL MARGIN          59,530.25
  Margin benefit       112,737.30
PositionSPANExposureTotalvs naked future
Long future, naked1,41,875.5030,392.051,72,267.55
+ long 23000 PE29,138.2030,392.0559,530.25-65%
+ put, + short 23700 CE (collar)29,138.2060,742.3789,880.57-48%

A put costing Rs 3,296 in premium releases Rs 1,12,737 of margin. That is a 34 to 1 return on the hedge in capital terms, before you count the downside protection you actually bought. This is the single most useful thing a margin calculator tells a futures trader, and it is why the SEBI hedge benefit framework exists.

Notice the collar costs more than the simple protective put, Rs 89,881 against Rs 59,530, even though it is a tighter risk structure. SPAN is identical at Rs 29,138, but the short call adds another Rs 30,350 of exposure margin. ELM never nets. Selling a call to finance a put reduces your cost basis and increases your margin at the same time.

8. Futures calendar spread

report("CALENDAR SPREAD - long 29SEP26 future, short 27OCT26 future",
       [leg("NIFTY26SEPFUT", "BUY",  "NIFTY"),
        leg("NIFTY26OCTFUT", "SELL", "NIFTY")])

Here the SPAN algorithm’s second component finally shows up. Using the lower level SpanCalculator API you can see it separately:

from marginism import SpanCalculator, Position

calc = SpanCalculator.from_file(SPN, symbols=["NIFTY"])
res = calc.calculate([
    Position("NIFTY", "FUT", quantity=65,  expiry="20260929"),
    Position("NIFTY", "FUT", quantity=-65, expiry="20261027"),
])
c = res.by_commodity["NIFTY"]
print("scan risk              :", round(c.scan_risk, 2))
print("calendar spread charge :", round(c.calendar_spread_charge, 2))
print("SPAN risk              :", round(c.span_risk, 2))
scan risk              : 684.45
calendar spread charge : 26845.0
SPAN risk              : 27529.45

The scan collapses to Rs 684, because the scan assumes September and October move together and the two legs cancel. The exchange knows that is optimistic, so it adds back a flat Rs 26,845 calendar spread charge for the basis risk. Without that term a calendar spread would look free, which it is not.

9. Single stock options and hedged stock futures

Stock derivatives work identically, only the ELM rate is higher (about 3.5 percent rather than 2 percent) and lot sizes are much larger.

report("RELIANCE SHORT PUT - 29SEP26 1200 PE",
       [leg("RELIANCE26SEP1200PE", "SELL", "RELIANCE")])

report("RELIANCE COVERED CALL - long future, short 1260 CE",
       [leg("RELIANCE26SEPFUT", "BUY", "RELIANCE"),
        leg("RELIANCE26SEP1260CE", "SELL", "RELIANCE")])
PositionSPANExposureTotalBenefit
RELIANCE short 1200 PE, 1 lot (500)69,780.0021,463.2391,243.230
RELIANCE covered call on future87,430.0043,187.731,30,617.7371,055

The covered call is worth a second look, because it exposes something traders often miss. The long RELIANCE future on its own carries SPAN of Rs 87,430. Add the short 1260 call and SPAN is still exactly Rs 87,430, unchanged to the rupee. The worst scenario for this book is a sharp fall, and a short call does nothing for you there. The entire Rs 71,055 benefit is simply the SPAN you would otherwise have paid on the short call as a separate position, not any genuine risk reduction.

A covered call caps a rise you do not mind and leaves the fall you actually fear wide open. SPAN prices it exactly that way. If you want margin relief on a long future, buy a put (see the 65 percent reduction above), do not sell a call.

10. A whole multi symbol book at once

SPAN nets within an underlying, never across underlyings. Marginism handles that correctly, which means you can margin your entire book in one call:

report("MULTI-SYMBOL PORTFOLIO",
       [leg("NIFTY26SEP23800CE",     "SELL", "NIFTY"),
        leg("NIFTY26SEP22900PE",     "SELL", "NIFTY"),
        leg("BANKNIFTY26SEP57500CE", "SELL", "BANKNIFTY"),
        leg("RELIANCE26SEP1260CE",   "SELL", "RELIANCE"),
        leg("SBIN26SEP1020CE",       "SELL", "SBIN"),
        leg("INFY26SEP1080CE",       "SELL", "INFY")])
  Sum of legs (standalone)   701,777.88
  SPAN margin                437,871.50
  Exposure (ELM)             156,845.52
  TOTAL MARGIN               594,717.02
  Margin benefit             107,060.85

The Rs 1.07 lakh benefit comes entirely from the NIFTY strangle netting inside NIFTY. The BANKNIFTY, RELIANCE, SBIN and INFY legs each stand alone. That is the rule worth internalising: a hedge only reduces margin if it is on the same underlying. Selling a BANKNIFTY call against a NIFTY call gives you a correlation hedge in P&L terms and exactly zero margin relief.

The whole cookbook in one table

Every strategy above, one lot each, on the 18-Sep-2026 file:

StrategyTotal margin (Rs)Margin benefit (Rs)
Long NIFTY future (29SEP26)1,72,2680
Hedged future (protective put)59,5301,12,737
Collar (future + put + short call)89,8812,27,423
Futures calendar spread (SEP/OCT)88,4462,55,853
Naked short call (29SEP26)1,63,5780
Short straddle (29SEP26)1,97,2851,33,389
Short strangle (weekly 22SEP26)1,77,2791,16,314
Bull call debit spread30,3501,14,686
Bull put credit spread47,9621,12,753
Iron condor (weekly 22SEP26)72,5852,21,008
Iron condor (monthly 29SEP26)78,4481,98,375
Long call butterfly60,7012,66,456
Iron butterfly79,7662,50,908
BANKNIFTY iron condor80,8882,21,383
RELIANCE covered call1,30,61871,055
Multi-symbol short book (6 legs)5,94,7171,07,061

Looking under the hood

The RiskEngine API is what you will use day to day. When you want to understand why a number came out the way it did, drop to SpanCalculator.

The 16 scenarios, and which one is hurting you

from marginism import SpanCalculator, Position, SCENARIO_LABELS

calc = SpanCalculator.from_file(SPN, symbols=["NIFTY"])
res = calc.calculate([
    Position("NIFTY", "CE", quantity=-65, expiry="20260929", strike=23350),
    Position("NIFTY", "PE", quantity=-65, expiry="20260929", strike=23350),
])
print(res.summary())

c = res.by_commodity["NIFTY"]
for i, (label, loss) in enumerate(zip(SCENARIO_LABELS, c.scenario_losses), 1):
    flag = "  <-- worst" if i == c.worst_scenario else ""
    print(f"{i:>2}. {label:<26}{loss:>14,.2f}{flag}")
SPAN Margin Summary
====================================================
  SPAN margin      :       136,584.50
  Exposure margin  :        60,700.64
  ----------------------------------------------
  Total margin     :       197,285.14
  Net option value :       -21,030.75

Per combined commodity:
  [NIFTY]
      scan risk        :     115,553.75   (worst: scenario 11 - price +3/3 / vol up)
      calendar spread  :           0.00
      SPAN risk        :     136,584.50

 1. price unch / vol up              4,397.25
 2. price unch / vol down          -10,775.05
 3. price +1/3 / vol up             25,256.40
 4. price +1/3 / vol down           21,944.65
 5. price -1/3 / vol up             22,400.30
 6. price -1/3 / vol down           18,880.55
 7. price +2/3 / vol up             68,926.00
 8. price +2/3 / vol down           68,487.25
 9. price -2/3 / vol up             65,683.15
10. price -2/3 / vol down           65,305.50
11. price +3/3 / vol up            115,553.75  <-- worst
12. price +3/3 / vol down          115,527.10
13. price -3/3 / vol up            112,358.35
14. price -3/3 / vol down          112,346.00
15. price +extreme (cover)          87,861.80
16. price -extreme (cover)          86,370.70

This table is genuinely useful risk information, not just margin trivia. For this short straddle:

  • Scenario 11, a full upside scan range move with volatility up, is the binding case at Rs 1,15,554. The upside is marginally worse than the downside (Rs 1,12,358), which tells you the position is slightly short delta at these strikes.
  • Scenario 2, price unchanged with volatility down, is a negative number, that is a gain of Rs 10,775. That is your theta and vega crush scenario.
  • Scan risk of Rs 1,15,554 becomes SPAN of Rs 1,36,585 once the net option value of -Rs 21,031 is subtracted. Negative net option value means you received premium, and CME SPAN adds that liability back to the requirement.

Reading a single contract’s risk array

c = calc.span_file.get("NIFTY")
opt = next(o for o in c.options
           if o.expiry == "20260929" and o.strike == 23350 and o.option_type == "C")
print(opt.price, opt.delta, opt.volatility, opt.risk_array.composite_delta)
print(opt.risk_array.values)
173.9  0.52617357  0.1585  0.5139

 1. price unch / vol up            -29.79
 2. price unch / vol down           86.92
 3. price +1/3 / vol up           -538.21
 ...
11. price +3/3 / vol up         -1,927.40
13. price -3/3 / vol up            173.90     <-- premium is the floor
15. price +extreme (cover)      -1,434.37

A positive value is a loss to a one unit long. Notice scenario 13: a full downside move caps the long call’s loss at 173.90, exactly the premium paid. That floor is why long options need no margin, and it is embedded in the exchange’s own data rather than being modelled by the library.

Expiry day ELM

NSE levies an additional ELM on short options on the day they expire. Marginism applies it automatically, defaulting the trading date to the SPAN file’s business date. Override it with as_of_date to see what a weekly position will cost you on expiry day:

legs = [leg("NIFTY2692223350CE", "SELL", "NIFTY"),
        leg("NIFTY2692223350PE", "SELL", "NIFTY")]

for asof in ["2026-09-18", "2026-09-22"]:
    f = eng.basket(legs, as_of_date=asof)["data"]["final"]
    print(asof, f["span"], f["exposure"], f["additional"], f["total"])
2026-09-18   136,911.45   60,700.64        0.00   197,612.09
2026-09-22   136,911.45   60,700.64   60,700.64   258,312.73

A short weekly straddle that costs Rs 1,97,612 on Friday costs Rs 2,58,313 on Tuesday expiry, a 31 percent jump, purely from the expiry day add-on. If you routinely hold short weeklies into expiry, model this before you size, not after your broker squares you off. Set ExposureConfig(expiry_day_elm_pct=0) to switch it off.

Customising exposure margin

Exposure rates come from exchange circulars and change. Override them per symbol:

from marginism import RiskEngine, ExposureConfig

cfg = ExposureConfig(
    overrides={"RELIANCE": 0.05},   # ELM 5% instead of the 3.5% default
    adhoc={"RELIANCE": 0.01},       # 1% additional / adhoc margin
    expiry_day_elm_pct=0.02,        # expiry-day add-on, 0 to disable
)
eng = RiskEngine.from_file(SPN, symbols=["RELIANCE"], exposure=cfg)
ConfigSPANExposureAdditionalTotal
Default (3.5%)69,780.0021,463.230.0091,243.23
Override (5% + 1% adhoc)69,780.0030,661.756,132.351,06,574.10

SPAN is untouched, because SPAN comes from the file. Only the pieces the exchange publishes separately move.

Validating against a broker calculator

A margin library is only worth using if it agrees with the entity that will actually debit your account. So all 19 baskets above were re-run through a live broker SPAN calculator on the same day, with the same contracts and quantities.

Here is the comparison:

StrategySPAN (Marginism)SPAN (broker)Difference
Long NIFTY future1,41,875.501,41,876.00-0.50
Hedged future + 23000 PE29,138.2029,138.50-0.30
Collar29,138.2029,137.75+0.45
Futures calendar spread27,529.4527,529.00+0.45
Naked short call 23400 CE1,33,227.901,33,227.50+0.40
Short straddle 233501,36,584.501,36,584.75-0.25
Short strangle weekly 22SEP261,16,578.801,16,579.00-0.20
Bull call debit spread0.000.000.00
Bull put credit spread17,611.7517,611.50+0.25
Iron condor weekly 22SEP2611,884.6011,884.75-0.15
Iron condor monthly 29SEP2617,746.9517,746.75+0.20
Long call butterfly0.000.25-0.25
Iron butterfly19,065.8019,066.25-0.45
BANKNIFTY iron condor13,257.6013,258.00-0.40
RELIANCE short put 1200 PE69,780.0069,780.000.00
RELIANCE covered call87,430.0087,430.000.00
SBIN short call 1020 CE84,817.5084,817.500.00
INFY short call 1080 CE53,452.0053,452.000.00
Multi-symbol book (6 legs)4,37,871.504,37,871.00+0.50

Largest SPAN difference across 19 baskets: Rs 0.50.

On margins running into lakhs, the worst disagreement is fifty paise, and it is rounding. That is as close to an exact match as this gets, and it holds across futures, naked options, debit spreads, credit spreads, condors, butterflies, calendar spreads and a six leg multi symbol book. The SPAN implementation is correct.

Where the numbers do diverge, and why

Exposure margin is a different story, and you should know this before you rely on the totals.

InstrumentMarginism ELMBroker ELMDifference
NIFTY short option, 1 lot30,350.3231,196.56-2.7%
NIFTY future, 1 lot30,392.0530,333.42+0.2%
BANKNIFTY short option, 1 lot33,815.2233,422.34+1.2%
RELIANCE short option, 1 lot21,463.2321,768.25-1.4%
SBIN short option, 1 lot26,146.8425,953.38+0.7%
INFY short option, 1 lot14,719.6014,820.40-0.7%
NIFTY futures calendar spread60,916.4410,158.20+500%

The first six rows are all within about 3 percent. That is expected: ELM is not in the SPAN file, so Marginism applies a flat percentage to the underlying price carried in the file, while a broker applies the circular rate to its own reference price. On a total margin of Rs 1.6 lakh, a 2.7 percent ELM difference is under Rs 850.

The last row is a real limitation, not rounding. NSE grants a large exposure margin concession on calendar spreads, and Marginism does not model it. It charges full ELM on both legs, Rs 60,916, where the broker charges Rs 10,158. If you trade futures calendar spreads, use the SPAN component (which matched to 45 paise) and apply the spread ELM rule yourself, or set an override:

# Calendar spreads: approximate NSE's spread ELM concession
cfg = ExposureConfig(overrides={"NIFTY": 0.02 / 6})
eng = RiskEngine.from_file(SPN, symbols=["NIFTY"], exposure=cfg)

eng.basket([o("NIFTY26SEPFUT", "BUY", 65), o("NIFTY26OCTFUT", "SELL", 65)])
# span 27,529.45  exposure 10,152.74  total 37,682.19
# broker:         exposure 10,158.20  total 37,687.20

With that one override the calendar spread total lands within Rs 5 of the broker figure. The point is that the SPAN engine was never wrong, only the ELM assumption was, and ELM is the part you are meant to configure.

Reproduce the validation yourself

The broker figures are hard coded so the comparison runs offline:

from marginism import RiskEngine

eng = RiskEngine.from_file("nsccl.20260918.s.spn",
                           symbols=["NIFTY", "BANKNIFTY", "RELIANCE", "SBIN", "INFY"])

def o(ts, side, qty):
    return {"tradingsymbol": ts, "transaction_type": side, "quantity": qty}

CASES = [
    ("Naked short call 23400 CE",
     [o("NIFTY26SEP23400CE", "SELL", 65)], 133227.50),
    ("Iron condor weekly 22SEP26",
     [o("NIFTY2692223700CE", "SELL", 65), o("NIFTY2692223900CE", "BUY", 65),
      o("NIFTY2692223000PE", "SELL", 65), o("NIFTY2692222800PE", "BUY", 65)], 11884.75),
    ("Iron butterfly",
     [o("NIFTY26SEP23350CE", "SELL", 65), o("NIFTY26SEP23350PE", "SELL", 65),
      o("NIFTY26SEP23650CE", "BUY", 65),  o("NIFTY26SEP23050PE", "BUY", 65)], 19066.25),
    ("Hedged future + 23000 PE",
     [o("NIFTY26SEPFUT", "BUY", 65), o("NIFTY26SEP23000PE", "BUY", 65)], 29138.50),
]

worst = 0.0
for name, legs, broker_span in CASES:
    mine = eng.basket(legs)["data"]["final"]["span"]
    diff = mine - broker_span
    worst = max(worst, abs(diff))
    print(f"{name:<32}{mine:>14,.2f}{broker_span:>14,.2f}{diff:>8,.2f}")

print(f"Largest SPAN difference: Rs {worst:,.2f}")

Gotchas worth knowing before you trust the output

  1. Lot sizes are not in the SPAN file. Quantity is in units. Keep your own lot size map and refresh it from the NSE contract file when the exchange revises it.
  2. There are no exchange tokens in the file. Contracts are keyed by trading symbol. If your system works in instrument tokens, map them through your broker’s instrument master first.
  3. Use the right file for the right date. A SPAN file is a snapshot. Margining Tuesday’s position against Friday’s file gives you Friday’s risk arrays, not Tuesday’s.
  4. Exposure margin is your responsibility. The defaults are reasonable NSE values, not gospel. Check the current circular, especially for stocks where the rate is the higher of 3.5 percent and 1.5 times the standard deviation.
  5. Calendar spread exposure is not modelled. As shown above, SPAN is right but exposure is charged on both legs.
  6. Long options show zero total and a separate premium. That is correct behaviour, not a bug. Premium is a debit, margin is a block, and they are different lines in your ledger.
  7. Margin benefit is margin only. Option premium is excluded from the benefit figure, because premium is a cost rather than a margin saving.

Putting it to work

Once margin is a local function call instead of a web form, a few things become easy that were not before:

  • Position sizing in a backtest. You can now ask how many condors your capital would actually have supported on each historical date, using that date’s SPAN file, instead of assuming a constant margin.
  • Strike selection by capital efficiency. Loop over candidate wing widths and rank them by credit divided by margin, not just by credit.
  • Pre-trade checks. Before firing an adjustment, compute the margin of the resulting book, not just the new leg. Baskets run in under a millisecond each.
  • Expiry day risk. Re-run your open short weeklies with as_of_date set to expiry and see the ELM jump before your broker does.
  • Hedge shopping. For any naked short, scan every available wing and find the cheapest option that releases the most margin. The protective put example above returned Rs 1.13 lakh of margin for Rs 3,296 of premium.

Links

Disclaimer

Margin figures produced by Marginism are estimates. They depend entirely on the SPAN file and the exposure rates you supply, and they may differ from what your broker blocks. Verify with your broker before trading. Nothing in this article is investment advice. Marginism is an independent open source project and is not affiliated with, sponsored by or endorsed by NSE, NSE Clearing (NSCCL), CME or any broker. SPAN is a registered trademark of Chicago Mercantile Exchange Inc.

Rajandran R Creator of OpenAlgo - OpenSource Algo Trading framework for Indian Traders. Building GenAI Applications. Telecom Engineer turned Full-time Derivative Trader. Mostly Trading Nifty, Banknifty, High Liquid Stock Derivatives. Trading the Markets Since 2006 onwards. Using Market Profile and Orderflow for more than a decade. Designed and published 100+ open source trading systems on various trading tools. Strongly believe that market understanding and robust trading frameworks are the key to the trading success. Building Algo Platforms, Writing about Markets, Trading System Design, Market Sentiment, Trading Softwares & Trading Nuances since 2007 onwards. Author of Marketcalls.in

Leave a Reply

Get Notifications, Alerts on Market Updates, Trading Tools, Automation & More