Page 3 of 9
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:29 am
by PTScalper
4. Critical Engineering Considerations for the EA
Session Anchors for Intraday Trading: If you are running this on 15-minute or lower timeframes, your VWAP must anchor correctly. A rolling VWAP is mathematically flawed for this filter. Anchor the VWAP to the daily open, the London session open, or the New York session open, depending on the asset being traded.
Volume Feed Integrity: In spot FX, standard tick volume can be unreliable for VWAP weighting. If your broker's tick volume does not accurately reflect institutional liquidity, this logic performs better on CFDs, Equities, or Futures feeds where volume profiling is centralized. If you must use it on FX, verify that your broker provides robust tick volume that correlates closely with centralized futures volume.
Decoupling the Data Arrays: Ensure your CopyBuffer calls for the Heikin Ashi indicator and the VWAP indicator are strictly separated. The most common logical error is accidentally passing the HA Close into the VWAP formula, which completely corrupts the Standard Deviation bands.
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:29 am
by PTScalper
Here is the complete architecture translated into TradingView’s Pine Script (v5).
This script solves the "backtesting illusion" by calculating the Heikin Ashi values mathematically in the background. You apply this script to a standard candlestick chart. It reads the real prices for execution, standard deviation, and trailing stops, but uses the invisible HA math purely for state logic and signaling.
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:30 am
by PTScalper
The Pine Script (v5) Implementation¨
Code: Select all
//@version=5
strategy("HA Wick & VWAP Execution Filter", overlay=true, calc_on_every_tick=true, initial_capital=10000, default_qty_type=strategy.percent_of_equity, default_qty_value=10)
// =========================================================================
// 1. INPUTS
// =========================================================================
stdevMult = input.float(2.0, title="VWAP StDev Band Multiplier", step=0.5)
stopBuffer = input.float(2.0, title="Stop Buffer (Pips/Ticks)", step=0.5)
// =========================================================================
// 2. BACKGROUND HEIKIN ASHI ENGINE
// =========================================================================
// We calculate HA manually so we can reference it without changing the chart type.
var float haOpen = na
haClose = (open + high + low + close) / 4.0
haOpen := na(haOpen[1]) ? (open + close) / 2.0 : (haOpen[1] + haClose[1]) / 2.0
haHigh = math.max(high, math.max(haOpen, haClose))
haLow = math.min(low, math.min(haOpen, haClose))
// HA State Logic:
// A flat bottom occurs when the HA Open is the absolute low of the HA candle.
isHAUp = haClose > haOpen
noLowerWick = isHAUp and (haOpen == haLow)
isHADown = haClose < haOpen
noUpperWick = isHADown and (haOpen == haHigh)
// =========================================================================
// 3. INSTITUTIONAL VWAP & STDEV BANDS
// =========================================================================
// Session anchored VWAP with Volume-Weighted Variance for true StDev bands
var float cumVol = 0.
var float cumVolPrice = 0.
var float cumVolPrice2 = 0.
isNewSession = timeframe.change("D") // Resets at the daily open
if isNewSession
cumVol := 0.
cumVolPrice := 0.
cumVolPrice2 := 0.
cumVol += volume
cumVolPrice += close * volume
cumVolPrice2 += volume * math.pow(close, 2)
vwapBase = cumVolPrice / cumVol
variance = (cumVolPrice2 / cumVol) - math.pow(vwapBase, 2)
vwapStDev = math.sqrt(math.max(0, variance))
upperVwapBand = vwapBase + (vwapStDev * stdevMult)
lowerVwapBand = vwapBase - (vwapStDev * stdevMult)
// Plotting VWAP for visual context on the standard chart
plot(vwapBase, color=color.new(color.blue, 0), title="VWAP")
plot(upperVwapBand, color=color.new(color.red, 50), title="Upper Band")
plot(lowerVwapBand, color=color.new(color.green, 50), title="Lower Band")
// =========================================================================
// 4. SIGNAL & FILTER LOGIC
// =========================================================================
// Block trades if the REAL close is piercing the StDev bands
overextendedLong = close >= upperVwapBand
overextendedShort = close <= lowerVwapBand
// Pristine setup: Flat bottom momentum, but safely inside the VWAP value zone
validLongSignal = noLowerWick and not overextendedLong
validShortSignal = noUpperWick and not overextendedShort
// =========================================================================
// 5. WICK-TRIGGERED TRAILING STOP EXECUTION
// =========================================================================
var float currentStop = na
bufferTicks = stopBuffer * syminfo.mintick
if strategy.position_size == 0
currentStop := na
// Entry Logic
if validLongSignal and strategy.position_size == 0
strategy.entry("Long", strategy.long)
currentStop := low - bufferTicks // Initial stop below the signal candle's REAL low
if validShortSignal and strategy.position_size == 0
strategy.entry("Short", strategy.short)
currentStop := high + bufferTicks // Initial stop above the signal candle's REAL high
// Trailing Logic: The "Flat-Bottom Anchor"
if strategy.position_size > 0
if strategy.position_size > 0 // We are Long
// Only tighten the stop if the momentum is pristine (no lower wick).
// If a wick appears, we freeze the stop and let the market breathe.
if noLowerWick
currentStop := math.max(currentStop, low - bufferTicks)
if strategy.position_size < 0 // We are Short
if noUpperWick
currentStop := math.min(currentStop, high + bufferTicks)
// Exit Execution (Hits the real market price)
if strategy.position_size > 0
strategy.exit("Exit Long", "Long", stop=currentStop)
if strategy.position_size < 0
strategy.exit("Exit Short", "Short", stop=currentStop)
// Plot the trailing stop visually on the chart
plot(strategy.position_size != 0 ? currentStop : na, color=color.new(color.fuchsia, 0), style=plot.style_linebr, linewidth=2, title="Trailing Stop")
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:30 am
by PTScalper
How This Pine Script Maps to the Mechanics
Invisible HA Engine: Lines 13-18 calculate haOpen and haLow manually. You do not need to switch TradingView to Heikin Ashi mode. Leave it on standard candlesticks. The script parses the raw open/high/low/close data, builds synthetic HA candles in its memory, and checks them for wicks (Lines 21-25).
True VWAP Bands: Lines 31-46 do not use a simple moving average standard deviation. They use volume-weighted variance. This is the institutional calculation for VWAP bands, accurately measuring liquidity dispersion.
The Overextension Filter: Lines 54-59 intercept the trade. Even if the background HA engine prints a massive, flat-bottom green candle, overextendedLong checks if the real market price (close) is currently hitting its head on the upper red VWAP band. If it is, the signal is killed.
The Flat-Bottom Anchor Trail: Lines 78-86 implement the wick logic. When you are long, the currentStop only moves up if noLowerWick is true. The moment the internal HA engine detects a lower wick (signaling a deep intraday pullback), the stop locks in place. It will survive the retracement unless the real market price actually hits the physical stop loss.
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:31 am
by PTScalper
To integrate Volume Profile logic in Pine Script without causing performance timeouts, the most efficient approach is to build a Session Point of Control (POC) Tracker using dynamic arrays.
Instead of tracking every single tick, we group prices into "bins" (e.g., 10-tick zones) and accumulate the volume traded in each bin throughout the day. The bin with the most volume is our primary High Volume Node (the POC).
The critical part of the execution logic is directional blocking:
You only want to block a Long if the current price is below the POC and firing directly up into it (buying into resistance).
You only want to block a Short if the current price is above the POC and firing down into it (selling into support).
If you are already past the POC, or bouncing away from it, the trade is permitted.
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:31 am
by PTScalper
Here is the updated v5 architecture combining the HA engine, VWAP bands, and the new HVN block logic.
Code: Select all
//@version=5
strategy("HA, VWAP & HVN Execution Filter", overlay=true, calc_on_every_tick=true, initial_capital=10000, default_qty_type=strategy.percent_of_equity, default_qty_value=10)
// =========================================================================
// 1. INPUTS
// =========================================================================
stdevMult = input.float(2.0, title="VWAP StDev Band Multiplier", step=0.5)
stopBuffer = input.float(2.0, title="Stop Buffer (Pips/Ticks)", step=0.5)
grp_hvn = "Volume Profile Settings"
hvnBinSize = input.int(10, title="Price Bin Size (Ticks)", group=grp_hvn, tooltip="Groups prices to calculate the POC. 10 is standard for scalping.")
hvnBlockZone = input.float(5.0, title="HVN Block Zone (Pips)", group=grp_hvn, tooltip="Blocks trades firing into the POC within this distance.")
// =========================================================================
// 2. BACKGROUND HEIKIN ASHI ENGINE
// =========================================================================
var float haOpen = na
haClose = (open + high + low + close) / 4.0
haOpen := na(haOpen[1]) ? (open + close) / 2.0 : (haOpen[1] + haClose[1]) / 2.0
haHigh = math.max(high, math.max(haOpen, haClose))
haLow = math.min(low, math.min(haOpen, haClose))
isHAUp = haClose > haOpen
noLowerWick = isHAUp and (haOpen == haLow)
isHADown = haClose < haOpen
noUpperWick = isHADown and (haOpen == haHigh)
// =========================================================================
// 3. INSTITUTIONAL VWAP & SESSION TRACKING
// =========================================================================
var float cumVol = 0.
var float cumVolPrice = 0.
var float cumVolPrice2 = 0.
isNewSession = timeframe.change("D")
if isNewSession
cumVol := 0.
cumVolPrice := 0.
cumVolPrice2 := 0.
cumVol += volume
cumVolPrice += close * volume
cumVolPrice2 += volume * math.pow(close, 2)
vwapBase = cumVolPrice / cumVol
variance = (cumVolPrice2 / cumVol) - math.pow(vwapBase, 2)
vwapStDev = math.sqrt(math.max(0, variance))
upperVwapBand = vwapBase + (vwapStDev * stdevMult)
lowerVwapBand = vwapBase - (vwapStDev * stdevMult)
// =========================================================================
// 4. VOLUME PROFILE (POC) ARRAY ENGINE
// =========================================================================
var priceBins = array.new_float(0)
var volBins = array.new_float(0)
var float sessionPOC = na
if isNewSession
array.clear(priceBins)
array.clear(volBins)
sessionPOC := na
// Group current close into a bin
tickSize = syminfo.mintick
binRounding = tickSize * hvnBinSize
currentBinPrice = math.round(close / binRounding) * binRounding
// Accumulate volume in the array
int binIndex = array.indexof(priceBins, currentBinPrice)
if binIndex == -1
// New price level reached, create new bin
array.push(priceBins, currentBinPrice)
array.push(volBins, volume)
else
// Existing price level, add volume
currentBinVol = array.get(volBins, binIndex)
array.set(volBins, binIndex, currentBinVol + volume)
// Find the node with the highest volume (The POC)
if array.size(volBins) > 0
maxVolIndex = array.indexof(volBins, array.max(volBins))
sessionPOC := array.get(priceBins, maxVolIndex)
// Plot POC as a solid orange line
plot(sessionPOC, color=color.new(color.orange, 0), style=plot.style_linebr, linewidth=2, title="Session POC (HVN)")
// =========================================================================
// 5. SIGNAL & FILTER LOGIC
// =========================================================================
// VWAP Overextension Filter
overextendedLong = close >= upperVwapBand
overextendedShort = close <= lowerVwapBand
// HVN Directional Block Filter
hvnBufferTicks = hvnBlockZone * syminfo.mintick * 10 // Convert pips to absolute price distance
// Block Long if price is BELOW the POC, and firing up into it within the danger zone
blockLongHVN = (close < sessionPOC) and (close >= sessionPOC - hvnBufferTicks)
// Block Short if price is ABOVE the POC, and firing down into it within the danger zone
blockShortHVN = (close > sessionPOC) and (close <= sessionPOC + hvnBufferTicks)
// Pristine setup calculation
validLongSignal = noLowerWick and not overextendedLong and not blockLongHVN
validShortSignal = noUpperWick and not overextendedShort and not blockShortHVN
// =========================================================================
// 6. EXECUTION & TRAILING STOP
// =========================================================================
var float currentStop = na
bufferTicks = stopBuffer * syminfo.mintick * 10
if strategy.position_size == 0
currentStop := na
if validLongSignal and strategy.position_size == 0
strategy.entry("Long", strategy.long)
currentStop := low - bufferTicks
if validShortSignal and strategy.position_size == 0
strategy.entry("Short", strategy.short)
currentStop := high + bufferTicks
if strategy.position_size > 0
if noLowerWick
currentStop := math.max(currentStop, low - bufferTicks)
if strategy.position_size < 0
if noUpperWick
currentStop := math.min(currentStop, high + bufferTicks)
if strategy.position_size > 0
strategy.exit("Exit Long", "Long", stop=currentStop)
if strategy.position_size < 0
strategy.exit("Exit Short", "Short", stop=currentStop)
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:32 am
by PTScalper
How the Volume Profile Integration Works
Price Binning (Lines 56-58): Trying to track volume at every single fractional tick will overload the array and crash the script. hvnBinSize aggregates the prices. If trading a Forex pair and the bin size is 10, the script groups the volume into 1-pip chunks, creating a clean, institutional-style profile.
Array Accumulation (Lines 60-69): Every bar, the script checks if the current aggregated price level exists in the priceBins array. If not, it creates it. If it does, it adds the current bar's volume to the volBins array at that specific index.
Dynamic POC Extraction (Lines 71-74): The script scans the array for the highest volume value (array.max) and dynamically updates the sessionPOC. This renders on your chart as a solid orange line, showing you exactly where the heaviest liquidity is sitting today.
Directional Wall Logic (Lines 87-92):
If sessionPOC is at 1.1050, and your hvnBlockZone is 5 pips.
If the real price is 1.1047 and a green HA candle prints, blockLongHVN triggers. The script recognizes you are trying to buy directly into a ceiling of passive limit sell orders. The trade is killed.
If the real price is 1.1052 and a green HA candle prints, the trade is allowed. You are above the POC, meaning that massive liquidity wall is now acting as your support/floor.
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:32 am
by PTScalper
To convert a backtesting strategy into an execution indicator for webhook automation, you must transition from TradingView's internal order engine (strategy.entry) to manual state management and dynamic string construction using the alert() function.
Because you no longer have strategy.position_size to tell the script when you are in a trade, you must build a custom state tracker (currentPos).
This version also includes the Session Open VWAP Time Filter to prevent false signals during the highly volatile morning reset, completing the institutional framework.
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:32 am
by PTScalper
The Live Execution Webhook Indicator (Pine Script v5)
Code: Select all
//@version=5
indicator("HA, VWAP & HVN Execution Alerts", overlay=true, calc_on_every_tick=true)
// =========================================================================
// 1. EXECUTION & ALERT INPUTS (JSON TEMPLATES)
// =========================================================================
grp_alerts = "Webhook JSON Payloads"
// Use inputs so you can easily modify the payload for PineConnector, 3Commas, etc.
in_webhookId = input.string("YOUR_WEBHOOK_ID", title="Webhook/Bot ID", group=grp_alerts)
in_tradeQty = input.string("1.0", title="Trade Size/Lots", group=grp_alerts)
grp_filters = "Filter Settings"
stdevMult = input.float(2.0, title="VWAP StDev Multiplier", group=grp_filters)
stopBuffer = input.float(2.0, title="Stop Buffer (Pips/Ticks)", group=grp_filters)
sessionDelay = input.int(30, title="Minutes to Block After Open", group=grp_filters, tooltip="Ignores signals while VWAP bands are resetting.")
grp_hvn = "Volume Profile"
hvnBinSize = input.int(10, title="Price Bin Size (Ticks)", group=grp_hvn)
hvnBlockZone = input.float(5.0, title="HVN Block Zone (Pips)", group=grp_hvn)
// =========================================================================
// 2. BACKGROUND HEIKIN ASHI ENGINE
// =========================================================================
var float haOpen = na
haClose = (open + high + low + close) / 4.0
haOpen := na(haOpen[1]) ? (open + close) / 2.0 : (haOpen[1] + haClose[1]) / 2.0
haHigh = math.max(high, math.max(haOpen, haClose))
haLow = math.min(low, math.min(haOpen, haClose))
isHAUp = haClose > haOpen
noLowerWick = isHAUp and (haOpen == haLow)
isHADown = haClose < haOpen
noUpperWick = isHADown and (haOpen == haHigh)
// =========================================================================
// 3. INSTITUTIONAL VWAP & SESSION TIMING
// =========================================================================
var float cumVol = 0.
var float cumVolPrice = 0.
var float cumVolPrice2 = 0.
var int sessionStartTime = na
isNewSession = timeframe.change("D")
if isNewSession
cumVol := 0.
cumVolPrice := 0.
cumVolPrice2 := 0.
sessionStartTime := time
cumVol += volume
cumVolPrice += close * volume
cumVolPrice2 += volume * math.pow(close, 2)
vwapBase = cumVolPrice / cumVol
variance = (cumVolPrice2 / cumVol) - math.pow(vwapBase, 2)
vwapStDev = math.sqrt(math.max(0, variance))
upperVwapBand = vwapBase + (vwapStDev * stdevMult)
lowerVwapBand = vwapBase - (vwapStDev * stdevMult)
// VWAP Time Filter (Blocks trades in the first X minutes of the day)
minutesSinceOpen = (time - sessionStartTime) / 60000
timeFilterPass = minutesSinceOpen >= sessionDelay
// =========================================================================
// 4. VOLUME PROFILE (POC) ARRAY ENGINE
// =========================================================================
var priceBins = array.new_float(0)
var volBins = array.new_float(0)
var float sessionPOC = na
if isNewSession
array.clear(priceBins)
array.clear(volBins)
sessionPOC := na
tickSize = syminfo.mintick
binRounding = tickSize * hvnBinSize
currentBinPrice = math.round(close / binRounding) * binRounding
int binIndex = array.indexof(priceBins, currentBinPrice)
if binIndex == -1
array.push(priceBins, currentBinPrice)
array.push(volBins, volume)
else
array.set(volBins, binIndex, array.get(volBins, binIndex) + volume)
if array.size(volBins) > 0
maxVolIndex = array.indexof(volBins, array.max(volBins))
sessionPOC := array.get(priceBins, maxVolIndex)
plot(sessionPOC, color=color.new(color.orange, 0), style=plot.style_linebr, linewidth=2, title="Session POC (HVN)")
// =========================================================================
// 5. SIGNAL LOGIC
// =========================================================================
overextendedLong = close >= upperVwapBand
overextendedShort = close <= lowerVwapBand
hvnBufferTicks = hvnBlockZone * tickSize * 10
blockLongHVN = (close < sessionPOC) and (close >= sessionPOC - hvnBufferTicks)
blockShortHVN = (close > sessionPOC) and (close <= sessionPOC + hvnBufferTicks)
validLongSignal = noLowerWick and not overextendedLong and not blockLongHVN and timeFilterPass
validShortSignal = noUpperWick and not overextendedShort and not blockShortHVN and timeFilterPass
// =========================================================================
// 6. CUSTOM STATE MACHINE & DYNAMIC JSON ALERTS
// =========================================================================
var int currentPos = 0 // 1 = Long, -1 = Short, 0 = Flat
var float currentStop = na
bufferTicks = stopBuffer * tickSize * 10
// Entry Logic
if currentPos == 0
if validLongSignal
currentPos := 1
currentStop := low - bufferTicks
// Construct dynamic JSON for Long Entry
jsonLong = '{"id":"' + in_webhookId + '","action":"buy","symbol":"' + syminfo.ticker + '","qty":' + in_tradeQty + ',"sl":' + str.tostring(currentStop) + '}'
alert(jsonLong, alert.freq_once_per_bar) // Firing once per bar prevents intrabar repainting spam
else if validShortSignal
currentPos := -1
currentStop := high + bufferTicks
// Construct dynamic JSON for Short Entry
jsonShort = '{"id":"' + in_webhookId + '","action":"sell","symbol":"' + syminfo.ticker + '","qty":' + in_tradeQty + ',"sl":' + str.tostring(currentStop) + '}'
alert(jsonShort, alert.freq_once_per_bar)
// Trailing Stop Logic (Wick-Anchored) & Exit Triggers
if currentPos == 1
if noLowerWick
currentStop := math.max(currentStop, low - bufferTicks) // Trail up
if low <= currentStop // Hard stop is hit by the real market price
jsonExitLong = '{"id":"' + in_webhookId + '","action":"close","symbol":"' + syminfo.ticker + '"}'
alert(jsonExitLong, alert.freq_all)
// Reset state machine
currentPos := 0
currentStop := na
if currentPos == -1
if noUpperWick
currentStop := math.min(currentStop, high + bufferTicks) // Trail down
if high >= currentStop
jsonExitShort = '{"id":"' + in_webhookId + '","action":"close","symbol":"' + syminfo.ticker + '"}'
alert(jsonExitShort, alert.freq_all)
// Reset state machine
currentPos := 0
currentStop := na
// Plot the trailing stop for visual confirmation
plot(currentPos != 0 ? currentStop : na, color=color.new(color.fuchsia, 0), style=plot.style_linebr, linewidth=2, title="Trailing Stop")
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:33 am
by PTScalper
Key Architectural Changes for Live Trading
The State Machine (currentPos): Because this is an indicator, it cannot natively hold simulated trades. Lines 93-94 establish an internal ledger. currentPos ensures the script doesn't fire 50 "buy" JSON alerts while the Heikin Ashi trend is green. It fires exactly once, locks the state to 1 (Long), and will not fire another entry until the currentStop is breached and resets the state to 0.
Dynamic JSON String Construction: In Lines 103 and 109, the str.tostring() function is used to physically inject the exact numerical value of your currentStop into the JSON payload. This allows your broker to attach a hard Stop Loss order on their servers the millisecond the trade is placed, rather than relying on TradingView to send a separate exit command later.
Frequency Control (alert.freq_once_per_bar vs. alert.freq_all):
Entries are set to freq_once_per_bar to prevent an intrabar price flutter from sending duplicate buy orders.
Exits (Lines 119 and 129) are set to freq_all. If a sudden flash-crash hits your trailing stop level in the middle of a 5-minute candle, the script will fire the exit JSON immediately, without waiting for the candle to close.
The VWAP Time Filter (timeFilterPass): At the daily reset, VWAP standard deviation is near zero. A 2-StDev band might be just 2 pips wide, causing the indicator to falsely block valid trades by thinking the price is "overextended". Line 48 tracks the time elapsed since the daily open and enforces the sessionDelay (default 30 minutes) before allowing the filters to evaluate.