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
- 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.
- 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.
- 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.