Automating Market Entropy Indicator: Trading System Based on Information Theory
Table of Contents
Introduction
We previously introduced the Market Entropy Indicator, which uses Shannon entropy to quantify uncertainty in price action and to classify market behavior into three regimes: trend, transition, and chaotic. While the indicator made these regimes and compression/decompression zones visible, using it profitably required continuous manual monitoring and the simultaneous interpretation of multiple cues (fast/slow entropy crossovers, momentum, regime, compression state). That manual workflow produced two practical failure modes: inconsistent execution (different traders acting differently on the same visual cue) and missed opportunities (transitions from compression to expansion not captured in time).
This article addresses those gaps by formalizing the indicator's logic and delivering a reproducible, testable artifact: an MQL5 Expert Advisor. The EA is designed for systematic traders and MQL5 developers and implements a clear, auditable pipeline—state classification, entropy calculation (fast/slow/base), divergence and momentum, signal rules, trade execution, and visualization. It runs on every tick, executes trades according to explicit, parameterized rules (LotSize, SL/TP, MagicNumber, MinSignalGap, optional reverse-on-opposite-signal), and logs actions for validation. The goal is to convert visual insight into deterministic, verifiable trading behavior so you can compile, run, and validate the strategy in the Strategy Tester and live environments.
Expert Overview and Understanding
The Market Entropy Expert Advisor updates entropy calculations on each market tick using bar-based price data. Each candle is classified as up, down, or flat, forming the basis for entropy computation. From this, the system derives fast and slow entropy values, calculates momentum, and detects compression and decompression zones. All analytical processes run in the background, while the trader interacts only with the resulting signals displayed on the chart.
A buy signal typically occurs when fast entropy crosses above slow entropy, supported by positive momentum and entropy levels that indicate non-chaotic conditions. In addition, buy opportunities may arise from compression breakouts, where entropy transitions from a compressed state into decompression with strengthening momentum. These signals are further validated by divergence thresholds, regime classification, and compression state transitions to improve reliability. When confirmed, the EA executes a buy position and plots a green upward arrow below the corresponding candle.

Sell signals follow the inverse logic. They occur when fast entropy crosses below slow entropy under conditions of elevated entropy or weakening momentum. Additional triggers include transitions into chaotic regimes or the end of decompression phases accompanied by negative momentum. Upon confirmation, the EA executes a sell position and displays a red downward arrow above the relevant candle.

When the reverse-on-opposite-signal feature is enabled, the EA dynamically adapts to changing market conditions. If an opposite signal is detected while a position is open, the system closes the existing trade and immediately opens a new position in the direction of the updated signal. If this feature is disabled, positions are held until either the stop-loss or take-profit level is reached.
Getting Started
//+------------------------------------------------------------------+ //| Auto Entropy IT.mq5 | //| Git, Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "Git, Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/johnhlomohang/" #property version "1.00" #property description "Based on Market Entropy Indicator" #property description "Market Entropy EA – Information Theory Based Automated Trading" #property description "Retains all indicator visualizations except separate histogram window" #property strict #include <Trade/Trade.mqh> //--- Indicator Inputs (original) input int EntropyPeriod = 50; // Base entropy period input int SmoothingPeriod = 10; // Smoothing of entropy line input int MomentumPeriod = 5; // Momentum period input int FastEntropyPeriod = 20; // Fast entropy period for signals input int SlowEntropyPeriod = 100; // Slow entropy period for signals input int PriceStep = 1; // Minimum price change (points) to classify as move input double SignalThreshold = 0.15; // Threshold for signal generation input bool ShowSignals = true; // Draw buy/sell arrows on main chart input int SignalArrowOffset = 15; // Offset in points for arrow placement input bool UseDailyReset = true; // Reset entropy calculation daily input double CompressionZone = 0.30; // Entropy level considered compression zone input double DecompressionZone = 0.50; // Entropy level considered decompression zone input int CompressionBars = 5; // Bars to confirm compression/decompression input int MinSignalGap = 10; // Minimum bars between same signal types //--- EA Trading Inputs input int StopLoss = 100; // Stop Loss (points) input int TakeProfit = 200; // Take Profit (points) input bool ExtOnOppstTrd = true; // Close opposite and reverse on opposite signal input ulong MagicNumber = 202503; // EA Magic Number input double LotSize = 0.1; // Fixed lot size input int MaxRetries = 3; // Max retries for order execution input int RetryDelay = 100; // Delay between retries (ms)
To get started, we will define all the core inputs that control how the Expert Advisor. These inputs cover analysis and trading. We set parameters for entropy calculation, including the base, fast, and slow periods, along with momentum and price sensitivity to classify market movement. We also configure how signals are generated and displayed, such as thresholds, compression and decompression zones, arrow visibility, and spacing between signals to avoid overtrading.
In addition, we include options for daily reset logic to keep calculations relevant. On the trading side, we establish risk management and execution rules by defining stop-loss and take-profit levels, position sizing, and a magic number to track trades. We also allow the system to reverse positions on opposite signals and include retry logic to ensure orders are executed reliably under varying market conditions.
//--- Internal calculation arrays double entropyBuffer[]; double smoothBuffer[]; double momentumBuffer[]; double fastEntropyBuffer[]; double slowEntropyBuffer[]; double divergenceBuffer[]; //--- State arrays int states[]; // 0=flat, 1=up, 2=down int regime[]; // 0=trend, 1=transition, 2=chaotic int compressionState[]; // -1=compressing, 0=neutral, 1=decompressing //--- Daily tracking datetime currentDay; int dailyUp, dailyDown, dailyFlat, dailyBars; //--- Compression tracking double compressionLevel[]; double decompressionLevel[]; //--- Signal tracking to prevent duplicates datetime lastBuySignalTime; datetime lastSellSignalTime; datetime lastCompressionStartTime; datetime lastDecompressionStartTime; int lastBuyBar; int lastSellBar; int lastCompressionBar; int lastDecompressionBar; //--- Current bar signal tracking int lastTradeBar; bool hasTradedThisBar; double lastTradePrice; //--- Chart object prefix string objPrefix; //--- Trading objects CTrade trade; int m_prevCalculated; int m_barsCount; bool m_firstTick; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Prefix for chart objects objPrefix = "MktEntropyEA_" + IntegerToString(ChartID()) + "_"; //--- Initialize tracking variables currentDay = 0; dailyUp = dailyDown = dailyFlat = dailyBars = 0; //--- Initialize signal tracking lastBuySignalTime = 0; lastSellSignalTime = 0; lastCompressionStartTime = 0; lastDecompressionStartTime = 0; lastBuyBar = -MinSignalGap; lastSellBar = -MinSignalGap; lastCompressionBar = -MinSignalGap; lastDecompressionBar = -MinSignalGap; lastTradeBar = -1; hasTradedThisBar = false; lastTradePrice = 0; //--- Trading initialization trade.SetExpertMagicNumber(MagicNumber); trade.SetDeviationInPoints(20); trade.SetTypeFilling(ORDER_FILLING_FOK); m_prevCalculated = 0; m_barsCount = 0; m_firstTick = true; return INIT_SUCCEEDED; }
Here, we begin by defining the internal arrays that store all calculations needed for the system to function. These include buffers for entropy values, smoothed data, momentum, and the fast and slow entropy components used for signal generation. We also track divergence between these values to better understand shifts in market behavior.
Additionally, we maintain state arrays that classify each bar as up, down, or flat, determine the current market regime, and identify whether the market is compressing or decompressing. Supporting variables track daily activity, including counts of directional movements, as well as compression levels to detect local extremes. To ensure clean signal generation, we store timestamps and bar indices of the last signals so we can prevent duplicates and enforce spacing between trades.
We initialize these components in OnInit(). This prepares the EA for execution. We assign a unique prefix for chart objects to keep drawings organized and avoid conflicts. All tracking variables are reset to their default states, ensuring that no residual data interferes with new calculations. Signal timers and bar trackers are initialized in a way that respects the minimum signal gap, while trade-related flags prevent multiple executions on the same bar. We also configure the trading object by setting the magic number, allowed slippage, and order filling type. Finally, we initialize calculation control variables so the EA can efficiently process new data as it arrives, ensuring accurate and consistent operation from the very first tick.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, objPrefix); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Update calculations int rates_total = Bars(_Symbol, _Period); if(rates_total < SlowEntropyPeriod) return; //--- Check if we're on a new bar static int lastBarsCount = 0; if(rates_total != lastBarsCount) { lastBarsCount = rates_total; hasTradedThisBar = false; } //--- Resize all calculation arrays ArrayResize(entropyBuffer, rates_total); ArrayResize(smoothBuffer, rates_total); ArrayResize(momentumBuffer, rates_total); ArrayResize(fastEntropyBuffer, rates_total); ArrayResize(slowEntropyBuffer, rates_total); ArrayResize(divergenceBuffer, rates_total); ArrayResize(states, rates_total); ArrayResize(regime, rates_total); ArrayResize(compressionState, rates_total); ArrayResize(compressionLevel, rates_total); ArrayResize(decompressionLevel, rates_total); //--- Determine starting point for calculation int start = m_prevCalculated; if(m_firstTick) { start = 0; m_firstTick = false; } else { if(rates_total > m_prevCalculated) start = MathMax(0, m_prevCalculated - 3); else start = MathMax(0, rates_total - 3); } //--- Copy price data arrays datetime time[]; double open[], high[], low[], close[]; if(CopyTime(_Symbol, _Period, 0, rates_total, time) < rates_total) return; if(CopyOpen(_Symbol, _Period, 0, rates_total, open) < rates_total) return; if(CopyHigh(_Symbol, _Period, 0, rates_total, high) < rates_total) return; if(CopyLow(_Symbol, _Period, 0, rates_total, low) < rates_total) return; if(CopyClose(_Symbol, _Period, 0, rates_total, close) < rates_total) return; //--- Process bars for(int i = start; i < rates_total; i++) { UpdateDailyReset(i, time); ClassifyPriceMovement(i, close, time); CalculateAllEntropy(i); CalculateDivergenceAndMomentum(i); DetectCompression(i); } m_prevCalculated = rates_total; m_barsCount = rates_total; //--- Check current bar for signals int currentBar = rates_total - 1; if(!hasTradedThisBar && currentBar >= SlowEntropyPeriod) { if(fastEntropyBuffer[currentBar] != EMPTY_VALUE && slowEntropyBuffer[currentBar] != EMPTY_VALUE && entropyBuffer[currentBar] != EMPTY_VALUE && momentumBuffer[currentBar] != EMPTY_VALUE) { bool buySig = IsBuySignal(currentBar); bool sellSig = IsSellSignal(currentBar); //--- Draw arrows first if(ShowSignals) { DrawCompressionArrows(currentBar, time, high, low); DrawTradingSignal(currentBar, time, high, low); } //--- Check for trades if(buySig || sellSig) { Print("=== Signal Detected ==="); //--- Check existing position bool hasBuy = false, hasSell = false; ulong ticketBuy = 0, ticketSell = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && PositionGetString(POSITION_SYMBOL) == _Symbol) { if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { hasBuy = true; ticketBuy = ticket; } else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { hasSell = true; ticketSell = ticket; } } } } Print("Current Position - Has Buy: ", hasBuy, " | Has Sell: ", hasSell); //--- Execute trades if(!hasBuy && !hasSell) { if(buySig && (currentBar - lastBuyBar >= MinSignalGap)) { Print(">>> Executing BUY trade <<<"); ExecuteTrade(ORDER_TYPE_BUY); lastBuyBar = currentBar; hasTradedThisBar = true; } else if(sellSig && (currentBar - lastSellBar >= MinSignalGap)) { Print(">>> Executing SELL trade <<<"); ExecuteTrade(ORDER_TYPE_SELL); lastSellBar = currentBar; hasTradedThisBar = true; } } else if(ExtOnOppstTrd) { if(hasBuy && sellSig) { Print(">>> Reversing: Close BUY, Open SELL <<<"); if(PositionSelectByTicket(ticketBuy)) { if(trade.PositionClose(ticketBuy)) { Sleep(RetryDelay); ExecuteTrade(ORDER_TYPE_SELL); lastSellBar = currentBar; hasTradedThisBar = true; } } } else if(hasSell && buySig) { Print(">>> Reversing: Close SELL, Open BUY <<<"); if(PositionSelectByTicket(ticketSell)) { if(trade.PositionClose(ticketSell)) { Sleep(RetryDelay); ExecuteTrade(ORDER_TYPE_BUY); lastBuyBar = currentBar; hasTradedThisBar = true; } } } } Print("========================"); } } } //--- Update label UpdateRegimeLabel(time, rates_total); }
The deinitialization function is responsible for cleanup, while the OnTick function handles the core execution cycle of the Expert Advisor. When the EA is removed, all chart objects created with the defined prefix are deleted to keep the chart clean and avoid leftover visuals. During runtime, OnTick first checks whether enough bars are available for reliable entropy calculations. It then updates the market data. It detects the formation of a new bar to reset trading permissions for that candle, then resizes all internal arrays to match the current number of bars. To improve efficiency, calculations do not restart from scratch on every tick; instead, only the most recent bars are recalculated. Price data is copied into local arrays, and each bar is processed sequentially to update daily resets, classify price movement, compute entropy values, derive momentum and divergence, and detect compression states.
Once the calculations are complete, the system evaluates the latest bar for valid trading signals. If all required data is available and no trade has been executed on the current bar, the EA checks for buy or sell conditions. Visual signals are drawn first to maintain consistency between chart output and execution. The EA then scans existing positions linked to its magic number and symbol, ensuring it understands the current exposure. If no position exists, it opens a new trade based on the detected signal while respecting the minimum signal gap. If a position is already open and reverse-on-opposite-signal is enabled, the EA closes the existing trade. It then opens a new trade in the opposite direction. Throughout this process, flags are updated to prevent duplicate trades within the same bar, and detailed logs are printed for transparency. Finally, we update the regime label on the chart to reflect the latest market condition.
//+------------------------------------------------------------------+ //| Execute Trade | //+------------------------------------------------------------------+ void ExecuteTrade(ENUM_ORDER_TYPE tradeType) { double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- Calculate stop loss double sl = (tradeType == ORDER_TYPE_BUY) ? price - StopLoss * _Point : price + StopLoss * _Point; //--- Calculate take profit double tp = (tradeType == ORDER_TYPE_BUY) ? price + TakeProfit * _Point : price - TakeProfit * _Point; //--- Normalize prices price = NormalizeDouble(price, _Digits); if(StopLoss > 0) sl = NormalizeDouble(sl, _Digits); if(TakeProfit > 0) tp = NormalizeDouble(tp, _Digits); //--- Use fixed lot size double volume = LotSize; //--- Execute trade with retries string comment = StringFormat("Entropy_%s", (tradeType == ORDER_TYPE_BUY) ? "BUY" : "SELL"); bool success = false; for(int retry = 0; retry < MaxRetries; retry++) { success = trade.PositionOpen(_Symbol, tradeType, volume, price, (StopLoss > 0) ? sl : 0, (TakeProfit > 0) ? tp : 0, comment); if(success) { lastTradePrice = price; Print(StringFormat("Trade Opened: %s | Price: %.5f | SL: %.5f | TP: %.5f | Lots: %.2f", (tradeType == ORDER_TYPE_BUY) ? "BUY" : "SELL", price, (StopLoss > 0) ? sl : 0, (TakeProfit > 0) ? tp : 0, volume)); break; } else { Print(StringFormat("Trade failed (attempt %d/%d): %s", retry+1, MaxRetries, trade.ResultRetcodeDescription())); if(retry < MaxRetries - 1) { Sleep(RetryDelay); // Refresh price for retry price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); price = NormalizeDouble(price, _Digits); if(StopLoss > 0) sl = (tradeType == ORDER_TYPE_BUY) ? price - StopLoss * _Point : price + StopLoss * _Point; if(TakeProfit > 0) tp = (tradeType == ORDER_TYPE_BUY) ? price + TakeProfit * _Point : price - TakeProfit * _Point; } } } }
This function handles trade execution. It selects the entry price based on order type: ask for buys and bid for sells. We then calculate the stop-loss and take-profit levels relative to that price and normalize all values to match the symbol’s precision. A fixed lot size is applied, and we attempt to execute the trade using a retry mechanism to improve reliability in case of temporary failures. If the order is successful, we store the entry price and print a detailed log showing the trade type, price, stop-loss, take-profit, and volume. If the order fails, we log the error, wait briefly, refresh the market price, recalculate the levels, and retry until the maximum number of attempts is reached.
//+------------------------------------------------------------------+ //| Check buy signal conditions | //+------------------------------------------------------------------+ bool IsBuySignal(int i) { if(i < 1) return false; //--- Crossover condition (primary) bool crossUp = (fastEntropyBuffer[i] > slowEntropyBuffer[i] && fastEntropyBuffer[i-1] <= slowEntropyBuffer[i-1]); //--- Entropy not too chaotic bool entropyOk = (entropyBuffer[i] < 0.70); // Relaxed from 0.65 //--- Positive momentum bool momentumOk = (momentumBuffer[i] > 0); //--- Divergence not too negative bool divergenceOk = (divergenceBuffer[i] > -SignalThreshold * 1.5); // Relaxed //--- Compression breakout bool compressionBreakout = (compressionState[i] == 1 && entropyBuffer[i] > 0.20 && entropyBuffer[i] < 0.50); // Relaxed range //--- Decompression end bool decompressionEnd = (compressionState[i-1] == -1 && compressionState[i] != -1 && momentumBuffer[i] > 0); //--- Simplified signal logic bool signal = crossUp || compressionBreakout || decompressionEnd; //--- Additional confirmation (optional) if(crossUp) signal = signal && entropyOk; // Only require entropy check for crossovers return signal; } //+------------------------------------------------------------------+ //| Check sell signal conditions | //+------------------------------------------------------------------+ bool IsSellSignal(int i) { if(i < 1) return false; //--- Crossover condition (primary) bool crossDown = (fastEntropyBuffer[i] < slowEntropyBuffer[i] && fastEntropyBuffer[i-1] >= slowEntropyBuffer[i-1]); //--- Entropy elevated bool entropyOk = (entropyBuffer[i] > 0.50); // Relaxed from 0.55 //--- Negative momentum bool momentumOk = (momentumBuffer[i] < 0); //--- Divergence negative bool divergenceOk = (divergenceBuffer[i] < SignalThreshold); //--- Chaotic regime entry bool chaoticEntry = (regime[i] == 2 && regime[i-1] != 2); //--- Strong negative divergence bool strongDivergence = (divergenceBuffer[i] < -SignalThreshold && MathAbs(divergenceBuffer[i]) > MathAbs(divergenceBuffer[i-1])); //--- Compression end (sell) bool compressionEnd = (compressionState[i-1] == 1 && compressionState[i] != 1 && momentumBuffer[i] < 0); //--- Simplified signal logic bool signal = crossDown || chaoticEntry || strongDivergence || compressionEnd; //--- Additional confirmation (optional) if(crossDown) signal = signal && entropyOk; // Only require entropy check for crossovers return signal; }
The buy signal logic evaluates multiple conditions to identify emerging bullish momentum. It checks for a fast/slow entropy crossover and then confirms it with entropy and momentum filters. It also supports compression breakout and decompression-end triggers. We also account for structural opportunities such as compression breakouts. These include compression breakouts, where the market transitions from a low-entropy state into expansion, and decompression endings, where a prior contraction phase resolves with upward pressure. The overall signal is simplified by allowing any of these key events to trigger a buy condition. While crossover signals receive an additional entropy filter to improve their quality.
The sell signal logic mirrors this structure but focuses on bearish conditions. We detect a primary crossover where fast entropy falls below slow entropy, signaling potential downside movement, and confirm that entropy levels reflect sufficient market activity. Negative momentum and divergence further support the bearish bias, while regime transitions into chaotic states can also act as standalone triggers for sell signals. Additional conditions such as strong negative divergence and the end of a decompression phase help capture reversal or continuation moves. Similar to the buy logic, the system combines these conditions into a simplified signal framework. This applies stricter confirmation only when the signal originates from a crossover event.
//+------------------------------------------------------------------+ //| Update daily reset and manage signal timers | //+------------------------------------------------------------------+ void UpdateDailyReset(int i, const datetime &time[]) { if(!UseDailyReset) return; MqlDateTime dt; TimeToStruct(time[i], dt); datetime barDate = dt.year * 10000 + dt.mon * 100 + dt.day; if(barDate != currentDay) { currentDay = barDate; dailyUp = dailyDown = dailyFlat = dailyBars = 0; if(ShowSignals) { ObjectsDeleteAll(0, objPrefix); ResetSignalTimers(); } } } //+------------------------------------------------------------------+ //| Reset all signal timers | //+------------------------------------------------------------------+ void ResetSignalTimers() { lastBuySignalTime = 0; lastSellSignalTime = 0; lastCompressionStartTime = 0; lastDecompressionStartTime = 0; lastBuyBar = -MinSignalGap; lastSellBar = -MinSignalGap; lastCompressionBar = -MinSignalGap; lastDecompressionBar = -MinSignalGap; lastTradeBar = -1; hasTradedThisBar = false;
This section manages daily state tracking and ensures that the system remains aligned with a fresh trading session. We check whether daily reset functionality is enabled, and if so, we extract the current bar’s date from its timestamp. When a new day is detected, we update the stored day reference and reset all daily counters. They track upward, downward, and flat movements, along with the total number of bars processed. This ensures that statistical measurements remain relevant and do not carry over outdated information from previous trading sessions. If signal visualization is enabled, all chart objects are cleared to avoid visual clutter, and the system prepares for a clean restart of signal tracking.
We also reset all signal timers to ensure proper spacing and prevent duplicate or outdated signals from influencing new trading decisions. This includes clearing timestamps for buy, sell, compression, and decompression signals, as well as resetting bar-based counters used to enforce minimum gaps between trades. In addition, we reset trade tracking flags so that the system can safely evaluate new opportunities without interference from previous state memory. This combination of daily reset and timer management ensures that the EA operates with consistent logic across trading sessions.
//+------------------------------------------------------------------+ //| Classify price movement into states (up/down/flat) | //+------------------------------------------------------------------+ void ClassifyPriceMovement(int i, const double &close[], const datetime &time[]) { if(i == 0) { states[i] = 0; return; } double change = close[i] - close[i-1]; double threshold = PriceStep * _Point; if(change > threshold) states[i] = 1; // up else if(change < -threshold) states[i] = 2; // down else states[i] = 0; // flat if(UseDailyReset && time[i] >= time[i-1]) { if(states[i] == 1) dailyUp++; else if(states[i] == 2) dailyDown++; else dailyFlat++; dailyBars++; } } //+------------------------------------------------------------------+ //| Calculate entropy for a given period | //+------------------------------------------------------------------+ double CalculateEntropy(int startIdx, int period, const int &statesArray[]) { int up = 0, down = 0, flat = 0; for(int j = startIdx - period; j < startIdx; j++) { if(j >= 0) { if(statesArray[j] == 1) up++; else if(statesArray[j] == 2) down++; else flat++; } } int total = up + down + flat; if(total == 0) return 0; double p_up = (double)up / total; double p_down = (double)down / total; double p_flat = (double)flat / total; double entropy = 0.0; if(p_up > 0) entropy -= p_up * MathLog(p_up) / M_LN2; if(p_down > 0) entropy -= p_down * MathLog(p_down) / M_LN2; if(p_flat > 0) entropy -= p_flat * MathLog(p_flat) / M_LN2; return entropy / (MathLog(3) / M_LN2); // Normalized entropy } //+------------------------------------------------------------------+ //| Calculate fast, slow, and base entropy | //+------------------------------------------------------------------+ void CalculateAllEntropy(int i) { //--- Fast entropy if(i >= FastEntropyPeriod) fastEntropyBuffer[i] = CalculateEntropy(i, FastEntropyPeriod, states); else fastEntropyBuffer[i] = EMPTY_VALUE; //--- Slow entropy if(i >= SlowEntropyPeriod) slowEntropyBuffer[i] = CalculateEntropy(i, SlowEntropyPeriod, states); else slowEntropyBuffer[i] = EMPTY_VALUE; //--- Base entropy if(i >= EntropyPeriod) { entropyBuffer[i] = CalculateEntropy(i, EntropyPeriod, states); ClassifyRegime(i); } else { entropyBuffer[i] = EMPTY_VALUE; regime[i] = -1; } }
This section converts raw price data into a structured format that can be used for entropy analysis. We classify each bar based on the change in closing price relative to the previous bar, using a minimum threshold to filter out insignificant movements. If the change exceeds the threshold, the bar is marked as either upward or downward; otherwise, it is considered flat. This discrete classification simplifies continuous price movement into a sequence of states, which is essential for probability-based calculations. When daily reset tracking is enabled, we also accumulate counts of upward, downward, and flat movements, allowing us to monitor how market behavior evolves within each trading day.
With these states defined, we then calculate entropy over a specified period by measuring the probability distribution of up, down, and flat occurrences. We count how often each state appears within the lookback window, convert these counts into probabilities, and then apply the entropy formula to quantify the level of randomness or disorder in the market. The result is normalized to ensure values remain within a consistent range, making interpretation easier. Low entropy reflects structured, trending conditions, while high entropy indicates randomness or chaotic behavior.
Finally, we compute multiple layers of entropy to capture both short-term and long-term dynamics. Fast entropy reacts quickly to recent changes, while slow entropy provides a broader view of market structure. A base entropy is also calculated and used to classify the current market regime. If there is not enough data for a given period, the corresponding values are marked as empty to avoid invalid signals. This multi-layered approach allows the system to detect shifts in market behavior with both sensitivity and stability.
//+------------------------------------------------------------------+ //| Classify market regime based on entropy value | //+------------------------------------------------------------------+ void ClassifyRegime(int i) { if(entropyBuffer[i] < 0.35) regime[i] = 0; // trend else if(entropyBuffer[i] < 0.65) regime[i] = 1; // transition else regime[i] = 2; // chaotic } //+------------------------------------------------------------------+ //| Calculate divergence and momentum | //+------------------------------------------------------------------+ void CalculateDivergenceAndMomentum(int i) { //--- Divergence (fast - slow) if(fastEntropyBuffer[i] != EMPTY_VALUE && slowEntropyBuffer[i] != EMPTY_VALUE) divergenceBuffer[i] = fastEntropyBuffer[i] - slowEntropyBuffer[i]; else divergenceBuffer[i] = EMPTY_VALUE; //--- Momentum if(i >= EntropyPeriod + MomentumPeriod && entropyBuffer[i] != EMPTY_VALUE && entropyBuffer[i - MomentumPeriod] != EMPTY_VALUE) momentumBuffer[i] = entropyBuffer[i] - entropyBuffer[i - MomentumPeriod]; else momentumBuffer[i] = EMPTY_VALUE; } //+------------------------------------------------------------------+ //| Detect compression/decompression states | //+------------------------------------------------------------------+ void DetectCompression(int i) { if(entropyBuffer[i] == EMPTY_VALUE) return; //--- Track levels compressionLevel[i] = entropyBuffer[i]; decompressionLevel[i] = entropyBuffer[i]; //--- Find local minima for compression if(i >= CompressionBars) { compressionLevel[i] = entropyBuffer[i]; for(int j = i - CompressionBars; j <= i; j++) { if(entropyBuffer[j] != EMPTY_VALUE && entropyBuffer[j] < compressionLevel[i]) compressionLevel[i] = entropyBuffer[j]; } } //--- Find local maxima for decompression if(i >= CompressionBars) { decompressionLevel[i] = entropyBuffer[i]; for(int j = i - CompressionBars; j <= i; j++) { if(entropyBuffer[j] != EMPTY_VALUE && entropyBuffer[j] > decompressionLevel[i]) decompressionLevel[i] = entropyBuffer[j]; } } //--- Determine state compressionState[i] = 0; // neutral if(i > 0 && entropyBuffer[i-1] != EMPTY_VALUE) { if(entropyBuffer[i] < entropyBuffer[i-1] && entropyBuffer[i] < CompressionZone) compressionState[i] = -1; // compressing else if(entropyBuffer[i] > entropyBuffer[i-1] && entropyBuffer[i] > DecompressionZone) compressionState[i] = 1; // decompressing } }
In this section, we translate entropy values into meaningful market structure by classifying each bar into a regime. We compare the current entropy level against predefined thresholds to determine whether the market is trending, transitioning, or chaotic. Low entropy reflects ordered price behavior and is labeled as a trend, while moderate entropy indicates a transition phase, and high entropy signals a chaotic environment. This classification provides a high-level view of market conditions, allowing the system to adapt its expectations depending on whether price action is structured or random.
We then measure how entropy is evolving through divergence and momentum calculations. Divergence is computed as the difference between fast and slow entropy, which gives us insight into short-term versus long-term shifts in market behavior. A widening gap suggests acceleration in a particular direction. Conversely, a narrowing gap may indicate weakening conditions. Momentum is calculated by comparing current entropy to its value several periods back, which helps us detect whether uncertainty is increasing or decreasing over time. These two components together provide a dynamic view of how the market is changing beneath the surface.
Here, we detect compression and decompression phases by analyzing how entropy behaves relative to recent extremes. We track local minima and maxima over a defined number of bars to identify periods where entropy is contracting or expanding. When entropy decreases below a certain threshold and continues falling, we classify the market as compressing, indicating consolidation. When entropy rises above a higher threshold and continues increasing, we mark it as decompressing, signaling expansion or potential breakout conditions. If neither condition is met, the market remains in a neutral state.
//+------------------------------------------------------------------+ //| Update regime label | //+------------------------------------------------------------------+ void UpdateRegimeLabel(const datetime &time[], int rates_total) { string labelText = ""; color labelColor = clrNONE; double lastEntropy = entropyBuffer[rates_total-1]; double lastFast = fastEntropyBuffer[rates_total-1]; double lastSlow = slowEntropyBuffer[rates_total-1]; double lastDiv = divergenceBuffer[rates_total-1]; int lastCompress = (rates_total-1 >= 0) ? compressionState[rates_total-1] : 0; if(lastEntropy != EMPTY_VALUE) { if(lastEntropy < 0.35) labelText = "TREND MODE (" + DoubleToString(lastEntropy, 2) + ")"; else if(lastEntropy < 0.65) labelText = "TRANSITION MODE (" + DoubleToString(lastEntropy, 2) + ")"; else labelText = "CHAOTIC MODE (" + DoubleToString(lastEntropy, 2) + ")"; if(lastCompress == -1) labelText += " | COMPRESSING"; else if(lastCompress == 1) labelText += " | DECOMPRESSING"; if(lastDiv != EMPTY_VALUE) { labelText += " | Div: " + DoubleToString(lastDiv, 2); if(lastFast != EMPTY_VALUE && lastSlow != EMPTY_VALUE) { if(lastFast > lastSlow) labelText += " (Bull)"; else labelText += " (Bear)"; } } labelColor = (lastEntropy < 0.35) ? clrGreen : (lastEntropy < 0.65) ? clrYellow : clrRed; } else { labelText = "MODE: WAITING FOR DATA"; labelColor = clrGray; } ObjectCreate(0, objPrefix + "regimeLabel", OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, objPrefix + "regimeLabel", OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, objPrefix + "regimeLabel", OBJPROP_YDISTANCE, 10); ObjectSetInteger(0, objPrefix + "regimeLabel", OBJPROP_CORNER, 0); ObjectSetString(0, objPrefix + "regimeLabel", OBJPROP_TEXT, labelText); ObjectSetInteger(0, objPrefix + "regimeLabel", OBJPROP_COLOR, labelColor); ObjectSetInteger(0, objPrefix + "regimeLabel", OBJPROP_FONTSIZE, 11); ObjectSetInteger(0, objPrefix + "regimeLabel", OBJPROP_BACK, false); }
The UpdateRegimeLabel function updates a chart label that provides a real-time summary of the current market regime based on the latest entropy and derived metrics. We retrieve the most recent values of entropy, fast and slow entropy, divergence, and compression state, which then classify the market into trend, transition, or chaotic conditions using predefined entropy thresholds. The label is enriched with additional context, such as whether the market is compressing or decompressing. Also, a simple bullish or bearish bias is derived from the relationship between fast and slow entropy. We then assign a color to the label to visually represent the regime strength. Finally, we create or update the chart object so that this information is continuously displayed in the top-left corner of the chart for quick interpretation.
//+------------------------------------------------------------------+ //| Draw compression/decompression circles | //+------------------------------------------------------------------+ void DrawCompressionArrows(int i, const datetime &time[], const double &high[], const double &low[]) { if(!ShowSignals || i == 0 || compressionState[i] == 0) return; //--- Compression start if(compressionState[i] == -1 && compressionState[i-1] != -1) { if(i - lastCompressionBar >= MinSignalGap && (lastCompressionStartTime == 0 || time[i] - lastCompressionStartTime > MinSignalGap * PeriodSeconds())) { double arrowY = low[i] - SignalArrowOffset * _Point * 2; string objName = objPrefix + "compress_" + IntegerToString(i); ObjectDelete(0, objName); if(ObjectCreate(0, objName, OBJ_ARROW, 0, time[i], arrowY)) { ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, 108); ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBlue); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, objName, OBJPROP_BACK, false); ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, objName, OBJPROP_HIDDEN, true); lastCompressionStartTime = time[i]; lastCompressionBar = i; } } } //--- Decompression start else if(compressionState[i] == 1 && compressionState[i-1] != 1) { if(i - lastDecompressionBar >= MinSignalGap && (lastDecompressionStartTime == 0 || time[i] - lastDecompressionStartTime > MinSignalGap * PeriodSeconds())) { double arrowY = high[i] + SignalArrowOffset * _Point * 2; string objName = objPrefix + "decompress_" + IntegerToString(i); ObjectDelete(0, objName); if(ObjectCreate(0, objName, OBJ_ARROW, 0, time[i], arrowY)) { ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, 108); ObjectSetInteger(0, objName, OBJPROP_COLOR, clrOrange); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, objName, OBJPROP_BACK, false); ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, objName, OBJPROP_HIDDEN, true); lastDecompressionStartTime = time[i]; lastDecompressionBar = i; } } } } //+------------------------------------------------------------------+ //| Draw buy/sell arrows | //+------------------------------------------------------------------+ void DrawTradingSignal(int i, const datetime &time[], const double &high[], const double &low[]) { if(!ShowSignals || i < SlowEntropyPeriod || fastEntropyBuffer[i] == EMPTY_VALUE || slowEntropyBuffer[i] == EMPTY_VALUE || entropyBuffer[i] == EMPTY_VALUE || momentumBuffer[i] == EMPTY_VALUE) return; bool buySig = IsBuySignal(i); bool sellSig = IsSellSignal(i); //--- Buy signal arrow if(buySig) { if(i - lastBuyBar >= MinSignalGap && (lastBuySignalTime == 0 || time[i] - lastBuySignalTime > MinSignalGap * PeriodSeconds())) { double arrowY = low[i] - SignalArrowOffset * _Point; string objName = objPrefix + "buy_" + IntegerToString(i) + "_" + IntegerToString((int)time[i]); ObjectDelete(0, objName); if(ObjectCreate(0, objName, OBJ_ARROW, 0, time[i], arrowY)) { ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, 217); ObjectSetInteger(0, objName, OBJPROP_COLOR, clrLime); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 3); ObjectSetInteger(0, objName, OBJPROP_BACK, false); ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, objName, OBJPROP_HIDDEN, true); ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP); lastBuySignalTime = time[i]; } } } //--- Sell signal arrow if(sellSig) { if(i - lastSellBar >= MinSignalGap && (lastSellSignalTime == 0 || time[i] - lastSellSignalTime > MinSignalGap * PeriodSeconds())) { double arrowY = high[i] + SignalArrowOffset * _Point; string objName = objPrefix + "sell_" + IntegerToString(i) + "_" + IntegerToString((int)time[i]); ObjectDelete(0, objName); if(ObjectCreate(0, objName, OBJ_ARROW, 0, time[i], arrowY)) { ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, 218); ObjectSetInteger(0, objName, OBJPROP_COLOR, clrRed); ObjectSetInteger(0, objName, OBJPROP_WIDTH, 3); ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM); ObjectSetInteger(0, objName, OBJPROP_BACK, false); ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, objName, OBJPROP_HIDDEN, true); lastSellSignalTime = time[i]; } } } } //+------------------------------------------------------------------+
This section handles all visual signal rendering on the chart. It draws compression and decompression markers, as well as buy and sell arrows, based on the current market state. For compression and decompression detection, we monitor changes in the compression state. A visual marker is only triggered when a transition occurs. This ensures we capture the start of a new phase instead of repeating signals. We also apply spacing rules using minimum bar and time gaps. This prevents overcrowding on the chart. When a valid transition is detected, we calculate the correct position above or below the candle. We then draw a labeled arrow with specific styling. Compression is shown in blue, and decompression is shown in orange. Internal tracking variables are also updated to maintain proper signal spacing.
For trading signals, we first confirm that all required entropy and momentum data is available. We then evaluate whether a buy or sell condition is met using the signal logic functions. If a valid signal is detected, we again enforce minimum spacing rules. This prevents duplicate arrows on nearby bars or timeframes. Buy signals are drawn below the candle using a green upward arrow. Sell signals are drawn above the candle using a red downward arrow. Each object is given a unique name using the bar index and timestamp. This avoids overlap between chart objects. After drawing, we update internal tracking variables. This ensures consistent timing and prevents repeated visual clutter on the chart.
Backtest
The backtest was conducted over roughly a 2-month testing window from 01 January 2026 to 28 February 2026, with the following settings:

Below are the equity curve and backtest results:


Conclusion
We converted the Market Entropy Indicator from a visual research tool into an executable trading system with formally defined rules and checkable behavior. The result is a compilable MQL5 Expert Advisor that:
- updates entropy (fast/slow/base), momentum, divergence, and compression state on every tick;
- formulates buy/sell signals from explicit, reproducible conditions (crossovers, compression/decompression transitions, divergence/momentum filters);
- opens and manages trades with configurable LotSize, StopLoss, TakeProfit and MagicNumber;
- optionally reverses positions immediately on opposite confirmed signals;
- renders regime labels and signal arrows for quick, visual validation while preserving all logs and state for backtesting.
This implementation closes the gap between “seeing” entropy-driven patterns and “capturing” them consistently: it is auditable (logs and chart objects), testable (Strategy Tester-ready), and measurable (checks for MinSignalGap, reversal behavior, SL/TP application, and execution success via retries). Recommended next steps are systematic parameter optimization, cross-symbol/timeframe robustness testing, enhanced risk/money management, and integration into a portfolio-level execution framework. The EA provides a solid, verifiable foundation to iterate from information-theoretic insight to repeatable trading performance.
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Features of Custom Indicators Creation
MQL5 Trading Tools (Part 27): Rendering Parametric Butterfly Curve on Canvas
Features of Experts Advisors
Foundation Models in Trading: Time Series Forecasting with Google's TimesFM 2.5 in MetaTrader 5
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use