Abstract

The Kinetic Compression Index (KCI) is a custom oscillator designed to detect market exhaustion and localized compression events. By calculating its kinematic metrics internally rather than relying on external standard indicator handles, the KCI reduces overhead and simplifies buffer management for Expert Advisor (EA) integration. This article details the mathematical foundation, system architecture, buffer mapping, and practical integration guides for developers looking to implement this tool in MetaTrader 5.

Introduction

In algorithmic trading, reducing dependency on multiple external indicator handles can streamline an Expert Advisor's architecture. Calling multiple iCustom or standard indicator handles (like moving averages and standard deviations) can sometimes complicate buffer management and initialization processes.

The KCI is engineered to process price and volume data natively within a single file. By identifying rapid deceleration in price movement combined with volume drops, it aims to pinpoint exhaustion events—points where a local mean-reversion is statistically probable.

Problem Statement

The primary goals of the KCI architecture are to:

  1. Reduce Handle Dependency: Calculate moving averages and deviations natively inside the indicator loop to minimize the use of external handles.

  2. Provide Reproducible Metrics: Expose clean, transparent mathematical metrics (Velocity, Deviation, and Dispersion) to EAs.

  3. Ensure Asynchronous Safety: Build signal buffers that only validate on closed bars, protecting the EA from intrabar repainting and asynchronous tick data delays.

Theory & Mathematical Foundation

KCI is built upon four fundamental metrics. To ensure full reproducibility, the core formulas are defined as follows (where $n$ is the BasePeriod ):

Text Figure :

1. **Velocity Quotient (VQ)** – Measures the rate of price change.
VQ_i=\frac{Close_i-Close_{i-n}}{n}
Where:
- \(Close_i\) = current closing price
- \(Close_{i-n}\) = closing price \(n\) bars ago
- \(n\) = lookback period

2. **Kinetic Displacement (KD)** – Measures the deviation of the current price from its local mean.
KD_i=Close_i-SMA(Close,n)_i
Where:
- \(SMA(Close,n)_i\) = Simple Moving Average of the closing price over the last \(n\) periods.
3. **Energy Dispersion (ED)** – Acts as a dynamic volatility proxy, calculated as the standard deviation of the closing price.
ED_i=\sqrt{\frac{\sum_{j=0}^{n-1}\left(Close_{i-j}-SMA_i\right)^2}{n}}
Where:
- \(SMA_i\) = local Simple Moving Average at bar \(i\).

4. **Phase Velocity (PV)** – Tracks the immediate momentum of the Velocity Quotient.
PV_i=VQ_i-VQ_{i-1}
Where:
- \(VQ_i\) = current Velocity Quotient
- \(VQ_{i-1}\) = previous Velocity Quotient

// Or

1. Velocity Quotient (VQ)
VQ(i) = (Close(i) - Close(i-n)) / n

2. Kinetic Displacement (KD)
KD(i) = Close(i) - SMA(Close, n)

3. Energy Dispersion (ED)
                n-1
               -----
               \
ED(i) = sqrt(  /    (Close(i-j) - SMA(i))² / n )
               -----
               j=0

4. Phase Velocity (PV)
PV(i) = VQ(i) - VQ(i-1)

**

Z-Score Normalization:** Because these four metrics have different scales (e.g., price points vs. pure rates), they are normalized using a rolling Z-Score over a defined ZScorePeriod before being summed into the Raw_KCI .

System Architecture & Unified Buffer Map

To prevent the common "Index Out of Bounds" or mismatched buffer errors during CopyBuffer() calls, the KCI strictly adheres to the following unified buffer map. This mapping is consistent across #property declarations, SetIndexBuffer() , and EA integration.

Buffer Index

Buffer Name

Type

Purpose

0

KCI_Main

INDICATOR_DATA

Visual Polarized Oscillator line

1

KCI_Color

INDICATOR_COLOR_INDEX

Dynamic color mapping for the main line

2

Signal_Buy

INDICATOR_DATA

Buy Signal (1.0 = Buy, 0.0 = Neutral)

3

Signal_Sell

INDICATOR_DATA

Sell Signal (1.0 = Sell, 0.0 = Neutral)

4

Internal_VQ

INDICATOR_CALCULATIONS

Velocity Quotient array

5

Internal_KD

INDICATOR_CALCULATIONS

Kinetic Displacement array

6

Internal_ED

INDICATOR_CALCULATIONS

Energy Dispersion array (used for SL/TP)

7

Internal_PV

INDICATOR_CALCULATIONS

Phase Velocity array

8

Raw_KCI

INDICATOR_CALCULATIONS

Sum of normalized matrices

Code Walkthrough: EA Integration & Risk Management

When calling KCI from an EA, the code must handle the indicator's handle lifecycle, verify that CopyBuffer successfully retrieved the data, and normalize all price calculations for Stop Loss (SL) and Take Profit (TP) according to the symbol's digits.

Here is the corrected, robust EA integration template:

#include <Trade\Trade.mqh>
CTrade trade;

int handle_kci;
datetime last_bar_time = 0;
double buf_buy[], buf_sell[], buf_ed[];

int OnInit()
{
    // Initialize KCI Handle
    handle_kci = iCustom(_Symbol, _Period, "KCI", 50, 2.0, 14);
    
    if(handle_kci == INVALID_HANDLE) 
    {
        Print("Failed to create KCI handle!");
        return(INIT_FAILED);
    }
    
    ArraySetAsSeries(buf_buy, true);
    ArraySetAsSeries(buf_sell, true);
    ArraySetAsSeries(buf_ed, true);
    
    return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
    if(handle_kci != INVALID_HANDLE)
        IndicatorRelease(handle_kci);
}

void OnTick()
{
    datetime current_time = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
    if(current_time == last_bar_time) return; 
    
    // Basic filter: only one position per symbol
    if(PositionsTotal() > 0) 
    {
        for(int i = PositionsTotal() - 1; i >= 0; i--)
        {
            if(PositionGetSymbol(i) == _Symbol) return; 
        }
    }

    // Retrieve Data (Buffer 2 = Buy, Buffer 3 = Sell, Buffer 6 = ED)
    if(CopyBuffer(handle_kci, 2, 1, 1, buf_buy) <= 0) return;
    if(CopyBuffer(handle_kci, 3, 1, 1, buf_sell) <= 0) return;
    if(CopyBuffer(handle_kci, 6, 1, 1, buf_ed) <= 0) return;

    // Lock the time ONLY after data is successfully copied
    last_bar_time = current_time;

    double dynamic_volatility = buf_ed[0];
    if(dynamic_volatility == 0) return;

    double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
    int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

    // Execution Logic with Normalized Dynamic Volatility Stop Loss
    if(buf_buy[0] == 1.0)
    {
        double sl = NormalizeDouble(ask - (dynamic_volatility * 1.5), digits);
        double tp = NormalizeDouble(ask + (dynamic_volatility * 3.0), digits);
        trade.Buy(0.01, _Symbol, ask, sl, tp, "KCI Buy");
    }
    
    if(buf_sell[0] == 1.0)
    {
        double sl = NormalizeDouble(bid + (dynamic_volatility * 1.5), digits);
        double tp = NormalizeDouble(bid - (dynamic_volatility * 3.0), digits);
        trade.Sell(0.01, _Symbol, bid, sl, tp, "KCI Sell");
    }
}

Performance & Limitations

To ensure realistic expectations, developers must account for the following structural limitations:

  1. Tick Volume Dependency: For Forex/CFDs, tick_volume represents the frequency of price changes, not actual liquidity. The volumetric compression metric may behave differently on centralized exchanges (Stocks/Futures) where real volume is available.

  2. Mean-Reversion Vulnerability: As a bounded oscillator, KCI aims to catch exhaustion. In a strong, trending market, it may generate consecutive premature reversal signals. It should be paired with a higher-timeframe trend filter (e.g., EMA 200).

  3. Low Timeframe Noise: Using this indicator on M1 or M5 exposes the logic to micro-structure noise. Z-Score normalization requires stable distribution, which is better found on M15 and above.

Conclusion

The Kinetic Compression Index provides a transparent, natively calculated alternative to stacking multiple standard indicators. By exposing both directional signals on closed bars and a dynamic volatility proxy (Energy Dispersion) through a unified buffer map, it serves as a reliable structural component for MQL5 Expert Advisors.

Implementation on Chart : (KCI Custom)

Custom")