Market Profile TPO MQL5: Code & Interpret Letters

Learn to build a custom Market Profile TPO indicator in MQL5 with session breakdown, volume at price, and letter-based time price opportunity analysis.

market-profile-tpo-mql5-code-interpret-letters

Most retail traders treat the daily chart like a flat line. Open, high, low, close — that's it. But there's a whole dimension of information hiding in when price traded at certain levels, not just where it ended up. That's what Market Profile and its TPO (Time Price Opportunity) chart reveal. And if you trade on MetaTrader 5, you've probably noticed the platform ships with volume profile but nothing that shows you the auction process itself.

That's the gap I want to fill here. I'll walk you through building a custom market profile TPO MQL5 indicator from scratch — one that plots letter-based TPO cells, tracks volume at price, and breaks everything down by trading session. You'll get working code you can compile and attach to any chart, plus the interpretation framework that turns those letters from pretty noise into a genuine edge.

What TPO Actually Tells You That Volume Profile Doesn't

Volume profile answers "how much traded at this price?" TPO answers a different question: "how long did price spend at this price?" That distinction matters more than most traders realize.

Think about a typical Asian session on EURUSD. Volume is thin, but price might sit in a 15-pip range for six hours. A volume profile barely registers that activity. A TPO profile, though, paints a full column of letters — every 30-minute period gets its own letter, and if price stays in that range, you get six or seven letters stacked at nearly the same price. That's acceptance. Price isn't just passing through; the market is building value there.

Compare that to a news spike. Price blasts through 40 pips in four minutes. Volume profile shows a massive bar. TPO shows a single letter at each price — price was there, but only briefly. That's rejection, not acceptance. Two completely different market states, and volume alone can't tell them apart.

The letters themselves are the core of the TPO indicator MQL5 implementation. Each time period (usually 30 minutes) gets assigned a letter from A through Z. If price trades at a given price level during that period, the letter gets plotted in that price row. Over a full session, you build a histogram of letters that shows you exactly where price spent the most time — and that's your value area.

Anatomy of a TPO Chart: Letters, Brackets, and Value

Before we touch code, let's get the vocabulary straight. You'll see these terms in every serious discussion of time price opportunity:

  • TPO count — the number of letters in a price row. Higher count = more time spent at that price = more acceptance.
  • Value Area (VA) — the price range containing roughly 70% of the day's TPOs. This is where the market "agreed" on price.
  • Point of Control (POC) — the single price row with the highest TPO count. The market's favorite price.
  • Initial Balance (IB) — the high and low of the first hour (letters A and B, typically). This sets the day's opening range.
  • Single prints — price levels with only one letter. These mark areas of fleeting interest, often precursors to moves.

The beauty of TPO is that it doesn't care about volume. It cares about time at price, which is arguably a purer measure of market participation. A level where price spent three hours is more significant than a level where a thousand contracts traded in three seconds. The first represents genuine two-sided flow; the second is often just a stop hunt.

For the MQL5 implementation, we need to track price in discrete buckets. I use a configurable tick size — typically 5 to 10 points for forex, but you'll want to adjust it per instrument. The TPO indicator MQL5 code essentially does three things on every tick:

  1. Determine which session the current time falls into.
  2. Map current price to a price level bucket.
  3. Store the current period's letter in that bucket.

Building the TPO Indicator in MQL5: The Core Logic

Let's get into the actual code. I'll show you the essential pieces — not the full 500-line indicator, but the parts that matter. You'll need a basic understanding of MQL5 buffers and chart events to follow along.

First, the input parameters. These define how your market profile MT5 indicator behaves:

ParameterTypeDefaultDescription
TPO_LetterMinutesint30Minutes per TPO letter. 30 is standard, but 15 works for scalping.
PriceStepdouble10 * _PointPrice bucket size. Adjust per instrument; 10 points on EURUSD is reasonable.
SessionStartHourint8Server hour when the trading session starts.
SessionEndHourint22Server hour when the session ends. Outside these hours, no TPOs are recorded.
ShowVolumeProfilebooltrueAlso draw a volume-at-price histogram alongside the TPO letters.
ShowPOCbooltrueHighlight the point of control with a distinct color.

Now the core data structure. In MQL5, I use a simple array of arrays — one array per price level, each holding a string of letters. The index maps to the price bucket:

// Global arrays
string g_tpoLetters[];        // TPO letters per price level
long   g_tpoVolume[];         // Volume per price level
double g_priceLevels[];       // The price for each level index
int    g_levelCount = 0;

// On every tick, record the current price and volume
void RecordTPO(datetime time, double price, long volume)
{
    // Check if we're in the trading session
    MqlDateTime dt;
    TimeToStruct(time, dt);
    if(dt.hour < SessionStartHour || dt.hour >= SessionEndHour)
        return;

    // Calculate the price level index
    int level = (int)MathRound((price - g_minPrice) / PriceStep);
    if(level < 0) level = 0;
    if(level >= g_levelCount) {
        // Expand arrays as needed
        ArrayResize(g_tpoLetters, level + 1);
        ArrayResize(g_tpoVolume, level + 1);
        ArrayResize(g_priceLevels, level + 1);
        g_levelCount = level + 1;
    }

    // Determine the current letter (A=0, B=1, etc.)
    int periodIndex = (dt.hour * 60 + dt.min) / TPO_LetterMinutes;
    char letter = (char)('A' + (periodIndex % 26));

    // Append the letter if it's not already there
    if(StringFind(g_tpoLetters[level], ShortToString(letter)) == -1)
        g_tpoLetters[level] += ShortToString(letter);

    // Accumulate volume
    g_tpoVolume[level] += volume;
}

That's the heart of it. The StringFind check prevents duplicate letters in the same price bucket — you only need one 'C' per price level, not six. The periodIndex % 26 handles sessions that span more than 26 periods; after Z, it wraps to A again, which is why you'll sometimes see multiple A's in a single day's profile.

For rendering, I use OBJ_LABEL objects positioned at each price level, or better yet, a custom drawing via the OnChartEvent handler. The cleanest approach for a TPO indicator MQL5 is to use OBJ_RECTANGLE_LABEL for each letter cell, colored by letter value. It's more objects on the chart, but it gives you the classic TPO look.

Interpreting the Letters: From Raw Data to Trading Decisions

Once you've got the TPO indicator running, the hard part begins: reading it. Here's where most traders get lost. They see a wall of letters and don't know what to do with it.

Start with the shape of the profile. A normal, balanced day looks like a bell curve — wide in the middle (lots of time at mid-prices), tapering at the extremes. That's a normal distribution, and it tells you the market is comfortable. Both buyers and sellers got what they wanted, and price is in equilibrium.

Now look for P-shapes and b-shapes. A P-shape has a long tail on one side — price spent a long time at the high or low, then rotated away. This often marks the start of a trend. The long tail is the market "testing" a level and failing, then moving decisively away. That's your signal to trade in the direction of the rotation.

Single prints are the most underrated signal in Market Profile trading. A single print at a price level means price was there for exactly one period and never returned. In a healthy market, price revisits levels. When it doesn't, that level becomes a magnet — price often returns to fill that "gap" in the profile. I've traded single-print retracements for years; they're the closest thing to a high-probability setup TPO offers.

The initial balance is your opening range. For the first two letters (typically the first hour), mark the high and low. If price breaks out of the IB with conviction — meaning it stays outside for more than a few minutes — you have a directional day. If it stays inside the IB for hours, expect a range day. This alone is worth more than most lagging indicators you're currently using.

Session Breakdown: Why One Profile Isn't Enough

Here's a subtlety most TPO implementations miss: not all sessions are created equal. The Asian session on EURUSD behaves completely differently from the London session. If you lump them together, you're averaging two different markets into one meaningless blob.

My TPO indicator MQL5 code handles this with separate profiles per session. You get a London profile, a New York profile, and an Asian profile — each with its own value area and POC. The volume at price MQL5 histogram also breaks down by session, so you can see which session actually moved the volume.

Why does this matter? Because the London POC and the New York POC are often different prices. When London hands off to New York, price frequently rotates to the New York POC before continuing. If you're only looking at a combined daily profile, you miss that rotation entirely — and you'll get stopped out wondering why price "rejected" the daily POC.

Session breakdown also improves your entries. If you know the Asian session built value in a tight 20-pip range, and London breaks out of that range, you have a clean directional signal with a tight stop. That's the kind of precision you can't get from a daily candle.

Pros, Cons, and Honest Limitations

Let me

Want to build your own version?

Recreate similar entry logic, risk rules, and filters in TradingBotMaker—no MQL coding. Start free.

Community

Clap for the article and open comments only when you want to read them.

0 claps0 comments

Related articles