multi-timeframe “Market Mode Index” that blends a fast and a slow version of the same measure, then adapts it dynamically using machine learning.
Let’s break it down step-by-step:
1. Core Idea: Market Mode Index (MMI)
MMI here is a composite metric built from three sub-indicators:
sineMI() – Compares the price’s deviation from a pure sine wave.
Measures cyclicity — whether the market is behaving like a smooth oscillation.
predictabilityMI() – Uses a simple linear regression on lagged prices.
Measures linear predictability — can future price be explained by the past two bars?
spectralEnergy() – Compares “low-band” vs. “high-band” volatility.
Measures energy distribution — is the market dominated by slow or fast components?
These are averaged and normalized between 0 and 1, where:
Low MMI (~0.3 or less) ? Market is more trending or predictable.
High MMI (~0.7 or more) ? Market is more noisy or mean-reverting.
2. Two Timeframes You calculate MMI in:
Fast timeframe ? Base BarPeriod (e.g., 60 min bars).
Slow timeframe ? SLOW_FACTOR * BarPeriod (e.g., 4h bars if SLOW_FACTOR=4).
This produces:
mmiFast = Short-term market mode.
mmiSlow = Longer-term market mode (re-projected to the base TF so they align).
3. Machine Learning Forecast
The script builds a feature vector from past values of both MMI series:
First 5 values from mmiFast (lagged 1..5 bars)
First 5 values from mmiSlow (lagged 1..5 bars)
That 10-dimensional vector goes into:
adviseLong(TRAIN_BARS, 0, features, MAX_FEATURES)
This uses Zorro’s built-in machine learning (default perceptron) to predict ? (change) in MMI from this combined history.
4. Adaptive Blending
The predicted ? (direction & magnitude) controls:
How much weight to give mmiFast vs. mmiSlow in the final blend.
How fast the blended MMI is smoothed (adaptive EMA period).
This way:
If the ML thinks fast MMI is stable ? more weight on fast component.
If ML thinks change is coming ? more weight on slow component, slower smoothing.
5. Outputs It plots:
Adaptive MMI (red) ? The blended, ML-weighted index.
Fast MMI (blue)
Slow MMI (black, projected to base TF)
Pred ? (purple) ? Normalized ML forecast of MMI change.
Low (green line at 0.3) ? Often a trend-friendly zone.
High (orange line at 0.7) ? Often a range/noise zone.
How to Use It
Traders often interpret MMI like this:
Below low threshold (~0.3) ? Favors trend-following systems.
Above high threshold (~0.7) ? Favors mean-reversion systems.
Between thresholds ? No clear bias.
Here, you have an adaptive, multi-TF version that tries to smooth noise and anticipate regime changes rather than reacting only to raw MMI.
// === Config ===
#define INPUT_SIZE 5 // past points per TF
#define TRAIN_BARS 500
#define SLOW_FACTOR 4 // slow TF = SLOW_FACTOR * BarPeriod
#define MAX_FEATURES 10 // 2 * INPUT_SIZE
#define LONGEST_EMA 89 // longest fixed EMA length in code
// === Safety Helpers ===
int safeWin(int requested)
{
return min(requested, max(2, Bar - 1)); // clamp to available bars
}
var clamp(var value, var min, var max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
// === Adaptive Window ===
var adaptiveWinLength()
{
static var* atrSeries;
atrSeries = series(ATR(14));
var atr_val = atrSeries[0];
return max(10, round(50 * (atr_val / priceClose()) * 1.0));
}
// === Indicators ===
var sineWave()
{
return sin(2 * PI * Bar / 20); // SINE_LEN = 20
}
var spectralEnergy(int baseWin)
{
static var* pClose;
static var* lowBandSeries;
static var* highBandSeries;
pClose = series(priceClose());
var ema34 = EMA(pClose, safeWin(34));
var ema89 = EMA(pClose, safeWin(LONGEST_EMA));
var lowBand = ema34 - ema89;
var ema8 = EMA(pClose, safeWin(8));
var highBand = pClose[0] - ema8;
lowBandSeries = series(lowBand);
highBandSeries = series(highBand);
var energyLow = Variance(lowBandSeries, safeWin(baseWin));
var energyHigh = Variance(highBandSeries, safeWin(baseWin));
var spectralRatio = energyHigh / (energyHigh + energyLow + 0.000001);
return clamp(spectralRatio, 0, 1);
}
var predictabilityMI(int window)
{
static var* p;
static var* p1;
static var* p2;
static var* p1_sq;
static var* p2_sq;
static var* p1p2;
static var* p1p;
static var* p2p;
static var* res_sq;
p = series(priceClose());
p1 = series(priceClose(1));
p2 = series(priceClose(2));
p1_sq = series(p1[0]*p1[0]);
p2_sq = series(p2[0]*p2[0]);
p1p2 = series(p1[0]*p2[0]);
p1p = series(p1[0]*p[0]);
p2p = series(p2[0]*p[0]);
var sum_x1 = EMA(p1, safeWin(window));
var sum_x2 = EMA(p2, safeWin(window));
var sum_y = EMA(p, safeWin(window));
var sum_x1x1 = EMA(p1_sq, safeWin(window));
var sum_x2x2 = EMA(p2_sq, safeWin(window));
var sum_x1x2 = EMA(p1p2, safeWin(window));
var sum_x1y = EMA(p1p, safeWin(window));
var sum_x2y = EMA(p2p, safeWin(window));
var denom = sum_x1x1 * sum_x2x2 - sum_x1x2 * sum_x1x2;
var a = ifelse(denom != 0, (sum_x2x2 * sum_x1y - sum_x1x2 * sum_x2y) / denom, 0);
var b = ifelse(denom != 0, (sum_x1x1 * sum_x2y - sum_x1x2 * sum_x1y) / denom, 0);
var y_hat = a * p1[0] + b * p2[0];
var residual = p[0] - y_hat;
res_sq = series(pow(residual, 2));
var mse_pred = EMA(res_sq, safeWin(window));
var var_price = Variance(p, safeWin(50));
return clamp(mse_pred / var_price, 0, 1);
}
var sineMI(int window)
{
static var* p;
static var* s;
static var* sine_dev_sq;
p = series(priceClose());
s = series(sineWave());
var sine_dev = s[0] - EMA(s, safeWin(window));
var price_dev = p[0] - EMA(p, safeWin(window));
sine_dev_sq = series(pow(price_dev - sine_dev, 2));
var mse_sine = EMA(sine_dev_sq, safeWin(window));
var var_price = Variance(p, safeWin(50));
return clamp(mse_sine / var_price, 0, 1);
}
var computeMMI(int win)
{
var mi_sine = sineMI(win);
var mi_pred = predictabilityMI(win);
var mi_spec = spectralEnergy(50);
return clamp((mi_sine + mi_pred + mi_spec) / 3, 0, 1);
}
// === Feature readiness ===
int featuresReady(var* fast, var* slow, int size)
{
int i;
for(i = 0; i < size; i++)
if(fast[i+1] == 0 || slow[i+1] == 0)
return 0;
return 1;
}
// === Main ===
function run()
{
set(PLOTNOW);
BarPeriod = 60;
LookBack = max(LONGEST_EMA * 2, 2*INPUT_SIZE + 12);
// Global warm-up guard
int slowBars = Bar / SLOW_FACTOR;
int minSlowBars = max(LONGEST_EMA * 2, 2*INPUT_SIZE + 12);
if (Bar < TRAIN_BARS || slowBars < minSlowBars)
return;
// ===== FAST TF =====
int fastWin = adaptiveWinLength();
static var* mmiFast;
mmiFast = series(EMA(series(computeMMI(fastWin)), safeWin(5)));
// ===== SLOW TF =====
int tfKeep = TimeFrame;
TimeFrame = SLOW_FACTOR;
int slowWin = max(10, round(fastWin / SLOW_FACTOR));
static var* mmiSlowTF;
mmiSlowTF = series(EMA(series(computeMMI(slowWin)), safeWin(5)));
var mmiSlowNow = mmiSlowTF[0];
TimeFrame = tfKeep;
static var* mmiSlowOnBase;
mmiSlowOnBase = series(mmiSlowNow);
// ===== Feature guard =====
if (!featuresReady(mmiFast, mmiSlowOnBase, INPUT_SIZE))
return;
// ===== ML Features =====
var features[MAX_FEATURES];
int i;
for(i=0; i<INPUT_SIZE; i++)
features[i] = mmiFast[i+1];
for(i=0; i<INPUT_SIZE; i++)
features[INPUT_SIZE + i] = mmiSlowOnBase[i+1];
var predicted_delta = adviseLong(TRAIN_BARS, 0, features, MAX_FEATURES);
var norm_delta = clamp(predicted_delta, -1, 1);
var adaptFactor = clamp(1 - fabs(norm_delta), 0.4, 0.9);
int adaptPeriod = max(2, min(50, (int)round(5./adaptFactor)));
var w_fast = clamp(0.5 + 0.5*fabs(norm_delta), 0.4, 0.9);
var w_slow = 1 - w_fast;
var cmi_blend = w_fast*mmiFast[0] + w_slow*mmiSlowOnBase[0];
var adaptiveMMI = clamp(EMA(series(cmi_blend), safeWin(adaptPeriod)), 0, 1);
// ===== Plots =====
plot("Adaptive MMI", adaptiveMMI, LINE, RED);
plot("Fast MMI", mmiFast[0], LINE, BLUE);
plot("Slow MMI", mmiSlowOnBase[0], LINE, BLACK);
plot("Pred ?", norm_delta, LINE, PURPLE);
plot("Low", 0.3, LINE, GREEN);
plot("High", 0.7, LINE, ORANGE);
}