Options Monitoring Labels For ThinkOrSwim

trader_joe

New member
VIP
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.

1756398444502.png



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:
I am currently working to implement a state-machine that will monitor for last recomendations, and if there's any change to the position, it will not show the same recomendation for a cooldown period (haven't had the opportunity to test it today)
 
Last edited:
The state machine worked as expected, with the caveat that it is ephemerous to the current session (it does not carry accross app shutdown)

Before: action recommended

1756399536975.png


After implementing the action:

1756400184440.png
 
Last edited by a moderator:

Join useThinkScript to post your question to a community of 21,000+ developers and traders.

Similar threads

Not the exact question you're looking for?

Start a new thread and receive assistance from our community.

87k+ Posts
247 Online
Create Post

Similar threads

Similar threads

The Market Trading Game Changer

Join 2,500+ subscribers inside the useThinkScript VIP Membership Club
  • Exclusive indicators
  • Proven strategies & setups
  • Private Discord community
  • ‘Buy The Dip’ signal alerts
  • Exclusive members-only content
  • Add-ons and resources
  • 1 full year of unlimited support

Frequently Asked Questions

What is useThinkScript?

useThinkScript is the #1 community of stock market investors using indicators and other tools to power their trading strategies. Traders of all skill levels use our forums to learn about scripting and indicators, help each other, and discover new ways to gain an edge in the markets.

How do I get started?

We get it. Our forum can be intimidating, if not overwhelming. With thousands of topics, tens of thousands of posts, our community has created an incredibly deep knowledge base for stock traders. No one can ever exhaust every resource provided on our site.

If you are new, or just looking for guidance, here are some helpful links to get you started.

What are the benefits of VIP Membership?
VIP members get exclusive access to these proven and tested premium indicators: Buy the Dip, Advanced Market Moves 2.0, Take Profit, and Volatility Trading Range. In addition, VIP members get access to over 50 VIP-only custom indicators, add-ons, and strategies, private VIP-only forums, private Discord channel to discuss trades and strategies in real-time, customer support, trade alerts, and much more. Learn all about VIP membership here.
How can I access the premium indicators?
To access the premium indicators, which are plug and play ready, sign up for VIP membership here.
Back
Top