Advertisement IC Markets

Heikin Ashi Candles, Smoothing the Noise

Optimize MetaTrader 4, MetaTrader 5, cTrader, and TradingView for speed. Discuss Level II Market Depth (DOM), custom hotkeys, and volume indicators.
PTScalper
Site Admin
Posts: 7538
Joined: Mon Jul 20, 2026 1:28 pm

Re: Heikin Ashi Candles, Smoothing the Noise

Post by PTScalper »

Setting Up the Webhook in TradingView

1.) Add the script to your chart and open the Settings gear for the indicator.

2.) Input your specific Webhook ID (used by services like PineConnector, AutoView, or 3Commas) and your trade size.

3.) Click the Alerts (Clock) icon on the right toolbar.

4.) Condition: Select the "HA, VWAP & HVN Execution Alerts" indicator.

5.) Set it to trigger on Any alert() function call.

6.) In the Notifications tab, check Webhook URL and paste your broker/bridge endpoint. Do not type a message in the text box; the Pine Script handles the JSON dynamically.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 7538
Joined: Mon Jul 20, 2026 1:28 pm

Re: Heikin Ashi Candles, Smoothing the Noise

Post by PTScalper »

The most common stumbling block when setting up PineConnector is the assumption that it requires a JSON webhook payload. PineConnector does not use JSON. It uses a proprietary, strict comma-separated values (CSV) string format.

If you send a JSON payload like {"action": "buy"}, the PineConnector Expert Advisor (EA) in MetaTrader will reject it or fail to parse it.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7538
Joined: Mon Jul 20, 2026 1:28 pm

Re: Heikin Ashi Candles, Smoothing the Noise

Post by PTScalper »

The Exact PineConnector Syntax

The core structure of a PineConnector message is:
LicenseID,Command,Symbol,Parameters

Entry Command Example: 123456789,buy,EURUSD,vol_lots=0.1,sl_price=1.0950

Exit Command Example: 123456789,closelong,EURUSD

Critical Syntax Rules for MT4/MT5:

No Spaces: Do not put spaces after commas.

Explicit Units: Use vol_lots=, vol_pct_bal_loss=, or vol_dollar= instead of legacy risk= commands.

Specific Price Stops: Use sl_price= to pass the exact dynamic stop-loss price calculated by your script, rather than a static sl_pips=.

Broker Symbol Matching: Your script must send the exact ticker as it appears in your MetaTrader terminal (e.g., EURUSD.m or EURUSD.pro), which often differs from the standard TradingView ticker.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7538
Joined: Mon Jul 20, 2026 1:28 pm

Re: Heikin Ashi Candles, Smoothing the Noise

Post by PTScalper »

The PineConnector Webhook Script (Pine Script v5)

Here is the previous institutional execution script, modified specifically to construct and fire native PineConnector CSV alerts.

Code: Select all

//@version=5
indicator("PineConnector HA/VWAP Execution", overlay=true, calc_on_every_tick=true)

// =========================================================================
// 1. PINECONNECTOR CREDENTIALS & SIZING
// =========================================================================
grp_pc = "PineConnector Settings"
in_licenseID = input.string("1234567890", title="License ID", group=grp_pc)
in_brokerSymbol = input.string("EURUSD", title="Broker Ticker (e.g. EURUSD.m)", group=grp_pc)
in_tradeLots = input.float(0.1, title="Trade Size (Lots)", step=0.01, group=grp_pc)

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)

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)

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)

// =========================================================================
// 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 & PINECONNECTOR ALERTS
// =========================================================================
var int currentPos = 0      
var float currentStop = na
bufferTicks = stopBuffer * tickSize * 10

// ─── ENTRIES ─────────────────────────────────────────────────────────────
if currentPos == 0
    if validLongSignal
        currentPos := 1
        currentStop := low - bufferTicks
        
        // Construct standard PineConnector String
        // Format: LicenseID,buy,EURUSD,vol_lots=0.1,sl_price=1.1000
        pcLong = in_licenseID + ',buy,' + in_brokerSymbol + ',vol_lots=' + str.tostring(in_tradeLots) + ',sl_price=' + str.tostring(currentStop)
        alert(pcLong, alert.freq_once_per_bar) 
        
    else if validShortSignal
        currentPos := -1
        currentStop := high + bufferTicks
        
        // Construct standard PineConnector String
        pcShort = in_licenseID + ',sell,' + in_brokerSymbol + ',vol_lots=' + str.tostring(in_tradeLots) + ',sl_price=' + str.tostring(currentStop)
        alert(pcShort, alert.freq_once_per_bar)

// ─── TRAILING STOPS & EXITS ──────────────────────────────────────────────
if currentPos == 1
    if noLowerWick 
        currentStop := math.max(currentStop, low - bufferTicks) 
        
    if low <= currentStop 
        // Force exit via TradingView when trailing stop is hit
        pcExitLong = in_licenseID + ',closelong,' + in_brokerSymbol
        alert(pcExitLong, alert.freq_all)
        
        currentPos := 0
        currentStop := na

if currentPos == -1
    if noUpperWick
        currentStop := math.min(currentStop, high + bufferTicks) 
        
    if high >= currentStop
        // Force exit via TradingView when trailing stop is hit
        pcExitShort = in_licenseID + ',closeshort,' + in_brokerSymbol
        alert(pcExitShort, alert.freq_all)
        
        currentPos := 0
        currentStop := na

plot(currentPos != 0 ? currentStop : na, color=color.new(color.fuchsia, 0), style=plot.style_linebr, linewidth=2, title="Trailing Stop")
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7538
Joined: Mon Jul 20, 2026 1:28 pm

Re: Heikin Ashi Candles, Smoothing the Noise

Post by PTScalper »

Setup Execution Workflow

Initial Hard Stop Backup: When the entry signal fires, TradingView passes sl_price= with the exact numerical value of your initial stop. This is sent to MetaTrader immediately. If your internet dies or TradingView goes offline, you are protected at the broker level.

Chart Master Synchronization: As the trend continues, the Pine Script internal trailing stop moves to protect profits. The script does not constantly spam MetaTrader with trailing stop updates. Instead, TradingView acts as the master brain.

The Kill Switch: When the live market price breaches the TradingView trailing stop, the script immediately fires the closelong or closeshort command. This commands MetaTrader to exit the position at market instantly, overriding the initial hard stop that was placed at entry.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7538
Joined: Mon Jul 20, 2026 1:28 pm

Re: Heikin Ashi Candles, Smoothing the Noise

Post by PTScalper »

Here is the complete, compiled-ready MQL4 Expert Advisor.

Translating this architecture to MetaTrader 4 requires shifting from TradingView's dynamic array processing to C++ style state management. Because standard MT4 Forex data uses Tick Volume instead of true traded volume, the script adapts by using tick density to calculate the VWAP bands and Volume Profile Point of Control (POC)—which in Forex, is a highly accurate proxy for liquidity.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7538
Joined: Mon Jul 20, 2026 1:28 pm

Re: Heikin Ashi Candles, Smoothing the Noise

Post by PTScalper »

This EA processes all logic on the close of the candle to guarantee zero repainting and to protect your broker API from tick-spam.

The MQL4 EA: Institutional_HA_VWAP.mq4

Code: Select all

//+------------------------------------------------------------------+
//|                                     Institutional_HA_VWAP.mq4    |
//|                    HA Wick + VWAP + HVN Session Execution        |
//+------------------------------------------------------------------+
#property copyright "Algorithmic Architecture"
#property link      ""
#property version   "1.00"
#property strict

//--- Inputs
input double LotSize = 0.1;
input int    MagicNumber = 88888;
input int    Slippage = 3;

input string _1 = "--- VWAP Settings ---";
input double StDevMult = 2.0;           // VWAP StDev Band Multiplier
input int    SessionDelayMinutes = 30;  // Minutes to Block After Open

input string _2 = "--- Volume Profile (HVN) ---";
input double HvnBinSizePips = 1.0;      // Price Bin Size (Pips)
input double HvnBlockZonePips = 5.0;    // Block trades firing into POC within this distance

input string _3 = "--- Trailing Stop ---";
input double StopBufferPips = 2.0;      // Buffer below HA Real Low (Pips)

//--- Global Variables
datetime lastBarTime;
datetime sessionStartTime;

// HA State
double prevHaOpen = 0;
double prevHaClose = 0;

// VWAP State
double cumVol = 0;
double cumVolPrice = 0;
double cumVolPrice2 = 0;

// HVN Arrays
double priceBins[];
long   volBins[];
double sessionPOC = 0;

double pipSize;
int    pipMult;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    // Configure pip sizing for 4/5 digit brokers
    pipMult = (Digits == 3 || Digits == 5) ? 10 : 1;
    pipSize = Point * pipMult;
    
    lastBarTime = 0;
    sessionStartTime = 0;
    
    ArrayResize(priceBins, 0);
    ArrayResize(volBins, 0);

    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // Only execute logic on the close of a bar to prevent repainting
    if(Time[0] == lastBarTime) return;
    
    // --- 1. SESSION RESET DETECTION ---
    if(TimeDay(Time[0]) != TimeDay(lastBarTime)) 
    {
        cumVol = 0;
        cumVolPrice = 0;
        cumVolPrice2 = 0;
        sessionStartTime = Time[0];
        
        ArrayResize(priceBins, 0);
        ArrayResize(volBins, 0);
        sessionPOC = 0;
        
        // Reset HA Engine base
        prevHaOpen = Open[1];
        prevHaClose = Close[1];
    }
    
    // --- 2. BACKGROUND HEIKIN ASHI ENGINE ---
    // Calculate HA values for the recently closed candle (Index 1)
    double haClose = (Open[1] + High[1] + Low[1] + Close[1]) / 4.0;
    double haOpen  = (prevHaOpen + prevHaClose) / 2.0;
    
    // Initialize first bar of EA if needed
    if(prevHaOpen == 0) haOpen = (Open[1] + Close[1]) / 2.0; 
    
    double haHigh  = MathMax(High[1], MathMax(haOpen, haClose));
    double haLow   = MathMin(Low[1], MathMin(haOpen, haClose));
    
    // Evaluate Wicks
    bool isHAUp = haClose > haOpen;
    // Normalize floats for exact equality check in MQL4
    bool noLowerWick = isHAUp && (NormalizeDouble(haOpen - haLow, Digits) == 0);
    
    bool isHADown = haClose < haOpen;
    bool noUpperWick = isHADown && (NormalizeDouble(haHigh - haOpen, Digits) == 0);
    
    // Save state for next bar
    prevHaOpen = haOpen;
    prevHaClose = haClose;
    
    // --- 3. INSTITUTIONAL VWAP CALCULATION ---
    double tickVol = (double)Volume[1];
    
    cumVol += tickVol;
    cumVolPrice += Close[1] * tickVol;
    cumVolPrice2 += tickVol * MathPow(Close[1], 2);
    
    double vwapBase = cumVolPrice / cumVol;
    double variance = (cumVolPrice2 / cumVol) - MathPow(vwapBase, 2);
    double vwapStDev = MathSqrt(MathMax(0, variance));
    
    double upperVwapBand = vwapBase + (vwapStDev * StDevMult);
    double lowerVwapBand = vwapBase - (vwapStDev * StDevMult);
    
    // --- 4. VOLUME PROFILE (POC) BINNING ---
    double binRounding = pipSize * HvnBinSizePips;
    double currentBinPrice = MathRound(Close[1] / binRounding) * binRounding;
    
    int binIndex = -1;
    for(int i = 0; i < ArraySize(priceBins); i++) {
        if(NormalizeDouble(MathAbs(priceBins[i] - currentBinPrice), Digits) < Point) {
            binIndex = i;
            break;
        }
    }
    
    if(binIndex == -1) {
        int newSize = ArraySize(priceBins) + 1;
        ArrayResize(priceBins, newSize);
        ArrayResize(volBins, newSize);
        priceBins[newSize-1] = currentBinPrice;
        volBins[newSize-1] = (long)tickVol;
    } else {
        volBins[binIndex] += (long)tickVol;
    }
    
    long maxVol = -1;
    for(int i = 0; i < ArraySize(volBins); i++) {
        if(volBins[i] > maxVol) {
            maxVol = volBins[i];
            sessionPOC = priceBins[i];
        }
    }
    
    // --- 5. SIGNAL FILTERS ---
    int minutesSinceOpen = (int)((Time[0] - sessionStartTime) / 60);
    bool timeFilterPass = minutesSinceOpen >= SessionDelayMinutes;
    
    bool overextendedLong = Close[1] >= upperVwapBand;
    bool overextendedShort = Close[1] <= lowerVwapBand;
    
    double hvnBufferPrice = HvnBlockZonePips * pipSize;
    bool blockLongHVN = (Close[1] < sessionPOC) && (Close[1] >= sessionPOC - hvnBufferPrice);
    bool blockShortHVN = (Close[1] > sessionPOC) && (Close[1] <= sessionPOC + hvnBufferPrice);
    
    bool validLongSignal = noLowerWick && !overextendedLong && !blockLongHVN && timeFilterPass;
    bool validShortSignal = noUpperWick && !overextendedShort && !blockShortHVN && timeFilterPass;
    
    // --- 6. EXECUTION & TRAILING STOP MANAGEMENT ---
    bool hasOpenLong = false;
    bool hasOpenShort = false;
    double slBuffer = StopBufferPips * pipSize;
    
    for(int i = OrdersTotal() - 1; i >= 0; i--) 
    {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) 
        {
            if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) 
            {
                if(OrderType() == OP_BUY) 
                {
                    hasOpenLong = true;
                    // HA Flat-Bottom Trail logic
                    if(noLowerWick) 
                    {
                        double newStop = NormalizeDouble(Low[1] - slBuffer, Digits);
                        // Only modify if new stop is better and respects broker StopLevel
                        if(newStop > OrderStopLoss() && (Bid - newStop) > MarketInfo(Symbol(), MODE_STOPLEVEL) * Point) 
                        {
                            bool mod = OrderModify(OrderTicket(), OrderOpenPrice(), newStop, OrderTakeProfit(), 0, clrFuchsia);
                        }
                    }
                }
                else if(OrderType() == OP_SELL) 
                {
                    hasOpenShort = true;
                    // HA Flat-Top Trail logic
                    if(noUpperWick) 
                    {
                        double newStop = NormalizeDouble(High[1] + slBuffer, Digits);
                        if((newStop < OrderStopLoss() || OrderStopLoss() == 0) && (newStop - Ask) > MarketInfo(Symbol(), MODE_STOPLEVEL) * Point) 
                        {
                            bool mod = OrderModify(OrderTicket(), OrderOpenPrice(), newStop, OrderTakeProfit(), 0, clrFuchsia);
                        }
                    }
                }
            }
        }
    }
    
    // --- ENTRY LOGIC ---
    if(!hasOpenLong && !hasOpenShort) 
    {
        if(validLongSignal) 
        {
            double initialSL = NormalizeDouble(Low[1] - slBuffer, Digits);
            int ticket = OrderSend(Symbol(), OP_BUY, LotSize, Ask, Slippage, initialSL, 0, "HA VWAP Long", MagicNumber, 0, clrBlue);
        }
        else if(validShortSignal) 
        {
            double initialSL = NormalizeDouble(High[1] + slBuffer, Digits);
            int ticket = OrderSend(Symbol(), OP_SELL, LotSize, Bid, Slippage, initialSL, 0, "HA VWAP Short", MagicNumber, 0, clrRed);
        }
    }
    
    lastBarTime = Time[0];
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7538
Joined: Mon Jul 20, 2026 1:28 pm

Re: Heikin Ashi Candles, Smoothing the Noise

Post by PTScalper »

Architectural Differences from Pine Script

Independent Internal HA Engine: Instead of relying on a custom MT4 Heikin Ashi indicator (which vary wildly in buffer implementation from broker to broker), the EA calculates the HA variables directly inside its own memory (prevHaOpen, prevHaClose). This makes the EA entirely self-contained; you apply it to a standard candlestick chart and it functions without any attached custom indicators.

OrderModify Efficiency: MetaTrader brokers will flag or ban accounts that spam OrderModify requests on every tick without actually moving the price. Lines 150 and 163 include a mathematical check: the EA will only send a webhook/server request to move the stop loss if the new HA real-low stop is mathematically higher (better) than the existing hard stop, and it rigorously enforces the broker's minimum MODE_STOPLEVEL distance.

Float Normalization: MQL4 handles float comparison strictly. haOpen == haLow fails constantly in C++ due to floating-point rounding errors (e.g., 1.10500000001 != 1.10500000000). Lines 88 and 91 utilize NormalizeDouble() to ensure the wick calculation matches exact broker ticks, preventing the EA from falsely identifying invisible wicks and skipping pristine trades.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7538
Joined: Mon Jul 20, 2026 1:28 pm

Re: Heikin Ashi Candles, Smoothing the Noise

Post by PTScalper »

Here is the complete cTrader Automate (cAlgo) implementation in C#.

Because cTrader operates on modern .NET, we can completely eliminate the clunky MQL4 dynamic arrays and replace them with a high-performance C# Dictionary and LINQ queries to calculate the Volume Profile (Point of Control) natively.

This architecture uses the OnBar method to execute logic exactly once per closed candle, ensuring zero repainting while protecting your CPU and broker API from tick-level spam.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7538
Joined: Mon Jul 20, 2026 1:28 pm

Re: Heikin Ashi Candles, Smoothing the Noise

Post by PTScalper »

The cTrader C# Bot: Institutional_HA_VWAP.cs

Code: Select all

using System;
using System.Linq;
using System.Collections.Generic;
using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class Institutional_HA_VWAP : Robot
    {
        // --- 1. SETTINGS & INPUTS ---
        [Parameter("Trade Size (Lots)", DefaultValue = 0.1, Group = "Risk Management", MinValue = 0.01)]
        public double LotSize { get; set; }

        [Parameter("VWAP StDev Multiplier", DefaultValue = 2.0, Group = "Institutional VWAP")]
        public double StDevMult { get; set; }

        [Parameter("Minutes to Block After Open", DefaultValue = 30, Group = "Institutional VWAP")]
        public int SessionDelayMinutes { get; set; }

        [Parameter("Price Bin Size (Pips)", DefaultValue = 1.0, Group = "Volume Profile (HVN)")]
        public double HvnBinSizePips { get; set; }

        [Parameter("HVN Block Zone (Pips)", DefaultValue = 5.0, Group = "Volume Profile (HVN)")]
        public double HvnBlockZonePips { get; set; }

        [Parameter("Stop Buffer (Pips)", DefaultValue = 2.0, Group = "Trailing Stop")]
        public double StopBufferPips { get; set; }

        // --- INTERNAL STATE ---
        private const string BotLabel = "HA_VWAP_Execute";
        
        // HA State
        private double _prevHaOpen;
        private double _prevHaClose;

        // VWAP State
        private double _cumVol;
        private double _cumVolPrice;
        private double _cumVolPrice2;
        private DateTime _sessionStartTime;

        // Volume Profile State (C# Dictionary is vastly superior to MQL arrays for this)
        private Dictionary<double, long> _volumeProfile = new Dictionary<double, long>();
        private double _sessionPOC;

        protected override void OnStart()
        {
            // Initialization if needed
        }

        protected override void OnBar()
        {
            // In cTrader OnBar(), Bars.Last(1) is the candle that just closed. 
            // Bars.Last(0) is the newly opened, incomplete candle.
            var closedBar = Bars.Last(1);
            var previousClosedBar = Bars.Last(2);

            // --- 1. SESSION RESET DETECTION (Daily Reset) ---
            if (closedBar.OpenTime.Date != previousClosedBar.OpenTime.Date || _sessionStartTime == default)
            {
                _cumVol = 0;
                _cumVolPrice = 0;
                _cumVolPrice2 = 0;
                _sessionStartTime = closedBar.OpenTime;

                _volumeProfile.Clear();
                _sessionPOC = 0;

                // Reset HA base to the previous day's close
                _prevHaOpen = previousClosedBar.Open;
                _prevHaClose = previousClosedBar.Close;
            }

            // --- 2. BACKGROUND HEIKIN ASHI ENGINE ---
            double haClose = (closedBar.Open + closedBar.High + closedBar.Low + closedBar.Close) / 4.0;
            double haOpen = (_prevHaOpen + _prevHaClose) / 2.0;
            
            // Failsafe for very first bar computation
            if (_prevHaOpen == 0) haOpen = (closedBar.Open + closedBar.Close) / 2.0;

            double haHigh = Math.Max(closedBar.High, Math.Max(haOpen, haClose));
            double haLow = Math.Min(closedBar.Low, Math.Min(haOpen, haClose));

            // Wick Math (Rounded to broker digits to prevent floating point false-positives)
            bool isHAUp = haClose > haOpen;
            bool noLowerWick = isHAUp && Math.Round(haOpen - haLow, Symbol.Digits) == 0;

            bool isHADown = haClose < haOpen;
            bool noUpperWick = isHADown && Math.Round(haHigh - haOpen, Symbol.Digits) == 0;

            // Save state for next bar iteration
            _prevHaOpen = haOpen;
            _prevHaClose = haClose;

            // --- 3. INSTITUTIONAL VWAP CALCULATION ---
            long tickVol = (long)closedBar.TickVolume;
            
            _cumVol += tickVol;
            _cumVolPrice += closedBar.Close * tickVol;
            _cumVolPrice2 += tickVol * Math.Pow(closedBar.Close, 2);

            double vwapBase = _cumVolPrice / _cumVol;
            double variance = (_cumVolPrice2 / _cumVol) - Math.Pow(vwapBase, 2);
            double vwapStDev = Math.Sqrt(Math.Max(0, variance));

            double upperVwapBand = vwapBase + (vwapStDev * StDevMult);
            double lowerVwapBand = vwapBase - (vwapStDev * StDevMult);

            // --- 4. C# LINQ VOLUME PROFILE (POC) ---
            double binRounding = HvnBinSizePips * Symbol.PipSize;
            double currentBinPrice = Math.Round(closedBar.Close / binRounding) * binRounding;
            currentBinPrice = Math.Round(currentBinPrice, Symbol.Digits);

            if (_volumeProfile.ContainsKey(currentBinPrice))
                _volumeProfile[currentBinPrice] += tickVol;
            else
                _volumeProfile[currentBinPrice] = tickVol;

            // Extract the Point of Control instantly using LINQ
            _sessionPOC = _volumeProfile.OrderByDescending(kvp => kvp.Value).First().Key;

            // --- 5. SIGNAL FILTERS ---
            TimeSpan timeSinceOpen = closedBar.OpenTime - _sessionStartTime;
            bool timeFilterPass = timeSinceOpen.TotalMinutes >= SessionDelayMinutes;

            bool overextendedLong = closedBar.Close >= upperVwapBand;
            bool overextendedShort = closedBar.Close <= lowerVwapBand;

            double hvnBufferPrice = HvnBlockZonePips * Symbol.PipSize;
            bool blockLongHVN = (closedBar.Close < _sessionPOC) && (closedBar.Close >= _sessionPOC - hvnBufferPrice);
            bool blockShortHVN = (closedBar.Close > _sessionPOC) && (closedBar.Close <= _sessionPOC + hvnBufferPrice);

            bool validLongSignal = noLowerWick && !overextendedLong && !blockLongHVN && timeFilterPass;
            bool validShortSignal = noUpperWick && !overextendedShort && !blockShortHVN && timeFilterPass;

            // --- 6. EXECUTION & TRAILING STOP MANAGEMENT ---
            var existingPosition = Positions.Find(BotLabel, SymbolName);
            double slBufferPrice = StopBufferPips * Symbol.PipSize;

            if (existingPosition != null)
            {
                // TRAILING STOP: Flat-Bottom / Flat-Top anchored trail
                if (existingPosition.TradeType == TradeType.Buy && noLowerWick)
                {
                    double newStop = Math.Round(closedBar.Low - slBufferPrice, Symbol.Digits);
                    // Only modify if the new trail is higher than the old stop
                    if (existingPosition.StopLoss == null || newStop > existingPosition.StopLoss)
                    {
                        ModifyPosition(existingPosition, newStop, existingPosition.TakeProfit);
                    }
                }
                else if (existingPosition.TradeType == TradeType.Sell && noUpperWick)
                {
                    double newStop = Math.Round(closedBar.High + slBufferPrice, Symbol.Digits);
                    // Only modify if the new trail is lower than the old stop
                    if (existingPosition.StopLoss == null || newStop < existingPosition.StopLoss)
                    {
                        ModifyPosition(existingPosition, newStop, existingPosition.TakeProfit);
                    }
                }
            }
            else
            {
                // ENTRY LOGIC
                double volumeInUnits = Symbol.QuantityToVolumeInUnits(LotSize);

                if (validLongSignal)
                {
                    double initialSL_Price = Math.Round(closedBar.Low - slBufferPrice, Symbol.Digits);
                    // cTrader ExecuteMarketOrder requires SL in Pips, not absolute price
                    double slPips = Math.Round((Symbol.Ask - initialSL_Price) / Symbol.PipSize, 1);
                    
                    ExecuteMarketOrder(TradeType.Buy, SymbolName, volumeInUnits, BotLabel, slPips, null);
                }
                else if (validShortSignal)
                {
                    double initialSL_Price = Math.Round(closedBar.High + slBufferPrice, Symbol.Digits);
                    double slPips = Math.Round((initialSL_Price - Symbol.Bid) / Symbol.PipSize, 1);
                    
                    ExecuteMarketOrder(TradeType.Sell, SymbolName, volumeInUnits, BotLabel, slPips, null);
                }
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply