Mid-Day Volume Spikes - with trail

# ─────────────────────────────────────────────
# │ MarketFragments.com | DNA & Market │
# │ info@marketfragments.com │
# │ www.marketfragments.com │
# ─────────────────────────────────────────────
# Time →
# │
# █ █ █│ █
# █ █ █ │ █ █
# █ █ █ │ █ █ █ ╭─╮
# █ █ █ │ █ █ █ █ ╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ ╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ █ █ ╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ █ █ █ █╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ █ █ █ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ █ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ ╰─────╯
# ──────┴──────────────────────────────────────────────────────────────
# T1 T2 T3 T4 T5 T6
#
# =============================================================================
# Free Open Source ThinkScript Study for ThinkOrSwim
#
# This code is provided free of charge and is open source under the MIT License.
# You are free to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of this software for any purpose, with or without
# modification, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Name: Mid day volume spikes with trail
# Author: Market Fragments team/mcdon
# Description: Intraday trading strategy with Heikin Ashi, ATR trailing stops,
# Kase deviation lines, midday pullback entries, and RVOL spike
# detection for signal confirmation.
# Inputs
input TrailType = { "Simple", default "Kase" }; # Hint: Type of trailing stop method
input KaseLine = { "Warning", default "StdDev1", "StdDev2", "StdDev3" }; # Hint: Kase deviation line selection
input AtrMult = 2.2; # Hint: ATR multiplier for simple trail (unused in current logic)
input PaintBars = yes; # Hint: Enable/disable bar coloring based on trade direction
input ShowBubbles = no; # Hint: Show bubbles on chart (unused in current code)
input AtrAvgType = AverageType.WILDERS; # Hint: Averaging type for ATR calculation
input AtrLength = 7; # Hint: Length for ATR moving average
input AtrMultiple = 1.9; # Hint: Multiplier for adjusted ATR in trailing stops
input offset = 0; # Hint: Offset for volume calculations (unused in signals)
input TimeBasedNumDays = 10; # Hint: Number of days for relative volume calculation
input threshold3 = 200; # Hint: Threshold for relative volume spike detection
input tckPadding = 1; # Hint: Padding in ticks for loss calculations
# Constants
def nan = Double.NaN; # Hint: Not a Number value for invalid or missing data
# Basic price and bar data
def h = high; # Hint: Current bar high price
def l = low; # Hint: Current bar low price
def c = close; # Hint: Current bar close price
def o = open; # Hint: Current bar open price
def v = volume; # Hint: Current bar volume
def tcnt = tick_count; # Hint: Tick count for the bar (for volume weighting)
def bn = BarNumber(); # Hint: Sequential bar number for indexing
# Volume-weighted calculations (simplified to typical price)
def isValue = v / tcnt; # Hint: Volume per tick (simplified, often 1)
def issvwap = (((h + l + c) / 3) * v) / v; # Hint: Session VWAP using typical price (h+l+c)/3
def isvwap2 = (((h + l + c) / 3) * isValue) / isValue; # Hint: Alternative VWAP using tick-adjusted volume (simplified to typical price)
# Heikin Ashi calculations (non-standard variant with VWAP offset)
def HAopen; # Hint: Heikin Ashi open price
def HAhigh; # Hint: Heikin Ashi high price
def HAlow; # Hint: Heikin Ashi low price
def HAclose; # Hint: Heikin Ashi close price
def HAclosevwap; # Hint: VWAP-adjusted Heikin Ashi close
HAopen = CompoundValue(1, (HAopen[1] + HAclosevwap[1]) / 2, (o[1] + c) / 2);
HAhigh = Max(h, c[1]); # Hint: Max of current high and previous close
HAlow = Min(l, c[1]); # Hint: Min of current low and previous close
HAclose = (HAopen + HAclose[1] + HAlow + c) / 4; # Hint: Average of HA open, previous HA close, HA low, and current close
HAclosevwap = (((HAhigh + HAlow + HAclose) / 3) * isValue) / isValue; # Hint: VWAP using HA typical price (simplified)
# Tick-related constants
def tickSize = if !IsNaN(TickSize()) then TickSize() else .01; # Hint: Instrument tick size, default 0.01
def tickValue = if !IsNaN(TickValue()) then TickValue() else .01; # Hint: Tick value in dollars, default 0.01 (unused)
def isTickpad = (tckPadding * tickSize); # Hint: Padded tick size for stop adjustments
# True Range and ATR (using HA values)
def tra = TrueRange(HAhigh, HAclose, HAlow); # Hint: True Range based on Heikin Ashi values
def atr = Round(MovingAverage(AtrAvgType, tra, AtrLength) / tickSize, 0) * tickSize; # Hint: ATR rounded to tick size
def adjustedATR = Round((atr * AtrMultiple) / tickSize, 0) * tickSize; # Hint: ATR scaled by multiple, rounded to ticks
def HAHL2 = (HAhigh + HAlow) / 2; # Hint: Heikin Ashi midpoint (HL/2)
def loss = adjustedATR; # Hint: Base loss distance (unused directly)
declare upper;
# Time-based definitions (RTH, sessions) - Intraday only
def RTH = GetTime() >= RegularTradingStart(GetYYYYMMDD()) &&
GetTime() <= RegularTradingEnd(GetYYYYMMDD()); # Hint: Regular Trading Hours flag
def OpeningBell = GetTime()[1] < RegularTradingStart(GetYYYYMMDD()) &&
GetTime() > RegularTradingStart(GetYYYYMMDD()); # Hint: First RTH bar after pre-market
def start = GetTime() >= OpeningBell &&
GetTime() <= RegularTradingStart(GetYYYYMMDD()) + 7000000 && RTH; # Hint: Morning session start (~11:30 AM from open)
def end = GetTime() >= OpeningBell &&
GetTime() <= RegularTradingStart(GetYYYYMMDD()) + 18300000 && RTH; # Hint: Midday session end (~2:30 PM from open)
def time330 = GetTime() >= OpeningBell &&
GetTime() <= RegularTradingStart(GetYYYYMMDD()) + 21600000 && RTH; # Hint: Midday session end (~3:30 PM from open)
def trigger330 = if RTH && !RTH[1] then 0 else if !time330 && time330[1] then 1 else trigger330[1];
def midday = if bn == 1 then 0
else if midday[1] == 0 && !start && start[1] then 1
else if midday[1] == 1 && !end && end[1] then 0
else midday[1]; # Hint: Flag for midday trading window (after morning, before afternoon)
def tr = Max(TrueRange(hahigh, haClose, halow), tickSize);
### RVOL
input threshold4 = 200;
def isOpen = GetTime() >= RegularTradingStart(GetYYYYMMDD()) and GetTime() < RegularTradingEnd(GetYYYYMMDD());
def bars = if bn == 1 then 1 else if isOpen then bars[1] + 1 else 0;
def bnx = HighestAll(bars);
## 1.
def up = haClose[offset] >= haOpen[offset];
def dn = !up[offset];
def v1 = v / tr;
def vSum = CompoundValue( 1, fold index = 1 to TimeBasedNumDays + 1 with a = 0 do a + GetValue(v1, index * bnx), 1);
def relvol1 = (v1[offset] / (vSum[offset] / TimeBasedNumDays)) * 100;
def curVol1;
if up {
curVol1 = Min(threshold4, relvol1);
} else {
curVol1 = Round(Max(-threshold4, -relvol1));
}
def RvolSTate1 = if curVol1 >= 200 then 100 else if curVol1 <= -200 then -200 else 0;
## 2.
def vSum2 = CompoundValue( 1, fold index2 = 1 to TimeBasedNumDays + 1 with b = 0 do b + GetValue(v, index2 * bnx), 1);
def relvol2 = (v[offset] / (vSum2[offset] / TimeBasedNumDays)) * 100;
#def curVol2 = if up2 then Min(threshold4, relvol2) else Round(Max(-threshold4, -relvol2));
def curVol2;
if up {
curVol2 = Min(threshold4, relvol2);
} else {
curVol2 = Round(Max(-threshold4, -relvol2));
}
def RvolSTate2 = if curVol2 >= 189 then 200 else if curVol2 <= -200 then -200 else 0;
### 3 Tick
def vtk = v /tcnt/ tr;
def vSumtk = CompoundValue( 1, fold index3 = 1 to TimeBasedNumDays + 1 with cx = 0 do cx + GetValue(vtk, index3 * bnx), 1);
def relvoltk = (v1[offset] / (vSum[offset] / TimeBasedNumDays)) * 100;
#def curVoltk = if up then Min(threshold4, relvoltk) else Round(Max(-threshold4, -relvoltk));
def curVoltk;
if up {
curVoltk = Min(threshold4, relvoltk);
} else {
curVoltk = Round(Max(-threshold4, -relvoltk));
}
def RvolSTate3 = if curVoltk >= 200 then 200 else if curVoltk <= -200 then -200 else 0;
### 4 shares
def vsh = v /tcnt;
def vSumsh = CompoundValue( 1, fold index4 = 1 to TimeBasedNumDays + 1 with d = 0 do d + GetValue(vsh, index4 * bnx), 1);
def relvolsh = (vsh[offset] / (vSumsh[offset] / TimeBasedNumDays)) * 100;
#def curVolsh = if up then Min(threshold4, relvolsh) else Round(Max(-threshold4, -relvolsh));
def curVolsh;
if up {
curVolsh = Min(threshold4, relvolsh);
} else {
curVolsh = Round(Max(-threshold4, -relvolsh));
}
def RvolSTate4 = if curVolsh >= 200 then 200 else if curVolsh <= -200 then -200 else 0;
## 5 v spikes RTH
def start5 = RTH && !RTH[1];
def cnt = if start5 then 1 else cnt[1] + 1;
def SumV = CompoundValue(1, if start5 then v else SumV [1] + v, 0);
def RTHAvVol = SumV / cnt;
def RvolSTate5 = if (Average(v, 1) - RTHAvVol) / RTHAvVol > 1 then 100 else 0;
## 6 AL Relavitive StandardD
input length = 14;
input numDev = 2.0;
input allowNegativeValues = no;
def rawRelVol = (v - Average(v, length)) / StDev(v, length);
def RelVol = if allowNegativeValues then rawRelVol else Max(0, rawRelVol);
def RvolSTate6 = if c >= o && RelVol > numDev then 100 else if c < o && RelVol > numDev then -100 else 0;
## 7 vspikes
def VolumeOsc1 = if (Average(v, 1) - Average(v, 9)) / Average(v, 9) > 1 then 100 else 0;
def VolumeOsc2 = if (Average(v, 1) - Average(v, 11)) / Average(v, 11) > 1 then 100 else 0;
def VolumeOsc3 = if (Average(v, 1) - Average(v, 13)) / Average(v, 13) > 1 then 100 else 0;
def VolumeOsc4 = if (Average(v, 1) - Average(v, 15)) / Average(v, 15) > 1 then 100 else 0;
def VolumeOsc5 = if (Average(v, 1) - Average(v, 17)) / Average(v, 17) > 1 then 100 else 0;
def VolumeOscSum = VolumeOsc1 + VolumeOsc2 + VolumeOsc3 + VolumeOsc4 + VolumeOsc5 ;
def RvolSTate7 = if VolumeOscSum>99 then 100 else 0;
def signal;
if (RvolSTate1!=0 or RvolSTate2!=0 or RvolSTate3!=0 or RvolSTate4!=0
or RvolSTate5!=0 or RvolSTate6!=0 or RvolSTate7!=0){
signal = 1;
} else {
signal = nan;
}
# Daily High/Low tracking (RTH) - Hint: Tracks session highs/lows for pullback detection
def TodayH = if RTH && !RTH[1] then h else if h > TodayH[1] then h else TodayH[1]; # Hint: Running session high
def TodayL = if RTH && !RTH[1] then l else if l < TodayL[1] then l else TodayL[1]; # Hint: Running session low
def isdayH = if h == TodayH then 100 else 0; # Hint: Flag for new session high
def isdayL = if l == TodayL then 100 else 0; # Hint: Flag for new session low
def isdayh15 = h == Highest(h, 20); # Hint: 20-bar high (recent swing high)
def isdayl15 = l == Lowest(l, 20); # Hint: 20-bar low (recent swing low)
# Entry conditions (midday pullbacks to recent lows/highs) - Hint: Enters on pullbacks during midday
def longentry = if !isnan(signal) && midday && (100 == Highest(isdayL, 3) or isdayl15) then 1 else 0; # Hint: Long on recent low during midday
def shortentry = if !isnan(signal) && midday && (100 == Highest(isdayH, 3) or isdayh15) then 1 else 0; # Hint: Short on recent high during midday
# Kase-specific standard deviation level - Hint: Selects deviation multiplier for Kase stops
def kasestd = if KaseLine == KaseLine."StdDev1" then 1
else if KaseLine == KaseLine."StdDev2" then 2
else 3;
# Loss calculations for trailing stops - Hint: Stop levels based on Kase deviation or warning line
def TRD = 2 * tra; # Hint: Doubled true range for Kase calculations
def longloss = if KaseLine == KaseLine."Warning" then c - Round(MovingAverage(AtrAvgType, TRD, AtrLength) / tickSize, 0) * tickSize
else Round(((c - Round(MovingAverage(AtrAvgType, TRD, AtrLength) / tickSize, 0) * tickSize) - kasestd * StDev(TRD, AtrLength)) / tickSize, 0) * tickSize - isTickpad; # Hint: Long stop: close - avg TRD - (std * kasestd), padded
def shortloss = if KaseLine == KaseLine."Warning" then c + Round(MovingAverage(AtrAvgType, TRD, AtrLength) / tickSize, 0) * tickSize
else Round(((c + Round(MovingAverage(AtrAvgType, TRD, AtrLength) / tickSize, 0) * tickSize) + kasestd * StDev(TRD, AtrLength)) / tickSize, 0) * tickSize - isTickpad; # Hint: Short stop: close + avg TRD + (std * kasestd), padded
# State machine for trailing stops - Hint: Manages long/short states and ratchets trail
def state = { default Inactive, long, short };
def trail;
switch (state[1]) {
case Inactive:
if longentry && state[1] == state.Inactive {
state = state.long;
trail = if TrailType == TrailType."Simple" then Round((HAHL2 - atr * AtrMultiple) / tickSize, 0) * tickSize # Hint: Simple trail: HA midpoint - adjusted ATR
else longloss;
} else if shortentry && state[1] == state.Inactive {
state = state.short;
trail = if TrailType == TrailType."Simple" then Round((HAHL2 + atr * AtrMultiple) / tickSize, 0) * tickSize # Hint: Simple trail: HA midpoint + adjusted ATR
else shortloss;
} else {
state = state.Inactive;
trail = nan;
}
case long:
if c > trail[1] && trigger330==0 {
state = state.long;
trail = Max(if TrailType == TrailType."Simple" then Round((HAHL2 - atr * AtrMultiple) / tickSize, 0) * tickSize else longloss, trail[1]); # Hint: Ratchet up if favorable
} else {
state = state.Inactive;
trail = nan; # Hint: Exit on stop hit
}
case short:
if c < trail[1] && trigger330==0 {
state = state.short;
trail = Min(if TrailType == TrailType."Simple" then Round((HAHL2 + atr * AtrMultiple) / tickSize, 0) * tickSize else shortloss, trail[1]); # Hint: Ratchet down if favorable
} else {
state = state.Inactive;
trail = nan; # Hint: Exit on stop hit
}
}
# Signal crossings (for orders) - Hint: Detects state changes for buy/sell/exit alerts
def buy = Crosses(state == state.long, 0, CrossingDirection.ABOVE);
def sell = Crosses(state == state.short, 0, CrossingDirection.ABOVE);
def out = Crosses(state == state.Inactive, 0, CrossingDirection.ABOVE);
# Plot trailing stop
plot trailstop = trail[1]; # Hint: Plots the trailing stop level (lagged by 1 bar)
trailstop.SetLineWeight(2);
trailstop.SetPaintingStrategy(PaintingStrategy.LINE);
trailstop.DefineColor("Buy", Color.GREEN);
trailstop.DefineColor("Sell", Color.RED);
trailstop.AssignValueColor(if state == state.long then trailstop.Color("Buy") else trailstop.Color("Sell"));
# Trade direction tracking - Hint: Maintains current direction until exit
def tradeDirection;
if buy {
tradeDirection = 1;
} else if tradeDirection[1] == 1 && out {
tradeDirection = 0;
} else if sell {
tradeDirection = -1;
} else if tradeDirection[1] == -1 && out {
tradeDirection = 0;
} else {
tradeDirection = tradeDirection[1];
}
# Bar coloring and label
AssignPriceColor(if PaintBars then
if tradeDirection == 1 then Color.GREEN # Hint: Green bars for long trades
else if tradeDirection == -1 then Color.RED # Hint: Red bars for short trades
else Color.GRAY # Hint: Gray for neutral/no trade
else Color.CURRENT);

