Page 4 of 9
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:34 am
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.
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:34 am
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.
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:35 am
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.
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:35 am
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")
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:35 am
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.
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:37 am
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.
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:37 am
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];
}
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:38 am
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.
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:38 am
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.
Re: Heikin Ashi Candles, Smoothing the Noise
Posted: Sat Oct 03, 2026 5:39 am
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);
}
}
}
}
}