Getting Data From An Illiquid Asset

StoneMan

Member
Plus
I'm working with options data which can be quite illiquid. On certain instruments you may get only a few ticks per day or even zero. Because of this bars based look back strategy will not work due to the chart being incomplete.

My workaround to this is using the time of day to get the data at the right time. I'm reaching out because I am not sure if thinkscript is capable of returning a bar based on the time.

The logic looks something like this:

If data is present in the time slot 1 on the current session get data, if not set relevant values to zero.

If data is present in the time slot 2 ob the current sesaion get data, if not set relevant values to zero.

.
.
.

I also want to know what time slot is currently active in a given session. The trade day is from 930 am to 4pm. That is 13 30 minute sessions over 6.5 hours. This is how I am planning on discritizing the trading day. I need a variable that can return 1-13 corresponding with the proper time slot. That way averages can be calculated no matter when you run the code. After the market closes that value would return 13.

Any insight would be greatly appreciated!
 
Solution
As I play around with calcuting theoretical options price something I have noticed is that from time to time the theoretical price is way off the price the option is actually transacting at. Because we know the theoretical price and the real price I believe we have the opportunity to implement some error correcting to hammer down those outliers.

This is my theory for a very rough and dirty way to implement some error correcting of theoretical options calculations based on the real data we have:

-We definitively "know" all the inputs that go into calcuting theoretical option price. Two of them are weak however and they are:

1.)The use of implied volitility of the underlying. This is a generalization across all options and could be...
I also want to know what time slot is currently active in a given session. The trade day is from 930 am to 4pm. That is 13 30 minute sessions over 6.5 hours. This is how I am planning on discritizing the trading day. I need a variable that can return 1-13 corresponding with the proper time slot. That way averages can be calculated no matter when you run the code. After the market closes that value would return 13.

Any insight would be greatly appreciated!
may be if you post a mockup or a illustration and any piece o code you already have, It might be easy to follow along and offer any assistance.
 
I can absolutely add a bit more context. The code is for a custom column that I created to scan for unusual options activity. I multiply together some ratios like option volume to open interest and dollar volume traded in the option to dollar volume traded in the underlying and a speacil leverage metric I created to scan against in the options hacker.

I use a daily aggregatetion for all of those values and it works well as is. But the metric could be improved by adding a volume to tick count ratio to help it prioritize large block trades which are more suggestive of institutional money.

The problem with tick count is that the documentation says it only works on intraday bars.

So I can't just throw that ratio into the daily scan and call it a day.

I am still interested in the data from the entire session being analyzed in a scan though. And given how illiquid options are just looking X bars back will not work.

This is why I'm curious about getting bar values from the current trading session using a time based approach. It's important to note that I am only interested in the current session due to the nature of the options metrics I am analyzing.
 
Code:
#Defining variables

def Delta = Delta();

def Ivol = imp_volatility(GetUnderlyingSymbol());

def OptionMarkPrice = close;

def UnderlyingPrice = close(getUnderlyingSymbol());

def UnderlyingVolume = volume(getUnderlyingSymbol());

#V.A.L.R - Volitility Adjusted Leverage Ratio

def Leverage = (Delta * UnderlyingPrice);

def LeverageRatio = Leverage / OptionMarkPrice;

def VALR = LeverageRatio * Ivol;

#Volume to open interest ratio (if statement pervents divide by zero errors)

def OpenInterest = if open_interest(period = AggregationPeriod.DAY) == 0 then 1 else open_interest(period = AggregationPeriod.DAY);

def volOI = volume / OpenInterest;

#Option dollar volume traded

def DvolumeOpt = volume * ohlc4 * 100;

#Underlying dollar volume traded

def DvolumeUnd = UnderlyingPrice * UnderlyingVolume;

def DvolumeRatio = DvolumeOpt / DvolumeUnd;

#Multiply by 1000 so output better displays in the columns

def WhaleScore = VALR * volOI * DvolumeRatio * 1000;

plot WhalePlot = WhaleScore;

Here is the logic behind the calculations:

def Leverage: We begin by multiplying the Delta of a contract by the price of the underlying to get how many dollars worth of the underlying one contract controls. Using the amount of dollars controlled per contract normalizes the measurement so it can be compared between instruments.

def LeverageRatio: We divide the dollar volume controlled per contract by the dollar amount of that contract. This gives us a unitless ratio that is how many dollars worth of the underlying you are controlling per dollar spent on the contract. This alone could be a useful metric.

def VALR: We multiply the LeverageRatio by the implied volitility (IVol) of the underlying instrument. This is done to normalize the value because low implied volitility instruments naturally allow for more leverage in their options contracts than high ones. So now all instruments dollar leverage per contract should be on an even playing field. This metric is still unitless.

def volOI: volume to open interest ratio. This is the classic metric considered when hunting for unusual options and is very important.

def DvolumeOpt: Dollar volume traded in the option contract, just multiplying option price by volume. Keeping to the theme of normalizing to dollars so we can better compare instruments.

def DvolumeUnd: Dollar volume traded in the underlying, just multiplying underlying price by underlying volume. Keeping to the theme of normalizing to dollars so we can better compare instruments.

def DvolumeRatio: Ratio of money spent on the option contract to the whole of the underlying. Represents underlyings capacity to absorb options activity. Higher values show that contract has more impact on the underlying by representing a higher proportion of liquidity. A problem I've noticed with other unusual options scanners is tickers like SPY and AAPL always showing lots of "unusual activity" due to massive dollar volumes traded at given strikes. Relative to the liquidity of SPY and AAPL the activity is actually not that unusual. This ratio is how we account for differing levels of liquidity across instruments.

Def WhaleScore: Our final unusual options activity metric, we get it by multiplying three ratios. The unitless metric VALR which measures how many dollars worth of the underlying you are controlling per dollar spent on a contract on a volitility adjusted basis. Higher values equate to more risk (and therefore more conviction) by naturally prioritizing out of the money high risk plays. Volume to open interest ratio shows money being put where it wasn't previously. DvolumeRatio shows the amount of money spent on the option contract relative to the whole of the underlying. Higher values are more unusual as it means option contract spend is a higher proportion of the total liquidity. All of these metrics are multiplied together to get a value where the farther you are from zero the more "unusual" the activity. Multiplying by 1000 is just to make the output more user friendly to display in the columns.
 
I realize this may not be a super common implementation of thinkscript so I'll describe how to get the code set up:

Navigate to options hacker and select the cog that allows you to customize the columns. From there find one of the documents labeled Custom and select the scroll. This will open up the ThinkScript condition wizard window. Remove the default filter and select thinkscript next to it. Copy paste the code and name the program WhaleScore. Select OK. The select WhaleScore and add it to the filter. In the Sorted by section select personal/custom and select WhaleScore. You are now looking ready to scan. A higher number is more unusual! Order the contracts from highest to lowest WhaleScore values to put the most unusual contract activity at the top. Call values are positive and put values are negative.
 
I realize this may not be a super common implementation of thinkscript so I'll describe how to get the code set up:

Navigate to options hacker and select the cog that allows you to customize the columns. From there find one of the documents labeled Custom and select the scroll. This will open up the ThinkScript condition wizard window. Remove the default filter and select thinkscript next to it. Copy paste the code and name the program WhaleScore. Select OK. The select WhaleScore and add it to the filter. In the Sorted by section select personal/custom and select WhaleScore. You are now looking ready to scan. A higher number is more unusual! Order the contracts from highest to lowest WhaleScore values to put the most unusual contract activity at the top. Call values are positive and put values are negative.
Hi bro, nice works! Hope you don't mind I add my two (2) cents here. For usual option activities (UOA), there are a couple of criteria (1) Volume very much greater than OI, (2) Usually shorter DTE (2-4 weeks), (3) they usually bought in "Sweep" and (4) OTM strike price. I think TOS has limitations to generate this data. If UOA data really helps in your trading, suggest you might want to look for some websites which able to provide "real time" data. In addition, you might want to explore gamma exposure (GEX) too (since you are quite into option trading). Cheers
 
Last edited:
I'm very much aware of the limitations TD has when it comes to options data. I use ConvexValue for my options stuff now. The truth is I dusted off this project that had been shelved for almost 2 years because I wanted to see much I could wring out of thinkorswim. Figured better to share it then let it rot. This metric is like trying to grow a garden in cracks of pavement. Despite its limitations it works surprisingly well. I recommend scanning by sector and using the filters so as not to overwhelm the scan. The original question pertains to tunning it to better detect block trades. You could create another column that returns what time zone had the highest WhaleScore so you now have a 30 minute window to scoure the options time and sales your self and determine what side of the spread the block was purchased on. I know NinjaScript has dedicated functions for returning bar values based on time but ThinkScript is far more limited. I'm also a bit rusty with it. Any insight about this would be greatly appreciated.
 
I'm very much aware of the limitations TD has when it comes to options data. I use ConvexValue for my options stuff now. The truth is I dusted off this project that had been shelved for almost 2 years because I wanted to see much I could wring out of thinkorswim. Figured better to share it then let it rot. This metric is like trying to grow a garden in cracks of pavement. Despite its limitations it works surprisingly well. I recommend scanning by sector and using the filters so as not to overwhelm the scan. The original question pertains to tunning it to better detect block trades. You could create another column that returns what time zone had the highest WhaleScore so you now have a 30 minute window to scoure the options time and sales your self and determine what side of the spread the block was purchased on. I know NinjaScript has dedicated functions for returning bar values based on time but ThinkScript is far more limited. I'm also a bit rusty with it. Any insight about this would be greatly appreciated.
Hi bro, I think TOS platform might be too lag to perform this type of scan you need. I have move on trading UOA. As mentioned, there are some good website providing these data you want! Eg, type of trade, Sweep, spilt and block! It will also show if spread trade, buy call or put and sell call or put. All the best!
 
Here is some more details for my purposed solution:

If GetTime is 0-1800 Seconds from regular trading start get data from time slot 1 if data is present in time slot 1. Else return zero for all time slot 1 values.

If GetTime is 1800-3600 Seconds from regular trading start get data from time slot 2 if data is present in time slot 2. Else return zero for all time slot 2 values.

.
.
.

The time slot values are everything calculated in the original code. But now you could use the tic_count function because you are working with intra day bars. And add volume/tic ratio to the WhaleScore metric. From there you can do whatever math you want with them to get options activity on a more discritized basis.

I just want some insight from someone on if calling data using logic around time like that would work on the platform.

This may eat up a ridiculous amount of memory though. The current iteration of the code works fine on my 8 year old machine up to 1000 instruments. So assuming this is 13 times more intense to run I would probably have to bump it down to 100. I'm curious how it runs on a new machine with 32 gigs of RAM (my work laptop has got that but it's strictly for work :/).

As to why I'm doing this when other services exist 1.) I just like to push things and try for stuff that hasn't been done before. 2.) This is a good practice run for when it comes time to teach myself some JSON and code out my own stuff with raw options data. So even of this is clunky it could provide valuable insight.
 
Here is some more details for my purposed solution:

If GetTime is 0-1800 Seconds from regular trading start get data from time slot 1 if data is present in time slot 1. Else return zero for all time slot 1 values.

If GetTime is 1800-3600 Seconds from regular trading start get data from time slot 2 if data is present in time slot 2. Else return zero for all time slot 2 values.

.
.
.

The time slot values are everything calculated in the original code. But now you could use the tic_count function because you are working with intra day bars. And add volume/tic ratio to the WhaleScore metric. From there you can do whatever math you want with them to get options activity on a more discritized basis.

I just want some insight from someone on if calling data using logic around time like that would work on the platform.

This may eat up a ridiculous amount of memory though. The current iteration of the code works fine on my 8 year old machine up to 1000 instruments. So assuming this is 13 times more intense to run I would probably have to bump it down to 100. I'm curious how it runs on a new machine with 32 gigs of RAM (my work laptop has got that but it's strictly for work :/).

As to why I'm doing this when other services exist 1.) I just like to push things and try for stuff that hasn't been done before. 2.) This is a good practice run for when it comes time to teach myself some JSON and code out my own stuff with raw options data. So even of this is clunky it could provide valuable insight.
so, the short answer, is it possible to get data from a time slot, sure, take a look at vZone.

I get volume data from 3 different slots of RTH Session. now for the 2nd request, return Zero where there is no data in a specific timeslot, should be possible.

-S

vZone Code: Feel free to use up as you choose.
https://usethinkscript.com/threads/vzone-volumes-based-support-resistance-for-thinkorswim.7169/
 
Unfortunately, the open interest function only works with AggregationPeriod.DAY for these option columns. And the scanner does not support higher aggregatetion periods. So we are locked out of adding this metric to the current column. It's been a while since I first made this and the AggregationPeriod.DAY is hard-coded in for a reason lol.

This prompted me to do some deeper research and I found that for the options scanner:

1.) Interday bars for calling open_interest are not supported. You must use an aggregatetion period of at least a day.

2.) Open interest always returns the same value regardless of what time period you select for your colum. If you select weekly data for the quote open interest will still display that days data. This is important to note when working with this variable and could maybe be used to your advantage if you just treat the higher aggs like a Sum all sort of function, could make bulky scans more efficient.

These quirks are why working with these columns can be so annoying. You get the same behavior for all other options specific metrics like delta or gamma.

Below is a link to some more code about OI changes. Could be used to improve things further. Being one of the "fundamentals" this is a value that TD actually makes accessible as far as historical data is concerned. I don't think that is the case for the options greeks though. Someone please correct me if I am wrong.

https://www.hahn-tech.com/ans/increasing-open-interest/

Not all is lost for our volume to tic count ratio though. We would just need to make a separate column. A third column would display what time slot volume / tic count is the highest so you can look in the time and sales of that time slot to determine the unusual activity your self.

So that would bring our unusual options column suite up to three. If I were to set it up I would order by WhaleScore. Check which high WhaleScore options also have a high volume to tic count ratio. Then go and look in the third columns time and sales to determine the direction.
 
Last edited:
Should show the period with the highest tick count on the latest day, but as you know, options columns are particularly wonky. You might want to check it against a few individual option charts to verify.

def x = if GetDay() == GetLastDay() then tick_count else 0;
def y = if x >= x[1] then secondsfromTime(0000) else y[1];
def a = Floor(y / 60 / 60);
def b = (y / 60 / 60 - a) * 60;
AddLabel(yes,a + ":" + (if b < 10 then AsPrice(b) + "0" else AsPrice(b)) + " (" + asPrice(x) + ")");
 
@Joshua I ran the code and it works perfectly. Regarding the block trades metric it would be cool if you can put that in the the same column as the one that highlights the tick count. This is because you are only allowed 1,000 instances of a custom column by ThinkOrSwim.

This combined column would now be displaying 4 values using the label. The time of high tic count, the high tic count, the time of high volume to tick count ratio, and the volume to tic count ratio.

To sort the output from highest to lowest for each metric would require 4 different versions of that same code where the first value displayed is the one we sort by. This will allow the user to specify what sort of activity they are looking for.

Would it possible to code that up? I think it would complete the quest for a fully built out unusual options scanner on thinkorswim, which would be pretty awesome :)
 
Probably, I can't foresee why that wouldn't be possible. I'll take a look at it maybe tomorrow during the globex session if I am home, otherwise, check back monday after the closing bell.
 
Try this for now. It does some goofy stuff occasionally, pulling zeros for a few things. Might be because its Sunday. I am also missing about 40 minutes of Globex data on TOS right now, not sure if its related. TradeStation's and IB's data are fine, its definitely TOS having issues, not the exchange.

Anyway, its goes...

Highest Tick Count Time (Tick Count at that time)
-
Highest Tick Count / Volume Time (Ratio at that time)
-
Raw Volume at the ratio's time "/" Raw Tick Count at the ratio's time

ZvrCPR5.png


Code:
def TickCount =
    if GetDay() == GetLastDay()
    then tick_count
    else 0;
def TickTime =
    if TickCount >= TickCount[1]
    then secondsfromTime(0000)
    else TickTime[1];
def TickAtTime =
    if TickCount >= TickCount[1]
    then Tick_Count
    else TickAtTime[1];
def TickHour =
    Floor(TickTime / 60 / 60);
def TickMinute =
    (TickTime / 60 / 60 - TickHour) * 60;
def VolRatio =
    if GetDay() == GetLastDay()
    then volume / tick_count
    else 0;
def VolTime =
    if VolRatio >= VolRatio[1]
    then secondsfromTime(0000)
    else VolTime[1];
def VolTest =
    if VolRatio >= VolRatio[1]
    then Volume
    else VolTest[1];
def TickTest =
    if VolRatio >= VolRatio[1]
    then Tick_Count
    else TickTest[1];
def RatioAtTime =
    if VolRatio >= VolRatio[1]
    then VolRatio
    else RatioAtTime[1];
def VolHour =
    Floor(VolTime / 60 / 60);
def VolMinute =
    Floor((VolTime / 60 / 60 - VolHour) * 60);
AddLabel(yes,
    TickHour + ":" +
    (if TickMinute < 10 then AsPrice(TickMinute) + "0" else AsPrice(TickMinute))
    + " (" + asPrice(TickAtTime) + ") - " +
    VolHour + ":" +
    (if VolMinute < 10 then AsPrice(VolMinute) + "0" else AsPrice(VolMinute))
    + " (" + asPrice(Round(RatioAtTime,2)) + ") - " +  VolTest + " / " + TickTest
);

This stuff is pretty easy if you need other adjustments, most of it is just copy/paste and rename variables.
 
Thank you so much for the code @Joshua. I noticed some discrepancies when running the code, for example, the largest block traded that day was picked up when when I set the scan period to an hour but not when I changed it to 1 min or 5 min. Is there a particular time period you should running this code in as written (1 min, 5 min, 1 hour)? I just want to make sure I am using it right. Thanks again for the help!
 
Define largest block traded, in detail. I need to make sure we're on the same page. If its an individual trade, as might be viewed in the time and sales, thinkscript's basic structure doesn't allow for that. Its on a period total by period total basis. I developed it using 30 minutes, but it should be dynamic. Its best to avoid aggregations that don't divide evenly into market hours with time dependent scripts though, just in general.
 

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