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:
| Style | Future | Monthly option | Weekly option |
|---|---|---|---|
| Compact | NIFTY26SEPFUT | NIFTY26SEP23400CE | NIFTY2692223700CE |
| Full date | NIFTY29SEP26FUT | NIFTY29SEP2623400CE | NIFTY22SEP2623700CE |
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:
| Underlying | Spot in file | 29SEP26 future | Lot size | Nearest weekly |
|---|---|---|---|---|
| NIFTY | 23,346.40 | 23,378.50 | 65 | 22-Sep-2026 |
| BANKNIFTY | 56,358.70 | 56,527.00 | 30 | monthly only |
| RELIANCE | 1,226.47 | 1,241.40 | 500 | monthly only |
| SBIN | 996.07 | 994.30 | 750 | monthly only |
| INFY | 1,051.40 | 1,049.60 | 400 | monthly 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")])
| Position | SPAN | Exposure | Total |
|---|---|---|---|
| Short 23400 CE | 1,33,227.90 | 30,350.32 | 1,63,578.22 |
| Short 23300 PE | 1,30,364.65 | 30,350.32 | 1,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:
| Strategy | Sum of legs | Basket total | Benefit |
|---|---|---|---|
| Short straddle 29SEP26 | 3,30,674 | 1,97,285 | 1,33,389 |
| Short strangle 22SEP26 | 2,93,594 | 1,77,279 | 1,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 condor | SPAN | Exposure | Total | Benefit |
|---|---|---|---|---|
| Weekly 22SEP26, 200 pt wings | 11,884.60 | 60,700.64 | 72,585.24 | 2,21,008 |
| Monthly 29SEP26, 300 pt wings | 17,746.95 | 60,700.64 | 78,447.59 | 1,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")])
| Strategy | SPAN | Exposure | Total | Benefit |
|---|---|---|---|---|
| Long call butterfly | 0.00 | 60,700.64 | 60,700.64 | 2,66,456 |
| Iron butterfly | 19,065.80 | 60,700.64 | 79,766.44 | 2,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")])
| Strategy | SPAN | Exposure | Total | Benefit |
|---|---|---|---|---|
| Short strangle | 1,19,486.10 | 67,630.44 | 1,87,116.54 | 1,15,155 |
| Iron condor, 500 pt wings | 13,257.60 | 67,630.44 | 80,888.04 | 2,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
| Position | SPAN | Exposure | Total | vs naked future |
|---|---|---|---|---|
| Long future, naked | 1,41,875.50 | 30,392.05 | 1,72,267.55 | – |
| + long 23000 PE | 29,138.20 | 30,392.05 | 59,530.25 | -65% |
| + put, + short 23700 CE (collar) | 29,138.20 | 60,742.37 | 89,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")])
| Position | SPAN | Exposure | Total | Benefit |
|---|---|---|---|---|
| RELIANCE short 1200 PE, 1 lot (500) | 69,780.00 | 21,463.23 | 91,243.23 | 0 |
| RELIANCE covered call on future | 87,430.00 | 43,187.73 | 1,30,617.73 | 71,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:
| Strategy | Total margin (Rs) | Margin benefit (Rs) |
|---|---|---|
| Long NIFTY future (29SEP26) | 1,72,268 | 0 |
| Hedged future (protective put) | 59,530 | 1,12,737 |
| Collar (future + put + short call) | 89,881 | 2,27,423 |
| Futures calendar spread (SEP/OCT) | 88,446 | 2,55,853 |
| Naked short call (29SEP26) | 1,63,578 | 0 |
| Short straddle (29SEP26) | 1,97,285 | 1,33,389 |
| Short strangle (weekly 22SEP26) | 1,77,279 | 1,16,314 |
| Bull call debit spread | 30,350 | 1,14,686 |
| Bull put credit spread | 47,962 | 1,12,753 |
| Iron condor (weekly 22SEP26) | 72,585 | 2,21,008 |
| Iron condor (monthly 29SEP26) | 78,448 | 1,98,375 |
| Long call butterfly | 60,701 | 2,66,456 |
| Iron butterfly | 79,766 | 2,50,908 |
| BANKNIFTY iron condor | 80,888 | 2,21,383 |
| RELIANCE covered call | 1,30,618 | 71,055 |
| Multi-symbol short book (6 legs) | 5,94,717 | 1,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)
| Config | SPAN | Exposure | Additional | Total |
|---|---|---|---|---|
| Default (3.5%) | 69,780.00 | 21,463.23 | 0.00 | 91,243.23 |
| Override (5% + 1% adhoc) | 69,780.00 | 30,661.75 | 6,132.35 | 1,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:
| Strategy | SPAN (Marginism) | SPAN (broker) | Difference |
|---|---|---|---|
| Long NIFTY future | 1,41,875.50 | 1,41,876.00 | -0.50 |
| Hedged future + 23000 PE | 29,138.20 | 29,138.50 | -0.30 |
| Collar | 29,138.20 | 29,137.75 | +0.45 |
| Futures calendar spread | 27,529.45 | 27,529.00 | +0.45 |
| Naked short call 23400 CE | 1,33,227.90 | 1,33,227.50 | +0.40 |
| Short straddle 23350 | 1,36,584.50 | 1,36,584.75 | -0.25 |
| Short strangle weekly 22SEP26 | 1,16,578.80 | 1,16,579.00 | -0.20 |
| Bull call debit spread | 0.00 | 0.00 | 0.00 |
| Bull put credit spread | 17,611.75 | 17,611.50 | +0.25 |
| Iron condor weekly 22SEP26 | 11,884.60 | 11,884.75 | -0.15 |
| Iron condor monthly 29SEP26 | 17,746.95 | 17,746.75 | +0.20 |
| Long call butterfly | 0.00 | 0.25 | -0.25 |
| Iron butterfly | 19,065.80 | 19,066.25 | -0.45 |
| BANKNIFTY iron condor | 13,257.60 | 13,258.00 | -0.40 |
| RELIANCE short put 1200 PE | 69,780.00 | 69,780.00 | 0.00 |
| RELIANCE covered call | 87,430.00 | 87,430.00 | 0.00 |
| SBIN short call 1020 CE | 84,817.50 | 84,817.50 | 0.00 |
| INFY short call 1080 CE | 53,452.00 | 53,452.00 | 0.00 |
| Multi-symbol book (6 legs) | 4,37,871.50 | 4,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.
| Instrument | Marginism ELM | Broker ELM | Difference |
|---|---|---|---|
| NIFTY short option, 1 lot | 30,350.32 | 31,196.56 | -2.7% |
| NIFTY future, 1 lot | 30,392.05 | 30,333.42 | +0.2% |
| BANKNIFTY short option, 1 lot | 33,815.22 | 33,422.34 | +1.2% |
| RELIANCE short option, 1 lot | 21,463.23 | 21,768.25 | -1.4% |
| SBIN short option, 1 lot | 26,146.84 | 25,953.38 | +0.7% |
| INFY short option, 1 lot | 14,719.60 | 14,820.40 | -0.7% |
| NIFTY futures calendar spread | 60,916.44 | 10,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
- 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.
- 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.
- 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.
- 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.
- Calendar spread exposure is not modelled. As shown above, SPAN is right but exposure is charged on both legs.
- 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.
- 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_dateset 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
- Marginism on GitHub: github.com/marketcalls/marginism
- Install:
pip install marginism - SPAN files: NSE Daily Reports, Derivatives
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.