Why Most Retail Traders Ignore the Best Data on Their Chart
Every tick that prints on your MetaTrader chart carries a story. Yet most EAs treat volume as an afterthought—a checkbox to tick before firing a moving average crossover. That's a mistake. Volume Profile—the vertical distribution of traded volume at each price level over a defined period—reveals exactly where the big money is parked. High-volume nodes (HVNs) act as price magnets; low-volume nodes (LVNs) become vacuum zones that prices slice through.
I've spent years coding EAs that exploit this structure. The strategy I'm sharing today anchors a Volume Profile to a specific trading session (London open, NY open, or a custom window), identifies the highest-volume price cluster, and enters on a retest of that level with a tight stop. No repainting indicators, no lookahead bias—just raw, time-stamped volume data processed bar by bar.
This isn't a "set and forget" system. It's a tactical tool for traders who understand that volume precedes price. Let's build it.
What Is a Volume Profile and Why Anchor It to a Session?
A standard Volume Profile (sometimes called VPVR—Volume Profile Visible Range) plots horizontal bars showing how much volume traded at each price level during a chosen time window. The peak of that distribution is the Point of Control (POC)—the price where the most shares or contracts changed hands. High-volume nodes (HVNs) are the levels within one standard deviation of the POC where volume clusters.
When you anchor the profile to a specific session—say 08:00 to 12:00 EST for the London/NY overlap—you get a clean picture of where institutional traders accumulated or distributed during that active window. Price often returns to these HVNs later in the day, either to test support/resistance or to fill resting orders. That retest is our entry trigger.
Why not use the built-in MetaTrader volume indicator? Two reasons. First, MetaTrader's tick volume is a proxy, not real volume—but it's consistent enough for forex EAs. Second, the built-in indicator gives you a running histogram, not a session-anchored profile. We need to reset the volume count at the session start and track cumulative volume at each price level until the session ends.
Building the Session-Anchored Volume Profile in MQL4
We'll write a custom MQL4 script that stores volume data in a two-dimensional array: price level (rounded to a user-defined tick size) and cumulative volume. Every time a new tick arrives, we increment the volume counter for the current Ask or Bid price level. When the session ends, we identify the highest-volume level (the POC) and draw a horizontal line at that price. On the next bar, if price retests that level within a tolerance, the EA enters.
Here's the core logic for the volume accumulation array:
//+------------------------------------------------------------------+
//| VolumeProfile.mqh - Accumulate volume per price level |
//+------------------------------------------------------------------+
#property strict
double g_volumeProfile[][2]; // [level][volume]
int g_levels = 0;
double g_tickSize;
bool InitVolumeProfile(double tickSize) {
g_tickSize = tickSize;
ArrayResize(g_volumeProfile, 1000, 1000);
g_levels = 0;
return true;
}
void AccumulateVolume(double price, double volume) {
double rounded = NormalizeDouble(MathRound(price / g_tickSize) * g_tickSize, Digits);
// Search for existing level
for(int i = 0; i < g_levels; i++) {
if(NormalizeDouble(g_volumeProfile[i][0] - rounded, Digits) == 0) {
g_volumeProfile[i][1] += volume;
return;
}
}
// New level
g_volumeProfile[g_levels][0] = rounded;
g_volumeProfile[g_levels][1] = volume;
g_levels++;
}
double GetPointOfControl() {
if(g_levels == 0) return 0;
double maxVol = 0;
double poc = 0;
for(int i = 0; i < g_levels; i++) {
if(g_volumeProfile[i][1] > maxVol) {
maxVol = g_volumeProfile[i][1];
poc = g_volumeProfile[i][0];
}
}
return poc;
}
This code runs inside the OnTick() handler. We check the current time against the session start/end parameters. Inside the session window, we call AccumulateVolume() with the current Bid price and the Volume[] array value (tick volume). At the session end, we store the POC and reset the array for the next session.
EA Entry Logic: Retest of the High-Volume Node
Once we have a valid POC from the previous session, we watch for price to return within a user-defined retest threshold (e.g., 5 pips). The EA enters a long position if price touches the POC from above (support test) or a short position if price touches from below (resistance test). We add a confirmation filter: the retest must occur within the first 4 hours of the next session, when liquidity is highest.
Here's the entry condition snippet:
//+------------------------------------------------------------------+
//| Check retest of POC |
//+------------------------------------------------------------------+
bool CheckRetest(double pocPrice, int &signal) {
double threshold = RetestPips * Point * 10; // Convert pips to price
if(Bid >= pocPrice - threshold && Bid <= pocPrice + threshold) {
// Price is within threshold of POC
// Determine direction based on approach
if(Bid <= pocPrice) {
signal = 1; // Long - price tested from above (support)
} else {
signal = -1; // Short - price tested from below (resistance)
}
return true;
}
return false;
}
The EA places a stop-loss 1.5 times the ATR of the last 20 bars (calculated on the H1 chart) below/above the entry. Take profit is set at the previous session's high/low or a fixed risk-reward ratio, whichever is reached first. We avoid trading during high-impact news using a simple time filter that blocks entries 30 minutes before and after major economic releases.
Input Parameters and Optimization Zones
Let's look at the key inputs you'll tweak when optimizing this EA. The most sensitive parameter is the RetestPips threshold. Too tight (1-2 pips) and you miss most entries; too wide (10+ pips) and you catch noise, not true retests.
| Parameter | Type | Default | Optimization Range |
|---|---|---|---|
| SessionStartHour | int (0-23) | 8 | 7-9 (London open) |
| SessionDuration | int (hours) | 4 | 3-6 (profile width) |
| RetestPips | double (pips) | 5.0 | 3-8 (sensitivity) |
| TickSize | double (points) | 10.0 | 5-20 (granularity) |
| ATRPeriod | int | 20 | 14-30 (stop distance) |
| RiskPercent | double | 1.0 | 0.5-2.0 (per trade) |
Optimize the SessionStartHour and SessionDuration together. For example, on EURUSD, the London open session (08:00-12:00 GMT) often produces a clean POC that holds for the rest of the day. On GBPJPY, the Asian session (00:00-04:00 GMT) can be equally effective. Run a walk-forward optimization over 6 months of tick data to find the best session window for each instrument.
Pros, Cons, and Risks
What Works Well
- Institutional footprint: High-volume nodes reflect where the biggest players have placed their bets. Trading retests of these levels aligns you with smart money.
- Session-specific clarity: Anchoring the profile to a defined window filters out overnight noise and gives you a clean reference level for the active trading day.
- Clear stop placement: The ATR-based stop keeps risk proportional to current volatility. You're not guessing where to put the stop—it's data-driven.
- Low trade frequency: Expect 1-3 trades per day on major pairs. This is a sniper strategy, not a machine gun. Fewer trades mean less exposure to slippage and spread costs.
The Hard Truths
- Tick volume is not real volume: In forex, MetaTrader reports tick count, not actual contracts. The correlation is high during liquid sessions but breaks down during quiet periods. Your POC might be less reliable on thinly traded pairs like NZDCHF.
- Session time dependency: If your broker's server time doesn't align with the session you're targeting (e.g., GMT+2 vs GMT), your POC will be off. Always verify server time against your session parameters.
- False retests: Price can slice through a POC and keep going, especially during news spikes. The ATR stop helps, but you'll still take losses when momentum overpowers the volume structure.
- No repaint guarantee: The POC is fixed once the session ends. But if you're using a different session window each day, the POC shifts. Stick to a fixed session time for consistency.
Worked Walkthrough: EURUSD on 14 March 2025
Let's trace a real trade using this EA. We set the session to 08:00-12:00 GMT (London open). TickSize = 10 points (1 pip on EURUSD). RetestPips = 5.
- Session accumulation: From 08:00 to 12:00 GMT, the EA accumulates tick volume. The highest volume cluster forms at 1.0870, with 2,300 ticks traded at that level. The POC is stored as 1.0870.
- Retest occurs: At 13:45 GMT, price drops from 1.0890 and touches 1.0872—within the 5-pip retest threshold. The EA receives a long signal.
- Entry and stops: A buy order is placed at 1.0872. The ATR on H1 is 12 pips. Stop-loss = 1.0872 - (12 * 1.5) = 1.0854 (18 pips). Take profit = 1.0890 (previous session high) + 5 pips buffer = 1.0895 (23 pips). Risk-reward = 1.28:1.
- Outcome: Price bounces from 1.0872, reaches 1.0895 by 15:






