Intraday Momentum, part 2 -- The Intraday Volatility Bands indicator
Building research backed intraday volatility bands indicators using C++ and Sierra Chart's ACSIL language. Breakdown and some code available to all readers.
Disclaimer: the following post is an organized representation of my research and project notes. It doesn’t represent any type of advice, financial or otherwise. Its purpose is to be informative and educational. Backtest results are based on historical data, not real-time data. There is no guarantee that these hypothetical results will continue in the future. Day trading is extremely risky, and I do not suggest running any of these strategies live.
Part two of this series covers creating the intraday volatility bands indicator that is being used in Maróy's[1] paper on intraday momentum strategies for SPY and QQQ. I created this indicator in using Sierra Chart's (SC) Advanced Custom Study Interface Language (ACSIL), which is just C++. The logic for this indicator can be translated into whatever flavor of language you want to use. I have done my best to mimic the logic presented in the research paper. I think I have done a good job, but I'm not a mathematician or computer engineer, so it's just my best attempt. We will really be able to tell how I did when I start the backtesting process.
In the previous post (the daily test of this concept), I believe I mentioned that my use of standard deviation for a quick calculation and that it differed from Maróy's[1-1] calculation, but now that I am going back through it to create this indicator it looks like they do use the standard deviation of mean returns. What they don't use are log returns, which I did use in the previous post. I will probably update this indicator in during the testing phase to have an option to use log returns instead. That way we can test the difference and see if we can use that to help make this indicator/strategy more robust.
I am leaving most of this post open for all readers. The pay wall will block the completed code, but there will be portions of the code used to create this indicator in the breakdown that can be used to help you recreate this indicator for yourself.
If you are a Sierra Chart user and you want to use this indicator, you will need to add all three files to the ACS_Source
folder and then build/compile the indicator using all three files in the built in build tool in SC. I am using my own headers and helper function files to create indicators, so it isn't all contained in a single file. This way, I can eventually create and entire library of custom indicators without having to type code more than once.
Intraday Volatility Bands
The Intraday Volatility Bands identify significant price movements by creating dynamic boundaries that adapt to the time of day. The core idea is that a price move should be compared to what's typically expected at that specific time in the trading session. Below is the core formula for this indicator1:
and
The Main Volatility Formula (σ):
σ (sigma) represents the historical volatility
It's taking a sum of squared differences between returns and their mean
The formula uses 14 days of data (hence the 14 in denominator)
The square root (√) is used to get back to the original units of measurement
The Mean Return Formula (μ):
μ (mu) represents the average return over the period
It's a simple arithmetic mean of returns over 14 days
Each ret₍ₜ₋ᵢ₎ represents the return from a previous day
We calculate the volatility by first finding the average return over the past 14 days. Then for each day, we subtract this average from the actual return, square the difference, sum up all these squared differences, divide by 14, and take the square root. This gives us a measure of how much returns typically deviate from their average.
This volatility measure then determines how wide our bands should be around the price. Higher volatility = wider bands, lower volatility = narrower bands.
Basic Calculation Logic
For any given time during the trading day (let's say 9:45 AM), we:
Look back at the same time (9:45 AM) on previous days for our lookback period
Calculate how much price typically moves from the open to that time
Use this average movement (σ) to create upper and lower boundaries
Adjust these boundaries if there was a gap at today's open
For each of those historical bars, it calculates the percentage move from that day's gap-adjusted open (max of that day's open or previous close) to the price at 9:45. Then it averages all those moves to get σ (sigma), which represents the typical percentage move you might expect at that specific time of day.
Gap Handling
The bands handle gaps using a simple max/min approach:
Upper Boundary Base = max(today's open, yesterday's close)
Lower Boundary Base = min(today's open, yesterday's close)
Then the volatility multiplier is applied to these bases:
Upper = upperBase × (1 + multiplier × σ)
Lower = lowerBase × (1 - multiplier × σ)
This approach automatically adjusts the noise area for gaps by anchoring one band to the previous close when appropriate, without needing explicit gap percentage calculations.
Implementation Details
Let's break down how this is implemented in the code:
1. Session Management
First, we determine if we're in an active trading session. If we are not in session, we will carry forward the previous values and skip calculations. The code below also handles overnight sessions, so we can turn off the chart session times being used and define our own time window if we want to.
bool isInSession;
if (MarketOpenSeconds > MarketCloseSeconds) {
// Overnight session handling
isInSession = (currentTimeSeconds >= MarketOpenSeconds ||
currentTimeSeconds <= MarketCloseSeconds);
} else {
// Regular session handling
isInSession = (currentTimeSeconds >= MarketOpenSeconds &&
currentTimeSeconds <= MarketCloseSeconds);
}
2. Historical Volatility (σ) Calculation
For each historical day, we:
Find the same time of day
Calculate the return from that day's effective open
Calculate the mean of these returns
Calculate the standard deviation using squared deviations from the mean
float sumReturns = 0.0f;
float returns[60]; // Assuming max lookback is 60 days
int validDays = 0;
// First pass - collect returns and calculate mean
for (int day = 1; day <= LookbackDays; day++) {
// Get historical date
SCDateTime HistoricalDate = sc.BaseDateTimeIn[sc.Index];
HistoricalDate.SubtractDays(day);
// Get session start and effective open
SCDateTime SessionStart = sc.GetTradingDayStartDateTimeOfBar(HistoricalDate);
float histOpen = sc.Open[SessionStartIndex];
float histPrevClose = (SessionStartIndex > 0 ?
sc.Close[SessionStartIndex - 1] : histOpen);
float histEffectiveOpen = (histOpen > histPrevClose ?
histOpen : histPrevClose);
// Calculate historical return
float historicalReturn = (sc.Close[targetBarIndex] / histEffectiveOpen) - 1;
returns[validDays] = historicalReturn;
sumReturns += historicalReturn;
validDays++;
}
float meanReturn = sumReturns / validDays;
float sumSquaredDeviations = 0.0f;
// Calculate squared deviations from mean
for (int i = 0; i < validDays; i++) {
float deviation = returns[i] - meanReturn;
sumSquaredDeviations += deviation * deviation;
}
float sigma = sqrt(sumSquaredDeviations / validDays);
3. Band Calculation With Gap Adjustments
Finally, we calculate today's bands with appropriate gap adjustments:
float todayOpen = sc.Open[TodayStartIndex];
float prevClose = (TodayStartIndex > 0 ? sc.Close[TodayStartIndex - 1] : todayOpen);
float effectiveUpperBasis = max(todayOpen, prevClose);
float effectiveLowerBasis = min(todayOpen, prevClose);
float upperMultiplier = (1 + VolatilityMultiplier * sigma);
float lowerMultiplier = (1 - VolatilityMultiplier * sigma);
UpperBoundary[sc.Index] = effectiveUpperBasis * upperMultiplier;
LowerBoundary[sc.Index] = effectiveLowerBasis * lowerMultiplier;
Key Implementation Features
Update Intervals: Bands only update at specified intervals to reduce noise
Gap Adjustment: One band gets "anchored" to the previous close during gaps
Effective Open: Uses the higher of today's open or previous close as the base
Data Validation: Includes checks for valid data and proper session times
Usage
The resulting bands create a "noise area" that:
Adapts to normal volatility patterns for each time of day
Accounts for overnight gaps
Updates at controlled intervals
Provides clear boundaries for significant moves
When price moves outside these bands, it signals a potentially significant trend that has broken out of the normal volatility pattern for that time of day. At least, that is the idea.
Complete Code
The rest of this post is for paid subscribers. Paid subscribers will have access to all paid publications, strategies, code associated with posts, and access to the HGT chat room. Red Team members will have access to the community GitHub and more.