Module E · Derivatives, Volatility & Options - Chapter 19

No-Arbitrage & the Synthetic Future

The most powerful idea in pricing - free money can't exist - and how put-call parity builds a synthetic future you can price options against.

NFOINDEX
What you'll learn
  • ·The no-arbitrage principle
  • ·Forwards & futures
  • ·Cost of carry & basis
  • ·Put-call parity
  • ·The synthetic future
  • ·Cash-futures basis in India

We arrive at derivatives - the most mathematical corner of markets, and the one where India does things its own way. Everything here rests on a single, almost philosophical idea: free money cannot exist. If two things deliver the same payoff, they must cost the same, or someone would buy the cheap one, sell the dear one, and pocket a riskless profit until the prices snapped together. This is no-arbitrage, and it's not a suggestion - it's the gravity that prices every future and option. Master it and derivatives stop being mysterious.

The no-arbitrage principle

State it plainly: if two portfolios have identical future payoffs, they must have identical prices today. Any gap is an arbitrage - a money pump - and in liquid markets, armies of traders (and the HFT firms of Chapter 10) close such gaps in milliseconds. So in practice the gaps don't persist, which means we can derive the fair price of a derivative by building an equivalent portfolio and insisting the prices match. No forecasting, no opinion - just the refusal to leave free money on the table.

Forwards, futures and the cost of carry

Start with the simplest derivative: a future, an agreement to buy something later at a price fixed now. What should that price be? No-arbitrage answers it exactly. Buying the future should cost the same as buying the asset today and holding it - so the future's price equals the spot price plus the cost of carry (the interest on the money you tie up, minus any dividends you'd earn). The gap between future and spot is the basis:

EX 1The basis: future minus spotINDEXNFOch19/01_basis.py
# The basis: how far the future trades from spot - and why no-arbitrage sets it.
import os

from openalgo import api

client = api(
    api_key=os.getenv("OPENALGO_API_KEY", "your_api_key_here"),
    host=os.getenv("OPENALGO_HOST", "http://127.0.0.1:5000"),
)

spot = client.quotes(symbol="NIFTY", exchange="NSE_INDEX")["data"]["ltp"]
fut = client.quotes(symbol="NIFTY30JUN26FUT", exchange="NFO")["data"]["ltp"]
basis = fut - spot

print(f"NIFTY spot   : {spot:.2f}")
print(f"NIFTY future : {fut:.2f}")
print(f"Basis (future - spot): {basis:+.2f}   ({'premium' if basis > 0 else 'discount'})")
print(f"As % of spot : {basis / spot * 100:+.2f}%")
print("\nNo-arbitrage ties them: future = spot + cost of carry.")
print("They MUST converge as expiry approaches - if they didn't, it would be free money.")
Live output
NIFTY spot   : 24021.65
NIFTY future : 24053.60
Basis (future - spot): +31.95   (premium)
As % of spot : +0.13%

No-arbitrage ties them: future = spot + cost of carry.
They MUST converge as expiry approaches - if they didn't, it would be free money.

Nifty's future trades at a small premium to spot - the +32 basis is essentially the cost of carrying the index to expiry. And here's the no-arbitrage punchline: as expiry approaches, that carry shrinks to nothing, so the future and spot are forced to converge. If they didn't, you could trade the gap for riskless profit - so the market never lets it survive.

Put-call parity

The same logic binds options together. Put-call parity says a call minus a put (at the same strike and expiry) must equal the forward minus that strike - because both sides have the identical payoff at expiry. Rearrange it and something beautiful falls out: you can combine options to manufacture a future.

The synthetic future

Buy an at-the-money call, sell the at-the-money put, add the strike, and you've built a synthetic future - a position that behaves exactly like being long the actual future, assembled purely from options. Let's see it track the real thing:

EX 2Building a synthetic futureINDEXNFOch19/02_synthetic_future.py
# Put-call parity builds a 'synthetic future' from options - the Black-76 forward.
import os
from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
import seaborn as sns
from openalgo import api

client = api(
    api_key=os.getenv("OPENALGO_API_KEY", "your_api_key_here"),
    host=os.getenv("OPENALGO_HOST", "http://127.0.0.1:5000"),
)

# The synthetic future = ATM strike + ATM call price - ATM put price (put-call parity).
sf = client.syntheticfuture(underlying="NIFTY", exchange="NSE_INDEX", expiry_date="30JUN26")
spot = sf["underlying_ltp"]
synthetic = sf["synthetic_future_price"]
fut = client.quotes(symbol="NIFTY30JUN26FUT", exchange="NFO")["data"]["ltp"]

# A zoomed bar chart: spot sits just below the future and synthetic (the cost of carry).
sns.set_theme(style="whitegrid")
fig, ax = plt.subplots(figsize=(7, 4.2))
labels = ["Spot", "Synthetic\nfuture", "Actual\nfuture"]
vals = [spot, synthetic, fut]
sns.barplot(x=labels, y=vals, hue=labels, legend=False,
            palette=["#888", "#16a34a", "#7c83ff"], ax=ax)
ax.set_ylim(min(vals) - 25, max(vals) + 15)        # zoom in to see the small gaps
for i, v in enumerate(vals):
    ax.text(i, v + 1, f"{v:.0f}", ha="center", fontsize=10)
ax.set_title("Spot vs synthetic future vs actual future")
ax.set_ylabel("NIFTY level")
plt.savefig(Path(__file__).with_suffix(".png"), dpi=110, bbox_inches="tight")

print(f"Spot LTP          : {spot:.2f}")
print(f"ATM strike        : {sf['atm_strike']:.0f}")
print(f"Synthetic future  : {synthetic:.2f}   (= strike + ATM call - ATM put)")
print(f"Actual future     : {fut:.2f}")
print(f"Synthetic vs real : {synthetic - fut:+.2f} difference")
print("\nThe synthetic future, built purely from option prices, tracks the real future -")
print("and it is the FORWARD that Indian F&O Greeks are priced against (Black-76, next chapter).")
Live output
Spot LTP          : 24021.65
ATM strike        : 24000
Synthetic future  : 24058.30   (= strike + ATM call - ATM put)
Actual future     : 24053.60
Synthetic vs real : +4.70 difference

The synthetic future, built purely from option prices, tracks the real future -
and it is the FORWARD that Indian F&O Greeks are priced against (Black-76, next chapter).
Building a synthetic future chart
Synthetic future strike + call - put 24058 Actual future NFO 24054 Spot + carry 24022 + ~32 24054
No-arbitrage forces the synthetic, the future and spot-plus-carry together

The numbers nearly coincide - the synthetic future (24058), assembled entirely from option prices, lands within a handful of points of the real future (24054), which in turn is spot plus carry. Three different routes, one forward price, all welded together by no-arbitrage. That's not a coincidence; it's the law.

Why this matters for Indian options

Here is the payoff for our whole derivatives module, and the thing many courses get wrong for India. Indian index and stock options are settled against the futures/forward, not the spot - so the correct price to feed an options model is the forward, and the cleanest forward available is the synthetic future we just built, derived from the very options being priced. This is precisely why OpenAlgo computes a per-expiry synthetic future and uses it as the forward.

Key idea

For Indian F&O, the right "underlying" for pricing an option isn't the spot - it's the forward, best captured by the synthetic future from put-call parity. Getting this right is the difference between textbook options theory and options theory that actually fits Indian markets. It's also why the next chapter uses Black-76, the model built for options on a forward - not vanilla Black-Scholes.

Try it yourself

  • Compute the basis on a stock future (say RELIANCE30JUN26FUT vs RELIANCE). Is its carry larger or smaller in percentage terms than the index's?
  • Track the Nifty basis daily into expiry. Does it shrink toward zero as no-arbitrage demands?
  • Pull the synthetic future for the next month's expiry too. Is the further-dated forward higher (more carry), as you'd expect?

Recap

  • No-arbitrage - free money can't exist - is the gravity that prices every derivative: identical payoffs must have identical prices.
  • A future equals spot plus the cost of carry; the gap is the basis, which is forced to converge to zero at expiry.
  • Put-call parity binds calls, puts and the forward together - and lets you build a synthetic future from options (strike + call − put).
  • The synthetic future (24058) tracks the actual future (24054) and spot-plus-carry - three routes to one forward, welded by no-arbitrage.
  • For Indian F&O, the correct underlying is the forward / synthetic future, not spot - which is why pricing uses Black-76, not Black-Scholes.

We've built the forward that everything is priced against. Now we use it: the Black-76 model that turns a forward, a strike and a volatility into an option's fair price - and the Greeks that reveal how that option breathes.