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

Tradingview Pinescript – Real-Time Option Greeks with Implied Volatility Using Claude Sonnet 3.5 (Newer Model)

9 min read

Converting an existing Option Greeks model from Amibroker AFL to TradingView Pinescript was an ambitious project, one that involved dealing with complex calculations like Implied Volatility (IV), Delta, Gamma, Theta, and Vega. I decided to tackle this challenge using Claude Sonnet 3.5 (newer model), to streamline the coding process.

Using Claude Sonnet 3.5(new) for coding proved to be an efficient way to handle the complexity of financial calculations and Pinescript’s intricacies. The model’s ability to quickly generate relevant code, suggest improvements, and adapt to different nuances in requirements made it a valuable tool throughout the development process.

Tradingview Pinescript – Realtime Option Greeks

// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © algostudio

//@version=5
indicator("Global Options Greeks Calculator", 
         shorttitle="Option Greeks", 
         overlay=false, 
         dynamic_requests = true,
         format=format.price,
         precision=4)

// Constants
var float PI = 3.14159265358979

// Input Parameters
underlyingInput = input.symbol(defval="NSE:NIFTY", title="Underlying Symbol", confirm=true)
riskFreeRate = input.float(defval=0.0, title="Risk Free Rate (%)", group="Option Parameters")/100
dividend = input.float(defval=0.0, title="Dividend Yield (%)", group="Option Parameters")/100

// Plot Selection
plotOptions = input.string("Premium", "Select Plot", options=["Premium", "IV", "Delta", "Gamma", "Theta", "Vega"], group="Plot Settings", confirm=true)
plotColor = input.color(color.yellow, "Plot Color", group="Plot Settings")
plotThickness = input.int(2, "Plot Thickness", minval=1, maxval=4, group="Plot Settings")

// Expiry time inputs
expiryHour = input.int(15, "Expiry Hour (24h)", minval=0, maxval=23, group="Expiry Settings", confirm=true)
expiryMinute = input.int(30, "Expiry Minute", minval=0, maxval=59, group="Expiry Settings", confirm=true)

// Input Variables for Table Display Settings
tablePosition = input.string(defval = "Top Right", title = "Greeks Table Position", 
  options=["Top Right", "Bottom Right", "Middle Right", "Top Left", "Middle Left"], group="Table Settings")
tableSize = input.string(defval = "Medium", title = "Table Size", 
  options = ["Small", "Large", "Medium"], group="Table Settings")
showTables = input.bool(true, "Show Tables?", group="Table Settings")

// Error Messages
var string ERROR_NOT_OPTION = "Error: Not an option symbol. Please apply this indicator to an option symbol (e.g., NSE:NIFTY24D28P21500)"
var string ERROR_INVALID_FORMAT = "Error: Invalid option symbol format. Expected format: SYMBOL[YYMMDD][C/P]STRIKE"
var string ERROR_MISSING_STRIKE = "Error: Cannot determine strike price from symbol"
var string ERROR_MISSING_EXPIRY = "Error: Cannot determine expiry from symbol"

// Initialize variables with default values
float marketPrice = 0.0
float impliedVolatility = 0.0
float volatility = 0.0
float deltaValue = 0.0
float gammaValue = 0.0
float thetaValue = 0.0
float vegaValue = 0.0
float plotValue = 0.0

// Helper function for rounding to 4 decimals
round4(float val) =>
    math.round(val * 10000) / 10000

// Helper function for normal distribution
normDist(z) =>
    b1 = 0.31938153
    b2 = -0.356563782
    b3 = 1.781477937
    b4 = -1.821255978
    b5 = 1.330274429
    p = 0.2316419
    c = 0.39894228

    abs_z = math.abs(z)
    k = 1.0 / (1.0 + p * abs_z)
    
    n = c * math.exp(-z * z / 2.0)
    phi = n * k * (b1 + k * (b2 + k * (b3 + k * (b4 + k * b5))))
    
    z < 0 ? phi : 1.0 - phi

// Option Price calculation function
optionPriceBS(float s, float x, float t, float r, float v, float d, bool isCall) =>
    d1 = (math.log(s/x) + (r - d + math.pow(v, 2)/2) * t) / (v * math.sqrt(t))
    d2 = d1 - v * math.sqrt(t)
    
    float result = 0.0
    if isCall
        result := s * math.exp(-d * t) * normDist(d1) - x * math.exp(-r * t) * normDist(d2)
    else
        result := x * math.exp(-r * t) * normDist(-d2) - s * normDist(-d1) * math.exp(-d * t)
    result

// Calculate Implied Volatility
calculateIV(bool isCall, float s, float x, float t, float r, float mp, float d) =>
    float hval = 3.0
    float lval = 0.0
    float result = 0.0
    
    if isCall
        while (hval - lval > 0.0001)
            mid = (hval + lval) / 2
            theoreticalPrice = optionPriceBS(s, x, t, r, mid, d, isCall)
            if theoreticalPrice > mp
                hval := mid
            else
                lval := mid
    else
        while (hval - lval > 0.0001)
            mid = (hval + lval) / 2
            theoreticalPrice = optionPriceBS(s, x, t, r, mid, d, isCall)
            if theoreticalPrice > mp
                hval := mid
            else
                lval := mid
    
    result := (hval + lval) / 2
    result

// Get spot price from underlying
getSpotPrice() =>
    request.security(underlyingInput, timeframe.period, close)

// Calculate days to expiry
getDaysToExpiry(int expiryYear, int expiryMonth, int expiryDay) =>
    currentTime = time
    expiryTime = timestamp(expiryYear, expiryMonth, expiryDay, expiryHour, expiryMinute)
    float timeToExpiry = (expiryTime - currentTime) / (1000 * 3600 * 24)
    
    float daysRemaining = math.floor(timeToExpiry)
    float hoursRemaining = math.floor((timeToExpiry - daysRemaining) * 24)
    
    [math.max(timeToExpiry, 0.0), daysRemaining, hoursRemaining]

// Symbol parsing function with runtime error
parseOptionSymbol() =>
    bool isCall = false
    float strikePrice = 0.0
    int expYear = 0
    int expMonth = 0
    int expDay = 0
    bool hasError = false
    string errorMsg = ""
    
    symbol = syminfo.ticker
    
    // Remove prefix if present
    if str.contains(symbol, ":")
        symbol := str.substring(symbol, str.pos(symbol, ":") + 1)

    // Find the option type (C or P)
    int optionTypePos = -1
    for i = str.length(symbol) - 1 to 0
        char = str.substring(symbol, i, i + 1)
        if char == "C" or char == "P"
            optionTypePos := i
            break
            
    if optionTypePos == -1
        runtime.error(ERROR_NOT_OPTION)
    
    // Determine option type
    optionType = str.substring(symbol, optionTypePos, optionTypePos + 1)
    isCall := optionType == "C"
    
    // The date should be 6 characters before the option type
    if optionTypePos < 6
        runtime.error(ERROR_INVALID_FORMAT)
        
    dateStart = optionTypePos - 6
    dateStr = str.substring(symbol, dateStart, dateStart + 6)
    
    // Extract strike price (everything after option type)
    strikeStr = str.substring(symbol, optionTypePos + 1)
    
    // Handle strike price
    float strikeNum = str.tonumber(strikeStr)
    if na(strikeNum)
        runtime.error(ERROR_MISSING_STRIKE)
    
    strikePrice := strikeNum
    
    // Parse date components
    if str.length(dateStr) != 6
        runtime.error(ERROR_INVALID_FORMAT)
    
    yearStr = str.substring(dateStr, 0, 2)
    monthStr = str.substring(dateStr, 2, 4)
    dayStr = str.substring(dateStr, 4, 6)
    
    float yearNum = str.tonumber(yearStr)
    float monthNum = str.tonumber(monthStr)
    float dayNum = str.tonumber(dayStr)
    
    if na(yearNum) or na(monthNum) or na(dayNum)
        runtime.error(ERROR_MISSING_EXPIRY)
    
    expYear := 2000 + int(yearNum)
    expMonth := int(monthNum)
    expDay := int(dayNum)
    
    [isCall, strikePrice, expYear, expMonth, expDay, hasError, errorMsg]

// Greeks calculations
delta(float spotPrice, float strikePrice, float annualizedTime, float volatility, bool isCall) =>
    float d1 = (math.log(spotPrice/strikePrice) + (riskFreeRate - dividend + math.pow(volatility, 2)/2) * annualizedTime) / (volatility * math.sqrt(annualizedTime))
    float result = 0.0
    if isCall
        result := normDist(d1)
    else
        result := normDist(d1) - 1
    round4(result)

gamma(float spotPrice, float strikePrice, float annualizedTime, float volatility) =>
    float d1 = (math.log(spotPrice/strikePrice) + (riskFreeRate - dividend + math.pow(volatility, 2)/2) * annualizedTime) / (volatility * math.sqrt(annualizedTime))
    float nd1 = math.exp(-(d1*d1) / 2) / math.sqrt(2 * PI)
    float result = nd1 / (spotPrice * (volatility * math.sqrt(annualizedTime)))
    round4(result)

theta(float spotPrice, float strikePrice, float annualizedTime, float volatility, bool isCall) =>
    float d1 = (math.log(spotPrice/strikePrice) + (riskFreeRate - dividend + math.pow(volatility, 2)/2) * annualizedTime) / (volatility * math.sqrt(annualizedTime))
    float d2 = d1 - volatility * math.sqrt(annualizedTime)
    float nd1 = math.exp(-(d1*d1) / 2) / math.sqrt(2 * PI)
    float nd2 = normDist(d2)

    float theta = 0.0
    if isCall
        theta := - (((spotPrice * volatility * nd1) / (2 * math.sqrt(annualizedTime))) - (riskFreeRate * strikePrice * math.exp(-riskFreeRate * annualizedTime) * nd2))
    else
        theta := - (((spotPrice * volatility * nd1) / (2 * math.sqrt(annualizedTime))) + (riskFreeRate * strikePrice * math.exp(-riskFreeRate * annualizedTime) * (1- nd2)))

    float result = 0.0
    if annualizedTime < float(1) / 365
        result := theta * annualizedTime
    else
        result := theta / 365
    round4(result)

vega(float spotPrice, float strikePrice, float annualizedTime, float volatility) =>
    float d1 = (math.log(spotPrice/strikePrice) + (riskFreeRate - dividend + math.pow(volatility, 2)/2) * annualizedTime) / (volatility * math.sqrt(annualizedTime))
    float nd1 = math.exp(-(d1*d1) / 2) / math.sqrt(2 * PI)
    float result = 0.01 * spotPrice * math.sqrt(annualizedTime) * nd1
    round4(result)

// Main calculation block
[isCall, strikePrice, expYear, expMonth, expDay, hasError, errorMsg] = parseOptionSymbol()
spotPrice = getSpotPrice()
[timeToExpiry, daysRemaining, hoursRemaining] = getDaysToExpiry(expYear, expMonth, expDay)
annualizedTime = timeToExpiry/365.0


// Calculate Greeks and update tables
marketPrice := close
impliedVolatility := round4(calculateIV(isCall, spotPrice, strikePrice, annualizedTime, riskFreeRate, marketPrice, dividend) * 100)
volatility := impliedVolatility/100
deltaValue := delta(spotPrice, strikePrice, annualizedTime, volatility, isCall)
gammaValue := gamma(spotPrice, strikePrice, annualizedTime, volatility)
thetaValue := theta(spotPrice, strikePrice, annualizedTime, volatility, isCall)
vegaValue := vega(spotPrice, strikePrice, annualizedTime, volatility)

// Calculate plot value
plotValue := switch plotOptions
    "Premium" => marketPrice
    "IV" => impliedVolatility
    "Delta" => deltaValue
    "Gamma" => gammaValue
    "Theta" => thetaValue
    "Vega" => vegaValue
    => marketPrice  // default case





// Function to Get Table Position
gettablePos(pos) =>
    switch pos
        "Top Right" => position.top_right
        "Bottom Right" => position.bottom_right
        "Middle Right" => position.middle_right
        "Top Left" => position.top_left
        "Middle Left" => position.middle_left

// Function to get the Table Size
gettableSize(size) =>
    switch size
        "Small" => size.small
        "Large" => size.large
        "Medium" => size.normal

// Creating Tables with improved styling
var table infoTable = table.new(position.bottom_left, 2, 6, 
  border_width=2, 
  border_color=color.gray, 
  frame_color=color.gray, 
  frame_width=2)

var table greeksTable = table.new(gettablePos(tablePosition), 2, 7, 
  border_width=2, 
  border_color=color.gray, 
  frame_color=color.gray, 
  frame_width=2)

// Function to plot Info Table
plotInfoTable(tbl, tblSize) =>
    if showTables
        table.cell(tbl, 0, 0, "Symbol Info", 
          bgcolor=color.black, text_color=color.white, text_size=tblSize)
        table.cell(tbl, 1, 0, "Value", 
          bgcolor=color.black, text_color=color.white, text_size=tblSize)
        
        table.cell(tbl, 0, 1, "Type", 
          bgcolor=color.rgb(0, 51, 102), text_color=color.white, text_size=tblSize)
        table.cell(tbl, 1, 1, isCall ? "Call" : "Put", 
          bgcolor=color.black, text_color=color.white, text_size=tblSize)
        
        table.cell(tbl, 0, 2, "Strike", 
          bgcolor=color.rgb(0, 51, 102), text_color=color.white, text_size=tblSize)
        table.cell(tbl, 1, 2, str.tostring(strikePrice), 
          bgcolor=color.black, text_color=color.white, text_size=tblSize)
        
        table.cell(tbl, 0, 3, "Spot Price", 
          bgcolor=color.rgb(0, 51, 102), text_color=color.white, text_size=tblSize)
        table.cell(tbl, 1, 3, str.tostring(spotPrice, "#.##"), 
          bgcolor=color.black, text_color=color.white, text_size=tblSize)
        
        table.cell(tbl, 0, 4, "Underlying", 
          bgcolor=color.rgb(0, 51, 102), text_color=color.white, text_size=tblSize)
        table.cell(tbl, 1, 4, underlyingInput, 
          bgcolor=color.black, text_color=color.white, text_size=tblSize)
        
        table.cell(tbl, 0, 5, "Time to Expiry", 
          bgcolor=color.rgb(0, 51, 102), text_color=color.white, text_size=tblSize)
        table.cell(tbl, 1, 5, str.tostring(daysRemaining, "#") + "d " + str.tostring(hoursRemaining, "#") + "h", 
          bgcolor=color.black, text_color=color.white, text_size=tblSize)

// Function to plot Greeks Table
plotGreeksTable(tbl, tblSize) =>
    if showTables
        table.cell(tbl, 0, 0, "Greeks", 
          bgcolor=color.black, text_color=color.white, text_size=tblSize)
        table.cell(tbl, 1, 0, "Value", 
          bgcolor=color.black, text_color=color.white, text_size=tblSize)
        
        table.cell(tbl, 0, 1, isCall ? "Call Price" : "Put Price", 
          bgcolor=color.rgb(51, 51, 0), text_color=color.white, text_size=tblSize)
        table.cell(tbl, 1, 1, str.tostring(marketPrice, "#.##"), 
          bgcolor=color.black, text_color=color.yellow, text_size=tblSize)
        
        table.cell(tbl, 0, 2, "IV %", 
          bgcolor=color.rgb(51, 51, 0), text_color=color.white, text_size=tblSize)
        table.cell(tbl, 1, 2, str.tostring(impliedVolatility, "#.##"), 
          bgcolor=color.black, text_color=color.white, text_size=tblSize)
        
        table.cell(tbl, 0, 3, "Delta", 
          bgcolor=color.rgb(0, 51, 0), text_color=color.white, text_size=tblSize)
        table.cell(tbl, 1, 3, str.tostring(deltaValue, "#.####"), 
          bgcolor=color.black, text_color=color.lime, text_size=tblSize)
        
        table.cell(tbl, 0, 4, "Gamma", 
          bgcolor=color.rgb(51, 0, 0), text_color=color.white, text_size=tblSize)
        table.cell(tbl, 1, 4, str.tostring(gammaValue, "#.####"), 
          bgcolor=color.black, text_color=color.red, text_size=tblSize)
        
        table.cell(tbl, 0, 5, "Theta", 
          bgcolor=color.rgb(51, 0, 51), text_color=color.white, text_size=tblSize)
        table.cell(tbl, 1, 5, str.tostring(thetaValue, "#.####"), 
          bgcolor=color.black, text_color=color.purple, text_size=tblSize)
        
        table.cell(tbl, 0, 6, "Vega", 
          bgcolor=color.rgb(51, 25, 0), text_color=color.white, text_size=tblSize)
        table.cell(tbl, 1, 6, str.tostring(vegaValue, "#.####"), 
          bgcolor=color.black, text_color=color.orange, text_size=tblSize)

// Update tables
if barstate.islast and not hasError
    plotInfoTable(infoTable, gettableSize(tableSize))
    plotGreeksTable(greeksTable, gettableSize(tableSize))

// Main plot and background plots remain the same...

// Main plot
plot(plotValue, color=plotColor, linewidth=plotThickness)

Experience with Claude Sonnet 3.5

Claude Sonnet 3.5’s efficiency in handling complex financial models was impressive. When converting the Amibroker AFL code to Pinescript, I needed to handle calculations like the Greeks—Implied Volatility, Delta, Gamma, Theta, and Vega—which require sophisticated mathematical functions. Claude Sonnet 3.5 made this possible by providing relevant code snippets and efficient methods to implement these calculations.

One of the key strengths of Claude Sonnet 3.5 was its ability to understand the context of financial markets, such as working with NSE symbols and interpreting specific requirements for different markets. This contextual understanding significantly reduced the time I would have spent manually tweaking code for compatibility.

The model also helped in optimizing error handling mechanisms, ensuring that the script could properly differentiate between option and non-option symbols, which is crucial for maintaining accuracy in real-time calculations. Claude Sonnet 3.5 provided effective error handling solutions, such as runtime error methods, that made the code cleaner and more efficient.

Visual Enhancements and Dynamic Updates

Beyond calculations, designing a visually appealing and user-friendly interface for the options data was another key aspect of the project. Claude Sonnet 3.5 suggested improved ways to design tables and ensure that the displayed information was both clear and aesthetically pleasing. The model’s suggestions for table enhancements, such as color coding and better layout organization, contributed to a more professional look for the calculator.

Handling dynamic value updates in real-time was another challenge, particularly due to the need for real-time plotting of data. Claude Sonnet 3.5 demonstrated a good understanding of how Pinescript handles variable updates and provided guidance on ensuring the plots updated smoothly without glitches. This was essential for creating an effective tool that traders could use to monitor changing market conditions.

Key Learnings

  1. Efficiency in Complex Coding: Claude Sonnet 3.5 significantly reduced the effort required to implement complex financial calculations, making the coding process faster and more manageable.
  2. Contextual Understanding: The model’s ability to understand financial context, like handling NSE options, made it much easier to adapt the code to specific market requirements.
  3. Visual and Functional Optimizations: Claude Sonnet 3.5 was not just helpful in calculations but also in making the output visually appealing and functionally efficient, ensuring that the end product was polished and user-friendly.

Final Thoughts

Building a real-time Option Greeks calculator with Implied Volatility in TradingView Pinescript was a complex task, but using Claude Sonnet 3.5 helped expedite the process significantly. The model’s contextual understanding, efficient coding suggestions, and ability to handle complex calculations made it an invaluable tool for this project.

Claude Sonnet 3.5 demonstrated that with the right AI support, even complex tasks like converting an AFL code for options trading into a robust, real-time calculator in Pinescript can be achieved effectively. The model’s contributions went beyond just code generation—it optimized processes, improved visual output, and made the entire development journey smoother and more productive.

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

SketchMaker AI: Create Stunning AI Visuals and Your Own…

SketchMaker AI is an open-source tool that transforms text into art, allowing you to create stunning AI images, blog banners, Instagram and YouTube thumbnails...
Rajandran R
3 min read

First Order and Second Order Option Greeks in Tradingview…

The provided Tradingview Pine Script™ code is an Options Greeks Calculator tailored for use on the TradingView platform. It provides real-time analysis and visualization...
Rajandran R
12 min read

Enhancing Algo Trading Development with Amazon AWS Bedrock &…

With tools like Amazon AWS Bedrock and the Cline VS Code extension (using the Claude Sonnet 3.5 model), traders can streamline the development of...
Rajandran R
4 min read

Leave a Reply

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