# Earnings IV Crush Label

#### bp805

##### Member
I'm trying to create a study that will plot a chart bubble on the earnings date that will show what the % IV drop was from IV % prior to the earnings report to the IV % on the day after the report. The below code is what I came up with but not sure if it is correct or not. I also am getting an "expected double" error. Can anyone help figure this out? Also, is it possible to have this use the IV% from the soonest expiring options contract, or does the ImpVolatility() pull a different value than the soonest expiring contract? Thanks for any help that can be provided!

Code:
``````def isBefore = HasEarnings(EarningTime.BEFORE_MARKET);
def isAfter  = HasEarnings(EarningTime.AFTER_MARKET);

def vol = imp_volatility();
def beforevol = if isAfter then vol else if isBefore then vol[1] else 0;
def aftervol = if isAfter then vol[-1] else if isBefore then vol else 0;

def volcalc = (aftervol - beforevol) / beforevol;
def downvol = volcalc < 0;

AddChartBubble(downvol, high[1], "IV Crush: " + AsPercent(aftervol - beforevol) / beforevol, Color.YELLOW);``````

in the bubble function, the aspercent( ) formula is dividing by 0 (beforevol) , which causes n/a , which causes the error message

change your volcalc formula, to check for a 0 value. then use this variable in the bubble, instead of recreating the formula
Ruby:
``  def volcalc =  if beforevol == 0  then 0 else  (AsPercent(aftervol - beforevol) / beforevol);``

to help me find this, i added this to your code ,to see variable values on each bar

Ruby:
``addchartbubble( 1, high, isbefore + "\n" + isafter + "\n" + volcalc, color.cyan, yes);``

Last edited:
in the bubble function, the aspercent( ) formula is dividing by 0 (beforevol) , which causes n/a , which causes the error message

change your volcalc formula, to check for a 0 value. then use this variable in the bubble, instead of recreating the formula
Ruby:
``  def volcalc =  if beforevol == 0  then 0 else  (AsPercent(aftervol - beforevol) / beforevol);``

to help me find this, i added this to your code ,to see variable values on each bar

Ruby:
``addchartbubble( 1, high, isbefore + "\n" + isafter + "\n" + volcalc, color.cyan, yes);``

I changed the code and replaced the volcalc line and the chart bubble line with your two suggested edits, but I still get an "expected double" error. What am I missing here?

Ruby:
``````def isBefore = HasEarnings(EarningTime.BEFORE_MARKET);
def isAfter  = HasEarnings(EarningTime.AFTER_MARKET);

def vol = imp_volatility();
def beforevol = if isAfter then vol else if isBefore then vol[1] else 0;
def aftervol = if isAfter then vol[-1] else if isBefore then vol else 0;

def volcalc =  if beforevol == 0  then 0 else  (AsPercent(aftervol - beforevol) / beforevol);

def downvol = volcalc < 0;

#AddChartBubble(downvol, high[1], "IV Crush: " + AsPercent(aftervol - beforevol) / beforevol, Color.YELLOW);

AddChartBubble( 1, high, isBefore + "\n" + isAfter + "\n" + volcalc, Color.CYAN, yes);``````

that's odd. it worked on mine. i don't see the issue in your code on my cell.... i will check back in an hour or so.
try to find the error code line, by starting at the last code line, and adding a # in front of the line. if the error still exists, move up and do the previous line.

I'm trying to create a study that will plot a chart bubble on the earnings date that will show what the % IV drop was from IV % prior to the earnings report to the IV % on the day after the report. The below code is what I came up with but not sure if it is correct or not. I also am getting an "expected double" error. Can anyone help figure this out? Also, is it possible to have this use the IV% from the soonest expiring options contract, or does the ImpVolatility() pull a different value than the soonest expiring contract? Thanks for any help that can be provided!

Code:
``````def isBefore = HasEarnings(EarningTime.BEFORE_MARKET);
def isAfter  = HasEarnings(EarningTime.AFTER_MARKET);``````

You can't rely on those two HasEarnings() calls alone. Sometimes TOS doesn't know whether earnings is/was before or after the session. Look at Twitter's last few earnings and you'll find one like I'm saying. The solution is to make a call to `HasEarnings()` without specifying any parameter and make a judgement call about whether you want to treat that as before or after the session and likely no bubble will show when it should for the ones where the assumption is wrong.

Ruby:
``````def isAfter = HasEarnings(EarningTime.AFTER_MARKET);
def isBefore = HasEarnings() and !isAfter;``````

Your expected double error is because of your AsPercent() call which converts the number to a string of text. If you remove that the error goes away. You can apply whatever formatting you need in the AddChartBubble() call instead.

Ruby:
``````def isAfter = HasEarnings(EarningTime.AFTER_MARKET);
def isBefore = HasEarnings() and !isAfter;

def vol = imp_volatility();
def beforevol = if isAfter then vol else if isBefore then vol[1] else 0;
def aftervol = if isAfter then vol[-1] else if isBefore then vol else 0;

def volcalc =  if beforevol == 0 then 0 else (aftervol - beforevol) / beforevol;
def downvol = volcalc < 0;

AddChartBubble(downvol, high[1], "IV Crush: " + AsPercent(volcalc), Color.YELLOW);``````

Last edited:
Ok, thank you both for the clarification.

I'm thinking that that due to the low %s that this script returns (AAPL IV crush on earnings is about -17% on average over the last 2 years) that imp_volatility() pulls the overall IV value of the ticker vs. the IV value for the closest options contract expiration (which is what I'm looking to achieve)? Is that correct? Is there any way to use the IV value for the soonest expiring options contract? See the pic below for clarification. (Wanting to use #1 instead of #2 in the picture if possible).

Looking at the ThinkScript Reference it looks like you might be able to cobble something together. Something like (untested, just writing this here)...

def v = imp_volatility(GetATMOption(GetSymbol(), GetYYYYMMDD()));

It looks like no default is supplied for the date so you'd have to experiment. See what it returns if you use GetYYYYMMDD(). If that returns something but not the upcoming expiration then you can use GetNextExpirationOption(String optionCode) to find the next expiration. Otherwise you might just have to hard code the date and keep updating it manually. There's other functions if you want ITM or OTM strikes.

You can't rely on those two HasEarnings() calls alone. Sometimes TOS doesn't know whether earnings is/was before or after the session. Look at Twitter's last few earnings and you'll find one like I'm saying. The solution is to make a call to `HasEarnings()` without specifying any parameter and make a judgement call about whether you want to treat that as before or after the session and likely no bubble will show when it should for the ones where the assumption is wrong.

Ruby:
``````def isAfter = HasEarnings(EarningTime.AFTER_MARKET);
def isBefore = HasEarnings() and !isAfter;``````

Your expected double error is because of your AsPercent() call which converts the number to a string of text. If you remove that the error goes away. You can apply whatever formatting you need in the AddChartBubble() call instead.

Ruby:
``````def isAfter = HasEarnings(EarningTime.AFTER_MARKET);
def isBefore = HasEarnings() and !isAfter;

def vol = imp_volatility();
def beforevol = if isAfter then vol else if isBefore then vol[1] else 0;
def aftervol = if isAfter then vol[-1] else if isBefore then vol else 0;

def volcalc =  if beforevol == 0 then 0 else (aftervol - beforevol) / beforevol;
def downvol = volcalc < 0;

AddChartBubble(downvol, high[1], "IV Crush: " + AsPercent(volcalc), Color.YELLOW);``````
thank you for correcting me. i learned something.

thank you for correcting me. i learned something.

Think nothing of it. We're all learning as we go. I only learned of that HasEarnings() quirk a couple of weeks ago.

I forgot to post after testing this the other night... What I found was that the error went away when the AddChartBubble() was commented out and also that both HasEarnings() calls returned 0... I didn't get any further, however, but that indicated an issue with HasEarnings()... I used AddLabel() to display the values being derived to get as far as I got...

Here is the final of what I came up with. I added an average IV crush label at the top as well to average everything from each earnings report displayed. Found it odd also that there were instances were I got no value for a certain earnings date and thought that maybe the IV didn't drop on that specific report? So I added a bubble to calculate expansion of IV and magically it plotted a bubble for that given earnings report. (See AAPL for July and November reports of 2020).

Regarding the closest options series IV, I emailed the thinkscript desk at TOS asking if it was possible to get the options series IV. They said that they don't have a function that would return that value. I then asked how the options series IV is calculated and if putting that Imp_Volatility() calculation into a study would return the correct value...still waiting for a response.

So @Slippage, that is my next question I guess. Do you know how the options series IV is calculated (presumably some equation incorporating the overall IV)? If so, would it be possible then to calculate it and arrive at the closest expiring options series IV? Im guessing this data is maybe only available for the last year to be able to work with, but would be better than nothing. Thanks!

Ruby:
``````def isAfter = HasEarnings(EarningTime.AFTER_MARKET);
def isBefore = HasEarnings() and !isAfter;

def vol = imp_volatility();
def beforevol = if isAfter then vol else if isBefore then vol[1] else 0;
def aftervol = if isAfter then vol[-1] else if isBefore then vol else 0;

#Calculates % change of IV from day prior to the earnings date to the day after the report.
def volcalc =  if beforevol == 0 then 0 else (aftervol - beforevol) / beforevol;

#Defines # of positive or negative vol crush after earnings.
def negvolcalc = volcalc < 0;
def posvolcalc = volcalc > 0;

AddChartBubble(negvolcalc, high[1], "IV Crush: " + AsPercent(volcalc), Color.YELLOW);
AddChartBubble(posvolcalc, high[1], "IV Expansion: " + AsPercent(volcalc), color.green);

AddLabel(1,"Average IV Crush : " + aspercent(totalsum(volcalc)/(totalsum(negvolcalc) + totalsum(posvolcalc))), color.yellow);``````

So @Slippage, that is my next question I guess. Do you know how the options series IV is calculated (presumably some equation incorporating the overall IV)?

I have no idea. ThinkOrSwim's docs reference the following book, though, if you want to pursue it.
The Complete Guide To Option Pricing Formulas" by Espen Gaarder Haug, McGraw-Hill, 1998

The below code will arrive at the current weekly options series IV. Doesn't look like it has any historical data, but I'm not certain. I replaced "def vol" in the code above with the code below, and changed the "vol" within the code to "series 1" but it didn't give me any results in the bubbles or label.

Ruby:
``def series1 = if isNaN(SeriesVolatility(series = 1)) then series1[1] else SeriesVolatility(series = 1);``

This is a great indicator and saved a lot of time.

I think it could be a lot more powerful if we could calculate the \$ and % move following X bars after earnings. I think you could just change

def vol = imp_volatility();

to:

def vol = close();

If someone could help by changing the code to generate the average only of down and only of up events, and the combined (as it is now), that would be great so the results aren't skewed and we could have a good idea of how the strongly the stock moves on good earning pops and how much on average it falls on poor reports

Also is it possible to have this study as a lower one with the labels

Last edited:
I've edited the code to separate the label calculations based on up and down events.

That way, it is way easier to understand the average of how much IV goes up and how much IV goes down. I've also included the number of events IV increases and the number of events it falls, as labels.

Code:
``````def isAfter = HasEarnings(EarningTime.AFTER_MARKET);
def isBefore = HasEarnings() and !isAfter;

def vol = imp_volatility();
def beforevol = if isAfter then vol else if isBefore then vol[1] else 0;
def aftervol = if isAfter then vol[-1] else if isBefore then vol else 0;

#Calculates % change of IV from day prior to the earnings date to the day after the report.
def volcalc =  if beforevol == 0 then 0 else (aftervol - beforevol) / beforevol;

def volcalcpositive =  if beforevol == 0 then 0 else Max(0,(aftervol - beforevol) / beforevol);

def volcalcnegative =  if beforevol == 0 then 0 else Min(0,(aftervol - beforevol) / beforevol);

#Defines # of positive or negative vol crush after earnings.
def negvolcalc = volcalc < 0;
def posvolcalc = volcalc > 0;

AddChartBubble(negvolcalc, high[1], "IVChng " + AsPercent(volcalc), Color.RED);
AddChartBubble(posvolcalc, high[1], "IVChng " + AsPercent(volcalc), Color.GREEN);

#Find number of up events
def barUpCount = IF (volcalc > 0) THEN IF IsNaN(barUpCount[1]) THEN 1 ELSE barUpCount[1] + 1 ELSE barUpCount[1];
#Find number of down events
def barDownCount = IF (volcalc < 0) THEN IF IsNaN(barDownCount[1]) THEN 1 ELSE barDownCount[1] + 1 ELSE barDownCount[1];

AddLabel(1, "IVUp " + AsPercent(TotalSum(volcalcpositive)/(barUpCount)), Color.white);

AddLabel(1, "IVDown " + AsPercent(TotalSum(volcalcnegative)/(barDownCount)), Color.white);

AddLabel(1,"Up/Down " + aspercent(barUpCount/(barUpCount + barDowncount)));

If you prefer to show the stock price changes, instead of volatility, the script becomes more powerful to indicate the historical moves in the stock price after earnings, again, the labels show the average move and the number of events, for up and down events.

The code is the exact same as above for IV crush, with the exception the one line is changed from def vol = imp_volatility(); to def vol = close(); ...... but here's the code anyway:

Code:
``````def isAfter = HasEarnings(EarningTime.AFTER_MARKET);
def isBefore = HasEarnings() and !isAfter;

def vol = close();
def beforevol = if isAfter then vol else if isBefore then vol[1] else 0;
def aftervol = if isAfter then vol[-1] else if isBefore then vol else 0;

#Calculates % change of IV from day prior to the earnings date to the day after the report.
def volcalc =  if beforevol == 0 then 0 else (aftervol - beforevol) / beforevol;

def volcalcpositive =  if beforevol == 0 then 0 else Max(0,(aftervol - beforevol) / beforevol);

def volcalcnegative =  if beforevol == 0 then 0 else Min(0,(aftervol - beforevol) / beforevol);

#Defines # of positive or negative vol crush after earnings.
def negvolcalc = volcalc < 0;
def posvolcalc = volcalc > 0;

AddChartBubble(negvolcalc, high[1], "Chng " + AsPercent(volcalc), Color.RED);
AddChartBubble(posvolcalc, high[1], "Chng " + AsPercent(volcalc), Color.GREEN);

#Find number of up events
def barUpCount = IF (volcalc > 0) THEN IF IsNaN(barUpCount[1]) THEN 1 ELSE barUpCount[1] + 1 ELSE barUpCount[1];
#Find number of down events
def barDownCount = IF (volcalc < 0) THEN IF IsNaN(barDownCount[1]) THEN 1 ELSE barDownCount[1] + 1 ELSE barDownCount[1];

AddLabel(1, "AvgUp " + AsPercent(TotalSum(volcalcpositive)/(barUpCount)), Color.white);

AddLabel(1, "AvgDown " + AsPercent(TotalSum(volcalcnegative)/(barDownCount)), Color.white);

AddLabel(1,"Up/Down " + aspercent(barUpCount/(barUpCount + barDowncount)));

This should be of big help to swing traders on earnings!

If someone could help modify the code to allow a user specified input to change the number of days in the calculation (instead of the following day after earnings, but X period of days after earnings), that would be great. I am by no means a programmer and spent a good chunk of time trying to put this together, thanks again to everyone and the person who posted initially @Slippage

Edit: The label does not work when we are on an earnings day, likely because the code cannot calculate a move today since earnings hasn't happened. If someone knows how to fix this that would be great! Thanks

There's some issues that study doesn't address. The date problem is one but a much more important problem is the fact that the study uses Imp_Volatility() which is the annualized volatility reduced to a 30 day value and it has nothing to do with that actual IV used in the calculations of the options chain. That IV is the SeriesVolatility(series = ??) where the series is the expiry series starting at 1 and raising by one for each next expiry. That SeriesVolatility() doesn't have a history so can not be used in the manner the study above is trying to use IV.

Last edited:
@MerryDay Check above the second code, it works, it successfully calculates the % stock price change on the day after earnings, and the averages are also correct. Try pasting the code in TOS.

Only the IV does not work correctly.

If someone could help modify the code to allow a user specified input to change the number of days in the calculation (instead of the following day after earnings, but X period of days after earnings), that would be great.
This is beyond the scope of this script

@MerryDay Check above the second code, it works, it successfully calculates the % stock price change on the day after earnings, and the averages are also correct. Try pasting the code in TOS.

Only the IV does not work correctly.
Thank you, I was answering your posts in the order that you make them. In the future if you find a solution, please edit your post and indicate that.
Good job, btw...

This program doesn't work on the day of earning when earnings are after close. Any suggestions as to how to fix that?

Also I added the following to lines of code at the bottom, so it gives the total avgerage crush in a label as well:

def total= barUpCount + barDowncount;
AddLabel(1, "Total Avg crush: " + (TotalSum(volcalcpositive+volcalcnegative)*100)/total );

This program doesn't work on the day of earning when earnings are after close. Any suggestions as to how to fix that?
No, the data would not be available on the day of earning.

87k+ Posts
270 Online

## The Market Trading Game Changer

Join 2,500+ subscribers inside the useThinkScript VIP Membership Club
• Exclusive indicators
• Proven strategies & setups
• Private Discord community
• Exclusive members-only content
• 1 full year of unlimited support

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?