Claude Skill

options-strategy-advisor

Options strategy analysis for Indian F&O markets (NSE). Use when user requests options strategy recommendations, P/L analysis, Greeks calculation, risk management, or F&O strategy planning for Nifty, Bank Nifty, or stock options.

LLM Mart · 0 points · 16 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download ajeeshworkspace-indian-trading-skills-skills_options-strategy-advisor-dc44698.zip · 29 KB
Part of ajeeshworkspace/indian-trading-skills — 10 skills

Install

skills CLI npx skills add https://github.com/ajeeshworkspace/indian-trading-skills/tree/master/skills/options-strategy-advisor
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install ajeeshworkspace-indian-trading-skills@llmmart
Git git clone https://github.com/ajeeshworkspace/indian-trading-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole ajeeshworkspace/indian-trading-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Options Strategy Advisor — Indian F&O Markets (NSE)

Overview

This skill provides comprehensive options strategy analysis tailored to the Indian Futures & Options market on the National Stock Exchange (NSE). It covers strategy selection, live data retrieval, margin estimation, profit/loss simulation, Greeks analysis, and risk management — all adapted for the specific characteristics of Indian F&O trading.


Indian F&O Market Characteristics

Exercise Style

  • European-style exercise only. Options on NSE can only be exercised at expiry, not before. This simplifies pricing (Black-Scholes applies directly without early-exercise adjustments) and means time value is always fully captured by the seller until expiry.

Expiry Schedule

Underlying Expiry Day Expiry Type
NIFTY Thursday Weekly + Monthly
BANK NIFTY Wednesday Weekly + Monthly
FINNIFTY Tuesday Weekly + Monthly
SENSEX (BSE) Friday Weekly + Monthly
Stock Options Last Thursday Monthly only
  • Monthly expiry is the last Thursday of the month (or preceding trading day if Thursday is a holiday).
  • Weekly expiries are available only for index options, not individual stocks.

Lot Sizes

Lot sizes are periodically revised by the exchanges. Always verify current lot sizes using the Groww MCP tool fno_mcx_contracts_search_tool before calculating margin or position size. Recent reference values:

  • NIFTY: 75 (recently changed — confirm via MCP)
  • BANK NIFTY: 15 (recently changed — confirm via MCP)
  • FINNIFTY: 25
  • Stock options: Varies by stock (check contract specifications)

Margin Requirements

SEBI mandates the following margin components for F&O:

  1. SPAN Margin — Risk-based margin calculated by the exchange clearing corporation.
  2. Exposure Margin — Additional margin over SPAN for market-wide risk.
  3. Peak Margin — Intraday margin snapshots; brokers must collect at least the peak margin observed during the day.

Use calculate_fno_margin to get exact margin for any trade before placing it.

Transaction Costs

  • STT (Securities Transaction Tax): Levied on the sell side of options at 0.0625% of the intrinsic value on exercise (for ITM options at expiry). For futures, STT is 0.0125% on sell side.
  • Brokerage: Varies by broker (Groww charges per-order flat fees).
  • Exchange charges, GST, SEBI turnover fee, stamp duty also apply.

F&O Ban Mechanism

When the market-wide position limit (MWPL) for a stock's F&O contracts exceeds 95%, SEBI places the stock under an F&O ban. During the ban:

  • No new positions can be initiated.
  • Only squaring off (closing) of existing positions is allowed.
  • The ban is lifted when MWPL drops below 80%.

Broker MCP Tool Integration

This skill uses broker MCP tools for live market data and execution support. Use whichever broker is connected (Groww or Zerodha Kite). Always prefer live data over assumptions.

Groww MCP Tools (if connected)

Tool Purpose
get_ltp (segment=FNO, query_type=fno) Live option/futures prices and OI
get_quotes_and_depth (segment=FNO) Bid/ask spreads and market depth
fno_mcx_contracts_search_tool Search F&O contracts, lot sizes, expiries
fetch_historical_candle_data (segment=FNO) Historical option price data
fetch_curated_fno F&O gainers, losers, most traded
get_open_interest_analysis OI structure, PCR, support/resistance
get_greeks_for_fno_contract Live Greeks for specific contracts
get_greeks_for_fno_symbol Greeks for all contracts of an underlying
get_atm_straddle_chart ATM straddle premium analysis
get_payoff_chart_steps Payoff diagram generation instructions
calculate_fno_margin Margin requirement calculation
get_available_margin_details User's available margin
resolve_market_time_and_calendar Market hours and trading calendar

Zerodha Kite MCP Tools (if connected)

Tool Purpose
get_ltp Last traded price for F&O instruments
get_quotes Real-time quotes with bid/ask depth
get_ohlc OHLC data for options/futures contracts
get_historical_data Historical candle data for F&O
search_instruments Search for F&O contracts by name/expiry
get_margins Account margins and available funds
get_positions Current F&O positions
get_orders / get_order_history Order status and execution details
place_order / modify_order / cancel_order Order management
place_gtt_order / get_gtts GTT order management

Tool Equivalence Map

Action Groww MCP Zerodha Kite MCP
Live price get_ltp get_ltp
Market depth get_quotes_and_depth get_quotes
Historical data fetch_historical_candle_data get_historical_data
Search contracts fno_mcx_contracts_search_tool search_instruments
Margin check calculate_fno_margin / get_available_margin_details get_margins
Positions get_my_trading_positions_today get_positions
Place orders place_fno_order place_order

Supported Strategies

Income Strategies

  1. Covered Call — Long underlying futures + Short OTM Call

    • Objective: Generate income on existing long position.
    • Best when: Mildly bullish, want to earn premium.
    • Indian note: Use futures as underlying (no direct stock delivery for covered calls in F&O segment).
  2. Cash-Secured Put — Short OTM Put (with margin set aside)

    • Objective: Earn premium while waiting to buy at a lower price.
    • Best when: Bullish on underlying, willing to take delivery equivalent.
    • Indian note: Physical settlement applies for stock options (ITM at expiry).

Protection Strategies

  1. Protective Put — Long underlying + Long Put

    • Objective: Insure existing long position against downside.
    • Best when: Want to cap losses while maintaining upside.
  2. Collar — Long underlying + Long Put + Short Call

    • Objective: Cap both upside and downside. Zero-cost collar if premiums offset.
    • Best when: Want protection without paying net premium.

Directional Strategies

  1. Bull Call Spread — Long lower-strike Call + Short higher-strike Call

    • Objective: Limited-risk bullish bet.
    • Best when: Moderately bullish, want defined risk.
  2. Bear Put Spread — Long higher-strike Put + Short lower-strike Put

    • Objective: Limited-risk bearish bet.
    • Best when: Moderately bearish, want defined risk.
  3. Bull Put Spread — Short higher-strike Put + Long lower-strike Put

    • Objective: Credit spread, profit if price stays above short strike.
    • Best when: Mildly bullish, want to collect premium.
  4. Bear Call Spread — Short lower-strike Call + Long higher-strike Call

    • Objective: Credit spread, profit if price stays below short strike.
    • Best when: Mildly bearish, want to collect premium.

Volatility Strategies

  1. Long Straddle — Long ATM Call + Long ATM Put

    • Objective: Profit from large move in either direction.
    • Best when: Expecting high volatility (e.g., pre-budget, RBI policy, earnings).
    • Indian note: Popular before Union Budget day, election results, RBI MPC.
  2. Short Straddle — Short ATM Call + Short ATM Put

    • Objective: Profit from time decay when expecting range-bound movement.
    • Best when: Low implied volatility expected, range-bound market.
    • Indian note: Very popular on weekly expiry day for Nifty/Bank Nifty.
  3. Long Strangle — Long OTM Call + Long OTM Put

    • Objective: Cheaper alternative to straddle for volatility plays.
    • Best when: Expecting very large move, want lower cost than straddle.
  4. Short Strangle — Short OTM Call + Short OTM Put

    • Objective: Wider profit zone than short straddle, less premium received.
    • Best when: Expecting range-bound, comfortable with wider risk.

Range-Bound Strategies

  1. Iron Condor — Bull Put Spread + Bear Call Spread

    • Objective: Defined-risk range-bound strategy.
    • Best when: Expecting low volatility, want defined max loss.
    • Indian note: Very popular for weekly Nifty expiry plays.
  2. Iron Butterfly — Short ATM Call + Short ATM Put + Long OTM Call + Long OTM Put

    • Objective: Defined-risk version of short straddle.
    • Best when: Expecting pin at a specific strike (max pain).

Advanced Strategies

  1. Calendar Spread (Time Spread) — Short near-expiry option + Long far-expiry option (same strike)

    • Objective: Profit from differential time decay.
    • Best when: Expecting current expiry to decay faster, longer-term view intact.
    • Indian note: Useful between weekly and monthly expiry cycles.
  2. Diagonal Spread — Calendar spread with different strikes.

    • Objective: Directional bias + time decay benefit.
    • Best when: Have a directional view and want to finance via near-expiry sale.
  3. Ratio Spread — Buy N options at one strike, sell M options at another (N != M).

    • Objective: Reduce cost of directional trade; accept risk on extreme moves.
    • Best when: Strong view on direction but want reduced cost.
    • Caution: Naked leg creates unlimited risk on one side.

Workflow

Follow this sequence when advising on an options strategy:

Step 1: Gather Input

Collect the following from the user:

  • Underlying: Which index or stock? (NIFTY, BANKNIFTY, FINNIFTY, or a specific stock)
  • Market View: Bullish, bearish, neutral, volatile, or range-bound?
  • Strategy Preference: Specific strategy or let the advisor recommend?
  • Expiry: Weekly or monthly? Specific date?
  • Risk Tolerance: Maximum loss acceptable? Capital available?
  • Objective: Income generation, hedging, speculation, or volatility play?

Step 2: Fetch Live Data via Groww MCP

  1. Resolve market time and calendar:

    resolve_market_time_and_calendar() → confirm market is open, get trading days to expiry
    
  2. Search for contracts:

    fno_mcx_contracts_search_tool(search_term="NIFTY 25 MAR") → get exact trading symbols
    
  3. Get live prices:

    get_ltp(search_queries=["nifty 24000 CE mar", "nifty 24000 PE mar"], segment="FNO", query_type="fno")
    
  4. Get Greeks:

    get_greeks_for_fno_contract(search_queries=["nifty 24000 mar CE"], expiry="2026-03-26")
    
  5. Analyze Open Interest:

    get_open_interest_analysis(symbol="NIFTY", view="all")
    
  6. Check ATM straddle premium (for volatility assessment):

    get_atm_straddle_chart(symbol="NIFTY")
    

Step 3: Calculate Margin Requirement

For each leg of the strategy that involves selling (writing) options:

calculate_fno_margin(
    trading_symbol="NIFTY25MAR24000CE",
    num_lots=1,
    transaction_type="SELL",
    product="NRML"
)

Also check user's available margin:

get_available_margin_details()

Step 4: Simulate P/L Across Price Range

Use the scripts/black_scholes.py script or manual calculation:

  • Define a price range (e.g., underlying +/- 5% from current price).
  • For each price point, calculate P/L for each leg.
  • Sum up P/L across all legs.
  • Identify breakeven points, max profit, max loss.

Key calculations:

  • Breakeven = Strike +/- Net Premium (for single-leg strategies)
  • Max Profit = Net Premium Received (for credit strategies) or Strike Width - Net Debit (for debit spreads)
  • Max Loss = Net Premium Paid (for debit strategies) or Strike Width - Net Credit (for credit spreads)

Step 5: Generate ASCII P/L Diagram

Create a visual payoff diagram showing:

  • X-axis: Underlying price at expiry
  • Y-axis: Profit/Loss per lot
  • Breakeven point(s) marked
  • Max profit and max loss zones labeled

Also use get_payoff_chart_steps() for Groww's built-in payoff chart generation.

Step 6: Provide Risk Management Guidance

Include in every recommendation:

  • Position sizing: How many lots based on capital and risk tolerance.
  • Stop-loss levels: When to exit (e.g., if loss exceeds 2x premium received).
  • Adjustment triggers: When and how to adjust the strategy.
  • Expiry management: Roll, close, or let expire — guidance based on ITM/OTM status.
  • STT warning: Remind about STT on ITM options at expiry (can erode profits significantly).
  • Margin monitoring: Warn about peak margin requirements and potential margin calls.

Step 7: Save Report

Present the complete analysis as a structured report:

=== OPTIONS STRATEGY REPORT ===
Date: [current date]
Underlying: [symbol] @ [current price]
Strategy: [strategy name]
Expiry: [expiry date] ([days to expiry] days)

--- LEGS ---
Leg 1: [BUY/SELL] [qty] [CALL/PUT] @ Strike [strike] for [premium]
Leg 2: [BUY/SELL] [qty] [CALL/PUT] @ Strike [strike] for [premium]

--- KEY METRICS ---
Net Premium: [debit/credit] [amount] per lot
Max Profit: [amount] per lot (at [price])
Max Loss: [amount] per lot (at [price])
Breakeven: [price(s)]
Risk-Reward Ratio: [ratio]
Probability of Profit: [estimate based on delta]

--- GREEKS (NET POSITION) ---
Delta: [value] | Gamma: [value] | Theta: [value] | Vega: [value]

--- MARGIN REQUIREMENT ---
Total Margin: [amount]
Available Margin: [amount]
Margin Utilization: [percentage]

--- P/L DIAGRAM ---
[ASCII payoff chart]

--- RISK MANAGEMENT ---
- Stop Loss: [criteria]
- Adjustment Plan: [when and how]
- Expiry Action: [recommendation]
- STT Impact: [if applicable]

Strategy Selection Guide

Use this decision tree to recommend strategies based on user's market view:

Bullish View

  • Strong bullish: Long Call or Bull Call Spread
  • Mildly bullish: Bull Put Spread (credit) or Covered Call
  • Bullish + high IV: Bull Put Spread (sell expensive puts)
  • Bullish + low IV: Long Call or Bull Call Spread (buy cheap options)

Bearish View

  • Strong bearish: Long Put or Bear Put Spread
  • Mildly bearish: Bear Call Spread (credit)
  • Bearish + high IV: Bear Call Spread (sell expensive calls)
  • Bearish + low IV: Long Put or Bear Put Spread

Neutral / Range-Bound View

  • Tight range expected: Short Straddle or Iron Butterfly
  • Wider range expected: Short Strangle or Iron Condor
  • Neutral + want defined risk: Iron Condor or Iron Butterfly

Volatile View (Expecting Big Move)

  • Direction unknown, big move expected: Long Straddle
  • Direction unknown, very big move expected: Long Strangle (cheaper)
  • Pre-event (budget, RBI, earnings): Long Straddle or Long Strangle

Time Decay Play

  • Near-term decay focus: Calendar Spread
  • Directional + decay: Diagonal Spread

Important Indian Market Considerations

India VIX

  • India VIX measures the market's expectation of 30-day volatility.
  • VIX > 20: High volatility environment — favor long volatility strategies.
  • VIX < 15: Low volatility environment — favor short volatility strategies.
  • VIX between 15-20: Normal range — use directional or range-bound strategies.
  • VIX typically spikes before elections, budgets, RBI policy, and global crises.

Weekly Expiry Trading

  • Thursday (Nifty): Most liquid expiry. Short straddle/strangle sellers dominate. Theta decay is highest on the expiry day.
  • Wednesday (Bank Nifty): High gamma risk. Moves can be sharp near expiry.
  • Premium sellers should be cautious of gamma risk on expiry day — a small move in the underlying can cause large P/L swings.

Physical Settlement (Stock Options)

  • Stock options that expire ITM are physically settled — actual delivery of shares.
  • This requires full delivery margin (value of shares). Plan exits before expiry to avoid unexpected margin requirements.
  • Index options are cash-settled — no delivery concerns.

Max Pain

  • Max Pain is the strike price at which the maximum number of options (calls + puts) expire worthless, causing minimum payout by option writers.
  • Indian markets tend to gravitate toward max pain on expiry day, especially for Nifty weekly expiry.
  • Use OI analysis to identify max pain and position accordingly.

OI-Based Analysis

  • High Call OI at a strike: Acts as resistance. Call writers are betting the price won't cross this level.
  • High Put OI at a strike: Acts as support. Put writers are betting the price won't fall below this level.
  • PCR (Put-Call Ratio):
    • PCR > 1.2: Bullish signal (more puts written, indicating support)
    • PCR < 0.8: Bearish signal (more calls written, indicating resistance)
    • PCR between 0.8-1.2: Neutral

Error Handling

  • If broker MCP tools (Groww or Zerodha) return errors, inform the user and suggest checking market hours or contract availability. Try the alternative broker's equivalent tool if available.
  • If a contract search yields no results, try alternative search terms or check if the expiry has passed.
  • If margin data is unavailable, provide theoretical estimates with a disclaimer.
  • Always validate that the market is open before fetching live data — use resolve_market_time_and_calendar().
  • If a stock is under F&O ban, alert the user immediately and suggest alternative underlyings.
Files (indian-trading-skills)
  • references
    • indian_fno_guide.md 32.1 KB
      # Indian F&O Market Reference Guide
      
      A comprehensive reference for options strategy advisors working with NSE Futures &
      Options. This document covers market structure, regulations, costs, and practical
      trading considerations specific to the Indian derivatives market.
      
      ---
      
      ## Table of Contents
      
      1. [NSE F&O Market Structure](#1-nse-fo-market-structure)
      2. [SEBI Margin Framework](#2-sebi-margin-framework)
      3. [STT and Transaction Charges](#3-stt-and-transaction-charges)
      4. [European vs American Exercise](#4-european-vs-american-exercise)
      5. [Weekly vs Monthly Expiry Strategies](#5-weekly-vs-monthly-expiry-strategies)
      6. [OI-Based Support and Resistance](#6-oi-based-support-and-resistance)
      7. [Max Pain Theory for Indian Expiry](#7-max-pain-theory-for-indian-expiry)
      8. [India VIX and Option Premiums](#8-india-vix-and-option-premiums)
      9. [F&O Ban Mechanism](#9-fo-ban-mechanism)
      10. [Common Strategies for Indian Traders](#10-common-strategies-for-indian-traders)
      11. [Position Sizing for Indian F&O](#11-position-sizing-for-indian-fo)
      12. [Rollover Strategies Near Expiry](#12-rollover-strategies-near-expiry)
      
      ---
      
      ## 1. NSE F&O Market Structure
      
      ### Contract Specifications
      
      #### Index Options
      
      | Parameter       | NIFTY 50        | BANK NIFTY      | FINNIFTY        | MIDCAP NIFTY    |
      |-----------------|-----------------|-----------------|-----------------|-----------------|
      | Lot Size        | 75 (verify)     | 15 (verify)     | 25              | 50              |
      | Tick Size       | 0.05            | 0.05            | 0.05            | 0.05            |
      | Strike Interval | 50              | 100             | 50              | 25              |
      | Expiry          | Weekly (Thu)    | Weekly (Wed)    | Weekly (Tue)    | Monthly (Mon)   |
      | Settlement      | Cash            | Cash            | Cash            | Cash            |
      | Exercise        | European        | European        | European        | European        |
      
      Note: Lot sizes are revised periodically by the exchange. Always verify the current
      lot size using `fno_mcx_contracts_search_tool` before trading.
      
      #### Stock Options
      
      | Parameter       | Value                                          |
      |-----------------|------------------------------------------------|
      | Lot Size        | Varies by stock (set by exchange)              |
      | Tick Size       | 0.05                                           |
      | Strike Interval | Varies by stock price level                    |
      | Expiry          | Monthly only (last Thursday of the month)      |
      | Settlement      | Physical delivery (shares change hands)        |
      | Exercise        | European                                       |
      
      #### Futures Contracts
      
      | Parameter       | Index Futures   | Stock Futures   |
      |-----------------|-----------------|-----------------|
      | Contract Months | Near, Next, Far | Near, Next, Far |
      | Lot Size        | Same as options | Same as options |
      | Settlement      | Cash (daily MTM)| Physical (at expiry) |
      | Last Trading Day| Last Thursday of expiry month                  |
      
      ### Trading Hours
      
      | Session         | Time (IST)       |
      |-----------------|------------------|
      | Pre-Open        | 09:00 - 09:08    |
      | Normal Trading  | 09:15 - 15:30    |
      | Post-Close      | 15:40 - 16:00    |
      
      ### Expiry Schedule
      
      - **NIFTY:** Every Thursday (weekly) + last Thursday of month (monthly)
      - **BANK NIFTY:** Every Wednesday (weekly) + last Wednesday of month (monthly)
      - **FINNIFTY:** Every Tuesday (weekly)
      - **SENSEX (BSE):** Every Friday (weekly)
      - **Stock Options:** Last Thursday of month only
      - **Stock Futures:** Last Thursday of month
      
      If the scheduled expiry day is a trading holiday, the expiry moves to the
      preceding trading day.
      
      ### Available Strikes
      
      The exchange lists strikes around the current spot price in predefined intervals.
      As the underlying moves, new strikes are added. Strikes well away from the
      current price (deep OTM or deep ITM) may have very low liquidity.
      
      ---
      
      ## 2. SEBI Margin Framework
      
      SEBI's margin framework for F&O trading consists of multiple components designed
      to cover different types of risk.
      
      ### Margin Components
      
      #### 1. SPAN Margin (Initial Margin)
      
      SPAN (Standard Portfolio Analysis of Risk) calculates the worst-case loss of a
      portfolio over a one-day time horizon. It considers:
      
      - Price scanning range (based on volatility)
      - Up and down movements in the underlying
      - Changes in volatility
      - Time decay
      - Correlation between instruments (for portfolio margining)
      
      SPAN margin is recalculated multiple times during the trading day.
      
      #### 2. Exposure Margin
      
      An additional margin collected over and above SPAN to cover risks not captured
      by SPAN:
      
      - **Index derivatives:** 3% of the notional value of the contract
      - **Stock derivatives:** Higher of 5% or 1.5 standard deviations of the
        logarithmic returns of the stock over the last 6 months
      
      #### 3. Premium Margin (for Option Buyers)
      
      Option buyers pay the full premium upfront. This is not a margin in the
      traditional sense but the cost of the option position.
      
      #### 4. Assignment Margin
      
      Collected from option sellers who are assigned. Covers the settlement obligation.
      
      ### Peak Margin Requirement
      
      Since September 2021, SEBI mandates **peak margin** collection:
      
      - Brokers must collect margins based on the peak (highest) margin required
        during the trading day, not just the end-of-day margin.
      - The clearing corporation takes at least 4 random snapshots during the day.
      - If a client's margin falls below the peak requirement, a penalty is levied.
      - This means intraday positions also require full margin.
      
      ### Margin for Multi-Leg Strategies
      
      For hedged positions (spreads, iron condors, etc.), SPAN recognizes the hedge
      benefit and reduces the margin requirement compared to naked positions. However,
      the margin benefit is only available if both legs are in the same expiry.
      
      For example:
      - **Naked short NIFTY call:** Requires full SPAN + exposure margin (often
        Rs. 1,00,000+ per lot).
      - **Bear Call Spread (short call + long call):** Margin is significantly lower
        because the long call limits the maximum loss.
      
      Use `calculate_fno_margin` to get the exact margin for any combination.
      
      ### Margin Penalty
      
      If the client's margin falls short of the required margin:
      
      | Shortfall                | Penalty                      |
      |--------------------------|------------------------------|
      | Up to 10% of required   | 0.5% per day of shortfall    |
      | More than 10%            | 1.0% per day of shortfall    |
      
      Penalties are levied per day and compounded for repeat offenses within a month.
      
      ---
      
      ## 3. STT and Transaction Charges
      
      ### Securities Transaction Tax (STT)
      
      STT is a tax levied by the government on securities transactions.
      
      | Transaction               | STT Rate                    | Levied On        |
      |---------------------------|-----------------------------|------------------|
      | Futures (sell)            | 0.0125%                     | Sell turnover     |
      | Options (sell)            | 0.0625%                     | Sell premium      |
      | Options (exercise at expiry, ITM) | 0.0625%            | Intrinsic value   |
      
      **Critical STT Trap for Options:**
      When an option expires in-the-money (ITM), STT is levied on the entire
      intrinsic value (settlement value), not just the premium. This can result in
      STT exceeding the actual profit.
      
      Example:
      - You sell a NIFTY 24000 CE at Rs. 5 (premium received: 5 x 75 = Rs. 375).
      - NIFTY expires at 24010 (option is ITM by Rs. 10).
      - Intrinsic value on exercise: 10 x 75 = Rs. 750.
      - STT on exercise: 0.0625% of Rs. 750 = Rs. 0.47 per share = Rs. 35.16.
      - But if NIFTY expires at 24200, intrinsic value = 200 x 75 = Rs. 15,000.
      - STT = 0.0625% of Rs. 15,000 = Rs. 9.38.
      
      For deep ITM options at expiry, this tax can be substantial. Always square off
      ITM positions before expiry to avoid the exercise-based STT.
      
      ### Other Transaction Charges
      
      | Charge                    | Rate (approximate)          |
      |---------------------------|-----------------------------|
      | Exchange Transaction Fee  | ~0.05% (NSE)                |
      | SEBI Turnover Fee         | Rs. 10 per crore            |
      | GST                       | 18% on brokerage + fees     |
      | Stamp Duty                | 0.003% (buy side) for options|
      
      ### Total Cost Per Trade (Approximate)
      
      For a typical NIFTY option trade (1 lot, premium Rs. 100):
      - Brokerage: Rs. 20 (flat per order, typical discount broker)
      - STT: Rs. 4.69 (on sell)
      - Exchange charges: Rs. 3.97
      - GST: Rs. 4.31
      - SEBI fee: Rs. 0.08
      - Stamp duty: Rs. 0.23
      - **Total: approximately Rs. 33 per side**
      
      ---
      
      ## 4. European vs American Exercise
      
      ### All NSE Options Are European
      
      Since 2010, all options on NSE (both index and stock) are European-style. This
      means:
      
      - **Cannot exercise before expiry.** The option can only be exercised (or is
        auto-exercised) at expiry.
      - **No early exercise risk for sellers.** Sellers do not need to worry about
        being assigned before expiry.
      - **Time value is always retained.** Unlike American options where early exercise
        can strip time value, European options always trade with full time value intact.
      
      ### Implications for Pricing
      
      - **Black-Scholes model applies directly.** No need for binomial trees or
        American option adjustments. The closed-form Black-Scholes formula is exact
        for European options.
      - **Put-Call Parity holds exactly:**
        ```
        Call - Put = S * exp(-q*T) - K * exp(-r*T)
        ```
        where S = spot, K = strike, r = risk-free rate, q = dividend yield, T = time.
      
      - **No early exercise premium.** American options sometimes carry a premium
        (especially deep ITM puts) because of the early exercise feature. European
        options have no such premium.
      
      ### Implications for Strategies
      
      - **Covered call sellers:** No risk of early assignment. The short call will
        only be exercised at expiry.
      - **Calendar spreads:** Work cleanly because both legs follow European rules.
      - **Deep ITM options:** May trade at a slight discount to intrinsic value
        (unlike American options which always trade at or above intrinsic). This is
        because you cannot exercise early to capture the intrinsic value.
      
      ---
      
      ## 5. Weekly vs Monthly Expiry Strategies
      
      ### Weekly Expiry Characteristics
      
      | Feature              | Weekly                    | Monthly                    |
      |----------------------|---------------------------|----------------------------|
      | Time to Expiry       | 0-7 days                  | 0-30+ days                 |
      | Theta Decay          | Very rapid (accelerating) | Gradual (more predictable) |
      | Gamma Risk           | Extremely high near expiry| Moderate                   |
      | Premium              | Lower (less time value)   | Higher                     |
      | Liquidity            | High for index options    | High for all               |
      | Available For        | Index options only        | All stocks and indices     |
      
      ### Weekly Expiry Strategies
      
      #### 1. Short Straddle / Strangle on Expiry Day
      
      The most popular strategy among Indian traders for weekly expiry:
      
      - Sell ATM straddle or OTM strangle on the expiry morning (or 1-2 days before).
      - Benefit from accelerated theta decay in the final hours.
      - Risk: Gamma is extremely high, meaning a small move in the underlying causes
        a large change in delta and hence P&L.
      
      **Typical approach:**
      - Sell straddle at 9:15 AM on expiry day.
      - Set stop-loss at 1.5x to 2x of premium received.
      - Close position by 2:30 PM or let expire.
      - Monitor continuously for sharp moves.
      
      #### 2. Iron Condor for Range Days
      
      - Deploy iron condor 1-2 days before expiry.
      - Choose strikes based on expected range (use OI data for support/resistance).
      - Benefit from rapid decay with defined risk.
      
      #### 3. Directional Plays with Weekly Options
      
      - Buy weekly OTM options when expecting a large move (event-based).
      - Very cheap premium but high risk of total loss.
      - Best used for asymmetric bets (small risk, large potential reward).
      
      ### Monthly Expiry Strategies
      
      #### 1. Positional Strategies
      
      - Deploy strategies 15-25 days before monthly expiry.
      - Allow time for the thesis to play out.
      - More stable Greeks compared to weekly options.
      
      #### 2. Earnings-Based Plays
      
      - Stock options are monthly only, so earnings plays use monthly expiry.
      - Long straddle/strangle before earnings announcement.
      - Short straddle/strangle after earnings if IV crush is expected.
      
      #### 3. Rollover Period Strategies
      
      - In the last week before monthly expiry, rollover activity creates opportunities.
      - Open interest shifts from current month to next month.
      - Basis (difference between futures and spot) widens or narrows, creating
        spread trading opportunities.
      
      ---
      
      ## 6. OI-Based Support and Resistance
      
      ### Interpreting Open Interest
      
      Open Interest (OI) represents the total number of outstanding derivative
      contracts that have not been settled.
      
      #### Call OI = Resistance
      
      When a large amount of call OI builds up at a particular strike, it suggests
      that:
      - Many traders have sold (written) calls at that strike.
      - These writers will actively defend their position by selling the underlying
        near that level.
      - This creates overhead resistance for the underlying.
      
      Example: If NIFTY 24500 CE has the highest call OI, 24500 is likely to act
      as resistance.
      
      #### Put OI = Support
      
      When a large amount of put OI builds up at a particular strike:
      - Many traders have sold (written) puts at that strike.
      - These writers will defend their position by buying the underlying near that level.
      - This creates support for the underlying.
      
      Example: If NIFTY 23500 PE has the highest put OI, 23500 is likely to act
      as support.
      
      ### Put-Call Ratio (PCR)
      
      PCR = Total Put OI / Total Call OI
      
      | PCR Range     | Interpretation                                        |
      |---------------|-------------------------------------------------------|
      | PCR > 1.3     | Very bullish (excessive put writing = strong support)  |
      | 1.1 - 1.3     | Moderately bullish                                    |
      | 0.9 - 1.1     | Neutral                                               |
      | 0.7 - 0.9     | Moderately bearish                                    |
      | PCR < 0.7     | Very bearish (excessive call writing = strong ceiling) |
      
      **Contrarian interpretation:** Extreme PCR values can also signal reversals:
      - Very high PCR (> 1.5): Market may be over-hedged, potential for short covering rally.
      - Very low PCR (< 0.5): Market may be complacent, potential for downside.
      
      ### Change in OI Analysis
      
      More informative than absolute OI is the change in OI during the day:
      
      | Price Move | OI Change | Interpretation        |
      |------------|-----------|----------------------|
      | Up         | Increase  | Long buildup (bullish)|
      | Up         | Decrease  | Short covering (less bullish) |
      | Down       | Increase  | Short buildup (bearish)|
      | Down       | Decrease  | Long unwinding (less bearish)|
      
      Use `get_open_interest_analysis` with `view='change_in_oi'` to get intraday OI
      change data.
      
      ### Practical OI Analysis Workflow
      
      1. Call `get_open_interest_analysis(symbol="NIFTY", view="all")` to get full picture.
      2. Identify the strikes with highest call OI (resistance) and put OI (support).
      3. Check PCR for directional bias.
      4. Use `view='change_in_oi'` to see if OI is building or unwinding during the day.
      5. Combine with price action and VIX for strategy selection.
      
      ---
      
      ## 7. Max Pain Theory for Indian Expiry
      
      ### What is Max Pain?
      
      Max Pain is the strike price at which the maximum number of options contracts
      (calls + puts combined) expire worthless. At this point, option buyers lose the
      most money and option sellers (writers) pay out the least.
      
      ### Calculation
      
      For each strike price:
      1. Calculate the total value of all call options that would be ITM if the
         underlying settled at that strike.
      2. Calculate the total value of all put options that would be ITM if the
         underlying settled at that strike.
      3. Add call pain + put pain = total pain at that strike.
      4. The strike with the minimum total pain is the Max Pain strike.
      
      ### Max Pain in Indian Markets
      
      Max Pain is particularly relevant for Indian weekly expiry because:
      
      1. **Large option writing community:** India has a massive number of retail
         option sellers. These sellers collectively have an interest in the underlying
         settling near max pain.
      
      2. **Weekly expiry concentration:** A disproportionate amount of OI is
         concentrated in weekly options, making max pain more influential.
      
      3. **Hedging activity by institutional writers:** When the underlying moves away
         from max pain, institutional option sellers hedge by buying/selling the
         underlying or futures, which pushes the price back toward max pain.
      
      4. **Empirical observation:** NIFTY weekly expiry often settles within 50-100
         points of the max pain strike, especially on low-volatility days.
      
      ### Trading with Max Pain
      
      - **Before expiry (2-3 days):** If the underlying is far from max pain, expect
        a gravitational pull toward max pain.
      - **On expiry day:** If the underlying is near max pain, deploy range-bound
        strategies (iron condor, short strangle).
      - **Deviation from max pain:** Strong trend days or event-driven moves can
        override max pain. Do not rely on max pain alone.
      
      ### Limitations
      
      - Max pain shifts throughout the week as OI changes.
      - On high-volatility days, max pain is less reliable.
      - It is a tendency, not a certainty. Use it as one data point among many.
      
      ---
      
      ## 8. India VIX and Option Premiums
      
      ### What is India VIX?
      
      India VIX (Volatility Index) measures the market's expectation of volatility
      over the next 30 calendar days. It is computed from NIFTY option prices using
      a model-free approach similar to the CBOE VIX methodology.
      
      - **India VIX is expressed as an annualized percentage.**
      - India VIX of 15 means the market expects NIFTY to move approximately
        15% / sqrt(12) = ~4.3% over the next month.
      - For a daily expected move: India VIX / sqrt(252).
      
      ### VIX Levels and Interpretation
      
      | VIX Level | Market Regime    | Strategy Implications                     |
      |-----------|------------------|-------------------------------------------|
      | < 12      | Very low vol     | Sell options cautiously; premiums are thin |
      | 12 - 15   | Low vol          | Favor selling strategies (iron condor)     |
      | 15 - 20   | Normal           | Balanced approach; both buying and selling |
      | 20 - 25   | Elevated         | Premiums are rich; selling is attractive    |
      | 25 - 35   | High vol         | Selling is lucrative but risky; wide stops |
      | > 35      | Crisis/Panic     | Extreme caution; only defined-risk trades  |
      
      ### VIX-Premium Relationship
      
      - **VIX rising:** Option premiums increase across all strikes. Sellers receive
        more premium but face higher risk. Buyers pay more but have higher probability
        of a profitable move.
      - **VIX falling (IV crush):** Option premiums shrink. This is painful for option
        buyers and beneficial for sellers.
      
      ### Common VIX Patterns in India
      
      1. **Pre-budget spike:** VIX typically rises 5-10 points in the 2-3 days before
         the Union Budget and crashes 3-5 points immediately after.
      2. **Pre-election volatility:** VIX can spike to 25-35 before general election
         results and crash dramatically after results.
      3. **RBI MPC meetings:** Moderate VIX increase before policy decisions, quick
         normalization after.
      4. **Global events:** VIX correlates with global volatility (US VIX, geopolitical
         events).
      
      ### Using VIX for Strategy Selection
      
      - **VIX > 20 and you are selling options:** Collect premium but use defined-risk
        strategies (iron condor, spreads) rather than naked selling.
      - **VIX < 13 and you are buying options:** Premiums are cheap; good for
        directional bets or straddles before expected events.
      - **VIX dropping sharply:** If you have long option positions, close them quickly
        to avoid IV crush losses.
      - **VIX at extremes:** Mean reversion is likely. VIX above 30 tends to revert;
        VIX below 10 tends to rise.
      
      ---
      
      ## 9. F&O Ban Mechanism
      
      ### What is F&O Ban?
      
      When the aggregate open interest in all F&O contracts of a particular stock
      exceeds 95% of the Market-Wide Position Limit (MWPL), the stock enters an
      F&O ban period.
      
      ### MWPL Calculation
      
      MWPL = 20% of the free-float market capitalization of the stock
      (expressed in number of shares and then converted to the nearest lot size).
      
      ### Ban Rules
      
      1. **Entry into ban:** OI > 95% of MWPL. The exchange announces the ban,
         effective from the next trading day.
      2. **During ban:**
         - No new F&O positions can be created (no fresh buying or selling).
         - Only squaring off (closing) of existing positions is allowed.
         - Any order that would increase OI is rejected by the exchange.
      3. **Exit from ban:** OI drops below 80% of MWPL. The ban is lifted from the
         next trading day.
      4. **Penalty for violating ban:** If a trader increases positions during the ban
         period, a penalty of 1% of the value of the increased position per day is
         levied (minimum Rs. 5,000 to maximum Rs. 1,00,000).
      
      ### Stocks Commonly Under F&O Ban
      
      Stocks with lower free-float capitalization or those experiencing speculative
      activity are more prone to F&O bans. Examples include mid-cap stocks with
      active F&O trading.
      
      ### Impact on Strategies
      
      - **Before ban:** If OI is approaching 90% MWPL, be cautious about entering
        new positions. Existing positions can become illiquid.
      - **During ban:** You can only exit. This may lead to wider bid-ask spreads
        and difficulty closing at fair prices.
      - **Approaching ban exit:** As OI decreases toward 80%, traders start to
        position for the ban lift, which can create trading opportunities.
      
      ### Monitoring
      
      Always check if a stock is near the ban threshold before deploying strategies.
      The exchanges publish daily MWPL utilization reports. Groww MCP tools can help
      identify stocks in or near ban.
      
      ---
      
      ## 10. Common Strategies for Indian Traders
      
      ### Strategy 1: Nifty Short Straddle on Thursday Expiry
      
      **Setup:**
      - Sell NIFTY ATM CE + ATM PE on Thursday morning (expiry day).
      - Or deploy 1-2 days before expiry for more premium.
      
      **Rationale:**
      - Theta decay is fastest on expiry day.
      - NIFTY tends to settle near max pain on non-event days.
      
      **Risk Management:**
      - Stop-loss at 1.5x-2x premium received on either side.
      - Adjust by adding a hedge leg (convert to strangle by shifting the losing side).
      - Maximum loss should not exceed 3% of capital.
      
      **Capital Required:** SPAN + Exposure margin for 2 naked option legs (typically
      Rs. 1,50,000 - Rs. 2,00,000 per lot for the straddle).
      
      ### Strategy 2: Bank Nifty Iron Condor on Wednesday Expiry
      
      **Setup:**
      - Sell OTM Call + Sell OTM Put.
      - Buy further OTM Call + Buy further OTM Put (wings for protection).
      - Deploy 1-2 days before Wednesday expiry.
      
      **Rationale:**
      - Bank Nifty is more volatile than Nifty, so premiums are richer.
      - Iron condor provides defined risk unlike naked straddles.
      
      **Risk Management:**
      - Max loss is limited to (strike width - net credit) x lot size.
      - Exit if underlying breaches the short strikes.
      
      **Capital Required:** Reduced margin due to hedged position (typically
      Rs. 50,000 - Rs. 80,000 per lot).
      
      ### Strategy 3: Event-Day Long Straddle (Budget/RBI/Elections)
      
      **Setup:**
      - Buy ATM Straddle 2-3 days before the event.
      - Choose monthly expiry to avoid extreme theta decay.
      
      **Rationale:**
      - VIX spikes before events, but the actual move can exceed the premium paid.
      - Elections and budgets can cause 2-5% moves in Nifty.
      
      **Risk Management:**
      - Maximum loss is the total premium paid.
      - Set a target of 1.5x-2x premium paid for exit.
      - Close immediately after the event (within the first hour) to capture the move
        before IV crush sets in.
      
      **Capital Required:** Premium paid upfront (no margin since buyer).
      
      ### Strategy 4: Covered Call with Futures
      
      **Setup:**
      - Buy 1 lot of NIFTY futures.
      - Sell 1 lot of OTM NIFTY call option (same expiry).
      
      **Rationale:**
      - Generate income on the long futures position.
      - OTM call sold provides a buffer against small declines.
      
      **Risk Management:**
      - Downside risk is the futures P/L minus the call premium received.
      - Roll the short call to the next expiry if not exercised.
      
      **Capital Required:** Futures margin + reduced margin for the covered call
      (hedge benefit).
      
      ### Strategy 5: Calendar Spread (Weekly-to-Monthly)
      
      **Setup:**
      - Sell current week ATM option (weekly expiry).
      - Buy next month ATM option (monthly expiry).
      
      **Rationale:**
      - Near-week option decays faster than the far-month option.
      - Net theta is positive (earning from time decay difference).
      
      **Risk Management:**
      - If the underlying moves sharply, both legs move similarly and net P/L is small.
      - Close the short leg at expiry and either close the long leg or sell a new
        short leg against it.
      
      **Capital Required:** The net debit of the spread plus any margin requirement
      for the short leg.
      
      ---
      
      ## 11. Position Sizing for Indian F&O
      
      ### Per-Lot Risk Approach
      
      The safest approach to position sizing is to calculate risk per lot and size
      positions based on a fixed percentage of capital at risk.
      
      #### Step-by-Step Process
      
      1. **Determine total trading capital** allocated to F&O.
      2. **Set maximum risk per trade:** 1-2% of total capital for beginners,
         3-5% for experienced traders.
      3. **Calculate risk per lot** for the chosen strategy:
         - For defined-risk strategies (spreads, iron condors): Max loss = (strike
           width - net credit) x lot size.
         - For undefined-risk strategies (naked options): Use the stop-loss amount
           as the risk per lot.
      4. **Number of lots = Maximum risk per trade / Risk per lot.**
      
      #### Example
      
      - Trading capital: Rs. 10,00,000
      - Maximum risk per trade: 2% = Rs. 20,000
      - Strategy: Nifty Iron Condor
        - Short put: 23800, Long put: 23500 (width = 300)
        - Short call: 24200, Long call: 24500 (width = 300)
        - Net credit: Rs. 80 per share
        - Max loss per lot: (300 - 80) x 75 = Rs. 16,500
      - Number of lots: 20,000 / 16,500 = 1.21 -> trade 1 lot.
      
      ### Capital Allocation Guidelines
      
      | Capital Range         | Recommended Approach                           |
      |-----------------------|------------------------------------------------|
      | < Rs. 2,00,000        | Only buy options (no writing). 1-2 lots max.  |
      | Rs. 2-5,00,000        | Defined-risk strategies (spreads). 1-3 lots.  |
      | Rs. 5-15,00,000       | Mixed strategies. Allocate 60% to defined-risk.|
      | Rs. 15-50,00,000      | Full strategy repertoire. Use portfolio margin.|
      | > Rs. 50,00,000       | Multi-strategy portfolios. Diversify across underlyings.|
      
      ### Diversification Rules
      
      - Do not allocate more than 30% of capital to a single underlying.
      - Do not allocate more than 50% of capital to a single strategy type.
      - Maintain at least 20% of capital as cash buffer for margin calls and adjustments.
      - For undefined-risk positions, the margin requirement itself limits position size.
      
      ### Scaling Positions
      
      - Start with 1 lot. Add lots only after the position moves in your favor.
      - Never average down on a losing options position (especially for buyers).
      - For sellers, consider adding lots at different strikes (laddering) rather
        than doubling at the same strike.
      
      ---
      
      ## 12. Rollover Strategies Near Expiry
      
      ### What is Rollover?
      
      Rollover is the process of closing a position in the expiring contract and
      simultaneously opening the same (or similar) position in the next expiry
      contract. This allows traders to maintain their market exposure without physical
      settlement or contract expiry.
      
      ### When to Roll
      
      | Scenario                        | Action                                    |
      |---------------------------------|-------------------------------------------|
      | Profitable position, view intact | Roll to next expiry to extend duration    |
      | Losing position, view still valid| Roll and adjust (different strike/strategy)|
      | At-the-money near expiry        | Roll to avoid gamma risk and STT on exercise|
      | Deep OTM with no value          | Let expire worthless (no need to roll)     |
      | Deep ITM near expiry (stocks)   | Roll to avoid physical settlement          |
      
      ### Rollover Mechanics
      
      #### For Index Options (Cash Settled)
      
      1. Close the current expiry position.
      2. Open the equivalent position in the next expiry.
      3. The difference in premium between the two expiries is the rollover cost
         (debit) or credit.
      
      #### For Stock Options (Physically Settled)
      
      1. Must close before expiry to avoid physical delivery and associated margins.
      2. Stock ITM options at expiry trigger physical settlement, requiring full
         delivery margin (entire stock value x lot size).
      3. Close at least 2-3 days before expiry to avoid margin issues.
      
      ### Rollover Cost Analysis
      
      The rollover cost depends on:
      
      - **Time value of the new contract:** More time = more expensive.
      - **Changes in implied volatility:** If IV has risen, the new contract is
        more expensive; if IV has fallen, it may be cheaper.
      - **Interest rate component:** Far-month contracts embed higher carry cost.
      - **Dividends:** Expected dividends reduce call premiums and increase put
        premiums in the far-month contract.
      
      ### Roll Strategies
      
      #### 1. Straight Roll (Same Strike, Next Expiry)
      
      - Close current month position.
      - Open same strike, same type in next month.
      - Simplest but may not be optimal if the underlying has moved significantly.
      
      #### 2. Roll and Adjust (Different Strike)
      
      - Close current position.
      - Open at a different strike in next month that better reflects the current
        market conditions and your updated view.
      - Example: If you sold NIFTY 24500 CE and NIFTY has risen to 24400, roll to
        selling NIFTY 25000 CE in the next month.
      
      #### 3. Roll Up / Roll Down
      
      - **Roll Up (for calls):** Close lower-strike call, open higher-strike call.
        Used when the underlying has moved up and you want to capture more upside
        in a covered call strategy.
      - **Roll Down (for puts):** Close higher-strike put, open lower-strike put.
        Used when the underlying has fallen and you want to adjust your support level.
      
      #### 4. Roll and Widen (for Spreads)
      
      - Close the current spread.
      - Open a wider spread in the next month.
      - Captures more premium but increases maximum risk.
      
      ### Rollover Timing
      
      - **Ideal rollover window:** 3-5 days before expiry for monthly contracts;
        1-2 days for weekly contracts.
      - **Liquidity consideration:** Rollover too close to expiry results in wide
        bid-ask spreads in the expiring contract. Rollover too early means you
        miss out on theta decay in the current contract.
      - **Cost consideration:** The rollover cost (net debit/credit) should be
        factored into the overall strategy P/L.
      
      ### Rollover Ratio (Market Indicator)
      
      The rollover ratio for Nifty and Bank Nifty futures is a widely watched
      market indicator:
      
      - **High rollover (> 75%):** Strong conviction; traders are carrying positions
        forward. Bullish if long rollover, bearish if short rollover.
      - **Low rollover (< 60%):** Lack of conviction. Traders are squaring off
        rather than rolling forward. Can signal a trend change.
      - **Rollover with basis:** If the basis (futures premium over spot) increases
        during rollover, it signals bullish sentiment. If the basis decreases
        (or becomes negative/backwardation), it signals bearish sentiment.
      
      ---
      
      ## Appendix: Quick Reference Formulas
      
      ### Black-Scholes (European Options)
      
      ```
      d1 = [ln(S/K) + (r - q + sigma^2/2) * T] / (sigma * sqrt(T))
      d2 = d1 - sigma * sqrt(T)
      
      Call = S * exp(-q*T) * N(d1) - K * exp(-r*T) * N(d2)
      Put  = K * exp(-r*T) * N(-d2) - S * exp(-q*T) * N(-d1)
      ```
      
      Where:
      - S = Spot price
      - K = Strike price
      - T = Time to expiry (in years)
      - r = Risk-free rate (India 91-day T-bill, ~6.5-7%)
      - q = Dividend yield
      - sigma = Annualized volatility
      - N() = Cumulative standard normal distribution
      
      ### Greeks
      
      | Greek | Call                              | Put                                |
      |-------|-----------------------------------|------------------------------------|
      | Delta | exp(-qT) * N(d1)                | exp(-qT) * [N(d1) - 1]           |
      | Gamma | exp(-qT) * n(d1) / (S*sigma*sqrt(T)) | Same as Call              |
      | Theta | -(S*exp(-qT)*n(d1)*sigma)/(2*sqrt(T)) - r*K*exp(-rT)*N(d2) + q*S*exp(-qT)*N(d1) | -(S*exp(-qT)*n(d1)*sigma)/(2*sqrt(T)) + r*K*exp(-rT)*N(-d2) - q*S*exp(-qT)*N(-d1) |
      | Vega  | S * exp(-qT) * n(d1) * sqrt(T)  | Same as Call                       |
      | Rho   | K * T * exp(-rT) * N(d2)        | -K * T * exp(-rT) * N(-d2)        |
      
      Where n() = standard normal PDF.
      
      ### Put-Call Parity (European)
      
      ```
      Call - Put = S * exp(-q*T) - K * exp(-r*T)
      ```
      
      ### Expected Daily Move
      
      ```
      Expected move = Spot * (India VIX / 100) / sqrt(252)
      ```
      
      ### Position Greeks (Multi-Leg)
      
      ```
      Net Delta = Sum of (Direction_i * Delta_i * Lots_i * LotSize_i)
      Net Gamma = Sum of (Direction_i * Gamma_i * Lots_i * LotSize_i)
      Net Theta = Sum of (Direction_i * Theta_i * Lots_i * LotSize_i)
      Net Vega  = Sum of (Direction_i * Vega_i  * Lots_i * LotSize_i)
      ```
      
      Where Direction = +1 for long, -1 for short.
      
  • scripts
    • black_scholes.py 50.7 KB
      #!/usr/bin/env python3
      """
      Black-Scholes Option Pricing Engine for Indian F&O Markets (NSE)
      
      Features:
      - European options pricing (NSE options are European-style)
      - Full Greeks: Delta, Gamma, Theta, Vega, Rho
      - Historical volatility calculation via yfinance
      - Multi-leg strategy P/L simulation
      - ASCII payoff diagram generation
      - CLI interface for standalone use
      
      Default risk-free rate: India 91-day T-bill rate (~6.5-7%)
      """
      
      import argparse
      import math
      import sys
      from dataclasses import dataclass, field
      from enum import Enum
      from typing import List, Optional, Tuple
      
      
      # ---------------------------------------------------------------------------
      # Constants
      # ---------------------------------------------------------------------------
      
      INDIA_RISK_FREE_RATE = 0.07  # ~7% (91-day T-bill rate approximation)
      TRADING_DAYS_PER_YEAR = 252  # NSE trading days per year
      
      # Common NSE F&O lot sizes (reference values — verify via MCP before trading)
      DEFAULT_LOT_SIZES = {
          "NIFTY": 75,
          "BANKNIFTY": 15,
          "FINNIFTY": 25,
          "MIDCPNIFTY": 50,
      }
      
      
      # ---------------------------------------------------------------------------
      # Enums
      # ---------------------------------------------------------------------------
      
      class OptionType(Enum):
          CALL = "CALL"
          PUT = "PUT"
      
      
      class PositionType(Enum):
          LONG = "LONG"
          SHORT = "SHORT"
      
      
      # ---------------------------------------------------------------------------
      # Math helpers — cumulative normal distribution
      # ---------------------------------------------------------------------------
      
      def _norm_pdf(x: float) -> float:
          """Standard normal probability density function."""
          return math.exp(-0.5 * x * x) / math.sqrt(2.0 * math.pi)
      
      
      def _norm_cdf(x: float) -> float:
          """
          Cumulative distribution function for the standard normal distribution.
          Uses the Abramowitz and Stegun rational approximation (max error ~7.5e-8).
          """
          if x >= 0:
              return _norm_cdf_positive(x)
          else:
              return 1.0 - _norm_cdf_positive(-x)
      
      
      def _norm_cdf_positive(x: float) -> float:
          """CDF for x >= 0 using Abramowitz & Stegun approximation 26.2.17."""
          b0 = 0.2316419
          b1 = 0.319381530
          b2 = -0.356563782
          b3 = 1.781477937
          b4 = -1.821255978
          b5 = 1.330274429
      
          t = 1.0 / (1.0 + b0 * x)
          t2 = t * t
          t3 = t2 * t
          t4 = t3 * t
          t5 = t4 * t
      
          return 1.0 - _norm_pdf(x) * (b1 * t + b2 * t2 + b3 * t3 + b4 * t4 + b5 * t5)
      
      
      # ---------------------------------------------------------------------------
      # Data classes
      # ---------------------------------------------------------------------------
      
      @dataclass
      class OptionLeg:
          """Represents a single option leg in a strategy."""
          option_type: OptionType
          position_type: PositionType
          strike: float
          premium: float
          quantity: int = 1  # Number of lots
          lot_size: int = 1  # Shares per lot
      
          @property
          def direction(self) -> int:
              """Returns +1 for long, -1 for short."""
              return 1 if self.position_type == PositionType.LONG else -1
      
          def payoff_at_expiry(self, spot: float) -> float:
              """Calculate payoff per unit at expiry for the given spot price."""
              if self.option_type == OptionType.CALL:
                  intrinsic = max(0.0, spot - self.strike)
              else:
                  intrinsic = max(0.0, self.strike - spot)
      
              payoff_per_unit = self.direction * (intrinsic - self.premium)
              return payoff_per_unit * self.quantity * self.lot_size
      
          def cost(self) -> float:
              """Total cost (debit) or credit of this leg."""
              return -self.direction * self.premium * self.quantity * self.lot_size
      
      
      @dataclass
      class Greeks:
          """Container for option Greeks."""
          delta: float = 0.0
          gamma: float = 0.0
          theta: float = 0.0  # Per calendar day
          vega: float = 0.0   # Per 1% change in volatility
          rho: float = 0.0    # Per 1% change in interest rate
      
      
      @dataclass
      class OptionResult:
          """Result of option pricing calculation."""
          price: float
          greeks: Greeks
          d1: float
          d2: float
      
      
      @dataclass
      class PLPoint:
          """A single point on the P/L curve."""
          spot: float
          pl: float
      
      
      @dataclass
      class StrategyAnalysis:
          """Complete analysis of an options strategy."""
          strategy_name: str
          legs: List[OptionLeg]
          underlying_price: float
          net_premium: float  # Positive = net debit, Negative = net credit
          max_profit: float
          max_loss: float
          breakeven_points: List[float]
          risk_reward_ratio: float
          pl_curve: List[PLPoint]
          net_greeks: Optional[Greeks] = None
      
      
      # ---------------------------------------------------------------------------
      # OptionPricer — Black-Scholes engine
      # ---------------------------------------------------------------------------
      
      class OptionPricer:
          """
          Black-Scholes option pricing engine for European options.
      
          Designed for Indian NSE F&O market where all options are European-style.
      
          Parameters
          ----------
          spot : float
              Current price of the underlying asset.
          strike : float
              Strike price of the option.
          time_to_expiry : float
              Time to expiry in years. For example, 7 days = 7/365.
          volatility : float
              Annualized volatility as a decimal (e.g., 0.15 for 15%).
          risk_free_rate : float
              Annualized risk-free rate (default: India 91-day T-bill rate).
          dividend_yield : float
              Continuous dividend yield as a decimal (default: 0 for index options).
          """
      
          def __init__(
              self,
              spot: float,
              strike: float,
              time_to_expiry: float,
              volatility: float,
              risk_free_rate: float = INDIA_RISK_FREE_RATE,
              dividend_yield: float = 0.0,
          ):
              self.S = spot
              self.K = strike
              self.T = max(time_to_expiry, 1e-10)  # Avoid division by zero
              self.sigma = volatility
              self.r = risk_free_rate
              self.q = dividend_yield
      
              # Pre-calculate d1 and d2
              self._d1, self._d2 = self._calculate_d1_d2()
      
          def _calculate_d1_d2(self) -> Tuple[float, float]:
              """Calculate the d1 and d2 parameters for Black-Scholes."""
              sqrt_T = math.sqrt(self.T)
              d1 = (
                  math.log(self.S / self.K)
                  + (self.r - self.q + 0.5 * self.sigma ** 2) * self.T
              ) / (self.sigma * sqrt_T)
              d2 = d1 - self.sigma * sqrt_T
              return d1, d2
      
          # --- Pricing ---
      
          def call_price(self) -> float:
              """Calculate the Black-Scholes price of a European call option."""
              return (
                  self.S * math.exp(-self.q * self.T) * _norm_cdf(self._d1)
                  - self.K * math.exp(-self.r * self.T) * _norm_cdf(self._d2)
              )
      
          def put_price(self) -> float:
              """Calculate the Black-Scholes price of a European put option."""
              return (
                  self.K * math.exp(-self.r * self.T) * _norm_cdf(-self._d2)
                  - self.S * math.exp(-self.q * self.T) * _norm_cdf(-self._d1)
              )
      
          def price(self, option_type: OptionType) -> float:
              """Calculate option price based on type."""
              if option_type == OptionType.CALL:
                  return self.call_price()
              else:
                  return self.put_price()
      
          # --- Greeks ---
      
          def delta(self, option_type: OptionType) -> float:
              """
              Delta: Rate of change of option price with respect to underlying price.
              Call delta: [0, 1], Put delta: [-1, 0]
              """
              eq_discount = math.exp(-self.q * self.T)
              if option_type == OptionType.CALL:
                  return eq_discount * _norm_cdf(self._d1)
              else:
                  return eq_discount * (_norm_cdf(self._d1) - 1.0)
      
          def gamma(self) -> float:
              """
              Gamma: Rate of change of delta with respect to underlying price.
              Same for both calls and puts.
              """
              eq_discount = math.exp(-self.q * self.T)
              return (
                  eq_discount * _norm_pdf(self._d1)
                  / (self.S * self.sigma * math.sqrt(self.T))
              )
      
          def theta(self, option_type: OptionType) -> float:
              """
              Theta: Rate of change of option price with respect to time.
              Returned as per-calendar-day decay (divide annual by 365).
              Negative for long positions (time decay hurts buyers).
              """
              sqrt_T = math.sqrt(self.T)
              eq_discount = math.exp(-self.q * self.T)
      
              # Common term
              term1 = -(self.S * eq_discount * _norm_pdf(self._d1) * self.sigma) / (2.0 * sqrt_T)
      
              if option_type == OptionType.CALL:
                  term2 = -self.r * self.K * math.exp(-self.r * self.T) * _norm_cdf(self._d2)
                  term3 = self.q * self.S * eq_discount * _norm_cdf(self._d1)
                  annual_theta = term1 + term2 + term3
              else:
                  term2 = self.r * self.K * math.exp(-self.r * self.T) * _norm_cdf(-self._d2)
                  term3 = -self.q * self.S * eq_discount * _norm_cdf(-self._d1)
                  annual_theta = term1 + term2 + term3
      
              return annual_theta / 365.0  # Per calendar day
      
          def vega(self) -> float:
              """
              Vega: Rate of change of option price with respect to volatility.
              Returned per 1% change in volatility (divide by 100).
              Same for both calls and puts.
              """
              eq_discount = math.exp(-self.q * self.T)
              raw_vega = self.S * eq_discount * _norm_pdf(self._d1) * math.sqrt(self.T)
              return raw_vega / 100.0  # Per 1% vol change
      
          def rho(self, option_type: OptionType) -> float:
              """
              Rho: Rate of change of option price with respect to interest rate.
              Returned per 1% change in interest rate (divide by 100).
              """
              if option_type == OptionType.CALL:
                  raw_rho = (
                      self.K * self.T * math.exp(-self.r * self.T) * _norm_cdf(self._d2)
                  )
              else:
                  raw_rho = (
                      -self.K * self.T * math.exp(-self.r * self.T) * _norm_cdf(-self._d2)
                  )
              return raw_rho / 100.0  # Per 1% rate change
      
          def all_greeks(self, option_type: OptionType) -> Greeks:
              """Calculate all Greeks for the given option type."""
              return Greeks(
                  delta=self.delta(option_type),
                  gamma=self.gamma(),
                  theta=self.theta(option_type),
                  vega=self.vega(),
                  rho=self.rho(option_type),
              )
      
          def full_result(self, option_type: OptionType) -> OptionResult:
              """Return complete pricing result with price, Greeks, and intermediates."""
              return OptionResult(
                  price=self.price(option_type),
                  greeks=self.all_greeks(option_type),
                  d1=self._d1,
                  d2=self._d2,
              )
      
          # --- Implied Volatility ---
      
          @staticmethod
          def implied_volatility(
              market_price: float,
              spot: float,
              strike: float,
              time_to_expiry: float,
              option_type: OptionType,
              risk_free_rate: float = INDIA_RISK_FREE_RATE,
              dividend_yield: float = 0.0,
              tolerance: float = 1e-6,
              max_iterations: int = 100,
          ) -> float:
              """
              Calculate implied volatility using the Newton-Raphson method.
      
              Parameters
              ----------
              market_price : float
                  The observed market price of the option.
              spot, strike, time_to_expiry : float
                  Option parameters.
              option_type : OptionType
                  CALL or PUT.
              risk_free_rate, dividend_yield : float
                  Market parameters.
              tolerance : float
                  Convergence tolerance.
              max_iterations : int
                  Maximum iterations for Newton-Raphson.
      
              Returns
              -------
              float
                  Implied volatility as a decimal.
      
              Raises
              ------
              ValueError
                  If the method does not converge.
              """
              # Initial guess using Brenner-Subrahmanyam approximation
              sigma = math.sqrt(2.0 * math.pi / time_to_expiry) * (market_price / spot)
              sigma = max(sigma, 0.01)  # Floor at 1%
      
              for i in range(max_iterations):
                  pricer = OptionPricer(
                      spot, strike, time_to_expiry, sigma, risk_free_rate, dividend_yield
                  )
                  price = pricer.price(option_type)
                  vega_raw = pricer.vega() * 100.0  # Convert back to raw vega
      
                  if abs(vega_raw) < 1e-12:
                      # Vega too small — bisection fallback
                      break
      
                  diff = price - market_price
      
                  if abs(diff) < tolerance:
                      return sigma
      
                  sigma = sigma - diff / vega_raw
                  sigma = max(sigma, 0.001)  # Prevent negative volatility
      
              # Bisection fallback if Newton-Raphson didn't converge
              return OptionPricer._iv_bisection(
                  market_price, spot, strike, time_to_expiry, option_type,
                  risk_free_rate, dividend_yield, tolerance, max_iterations
              )
      
          @staticmethod
          def _iv_bisection(
              market_price: float,
              spot: float,
              strike: float,
              time_to_expiry: float,
              option_type: OptionType,
              risk_free_rate: float,
              dividend_yield: float,
              tolerance: float,
              max_iterations: int,
          ) -> float:
              """Bisection method fallback for implied volatility."""
              low = 0.001
              high = 5.0  # 500% volatility upper bound
      
              for _ in range(max_iterations):
                  mid = (low + high) / 2.0
                  pricer = OptionPricer(
                      spot, strike, time_to_expiry, mid, risk_free_rate, dividend_yield
                  )
                  price = pricer.price(option_type)
                  diff = price - market_price
      
                  if abs(diff) < tolerance:
                      return mid
      
                  if diff > 0:
                      high = mid
                  else:
                      low = mid
      
              raise ValueError(
                  f"Implied volatility did not converge after {max_iterations} iterations. "
                  f"Market price: {market_price}, Spot: {spot}, Strike: {strike}, "
                  f"T: {time_to_expiry}, Type: {option_type.value}"
              )
      
      
      # ---------------------------------------------------------------------------
      # Historical Volatility Calculator
      # ---------------------------------------------------------------------------
      
      def calculate_historical_volatility(
          ticker: str,
          days: int = 30,
          annualize: bool = True,
      ) -> float:
          """
          Calculate historical volatility from daily returns using yfinance data.
      
          Parameters
          ----------
          ticker : str
              Yahoo Finance ticker symbol. For Indian stocks use ".NS" suffix
              (e.g., "RELIANCE.NS", "^NSEI" for Nifty, "^NSEBANK" for Bank Nifty).
          days : int
              Number of trading days to look back (default: 30).
          annualize : bool
              If True, annualize the volatility (multiply by sqrt(252)).
      
          Returns
          -------
          float
              Historical volatility as a decimal.
          """
          try:
              import yfinance as yf
          except ImportError:
              print(
                  "ERROR: yfinance is not installed. Install it with: pip install yfinance",
                  file=sys.stderr,
              )
              print(
                  "Returning default volatility of 15% as fallback.",
                  file=sys.stderr,
              )
              return 0.15
      
          # Map common Indian index names to Yahoo Finance tickers
          ticker_map = {
              "NIFTY": "^NSEI",
              "NIFTY50": "^NSEI",
              "BANKNIFTY": "^NSEBANK",
              "NIFTYBANK": "^NSEBANK",
              "FINNIFTY": "NIFTY_FIN_SERVICE.NS",
              "INDIAVIX": "^INDIAVIX",
              "SENSEX": "^BSESN",
          }
      
          yf_ticker = ticker_map.get(ticker.upper(), ticker)
      
          # Add .NS suffix for Indian stocks if not already present
          if (
              not yf_ticker.startswith("^")
              and not yf_ticker.endswith(".NS")
              and not yf_ticker.endswith(".BO")
          ):
              yf_ticker = yf_ticker + ".NS"
      
          # Fetch extra days to account for weekends and holidays
          fetch_days = int(days * 1.6) + 10
      
          try:
              data = yf.download(
                  yf_ticker,
                  period=f"{fetch_days}d",
                  progress=False,
                  auto_adjust=True,
              )
      
              if data.empty or len(data) < 5:
                  print(
                      f"WARNING: Insufficient data for {yf_ticker}. "
                      f"Returning default volatility of 15%.",
                      file=sys.stderr,
                  )
                  return 0.15
      
              # Use closing prices
              closes = data["Close"].dropna().values.flatten()
              if len(closes) < 2:
                  return 0.15
      
              # Take the most recent 'days' trading days
              closes = closes[-(days + 1):]
      
              # Calculate log returns
              log_returns = []
              for i in range(1, len(closes)):
                  if closes[i - 1] > 0 and closes[i] > 0:
                      log_returns.append(math.log(closes[i] / closes[i - 1]))
      
              if len(log_returns) < 2:
                  return 0.15
      
              # Standard deviation of log returns
              mean_return = sum(log_returns) / len(log_returns)
              variance = sum((r - mean_return) ** 2 for r in log_returns) / (len(log_returns) - 1)
              daily_vol = math.sqrt(variance)
      
              if annualize:
                  return daily_vol * math.sqrt(TRADING_DAYS_PER_YEAR)
              else:
                  return daily_vol
      
          except Exception as e:
              print(
                  f"WARNING: Could not fetch data for {yf_ticker}: {e}. "
                  f"Returning default volatility of 15%.",
                  file=sys.stderr,
              )
              return 0.15
      
      
      # ---------------------------------------------------------------------------
      # P/L Simulation
      # ---------------------------------------------------------------------------
      
      def simulate_pl(
          strategy_legs: List[OptionLeg],
          price_range: Tuple[float, float],
          num_points: int = 50,
      ) -> List[PLPoint]:
          """
          Simulate profit/loss at expiry across a range of underlying prices.
      
          Parameters
          ----------
          strategy_legs : list of OptionLeg
              The legs of the options strategy.
          price_range : tuple of (float, float)
              (min_price, max_price) for simulation.
          num_points : int
              Number of price points to simulate.
      
          Returns
          -------
          list of PLPoint
              P/L at each price point.
          """
          min_price, max_price = price_range
          step = (max_price - min_price) / max(num_points - 1, 1)
          pl_curve = []
      
          for i in range(num_points):
              spot = min_price + i * step
              total_pl = sum(leg.payoff_at_expiry(spot) for leg in strategy_legs)
              pl_curve.append(PLPoint(spot=round(spot, 2), pl=round(total_pl, 2)))
      
          return pl_curve
      
      
      def find_breakeven_points(
          strategy_legs: List[OptionLeg],
          price_range: Tuple[float, float],
          num_points: int = 1000,
      ) -> List[float]:
          """
          Find breakeven points where P/L crosses zero.
      
          Uses linear interpolation between simulated points.
          """
          pl_curve = simulate_pl(strategy_legs, price_range, num_points)
          breakevens = []
      
          for i in range(1, len(pl_curve)):
              prev = pl_curve[i - 1]
              curr = pl_curve[i]
      
              # Check for sign change
              if prev.pl * curr.pl < 0:
                  # Linear interpolation
                  if abs(curr.pl - prev.pl) > 1e-10:
                      ratio = abs(prev.pl) / abs(curr.pl - prev.pl)
                      be_price = prev.spot + ratio * (curr.spot - prev.spot)
                      breakevens.append(round(be_price, 2))
              elif abs(curr.pl) < 0.01:
                  breakevens.append(curr.spot)
      
          return breakevens
      
      
      def analyze_strategy(
          strategy_name: str,
          legs: List[OptionLeg],
          underlying_price: float,
          price_range: Optional[Tuple[float, float]] = None,
          num_points: int = 50,
          net_greeks: Optional[Greeks] = None,
      ) -> StrategyAnalysis:
          """
          Perform complete analysis of an options strategy.
      
          Parameters
          ----------
          strategy_name : str
              Name of the strategy (e.g., "Iron Condor").
          legs : list of OptionLeg
              All legs of the strategy.
          underlying_price : float
              Current price of the underlying.
          price_range : tuple of (float, float), optional
              Price range for simulation. Defaults to +/- 10% of underlying.
          num_points : int
              Number of simulation points.
          net_greeks : Greeks, optional
              Pre-calculated net Greeks for the position.
      
          Returns
          -------
          StrategyAnalysis
              Complete strategy analysis.
          """
          if price_range is None:
              margin = underlying_price * 0.10
              price_range = (underlying_price - margin, underlying_price + margin)
      
          # Net premium
          net_premium = sum(
              leg.direction * leg.premium * leg.quantity * leg.lot_size
              for leg in legs
          )
      
          # P/L curve
          pl_curve = simulate_pl(legs, price_range, num_points)
      
          # Max profit and max loss
          max_profit = max(point.pl for point in pl_curve)
          max_loss = min(point.pl for point in pl_curve)
      
          # Breakeven points
          breakevens = find_breakeven_points(legs, price_range, num_points=1000)
      
          # Risk-reward ratio
          if abs(max_loss) > 1e-10:
              risk_reward = abs(max_profit / max_loss)
          else:
              risk_reward = float("inf")
      
          return StrategyAnalysis(
              strategy_name=strategy_name,
              legs=legs,
              underlying_price=underlying_price,
              net_premium=net_premium,
              max_profit=max_profit,
              max_loss=max_loss,
              breakeven_points=breakevens,
              risk_reward_ratio=round(risk_reward, 2),
              pl_curve=pl_curve,
              net_greeks=net_greeks,
          )
      
      
      # ---------------------------------------------------------------------------
      # ASCII P/L Diagram Generator
      # ---------------------------------------------------------------------------
      
      def generate_ascii_pl_diagram(
          pl_data: List[PLPoint],
          width: int = 60,
          height: int = 20,
          title: str = "P/L at Expiry",
      ) -> str:
          """
          Generate an ASCII art P/L diagram.
      
          Parameters
          ----------
          pl_data : list of PLPoint
              P/L data points to plot.
          width : int
              Width of the chart in characters.
          height : int
              Height of the chart in characters.
          title : str
              Title to display above the chart.
      
          Returns
          -------
          str
              ASCII art P/L diagram as a multi-line string.
          """
          if not pl_data:
              return "No data to plot."
      
          spots = [p.spot for p in pl_data]
          pls = [p.pl for p in pl_data]
      
          min_spot = min(spots)
          max_spot = max(spots)
          min_pl = min(pls)
          max_pl = max(pls)
      
          # Add padding to P/L range
          pl_range = max_pl - min_pl
          if pl_range < 1e-10:
              pl_range = 1.0
              min_pl -= 0.5
              max_pl += 0.5
      
          spot_range = max_spot - min_spot
          if spot_range < 1e-10:
              spot_range = 1.0
      
          # Determine zero line position
          if min_pl <= 0 <= max_pl:
              zero_row = height - 1 - int((0 - min_pl) / pl_range * (height - 1))
          else:
              zero_row = -1  # Zero line is outside the chart
      
          # Build the grid
          grid = [[" " for _ in range(width)] for _ in range(height)]
      
          # Draw zero line
          if 0 <= zero_row < height:
              for c in range(width):
                  grid[zero_row][c] = "-"
      
          # Plot the P/L curve
          for point in pl_data:
              col = int((point.spot - min_spot) / spot_range * (width - 1))
              row = height - 1 - int((point.pl - min_pl) / pl_range * (height - 1))
              col = max(0, min(col, width - 1))
              row = max(0, min(row, height - 1))
      
              if point.pl > 0:
                  grid[row][col] = "+"
              elif point.pl < 0:
                  grid[row][col] = "o"
              else:
                  grid[row][col] = "*"
      
          # Build output
          lines = []
          lines.append("")
          lines.append(f"  {title}")
          lines.append(f"  {'=' * (width + 12)}")
      
          # Y-axis labels
          for r in range(height):
              pl_value = max_pl - (r / (height - 1)) * pl_range
              label = f"{pl_value:>10.0f}"
              row_str = "".join(grid[r])
      
              if r == zero_row:
                  lines.append(f"{label} |{row_str}|  <- Zero")
              else:
                  lines.append(f"{label} |{row_str}|")
      
          # X-axis
          lines.append(f"{'':>10} +{'=' * width}+")
      
          # X-axis labels
          x_label_positions = [0, width // 4, width // 2, 3 * width // 4, width - 1]
          x_labels = []
          for pos in x_label_positions:
              spot_value = min_spot + (pos / (width - 1)) * spot_range
              x_labels.append(f"{spot_value:.0f}")
      
          # Distribute labels across the x-axis
          x_label_line = " " * 11
          positions_used = 0
          for i, pos in enumerate(x_label_positions):
              label = x_labels[i]
              actual_pos = 11 + pos
              padding = actual_pos - len(x_label_line)
              if padding > 0:
                  x_label_line += " " * padding
              x_label_line += label
          lines.append(x_label_line)
      
          # Legend
          lines.append("")
          lines.append(f"  Legend: '+' = Profit | 'o' = Loss | '*' = Breakeven | '-' = Zero line")
          lines.append(f"  X-axis: Underlying price at expiry")
          lines.append(f"  Y-axis: Profit / Loss")
      
          return "\n".join(lines)
      
      
      # ---------------------------------------------------------------------------
      # Strategy Report Generator
      # ---------------------------------------------------------------------------
      
      def generate_strategy_report(analysis: StrategyAnalysis) -> str:
          """Generate a formatted text report of the strategy analysis."""
          lines = []
          lines.append("")
          lines.append("=" * 70)
          lines.append("  OPTIONS STRATEGY REPORT")
          lines.append("=" * 70)
          lines.append("")
          lines.append(f"  Strategy     : {analysis.strategy_name}")
          lines.append(f"  Underlying   : {analysis.underlying_price:.2f}")
          lines.append("")
      
          # Legs
          lines.append("-" * 70)
          lines.append("  STRATEGY LEGS")
          lines.append("-" * 70)
          for i, leg in enumerate(analysis.legs, 1):
              direction_str = "BUY" if leg.position_type == PositionType.LONG else "SELL"
              lines.append(
                  f"  Leg {i}: {direction_str:4s} {leg.quantity} x "
                  f"{leg.option_type.value:4s} @ Strike {leg.strike:.2f} "
                  f"for {leg.premium:.2f} "
                  f"(Lot size: {leg.lot_size})"
              )
          lines.append("")
      
          # Key Metrics
          lines.append("-" * 70)
          lines.append("  KEY METRICS")
          lines.append("-" * 70)
      
          premium_type = "DEBIT" if analysis.net_premium > 0 else "CREDIT"
          lines.append(
              f"  Net Premium     : {premium_type} {abs(analysis.net_premium):.2f}"
          )
          lines.append(f"  Max Profit      : {analysis.max_profit:.2f}")
          lines.append(f"  Max Loss        : {analysis.max_loss:.2f}")
      
          if analysis.breakeven_points:
              be_str = ", ".join(f"{be:.2f}" for be in analysis.breakeven_points)
              lines.append(f"  Breakeven(s)    : {be_str}")
          else:
              lines.append(f"  Breakeven(s)    : None found in range")
      
          lines.append(f"  Risk-Reward     : 1:{analysis.risk_reward_ratio:.2f}")
          lines.append("")
      
          # Net Greeks
          if analysis.net_greeks:
              lines.append("-" * 70)
              lines.append("  NET POSITION GREEKS")
              lines.append("-" * 70)
              g = analysis.net_greeks
              lines.append(f"  Delta : {g.delta:>10.4f}")
              lines.append(f"  Gamma : {g.gamma:>10.6f}")
              lines.append(f"  Theta : {g.theta:>10.4f}  (per day)")
              lines.append(f"  Vega  : {g.vega:>10.4f}  (per 1% IV change)")
              lines.append(f"  Rho   : {g.rho:>10.4f}  (per 1% rate change)")
              lines.append("")
      
          # P/L Diagram
          if analysis.pl_curve:
              diagram = generate_ascii_pl_diagram(
                  analysis.pl_curve, title=f"P/L Diagram: {analysis.strategy_name}"
              )
              lines.append("-" * 70)
              lines.append("  PAYOFF DIAGRAM")
              lines.append("-" * 70)
              lines.append(diagram)
              lines.append("")
      
          # Risk Management
          lines.append("-" * 70)
          lines.append("  RISK MANAGEMENT NOTES")
          lines.append("-" * 70)
          lines.append("  - Set stop-loss if unrealized loss exceeds 2x premium received")
          lines.append("  - Monitor margin requirements daily (SEBI peak margin rules)")
          lines.append("  - Watch for F&O ban risk if stock OI approaches 95% MWPL")
          lines.append("  - Close ITM options before expiry to avoid STT on exercise")
          lines.append("  - For stock options: physical settlement requires delivery margin")
          lines.append("  - Review position 2-3 days before expiry for rollover decision")
          lines.append("")
          lines.append("=" * 70)
      
          return "\n".join(lines)
      
      
      # ---------------------------------------------------------------------------
      # Pre-built Strategy Constructors
      # ---------------------------------------------------------------------------
      
      def build_bull_call_spread(
          lower_strike: float,
          upper_strike: float,
          lower_premium: float,
          upper_premium: float,
          lots: int = 1,
          lot_size: int = 75,
      ) -> List[OptionLeg]:
          """Build a Bull Call Spread: Buy lower strike call, sell upper strike call."""
          return [
              OptionLeg(OptionType.CALL, PositionType.LONG, lower_strike, lower_premium, lots, lot_size),
              OptionLeg(OptionType.CALL, PositionType.SHORT, upper_strike, upper_premium, lots, lot_size),
          ]
      
      
      def build_bear_put_spread(
          upper_strike: float,
          lower_strike: float,
          upper_premium: float,
          lower_premium: float,
          lots: int = 1,
          lot_size: int = 75,
      ) -> List[OptionLeg]:
          """Build a Bear Put Spread: Buy higher strike put, sell lower strike put."""
          return [
              OptionLeg(OptionType.PUT, PositionType.LONG, upper_strike, upper_premium, lots, lot_size),
              OptionLeg(OptionType.PUT, PositionType.SHORT, lower_strike, lower_premium, lots, lot_size),
          ]
      
      
      def build_bull_put_spread(
          upper_strike: float,
          lower_strike: float,
          upper_premium: float,
          lower_premium: float,
          lots: int = 1,
          lot_size: int = 75,
      ) -> List[OptionLeg]:
          """Build a Bull Put Spread: Sell higher strike put, buy lower strike put."""
          return [
              OptionLeg(OptionType.PUT, PositionType.SHORT, upper_strike, upper_premium, lots, lot_size),
              OptionLeg(OptionType.PUT, PositionType.LONG, lower_strike, lower_premium, lots, lot_size),
          ]
      
      
      def build_bear_call_spread(
          lower_strike: float,
          upper_strike: float,
          lower_premium: float,
          upper_premium: float,
          lots: int = 1,
          lot_size: int = 75,
      ) -> List[OptionLeg]:
          """Build a Bear Call Spread: Sell lower strike call, buy upper strike call."""
          return [
              OptionLeg(OptionType.CALL, PositionType.SHORT, lower_strike, lower_premium, lots, lot_size),
              OptionLeg(OptionType.CALL, PositionType.LONG, upper_strike, upper_premium, lots, lot_size),
          ]
      
      
      def build_long_straddle(
          strike: float,
          call_premium: float,
          put_premium: float,
          lots: int = 1,
          lot_size: int = 75,
      ) -> List[OptionLeg]:
          """Build a Long Straddle: Buy ATM call and ATM put at same strike."""
          return [
              OptionLeg(OptionType.CALL, PositionType.LONG, strike, call_premium, lots, lot_size),
              OptionLeg(OptionType.PUT, PositionType.LONG, strike, put_premium, lots, lot_size),
          ]
      
      
      def build_short_straddle(
          strike: float,
          call_premium: float,
          put_premium: float,
          lots: int = 1,
          lot_size: int = 75,
      ) -> List[OptionLeg]:
          """Build a Short Straddle: Sell ATM call and ATM put at same strike."""
          return [
              OptionLeg(OptionType.CALL, PositionType.SHORT, strike, call_premium, lots, lot_size),
              OptionLeg(OptionType.PUT, PositionType.SHORT, strike, put_premium, lots, lot_size),
          ]
      
      
      def build_long_strangle(
          call_strike: float,
          put_strike: float,
          call_premium: float,
          put_premium: float,
          lots: int = 1,
          lot_size: int = 75,
      ) -> List[OptionLeg]:
          """Build a Long Strangle: Buy OTM call and OTM put."""
          return [
              OptionLeg(OptionType.CALL, PositionType.LONG, call_strike, call_premium, lots, lot_size),
              OptionLeg(OptionType.PUT, PositionType.LONG, put_strike, put_premium, lots, lot_size),
          ]
      
      
      def build_short_strangle(
          call_strike: float,
          put_strike: float,
          call_premium: float,
          put_premium: float,
          lots: int = 1,
          lot_size: int = 75,
      ) -> List[OptionLeg]:
          """Build a Short Strangle: Sell OTM call and OTM put."""
          return [
              OptionLeg(OptionType.CALL, PositionType.SHORT, call_strike, call_premium, lots, lot_size),
              OptionLeg(OptionType.PUT, PositionType.SHORT, put_strike, put_premium, lots, lot_size),
          ]
      
      
      def build_iron_condor(
          put_buy_strike: float,
          put_sell_strike: float,
          call_sell_strike: float,
          call_buy_strike: float,
          put_buy_premium: float,
          put_sell_premium: float,
          call_sell_premium: float,
          call_buy_premium: float,
          lots: int = 1,
          lot_size: int = 75,
      ) -> List[OptionLeg]:
          """
          Build an Iron Condor:
          Buy OTM Put (lowest) + Sell Put (lower-mid) + Sell Call (upper-mid) + Buy OTM Call (highest).
          """
          return [
              OptionLeg(OptionType.PUT, PositionType.LONG, put_buy_strike, put_buy_premium, lots, lot_size),
              OptionLeg(OptionType.PUT, PositionType.SHORT, put_sell_strike, put_sell_premium, lots, lot_size),
              OptionLeg(OptionType.CALL, PositionType.SHORT, call_sell_strike, call_sell_premium, lots, lot_size),
              OptionLeg(OptionType.CALL, PositionType.LONG, call_buy_strike, call_buy_premium, lots, lot_size),
          ]
      
      
      def build_iron_butterfly(
          put_buy_strike: float,
          atm_strike: float,
          call_buy_strike: float,
          put_buy_premium: float,
          atm_put_premium: float,
          atm_call_premium: float,
          call_buy_premium: float,
          lots: int = 1,
          lot_size: int = 75,
      ) -> List[OptionLeg]:
          """
          Build an Iron Butterfly:
          Buy OTM Put + Sell ATM Put + Sell ATM Call + Buy OTM Call.
          """
          return [
              OptionLeg(OptionType.PUT, PositionType.LONG, put_buy_strike, put_buy_premium, lots, lot_size),
              OptionLeg(OptionType.PUT, PositionType.SHORT, atm_strike, atm_put_premium, lots, lot_size),
              OptionLeg(OptionType.CALL, PositionType.SHORT, atm_strike, atm_call_premium, lots, lot_size),
              OptionLeg(OptionType.CALL, PositionType.LONG, call_buy_strike, call_buy_premium, lots, lot_size),
          ]
      
      
      # ---------------------------------------------------------------------------
      # Command-Line Interface
      # ---------------------------------------------------------------------------
      
      def main():
          parser = argparse.ArgumentParser(
              description="Black-Scholes Option Pricing Engine for Indian F&O Markets (NSE)",
              formatter_class=argparse.RawDescriptionHelpFormatter,
              epilog="""
      Examples:
        # Price a NIFTY call option
        python black_scholes.py price --spot 24000 --strike 24500 --expiry-days 7 --vol 0.13 --type CALL
      
        # Calculate all Greeks
        python black_scholes.py greeks --spot 24000 --strike 24500 --expiry-days 7 --vol 0.13 --type CALL
      
        # Calculate implied volatility from market price
        python black_scholes.py iv --spot 24000 --strike 24500 --expiry-days 7 --market-price 120 --type CALL
      
        # Calculate historical volatility for NIFTY
        python black_scholes.py hvol --ticker NIFTY --days 30
      
        # Analyze a Bull Call Spread
        python black_scholes.py strategy --type bull-call-spread \\
            --spot 24000 --lower-strike 24000 --upper-strike 24500 \\
            --lower-premium 250 --upper-premium 80 --lot-size 75
      
        # Analyze a Short Straddle
        python black_scholes.py strategy --type short-straddle \\
            --spot 24000 --atm-strike 24000 \\
            --call-premium 200 --put-premium 180 --lot-size 75
      
        # Analyze an Iron Condor
        python black_scholes.py strategy --type iron-condor \\
            --spot 24000 --lot-size 75 \\
            --put-buy-strike 23500 --put-sell-strike 23800 \\
            --call-sell-strike 24200 --call-buy-strike 24500 \\
            --put-buy-premium 30 --put-sell-premium 70 \\
            --call-sell-premium 65 --call-buy-premium 25
              """,
          )
      
          subparsers = parser.add_subparsers(dest="command", help="Command to run")
      
          # --- Price command ---
          price_parser = subparsers.add_parser("price", help="Calculate option price")
          price_parser.add_argument("--spot", type=float, required=True, help="Underlying spot price")
          price_parser.add_argument("--strike", type=float, required=True, help="Strike price")
          price_parser.add_argument("--expiry-days", type=float, required=True, help="Days to expiry")
          price_parser.add_argument("--vol", type=float, required=True, help="Annualized volatility (decimal, e.g., 0.15)")
          price_parser.add_argument("--type", choices=["CALL", "PUT"], required=True, help="Option type")
          price_parser.add_argument("--rate", type=float, default=INDIA_RISK_FREE_RATE, help=f"Risk-free rate (default: {INDIA_RISK_FREE_RATE})")
          price_parser.add_argument("--div-yield", type=float, default=0.0, help="Dividend yield (default: 0)")
      
          # --- Greeks command ---
          greeks_parser = subparsers.add_parser("greeks", help="Calculate all Greeks")
          greeks_parser.add_argument("--spot", type=float, required=True, help="Underlying spot price")
          greeks_parser.add_argument("--strike", type=float, required=True, help="Strike price")
          greeks_parser.add_argument("--expiry-days", type=float, required=True, help="Days to expiry")
          greeks_parser.add_argument("--vol", type=float, required=True, help="Annualized volatility (decimal)")
          greeks_parser.add_argument("--type", choices=["CALL", "PUT"], required=True, help="Option type")
          greeks_parser.add_argument("--rate", type=float, default=INDIA_RISK_FREE_RATE, help=f"Risk-free rate (default: {INDIA_RISK_FREE_RATE})")
          greeks_parser.add_argument("--div-yield", type=float, default=0.0, help="Dividend yield (default: 0)")
      
          # --- Implied Volatility command ---
          iv_parser = subparsers.add_parser("iv", help="Calculate implied volatility")
          iv_parser.add_argument("--spot", type=float, required=True, help="Underlying spot price")
          iv_parser.add_argument("--strike", type=float, required=True, help="Strike price")
          iv_parser.add_argument("--expiry-days", type=float, required=True, help="Days to expiry")
          iv_parser.add_argument("--market-price", type=float, required=True, help="Observed market price of the option")
          iv_parser.add_argument("--type", choices=["CALL", "PUT"], required=True, help="Option type")
          iv_parser.add_argument("--rate", type=float, default=INDIA_RISK_FREE_RATE, help=f"Risk-free rate (default: {INDIA_RISK_FREE_RATE})")
          iv_parser.add_argument("--div-yield", type=float, default=0.0, help="Dividend yield (default: 0)")
      
          # --- Historical Volatility command ---
          hvol_parser = subparsers.add_parser("hvol", help="Calculate historical volatility")
          hvol_parser.add_argument("--ticker", type=str, required=True, help="Ticker symbol (e.g., NIFTY, RELIANCE, TCS)")
          hvol_parser.add_argument("--days", type=int, default=30, help="Lookback period in trading days (default: 30)")
      
          # --- Strategy command ---
          strat_parser = subparsers.add_parser("strategy", help="Analyze an options strategy")
          strat_parser.add_argument(
              "--type",
              choices=[
                  "bull-call-spread", "bear-put-spread", "bull-put-spread", "bear-call-spread",
                  "long-straddle", "short-straddle", "long-strangle", "short-strangle",
                  "iron-condor", "iron-butterfly",
              ],
              required=True,
              help="Strategy type",
          )
          strat_parser.add_argument("--spot", type=float, required=True, help="Underlying spot price")
          strat_parser.add_argument("--lot-size", type=int, default=75, help="Lot size (default: 75 for NIFTY)")
          strat_parser.add_argument("--lots", type=int, default=1, help="Number of lots (default: 1)")
      
          # Strike arguments for various strategies
          strat_parser.add_argument("--lower-strike", type=float, help="Lower strike price")
          strat_parser.add_argument("--upper-strike", type=float, help="Upper strike price")
          strat_parser.add_argument("--atm-strike", type=float, help="ATM strike price")
          strat_parser.add_argument("--call-strike", type=float, help="Call strike (for strangles)")
          strat_parser.add_argument("--put-strike", type=float, help="Put strike (for strangles)")
          strat_parser.add_argument("--put-buy-strike", type=float, help="Put buy strike (for iron condor/butterfly)")
          strat_parser.add_argument("--put-sell-strike", type=float, help="Put sell strike (for iron condor)")
          strat_parser.add_argument("--call-sell-strike", type=float, help="Call sell strike (for iron condor)")
          strat_parser.add_argument("--call-buy-strike", type=float, help="Call buy strike (for iron condor/butterfly)")
      
          # Premium arguments
          strat_parser.add_argument("--lower-premium", type=float, help="Lower strike premium")
          strat_parser.add_argument("--upper-premium", type=float, help="Upper strike premium")
          strat_parser.add_argument("--call-premium", type=float, help="Call premium")
          strat_parser.add_argument("--put-premium", type=float, help="Put premium")
          strat_parser.add_argument("--put-buy-premium", type=float, help="Put buy premium (iron condor/butterfly)")
          strat_parser.add_argument("--put-sell-premium", type=float, help="Put sell premium (iron condor)")
          strat_parser.add_argument("--call-sell-premium", type=float, help="Call sell premium (iron condor)")
          strat_parser.add_argument("--call-buy-premium", type=float, help="Call buy premium (iron condor/butterfly)")
          strat_parser.add_argument("--atm-call-premium", type=float, help="ATM call premium (iron butterfly)")
          strat_parser.add_argument("--atm-put-premium", type=float, help="ATM put premium (iron butterfly)")
      
          args = parser.parse_args()
      
          if not args.command:
              parser.print_help()
              sys.exit(1)
      
          # --- Execute commands ---
      
          if args.command == "price":
              opt_type = OptionType.CALL if args.type == "CALL" else OptionType.PUT
              pricer = OptionPricer(
                  spot=args.spot,
                  strike=args.strike,
                  time_to_expiry=args.expiry_days / 365.0,
                  volatility=args.vol,
                  risk_free_rate=args.rate,
                  dividend_yield=args.div_yield,
              )
              price = pricer.price(opt_type)
              print(f"\n{'=' * 50}")
              print(f"  Black-Scholes Option Price")
              print(f"{'=' * 50}")
              print(f"  Underlying  : {args.spot:.2f}")
              print(f"  Strike      : {args.strike:.2f}")
              print(f"  Days to Exp : {args.expiry_days:.0f}")
              print(f"  Volatility  : {args.vol:.2%}")
              print(f"  Risk-Free   : {args.rate:.2%}")
              print(f"  Type        : {args.type}")
              print(f"  ---------------------")
              print(f"  PRICE       : {price:.2f}")
              print(f"{'=' * 50}\n")
      
          elif args.command == "greeks":
              opt_type = OptionType.CALL if args.type == "CALL" else OptionType.PUT
              pricer = OptionPricer(
                  spot=args.spot,
                  strike=args.strike,
                  time_to_expiry=args.expiry_days / 365.0,
                  volatility=args.vol,
                  risk_free_rate=args.rate,
                  dividend_yield=args.div_yield,
              )
              result = pricer.full_result(opt_type)
              print(f"\n{'=' * 50}")
              print(f"  Black-Scholes Greeks")
              print(f"{'=' * 50}")
              print(f"  Underlying  : {args.spot:.2f}")
              print(f"  Strike      : {args.strike:.2f}")
              print(f"  Days to Exp : {args.expiry_days:.0f}")
              print(f"  Volatility  : {args.vol:.2%}")
              print(f"  Risk-Free   : {args.rate:.2%}")
              print(f"  Type        : {args.type}")
              print(f"  ---------------------")
              print(f"  Price       : {result.price:.4f}")
              print(f"  Delta       : {result.greeks.delta:.6f}")
              print(f"  Gamma       : {result.greeks.gamma:.6f}")
              print(f"  Theta       : {result.greeks.theta:.4f}  (per day)")
              print(f"  Vega        : {result.greeks.vega:.4f}  (per 1% vol)")
              print(f"  Rho         : {result.greeks.rho:.4f}  (per 1% rate)")
              print(f"  d1          : {result.d1:.6f}")
              print(f"  d2          : {result.d2:.6f}")
              print(f"{'=' * 50}\n")
      
          elif args.command == "iv":
              opt_type = OptionType.CALL if args.type == "CALL" else OptionType.PUT
              try:
                  iv = OptionPricer.implied_volatility(
                      market_price=args.market_price,
                      spot=args.spot,
                      strike=args.strike,
                      time_to_expiry=args.expiry_days / 365.0,
                      option_type=opt_type,
                      risk_free_rate=args.rate,
                      dividend_yield=args.div_yield,
                  )
                  print(f"\n{'=' * 50}")
                  print(f"  Implied Volatility")
                  print(f"{'=' * 50}")
                  print(f"  Underlying    : {args.spot:.2f}")
                  print(f"  Strike        : {args.strike:.2f}")
                  print(f"  Days to Exp   : {args.expiry_days:.0f}")
                  print(f"  Market Price  : {args.market_price:.2f}")
                  print(f"  Type          : {args.type}")
                  print(f"  ---------------------")
                  print(f"  IMPLIED VOL   : {iv:.4f} ({iv:.2%})")
                  print(f"{'=' * 50}\n")
              except ValueError as e:
                  print(f"\nERROR: {e}\n", file=sys.stderr)
                  sys.exit(1)
      
          elif args.command == "hvol":
              hvol = calculate_historical_volatility(args.ticker, args.days)
              print(f"\n{'=' * 50}")
              print(f"  Historical Volatility")
              print(f"{'=' * 50}")
              print(f"  Ticker        : {args.ticker}")
              print(f"  Lookback      : {args.days} trading days")
              print(f"  ---------------------")
              print(f"  HIST VOL      : {hvol:.4f} ({hvol:.2%})")
              print(f"{'=' * 50}\n")
      
          elif args.command == "strategy":
              legs = _build_strategy_from_args(args)
              if legs is None:
                  sys.exit(1)
      
              strategy_name = args.type.replace("-", " ").title()
              analysis = analyze_strategy(
                  strategy_name=strategy_name,
                  legs=legs,
                  underlying_price=args.spot,
              )
              report = generate_strategy_report(analysis)
              print(report)
      
      
      def _build_strategy_from_args(args) -> Optional[List[OptionLeg]]:
          """Build strategy legs from CLI arguments."""
          lot_size = args.lot_size
          lots = args.lots
      
          if args.type == "bull-call-spread":
              if not all([args.lower_strike, args.upper_strike, args.lower_premium, args.upper_premium]):
                  print("ERROR: Bull Call Spread requires --lower-strike, --upper-strike, --lower-premium, --upper-premium", file=sys.stderr)
                  return None
              return build_bull_call_spread(
                  args.lower_strike, args.upper_strike,
                  args.lower_premium, args.upper_premium,
                  lots, lot_size,
              )
      
          elif args.type == "bear-put-spread":
              if not all([args.upper_strike, args.lower_strike, args.upper_premium, args.lower_premium]):
                  print("ERROR: Bear Put Spread requires --upper-strike, --lower-strike, --upper-premium, --lower-premium", file=sys.stderr)
                  return None
              return build_bear_put_spread(
                  args.upper_strike, args.lower_strike,
                  args.upper_premium, args.lower_premium,
                  lots, lot_size,
              )
      
          elif args.type == "bull-put-spread":
              if not all([args.upper_strike, args.lower_strike, args.upper_premium, args.lower_premium]):
                  print("ERROR: Bull Put Spread requires --upper-strike, --lower-strike, --upper-premium, --lower-premium", file=sys.stderr)
                  return None
              return build_bull_put_spread(
                  args.upper_strike, args.lower_strike,
                  args.upper_premium, args.lower_premium,
                  lots, lot_size,
              )
      
          elif args.type == "bear-call-spread":
              if not all([args.lower_strike, args.upper_strike, args.lower_premium, args.upper_premium]):
                  print("ERROR: Bear Call Spread requires --lower-strike, --upper-strike, --lower-premium, --upper-premium", file=sys.stderr)
                  return None
              return build_bear_call_spread(
                  args.lower_strike, args.upper_strike,
                  args.lower_premium, args.upper_premium,
                  lots, lot_size,
              )
      
          elif args.type == "long-straddle":
              if not all([args.atm_strike, args.call_premium, args.put_premium]):
                  print("ERROR: Long Straddle requires --atm-strike, --call-premium, --put-premium", file=sys.stderr)
                  return None
              return build_long_straddle(
                  args.atm_strike, args.call_premium, args.put_premium,
                  lots, lot_size,
              )
      
          elif args.type == "short-straddle":
              if not all([args.atm_strike, args.call_premium, args.put_premium]):
                  print("ERROR: Short Straddle requires --atm-strike, --call-premium, --put-premium", file=sys.stderr)
                  return None
              return build_short_straddle(
                  args.atm_strike, args.call_premium, args.put_premium,
                  lots, lot_size,
              )
      
          elif args.type == "long-strangle":
              if not all([args.call_strike, args.put_strike, args.call_premium, args.put_premium]):
                  print("ERROR: Long Strangle requires --call-strike, --put-strike, --call-premium, --put-premium", file=sys.stderr)
                  return None
              return build_long_strangle(
                  args.call_strike, args.put_strike,
                  args.call_premium, args.put_premium,
                  lots, lot_size,
              )
      
          elif args.type == "short-strangle":
              if not all([args.call_strike, args.put_strike, args.call_premium, args.put_premium]):
                  print("ERROR: Short Strangle requires --call-strike, --put-strike, --call-premium, --put-premium", file=sys.stderr)
                  return None
              return build_short_strangle(
                  args.call_strike, args.put_strike,
                  args.call_premium, args.put_premium,
                  lots, lot_size,
              )
      
          elif args.type == "iron-condor":
              required = [
                  args.put_buy_strike, args.put_sell_strike,
                  args.call_sell_strike, args.call_buy_strike,
                  args.put_buy_premium, args.put_sell_premium,
                  args.call_sell_premium, args.call_buy_premium,
              ]
              if not all(v is not None for v in required):
                  print(
                      "ERROR: Iron Condor requires --put-buy-strike, --put-sell-strike, "
                      "--call-sell-strike, --call-buy-strike, --put-buy-premium, "
                      "--put-sell-premium, --call-sell-premium, --call-buy-premium",
                      file=sys.stderr,
                  )
                  return None
              return build_iron_condor(
                  args.put_buy_strike, args.put_sell_strike,
                  args.call_sell_strike, args.call_buy_strike,
                  args.put_buy_premium, args.put_sell_premium,
                  args.call_sell_premium, args.call_buy_premium,
                  lots, lot_size,
              )
      
          elif args.type == "iron-butterfly":
              required = [
                  args.put_buy_strike, args.atm_strike, args.call_buy_strike,
                  args.put_buy_premium, args.atm_put_premium,
                  args.atm_call_premium, args.call_buy_premium,
              ]
              if not all(v is not None for v in required):
                  print(
                      "ERROR: Iron Butterfly requires --put-buy-strike, --atm-strike, "
                      "--call-buy-strike, --put-buy-premium, --atm-put-premium, "
                      "--atm-call-premium, --call-buy-premium",
                      file=sys.stderr,
                  )
                  return None
              return build_iron_butterfly(
                  args.put_buy_strike, args.atm_strike, args.call_buy_strike,
                  args.put_buy_premium, args.atm_put_premium,
                  args.atm_call_premium, args.call_buy_premium,
                  lots, lot_size,
              )
      
          print(f"ERROR: Unknown strategy type: {args.type}", file=sys.stderr)
          return None
      
      
      # ---------------------------------------------------------------------------
      # Entry point
      # ---------------------------------------------------------------------------
      
      if __name__ == "__main__":
          main()
      
  • SKILL.md 17.3 KB
    ---
    name: options-strategy-advisor
    description: >
      Options strategy analysis for Indian F&O markets (NSE). Use when user requests
      options strategy recommendations, P/L analysis, Greeks calculation, risk management,
      or F&O strategy planning for Nifty, Bank Nifty, or stock options.
    ---
    
    # Options Strategy Advisor — Indian F&O Markets (NSE)
    
    ## Overview
    
    This skill provides comprehensive options strategy analysis tailored to the Indian
    Futures & Options market on the National Stock Exchange (NSE). It covers strategy
    selection, live data retrieval, margin estimation, profit/loss simulation, Greeks
    analysis, and risk management — all adapted for the specific characteristics of
    Indian F&O trading.
    
    ---
    
    ## Indian F&O Market Characteristics
    
    ### Exercise Style
    - **European-style exercise only.** Options on NSE can only be exercised at expiry,
      not before. This simplifies pricing (Black-Scholes applies directly without
      early-exercise adjustments) and means time value is always fully captured by
      the seller until expiry.
    
    ### Expiry Schedule
    | Underlying   | Expiry Day  | Expiry Type          |
    |--------------|-------------|----------------------|
    | NIFTY        | Thursday    | Weekly + Monthly     |
    | BANK NIFTY   | Wednesday   | Weekly + Monthly     |
    | FINNIFTY     | Tuesday     | Weekly + Monthly     |
    | SENSEX (BSE) | Friday      | Weekly + Monthly     |
    | Stock Options | Last Thursday | Monthly only       |
    
    - Monthly expiry is the last Thursday of the month (or preceding trading day if
      Thursday is a holiday).
    - Weekly expiries are available only for index options, not individual stocks.
    
    ### Lot Sizes
    Lot sizes are periodically revised by the exchanges. Always verify current lot
    sizes using the Groww MCP tool `fno_mcx_contracts_search_tool` before calculating
    margin or position size. Recent reference values:
    - NIFTY: 75 (recently changed — confirm via MCP)
    - BANK NIFTY: 15 (recently changed — confirm via MCP)
    - FINNIFTY: 25
    - Stock options: Varies by stock (check contract specifications)
    
    ### Margin Requirements
    SEBI mandates the following margin components for F&O:
    1. **SPAN Margin** — Risk-based margin calculated by the exchange clearing corporation.
    2. **Exposure Margin** — Additional margin over SPAN for market-wide risk.
    3. **Peak Margin** — Intraday margin snapshots; brokers must collect at least the
       peak margin observed during the day.
    
    Use `calculate_fno_margin` to get exact margin for any trade before placing it.
    
    ### Transaction Costs
    - **STT (Securities Transaction Tax):** Levied on the sell side of options at
      0.0625% of the intrinsic value on exercise (for ITM options at expiry). For
      futures, STT is 0.0125% on sell side.
    - **Brokerage:** Varies by broker (Groww charges per-order flat fees).
    - **Exchange charges, GST, SEBI turnover fee, stamp duty** also apply.
    
    ### F&O Ban Mechanism
    When the market-wide position limit (MWPL) for a stock's F&O contracts exceeds
    95%, SEBI places the stock under an F&O ban. During the ban:
    - No new positions can be initiated.
    - Only squaring off (closing) of existing positions is allowed.
    - The ban is lifted when MWPL drops below 80%.
    
    ---
    
    ## Broker MCP Tool Integration
    
    This skill uses broker MCP tools for live market data and execution support. Use whichever broker is connected (Groww or Zerodha Kite). Always prefer live data over assumptions.
    
    ### Groww MCP Tools (if connected)
    
    | Tool | Purpose |
    |------|---------|
    | `get_ltp` (segment=FNO, query_type=fno) | Live option/futures prices and OI |
    | `get_quotes_and_depth` (segment=FNO) | Bid/ask spreads and market depth |
    | `fno_mcx_contracts_search_tool` | Search F&O contracts, lot sizes, expiries |
    | `fetch_historical_candle_data` (segment=FNO) | Historical option price data |
    | `fetch_curated_fno` | F&O gainers, losers, most traded |
    | `get_open_interest_analysis` | OI structure, PCR, support/resistance |
    | `get_greeks_for_fno_contract` | Live Greeks for specific contracts |
    | `get_greeks_for_fno_symbol` | Greeks for all contracts of an underlying |
    | `get_atm_straddle_chart` | ATM straddle premium analysis |
    | `get_payoff_chart_steps` | Payoff diagram generation instructions |
    | `calculate_fno_margin` | Margin requirement calculation |
    | `get_available_margin_details` | User's available margin |
    | `resolve_market_time_and_calendar` | Market hours and trading calendar |
    
    ### Zerodha Kite MCP Tools (if connected)
    
    | Tool | Purpose |
    |------|---------|
    | `get_ltp` | Last traded price for F&O instruments |
    | `get_quotes` | Real-time quotes with bid/ask depth |
    | `get_ohlc` | OHLC data for options/futures contracts |
    | `get_historical_data` | Historical candle data for F&O |
    | `search_instruments` | Search for F&O contracts by name/expiry |
    | `get_margins` | Account margins and available funds |
    | `get_positions` | Current F&O positions |
    | `get_orders` / `get_order_history` | Order status and execution details |
    | `place_order` / `modify_order` / `cancel_order` | Order management |
    | `place_gtt_order` / `get_gtts` | GTT order management |
    
    ### Tool Equivalence Map
    
    | Action | Groww MCP | Zerodha Kite MCP |
    |--------|-----------|------------------|
    | Live price | `get_ltp` | `get_ltp` |
    | Market depth | `get_quotes_and_depth` | `get_quotes` |
    | Historical data | `fetch_historical_candle_data` | `get_historical_data` |
    | Search contracts | `fno_mcx_contracts_search_tool` | `search_instruments` |
    | Margin check | `calculate_fno_margin` / `get_available_margin_details` | `get_margins` |
    | Positions | `get_my_trading_positions_today` | `get_positions` |
    | Place orders | `place_fno_order` | `place_order` |
    
    ---
    
    ## Supported Strategies
    
    ### Income Strategies
    1. **Covered Call** — Long underlying futures + Short OTM Call
       - Objective: Generate income on existing long position.
       - Best when: Mildly bullish, want to earn premium.
       - Indian note: Use futures as underlying (no direct stock delivery for covered calls in F&O segment).
    
    2. **Cash-Secured Put** — Short OTM Put (with margin set aside)
       - Objective: Earn premium while waiting to buy at a lower price.
       - Best when: Bullish on underlying, willing to take delivery equivalent.
       - Indian note: Physical settlement applies for stock options (ITM at expiry).
    
    ### Protection Strategies
    3. **Protective Put** — Long underlying + Long Put
       - Objective: Insure existing long position against downside.
       - Best when: Want to cap losses while maintaining upside.
    
    4. **Collar** — Long underlying + Long Put + Short Call
       - Objective: Cap both upside and downside. Zero-cost collar if premiums offset.
       - Best when: Want protection without paying net premium.
    
    ### Directional Strategies
    5. **Bull Call Spread** — Long lower-strike Call + Short higher-strike Call
       - Objective: Limited-risk bullish bet.
       - Best when: Moderately bullish, want defined risk.
    
    6. **Bear Put Spread** — Long higher-strike Put + Short lower-strike Put
       - Objective: Limited-risk bearish bet.
       - Best when: Moderately bearish, want defined risk.
    
    7. **Bull Put Spread** — Short higher-strike Put + Long lower-strike Put
       - Objective: Credit spread, profit if price stays above short strike.
       - Best when: Mildly bullish, want to collect premium.
    
    8. **Bear Call Spread** — Short lower-strike Call + Long higher-strike Call
       - Objective: Credit spread, profit if price stays below short strike.
       - Best when: Mildly bearish, want to collect premium.
    
    ### Volatility Strategies
    9. **Long Straddle** — Long ATM Call + Long ATM Put
       - Objective: Profit from large move in either direction.
       - Best when: Expecting high volatility (e.g., pre-budget, RBI policy, earnings).
       - Indian note: Popular before Union Budget day, election results, RBI MPC.
    
    10. **Short Straddle** — Short ATM Call + Short ATM Put
        - Objective: Profit from time decay when expecting range-bound movement.
        - Best when: Low implied volatility expected, range-bound market.
        - Indian note: Very popular on weekly expiry day for Nifty/Bank Nifty.
    
    11. **Long Strangle** — Long OTM Call + Long OTM Put
        - Objective: Cheaper alternative to straddle for volatility plays.
        - Best when: Expecting very large move, want lower cost than straddle.
    
    12. **Short Strangle** — Short OTM Call + Short OTM Put
        - Objective: Wider profit zone than short straddle, less premium received.
        - Best when: Expecting range-bound, comfortable with wider risk.
    
    ### Range-Bound Strategies
    13. **Iron Condor** — Bull Put Spread + Bear Call Spread
        - Objective: Defined-risk range-bound strategy.
        - Best when: Expecting low volatility, want defined max loss.
        - Indian note: Very popular for weekly Nifty expiry plays.
    
    14. **Iron Butterfly** — Short ATM Call + Short ATM Put + Long OTM Call + Long OTM Put
        - Objective: Defined-risk version of short straddle.
        - Best when: Expecting pin at a specific strike (max pain).
    
    ### Advanced Strategies
    15. **Calendar Spread (Time Spread)** — Short near-expiry option + Long far-expiry option (same strike)
        - Objective: Profit from differential time decay.
        - Best when: Expecting current expiry to decay faster, longer-term view intact.
        - Indian note: Useful between weekly and monthly expiry cycles.
    
    16. **Diagonal Spread** — Calendar spread with different strikes.
        - Objective: Directional bias + time decay benefit.
        - Best when: Have a directional view and want to finance via near-expiry sale.
    
    17. **Ratio Spread** — Buy N options at one strike, sell M options at another (N != M).
        - Objective: Reduce cost of directional trade; accept risk on extreme moves.
        - Best when: Strong view on direction but want reduced cost.
        - Caution: Naked leg creates unlimited risk on one side.
    
    ---
    
    ## Workflow
    
    Follow this sequence when advising on an options strategy:
    
    ### Step 1: Gather Input
    Collect the following from the user:
    - **Underlying:** Which index or stock? (NIFTY, BANKNIFTY, FINNIFTY, or a specific stock)
    - **Market View:** Bullish, bearish, neutral, volatile, or range-bound?
    - **Strategy Preference:** Specific strategy or let the advisor recommend?
    - **Expiry:** Weekly or monthly? Specific date?
    - **Risk Tolerance:** Maximum loss acceptable? Capital available?
    - **Objective:** Income generation, hedging, speculation, or volatility play?
    
    ### Step 2: Fetch Live Data via Groww MCP
    
    1. **Resolve market time and calendar:**
       ```
       resolve_market_time_and_calendar() → confirm market is open, get trading days to expiry
       ```
    
    2. **Search for contracts:**
       ```
       fno_mcx_contracts_search_tool(search_term="NIFTY 25 MAR") → get exact trading symbols
       ```
    
    3. **Get live prices:**
       ```
       get_ltp(search_queries=["nifty 24000 CE mar", "nifty 24000 PE mar"], segment="FNO", query_type="fno")
       ```
    
    4. **Get Greeks:**
       ```
       get_greeks_for_fno_contract(search_queries=["nifty 24000 mar CE"], expiry="2026-03-26")
       ```
    
    5. **Analyze Open Interest:**
       ```
       get_open_interest_analysis(symbol="NIFTY", view="all")
       ```
    
    6. **Check ATM straddle premium (for volatility assessment):**
       ```
       get_atm_straddle_chart(symbol="NIFTY")
       ```
    
    ### Step 3: Calculate Margin Requirement
    
    For each leg of the strategy that involves selling (writing) options:
    ```
    calculate_fno_margin(
        trading_symbol="NIFTY25MAR24000CE",
        num_lots=1,
        transaction_type="SELL",
        product="NRML"
    )
    ```
    
    Also check user's available margin:
    ```
    get_available_margin_details()
    ```
    
    ### Step 4: Simulate P/L Across Price Range
    
    Use the `scripts/black_scholes.py` script or manual calculation:
    - Define a price range (e.g., underlying +/- 5% from current price).
    - For each price point, calculate P/L for each leg.
    - Sum up P/L across all legs.
    - Identify breakeven points, max profit, max loss.
    
    Key calculations:
    - **Breakeven** = Strike +/- Net Premium (for single-leg strategies)
    - **Max Profit** = Net Premium Received (for credit strategies) or Strike Width - Net Debit (for debit spreads)
    - **Max Loss** = Net Premium Paid (for debit strategies) or Strike Width - Net Credit (for credit spreads)
    
    ### Step 5: Generate ASCII P/L Diagram
    
    Create a visual payoff diagram showing:
    - X-axis: Underlying price at expiry
    - Y-axis: Profit/Loss per lot
    - Breakeven point(s) marked
    - Max profit and max loss zones labeled
    
    Also use `get_payoff_chart_steps()` for Groww's built-in payoff chart generation.
    
    ### Step 6: Provide Risk Management Guidance
    
    Include in every recommendation:
    - **Position sizing:** How many lots based on capital and risk tolerance.
    - **Stop-loss levels:** When to exit (e.g., if loss exceeds 2x premium received).
    - **Adjustment triggers:** When and how to adjust the strategy.
    - **Expiry management:** Roll, close, or let expire — guidance based on ITM/OTM status.
    - **STT warning:** Remind about STT on ITM options at expiry (can erode profits significantly).
    - **Margin monitoring:** Warn about peak margin requirements and potential margin calls.
    
    ### Step 7: Save Report
    
    Present the complete analysis as a structured report:
    
    ```
    === OPTIONS STRATEGY REPORT ===
    Date: [current date]
    Underlying: [symbol] @ [current price]
    Strategy: [strategy name]
    Expiry: [expiry date] ([days to expiry] days)
    
    --- LEGS ---
    Leg 1: [BUY/SELL] [qty] [CALL/PUT] @ Strike [strike] for [premium]
    Leg 2: [BUY/SELL] [qty] [CALL/PUT] @ Strike [strike] for [premium]
    
    --- KEY METRICS ---
    Net Premium: [debit/credit] [amount] per lot
    Max Profit: [amount] per lot (at [price])
    Max Loss: [amount] per lot (at [price])
    Breakeven: [price(s)]
    Risk-Reward Ratio: [ratio]
    Probability of Profit: [estimate based on delta]
    
    --- GREEKS (NET POSITION) ---
    Delta: [value] | Gamma: [value] | Theta: [value] | Vega: [value]
    
    --- MARGIN REQUIREMENT ---
    Total Margin: [amount]
    Available Margin: [amount]
    Margin Utilization: [percentage]
    
    --- P/L DIAGRAM ---
    [ASCII payoff chart]
    
    --- RISK MANAGEMENT ---
    - Stop Loss: [criteria]
    - Adjustment Plan: [when and how]
    - Expiry Action: [recommendation]
    - STT Impact: [if applicable]
    ```
    
    ---
    
    ## Strategy Selection Guide
    
    Use this decision tree to recommend strategies based on user's market view:
    
    ### Bullish View
    - **Strong bullish:** Long Call or Bull Call Spread
    - **Mildly bullish:** Bull Put Spread (credit) or Covered Call
    - **Bullish + high IV:** Bull Put Spread (sell expensive puts)
    - **Bullish + low IV:** Long Call or Bull Call Spread (buy cheap options)
    
    ### Bearish View
    - **Strong bearish:** Long Put or Bear Put Spread
    - **Mildly bearish:** Bear Call Spread (credit)
    - **Bearish + high IV:** Bear Call Spread (sell expensive calls)
    - **Bearish + low IV:** Long Put or Bear Put Spread
    
    ### Neutral / Range-Bound View
    - **Tight range expected:** Short Straddle or Iron Butterfly
    - **Wider range expected:** Short Strangle or Iron Condor
    - **Neutral + want defined risk:** Iron Condor or Iron Butterfly
    
    ### Volatile View (Expecting Big Move)
    - **Direction unknown, big move expected:** Long Straddle
    - **Direction unknown, very big move expected:** Long Strangle (cheaper)
    - **Pre-event (budget, RBI, earnings):** Long Straddle or Long Strangle
    
    ### Time Decay Play
    - **Near-term decay focus:** Calendar Spread
    - **Directional + decay:** Diagonal Spread
    
    ---
    
    ## Important Indian Market Considerations
    
    ### India VIX
    - India VIX measures the market's expectation of 30-day volatility.
    - VIX > 20: High volatility environment — favor long volatility strategies.
    - VIX < 15: Low volatility environment — favor short volatility strategies.
    - VIX between 15-20: Normal range — use directional or range-bound strategies.
    - VIX typically spikes before elections, budgets, RBI policy, and global crises.
    
    ### Weekly Expiry Trading
    - **Thursday (Nifty):** Most liquid expiry. Short straddle/strangle sellers dominate.
      Theta decay is highest on the expiry day.
    - **Wednesday (Bank Nifty):** High gamma risk. Moves can be sharp near expiry.
    - Premium sellers should be cautious of gamma risk on expiry day — a small move
      in the underlying can cause large P/L swings.
    
    ### Physical Settlement (Stock Options)
    - Stock options that expire ITM are physically settled — actual delivery of shares.
    - This requires full delivery margin (value of shares). Plan exits before expiry
      to avoid unexpected margin requirements.
    - Index options are cash-settled — no delivery concerns.
    
    ### Max Pain
    - Max Pain is the strike price at which the maximum number of options (calls + puts)
      expire worthless, causing minimum payout by option writers.
    - Indian markets tend to gravitate toward max pain on expiry day, especially for
      Nifty weekly expiry.
    - Use OI analysis to identify max pain and position accordingly.
    
    ### OI-Based Analysis
    - **High Call OI at a strike:** Acts as resistance. Call writers are betting the
      price won't cross this level.
    - **High Put OI at a strike:** Acts as support. Put writers are betting the price
      won't fall below this level.
    - **PCR (Put-Call Ratio):**
      - PCR > 1.2: Bullish signal (more puts written, indicating support)
      - PCR < 0.8: Bearish signal (more calls written, indicating resistance)
      - PCR between 0.8-1.2: Neutral
    
    ---
    
    ## Error Handling
    
    - If broker MCP tools (Groww or Zerodha) return errors, inform the user and suggest checking market
      hours or contract availability. Try the alternative broker's equivalent tool if available.
    - If a contract search yields no results, try alternative search terms or check
      if the expiry has passed.
    - If margin data is unavailable, provide theoretical estimates with a disclaimer.
    - Always validate that the market is open before fetching live data — use
      `resolve_market_time_and_calendar()`.
    - If a stock is under F&O ban, alert the user immediately and suggest alternative
      underlyings.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related