The 3 Market States (Clusters)
Blue (State 0): Low Volatility & Choppy Momentum. (Ideal for mean-reversion and range scalping).
Green (State 1): Stable Volatility & Directional Momentum. (Ideal for trend-following entries).
Red (State 2): High Volatility Anomalies. (News spikes, liquidity sweeps—stay out or trade the breakout).
The height of the histogram reflects the raw ATR (to give you a visual scale of the price action size), while the color dictates the exact machine-calculated market state.
Drop the .mq4 file into your Indicators folder and compile. Let me know how it filters your current strategies!
Code: Select all
//+------------------------------------------------------------------+
//| KMeans_Market_State.mq4 |
//| forex-scalping.com |
//+------------------------------------------------------------------+
#property copyright "forex-scalping.com"
#property link "https://forex-scalping.com"
#property version "1.00"
#property strict
#property indicator_separate_window
#property indicator_buffers 4
#property indicator_color1 clrNONE
#property indicator_color2 clrDodgerBlue // State 0: Range
#property indicator_color3 clrMediumSeaGreen // State 1: Trend
#property indicator_color4 clrOrangeRed // State 2: High Volatility
//--- Inputs
input int LookbackBars = 1000; // Training Data Size
input int RSI_Period = 14;
input int ATR_Period = 14;
input int K_Clusters = 3; // Number of States
input int Max_Iterations = 50; // ML Convergence Iterations
//--- Buffers
double DummyBuffer[];
double State0Buffer[];
double State1Buffer[];
double State2Buffer[];
//+------------------------------------------------------------------+
//| K-Means Class Definition |
//+------------------------------------------------------------------+
class CKMeans {
private:
int m_k;
int m_iters;
double m_centroids[][2];
public:
CKMeans(int k, int iters) {
m_k = k;
m_iters = iters;
ArrayResize(m_centroids, m_k);
}
double EuclideanDistance(double x1, double y1, double x2, double y2) {
return MathSqrt(MathPow(x1 - x2, 2) + MathPow(y1 - y2, 2));
}
void Train(double &data[][2]) {
int n = ArrayRange(data, 0);
if(n < m_k) return;
// Random initialization of centroids
MathSrand(GetTickCount());
for(int i = 0; i < m_k; i++) {
int rand_idx = MathRand() % n;
m_centroids[i][0] = data[rand_idx][0];
m_centroids[i][1] = data[rand_idx][1];
}
int assignments[];
ArrayResize(assignments, n);
for(int iter = 0; iter < m_iters; iter++) {
bool changed = false;
// Assignment step
for(int i = 0; i < n; i++) {
double min_dist = -1.0;
int best_cluster = 0;
for(int c = 0; c < m_k; c++) {
double dist = EuclideanDistance(data[i][0], data[i][1], m_centroids[c][0], m_centroids[c][1]);
if(min_dist < 0 || dist < min_dist) {
min_dist = dist;
best_cluster = c;
}
}
if(assignments[i] != best_cluster) {
assignments[i] = best_cluster;
changed = true;
}
}
if(!changed) break; // Reached convergence
// Update step
double sums[][2];
int counts[];
ArrayResize(sums, m_k);
ArrayResize(counts, m_k);
ArrayInitialize(sums, 0.0);
ArrayInitialize(counts, 0);
for(int i = 0; i < n; i++) {
int c = assignments[i];
sums[c][0] += data[i][0];
sums[c][1] += data[i][1];
counts[c]++;
}
for(int c = 0; c < m_k; c++) {
if(counts[c] > 0) {
m_centroids[c][0] = sums[c][0] / counts[c];
m_centroids[c][1] = sums[c][1] / counts[c];
}
}
}
SortCentroids();
}
// Sort centroids by Volatility (Feature 1) to maintain consistent colors on chart reload
void SortCentroids() {
for(int i = 0; i < m_k - 1; i++) {
for(int j = 0; j < m_k - i - 1; j++) {
if(m_centroids[j][1] > m_centroids[j+1][1]) {
double temp0 = m_centroids[j][0];
m_centroids[j][0] = m_centroids[j+1][0];
m_centroids[j+1][0] = temp0;
double temp1 = m_centroids[j][1];
m_centroids[j][1] = m_centroids[j+1][1];
m_centroids[j+1][1] = temp1;
}
}
}
}
int Predict(double f1, double f2) {
double min_dist = -1.0;
int best_cluster = 0;
for(int c = 0; c < m_k; c++) {
double dist = EuclideanDistance(f1, f2, m_centroids[c][0], m_centroids[c][1]);
if(min_dist < 0 || dist < min_dist) {
min_dist = dist;
best_cluster = c;
}
}
return best_cluster;
}
};
//--- Global Variables
CKMeans *Model;
bool isModelTrained = false;
double maxATR = 0.0001;
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit() {
SetIndexBuffer(0, DummyBuffer);
SetIndexBuffer(1, State0Buffer);
SetIndexBuffer(2, State1Buffer);
SetIndexBuffer(3, State2Buffer);
SetIndexStyle(1, DRAW_HISTOGRAM, STYLE_SOLID, 3);
SetIndexStyle(2, DRAW_HISTOGRAM, STYLE_SOLID, 3);
SetIndexStyle(3, DRAW_HISTOGRAM, STYLE_SOLID, 3);
IndicatorShortName("K-Means Market State");
Model = new CKMeans(K_Clusters, Max_Iterations);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
delete Model;
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[]) {
if(rates_total < LookbackBars) return 0;
// 1. Train the model dynamically on the first pass
if(!isModelTrained) {
double training_data[][2];
ArrayResize(training_data, LookbackBars);
maxATR = 0.00001;
for(int i = 1; i <= LookbackBars; i++) {
double atr = iATR(Symbol(), 0, ATR_Period, i);
if (atr > maxATR) maxATR = atr;
}
for(int i = 0; i < LookbackBars; i++) {
double rsi = iRSI(Symbol(), 0, RSI_Period, PRICE_CLOSE, i + 1);
double atr = iATR(Symbol(), 0, ATR_Period, i + 1);
training_data[i][0] = rsi / 100.0;
training_data[i][1] = atr / maxATR;
}
Model.Train(training_data);
isModelTrained = true;
}
// 2. Predict states for new bars
int limit = rates_total - prev_calculated;
if(prev_calculated == 0) limit = rates_total - 1;
for(int i = limit; i >= 0; i--) {
double rsi = iRSI(Symbol(), 0, RSI_Period, PRICE_CLOSE, i);
double atr = iATR(Symbol(), 0, ATR_Period, i);
double f1 = rsi / 100.0;
double f2 = atr / (maxATR == 0 ? 0.0001 : maxATR);
int state = Model.Predict(f1, f2);
State0Buffer[i] = EMPTY_VALUE;
State1Buffer[i] = EMPTY_VALUE;
State2Buffer[i] = EMPTY_VALUE;
double height = atr; // Use ATR for visual histogram scaling
if(state == 0) State0Buffer[i] = height;
else if(state == 1) State1Buffer[i] = height;
else if(state == 2) State2Buffer[i] = height;
}
return(rates_total);
}
//+------------------------------------------------------------------+