Hi! I wanted to share a label indicator I created using parts from other studies and generating some with aid from ChatGPT (not sure where you guys stand on this).
I've been working on this to help me monitor and decide what to do with the contracts I have (I don't usually handle more than 5-6 contracts at a time). It gives me information about the contract at a glance, including actions based on greeks, position, moneyness, DTE, etc. It autodetects a position, direction, P/L. I am fine tuning it and validating if it works properly. All logic about actions, percentages, levels, etc was all established by ChatGPT using best practices and personal objectives.
Here is a snpashot of my options monitoring charts. I am also using some studies from @khpro59 setup (AutoStop, Scalper, Percentile), with some adaptations.
Make sure to update the Expiration on the input, the rest should be fetched automagically.
Here is the current code:
I've been working on this to help me monitor and decide what to do with the contracts I have (I don't usually handle more than 5-6 contracts at a time). It gives me information about the contract at a glance, including actions based on greeks, position, moneyness, DTE, etc. It autodetects a position, direction, P/L. I am fine tuning it and validating if it works properly. All logic about actions, percentages, levels, etc was all established by ChatGPT using best practices and personal objectives.
Here is a snpashot of my options monitoring charts. I am also using some studies from @khpro59 setup (AutoStop, Scalper, Percentile), with some adaptations.
Make sure to update the Expiration on the input, the rest should be fetched automagically.
Here is the current code:
Code:
plot Data = close; # required
# ==============================================
# Option Position Snapshot – v6.6 (auto, adaptive, neutral)
# Attach to OPTION chart
# ==============================================
# ---------- Inputs
input optionType = {default Call, Put};
input expirationDate = 20250912;
input strikeStep = 0.50;
# Time buckets
input dteImmediateMax = 5;
input dteNearMax = 12;
# Roll target DTE (guidelines)
input rollDTE_immediate = 21;
input rollDTE_near = 21;
input rollDTE_ITM_def = 28;
# Moneyness via |Delta|
input deltaDeepITM = 0.75;
input deltaITM = 0.55;
input deltaATM = 0.35;
input deltaDeepOTM = 0.15;
# Roll target Delta magnitudes
input rollTargetDeltaShort = 0.25; # short aim ~0.20..0.30
input rollTargetDeltaLong = 0.60; # long aim ~0.50..0.65
# Base alert thresholds
input deltaAlertFar = 0.25;
input deltaAlertNear = 0.35;
input deltaAlertImmediate = 0.45;
input gammaWatchNear = 0.08;
input gammaWatchImmediate = 0.12;
# Quotes for roll estimate (optional)
input currLegBid = 0.0;
input currLegAsk = 0.0;
input newLegBid = 0.0;
input newLegAsk = 0.0;
# UI and Signal
input uiMode = {default Auto, Compact, Full};
input signal = {default Neutral, Long, Short};
# Volatility adaptation (underlying ATR%)
input volAtrLen = 14;
input volLowPct = 2.5;
input volHighPct = 4.5;
# Neutralize Action after adjust
input neutralCooldownBars = 3;
input sigDeltaStep = 0.10;
input sigATRlen = 14;
input sigMoveATRk = 0.75;
# ---------- Basics
def isCall = optionType == optionType.Call;
def und = close(GetUnderlyingSymbol());
def dte = DaysTillDate(expirationDate);
# Auto strike (keeps .5 etc.)
def strikePx = GetStrike();
# Position (auto)
def qty = GetQuantity();
def hasPos = qty != 0;
def isLong = qty > 0;
def isShort = qty < 0;
def qtyAbs = AbsValue(qty);
def avgPx = AbsValue(GetAveragePrice());
def optPx = close;
def contractSize = 100;
# Greeks
def d = Delta();
def ad = AbsValue(d);
def g = Gamma();
def t = Theta();
# Moneyness code via |Delta|
# +2 deep ITM, +1 ITM, 0 ATM, -1 OTM, -2 deep OTM
def mnyCode =
if ad >= deltaDeepITM then 2 else
if ad >= deltaITM then 1 else
if ad >= deltaATM then 0 else
if ad > deltaDeepOTM then -1 else -2;
def favorable = if isShort then (mnyCode <= -1) else (mnyCode >= 1);
def risky = if isShort then (mnyCode >= 1) else (mnyCode <= -1);
# Probabilities (from Delta, bounded)
def d_clamped = if d > 1 then 1 else if d < -1 then -1 else d;
def probOTM = (if isCall then (1 - d_clamped) else (1 + d_clamped)) * 100;
def probITM = 100 - probOTM;
# Intrinsic / Breakeven
def intrinsic = if isCall then Max(0, und - strikePx) else Max(0, strikePx - und);
def breakeven = if isCall then strikePx + avgPx else strikePx - avgPx;
# DTE buckets
def isImmediate = dte <= dteImmediateMax;
def isNear = dte > dteImmediateMax and dte <= dteNearMax;
def isFar = dte > dteNearMax;
# ---------- P/L using real average price
def pnlPer = if hasPos then (if isLong then optPx - avgPx else avgPx - optPx) else Double.NaN;
def pnlTot = pnlPer * qtyAbs * contractSize;
def pctPnL = if hasPos and avgPx > 0 then 100 * pnlPer / avgPx else Double.NaN;
# ---------- Roll target (Delta -> Strike via Gamma)
def tgtDeltaSigned = (if isCall then 1 else -1) * (if isShort then rollTargetDeltaShort else rollTargetDeltaLong);
def deltaToTarget = tgtDeltaSigned - d;
def estShift = if AbsValue(g) > 0.000001 then (-deltaToTarget) / g else 0;
def newStrikeRaw = strikePx + estShift;
def qStep = Round(newStrikeRaw / strikeStep, 0);
def newStrike = Max(strikeStep, qStep * strikeStep);
def baseRollDTE = if isImmediate then rollDTE_immediate else rollDTE_near;
def rollDTEsugg = if (mnyCode >= 1) then Max(baseRollDTE, rollDTE_ITM_def) else baseRollDTE;
# Signal flags
def sigLong = signal == signal.Long;
def sigShort = signal == signal.Short;
# Roll suggestion (core + bias)
def suggestRollCore =
(isShort and (mnyCode >= 1 or (mnyCode == 0 and (isNear or isImmediate))))
or (isLong and (mnyCode == 0 and (isNear or isImmediate)))
or (isLong and (mnyCode >= 1 and isImmediate));
def suggestRollSignal = (isShort and sigLong and (isNear or isImmediate))
or (isLong and sigShort and (isNear or isImmediate));
def suggestRoll = suggestRollCore or suggestRollSignal;
# ---------- Underlying ATR% (vol engine)
def uh = high(GetUnderlyingSymbol());
def ul = low(GetUnderlyingSymbol());
def uc = close(GetUnderlyingSymbol());
def utr = TrueRange(uh, uc[1], ul);
def uatr = Average(utr, volAtrLen);
def uVolPct = if uc > 0 then 100 * uatr / uc else 0;
# ---------- Partial sizes (rounded to nearest int)
def pct1 = if qtyAbs >= 20 then 0.25 else if qtyAbs >= 10 then 1.0 / 3.0 else 0.50;
def pct2 = 0.50;
def qty25 = if hasPos then Max(1, Round(qtyAbs * pct1, 0)) else 0;
def qty50 = if hasPos then Max(qty25, Round(qtyAbs * pct2, 0)) else 0;
# ---------- Adaptive targets by volatility
# Long trims (pct P/L per contract)
def longTP1 = if uVolPct >= volHighPct then 35 else if uVolPct >= volLowPct then 25 else 15;
def longTP2 = if uVolPct >= volHighPct then 70 else if uVolPct >= volLowPct then 50 else 30;
# Short buyback factors (option price vs avg)
def shortCloseFactor25 = if uVolPct >= volHighPct then 0.40 else if uVolPct >= volLowPct then 0.50 else 0.70;
def shortCloseFactor50 = if uVolPct >= volHighPct then 0.25 else if uVolPct >= volLowPct then 0.33 else 0.50;
# ---------- Dynamic partial triggers
def longTrim50 = isLong and pctPnL >= longTP2;
def longTrim25 = isLong and pctPnL >= longTP1 and !longTrim50;
def longCut50 = isLong and isImmediate and mnyCode <= -1;
def shortClose50 = isShort and (optPx <= avgPx * shortCloseFactor50) and (isNear or isImmediate);
def shortClose25 = isShort and (optPx <= avgPx * shortCloseFactor25) and !shortClose50;
# ---------- Adaptive Delta/Gamma alert thresholds
def thrD_F = if uVolPct >= volHighPct then 0.30 else if uVolPct >= volLowPct then deltaAlertFar else 0.20;
def thrD_N = if uVolPct >= volHighPct then 0.40 else if uVolPct >= volLowPct then deltaAlertNear else 0.30;
def thrD_I = if uVolPct >= volHighPct then 0.50 else if uVolPct >= volLowPct then deltaAlertImmediate else 0.40;
def thrG_N = if uVolPct >= volHighPct then 0.10 else if uVolPct >= volLowPct then gammaWatchNear else 0.06;
def thrG_I = if uVolPct >= volHighPct then 0.14 else if uVolPct >= volLowPct then gammaWatchImmediate else 0.10;
# Rewritten triggers
def showDelta = isShort and (mnyCode <= -1);
def trigDeltaFar = showDelta and isFar and ad > thrD_F and ad[1] <= thrD_F;
def trigDeltaNear = showDelta and isNear and ad > thrD_N and ad[1] <= thrD_N;
def trigDeltaImmediate = showDelta and isImmediate and ad > thrD_I and ad[1] <= thrD_I;
def showGamma = (mnyCode == 0) and (isNear or isImmediate);
def trigGammaNear = (mnyCode == 0) and isNear and g > thrG_N and g[1] <= thrG_N;
def trigGammaImmediate = (mnyCode == 0) and isImmediate and g > thrG_I and g[1] <= thrG_I;
# ---------- Neutralize Action after adjust, re-enable on significant change
def qtyAbsNow = qtyAbs;
def pmNow = avgPx;
def qtyChanged = qtyAbsNow != qtyAbsNow[1];
def pmChanged = pmNow > 0 and pmNow[1] > 0 and AbsValue(pmNow - pmNow[1]) > 0.01 * pmNow;
def didAdjust = qtyChanged or pmChanged;
def bn = BarNumber();
rec lastAdjustBar = if didAdjust then bn else CompoundValue(1, lastAdjustBar[1], 0);
def barsSinceAdj = bn - lastAdjustBar;
rec refDelta = if didAdjust then ad else CompoundValue(1, refDelta[1], ad);
rec refPrice = if didAdjust then optPx else CompoundValue(1, refPrice[1], optPx);
rec refMny = if didAdjust then mnyCode else CompoundValue(1, refMny[1], mnyCode);
def trOpt = TrueRange(high, close[1], low);
def atrOpt = Average(trOpt, sigATRlen);
def movedDelta = AbsValue(ad - refDelta) >= sigDeltaStep;
def movedPrice = AbsValue(optPx - refPrice) >= sigMoveATRk * atrOpt;
def changedMny = mnyCode != refMny;
def significantChange = movedDelta or movedPrice or changedMny;
def neutralHold = (barsSinceAdj < neutralCooldownBars) and !significantChange;
# ---------- UI gates
def wantFull = uiMode == uiMode.Full;
def wantCompact = uiMode == uiMode.Compact;
def wantAuto = uiMode == uiMode.Auto;
def showStatus = wantFull or (wantAuto and (isNear or isImmediate)) or (wantAuto and risky);
def showPL = hasPos and (wantFull or (isImmediate or isNear) or (AbsValue(pctPnL) >= 25));
def showProb = (isShort and (wantFull or isNear or isImmediate)) or (isLong and wantFull);
def showBE = wantFull or (isNear or isImmediate);
def showAction = hasPos and (wantFull or wantAuto);
def showRoll = suggestRoll;
# ---------- Labels
AddLabel(yes,
(if hasPos then (if isLong then "Long " else "Short ") else "No Pos ") +
(if isCall then "Call" else "Put") +
(if hasPos then " | Qty " + qtyAbs else "") +
" | K " + Round(strikePx, 2) + " | DTE " + dte,
Color.WHITE);
AddLabel(showStatus,
"Window " + (if isImmediate then "EXP" else if isNear then "W-1" else "2+W") +
" | Money " +
(if mnyCode == 2 then "Deep ITM"
else if mnyCode == 1 then "ITM"
else if mnyCode == 0 then "ATM"
else if mnyCode == -1 then "OTM"
else "Deep OTM") +
" | " + (if intrinsic > 0 then "Intrinsic" else "Extrinsic"),
(if isImmediate then Color.RED
else if isNear then Color.YELLOW
else (if favorable then Color.GREEN else Color.YELLOW))
);
AddLabel(
(isNear or isImmediate) or wantFull or trigDeltaFar or trigDeltaNear or trigDeltaImmediate or trigGammaNear or trigGammaImmediate,
"Delta " + Round(d, 2) +
(if showGamma then " | Gamma " + Round(g, 4) else "") +
(if showProb then (if isShort then " | Prob OTM " + Round(probOTM, 0) + "%" else " | Prob ITM " + Round(probITM, 0) + "%") else ""),
(if isShort then (if ad < 0.30 then Color.GREEN else if ad < 0.60 then Color.YELLOW else Color.RED)
else (if ad >= 0.60 then Color.GREEN else if ad >= 0.30 then Color.YELLOW else Color.RED))
);
AddLabel(showPL,
"P/L " + AsDollars(pnlTot) +
(if hasPos then " (" + Round(pctPnL, 0) + "%)" else "") +
(if showBE and hasPos then " | BE " + Round(breakeven, 2) else "") +
(if isLong and (isNear or isImmediate or wantFull) then " | Theta/Day " + Round(t, 2) else ""),
(if pnlTot >= 0 then Color.GREEN else Color.RED)
);
# Action label (neutral after adjust; re-enable on significant change)
def actionUrg =
if isImmediate and risky then 2
else if isNear and risky then 1
else if favorable then 0
else 1;
AddLabel(
showAction,
(if neutralHold then
"Action: Neutral (recent adjust)."
else
"Action: " +
(if isShort then
(if shortClose50 then "Close " + qty50 + "."
else if shortClose25 then "Close " + qty25 + "."
else if (isImmediate and mnyCode >= 1) then "Roll up/out NOW."
else "Carry; manage risk.")
else
(if longCut50 then "Cut " + qty50 + "."
else if longTrim50 then "Trim " + qty50 + "."
else if longTrim25 then "Trim " + qty25 + "."
else "Hold; trail.")) +
(if suggestRoll then " | Roll -> ~" + rollDTEsugg + "DTE @ K~" + Round(newStrike, 2) else "")
),
(if neutralHold then Color.WHITE
else if actionUrg >= 2 then Color.RED
else if actionUrg == 1 then Color.YELLOW
else Color.WHITE)
);
AddLabel(showRoll,
"Roll -> ~" + rollDTEsugg + " DTE | K~ " + Round(newStrike, 2) + " (step " + strikeStep + ")",
(if (mnyCode >= 1) then (if isImmediate then Color.RED else Color.YELLOW)
else (if isImmediate then Color.YELLOW else Color.GREEN))
);
# Roll estimate
def haveCurrQuotes = (currLegBid > 0 and currLegAsk > 0);
def haveNewQuotes = (newLegBid > 0 and newLegAsk > 0);
def currMid = (currLegBid + currLegAsk) / 2;
def newMid = (newLegBid + newLegAsk) / 2;
def netMidPer = if haveCurrQuotes and haveNewQuotes
then (if isShort then (newMid - currMid) else (currMid - newMid))
else Double.NaN;
def netWorstPer = if haveCurrQuotes and haveNewQuotes
then (if isShort then (newLegBid - currLegAsk) else (currLegBid - newLegAsk))
else Double.NaN;
def netMidTot = netMidPer * qtyAbs * contractSize;
def netWorstTot = netWorstPer * qtyAbs * contractSize;
AddLabel(suggestRoll and haveCurrQuotes and haveNewQuotes,
"Roll est (mid/worst): " + AsDollars(netMidTot) + " / " + AsDollars(netWorstTot),
(if IsNaN(netMidTot) then Color.GRAY else if netMidTot >= 0 then Color.GREEN else Color.RED)
);
# ---------- Alerts (always last)
Alert(trigDeltaFar, "Delta rising above FAR threshold (short OTM).", Alert.BAR, Sound.Ding);
Alert(trigDeltaNear, "Delta rising above NEAR threshold (short OTM).", Alert.BAR, Sound.Ding);
Alert(trigDeltaImmediate, "Delta rising above EXP-WEEK threshold (short OTM).", Alert.BAR, Sound.Ding);
Alert(trigGammaNear, "Gamma above NEAR watch level (ATM).", Alert.BAR, Sound.Ding);
Alert(trigGammaImmediate, "Gamma above EXP-WEEK watch level (ATM).", Alert.BAR, Sound.Ding);
Last edited: