The wings of a computer engineer
The wings of a computer engineer

Personal blog for Timothy D Meadows II

ʍɐɔ ʍɐɔ ʍɐɔ

Share


Twitter


Modeling Subsurface Market Dynamics

Timothy D Meadows IITimothy D Meadows II

⚠️This article is for educational and engineering purposes only. It does not constitute financial advice, investment recommendations, or trading signals. Use the information at your own risk. Currency markets are volatile, and you should do your own research before making any financial decisions.

In the first iteration called the tides model, we looked at market behavior as surface tides of predictable oscillations captured by NickRypock Trailing Reverse (NRTR) and NickRypock Moving Average (NRMA) indicators.

They expressed the visible rhythm of market movement, the waves, if you will, that traders "surf" on.

But beneath that surface lie deeper currents!

Liquidity pockets, order flow imbalances, and liquidation clusters act like thermohaline currents, quietly steering large-scale behavior long before it’s visible above.
Meanwhile, sentiment events and social cascades act as storms, transferring sudden bursts of energy into the market’s atmosphere.

The new model, called the ocean model now introduces three-layers inspired by fluid dynamics:

Together, these layers form a coupled system of forces that can explain how market energy builds, transfers, and releases!

The Roaring Ocean Model

Layer Analogy Market Mechanism Symbol
Surface Waves, tides Price action (NRMA/NRTR) Tt
Subsurface Deep currents, jets Order flow, liquidation heat Ut
Atmosphere Storms, wind pressure Sentiment dynamics αt

Each layer exchanges energy with the others:

This represents the hydrodynamic energy balance of the market in one large expressive formula. Now, let's break this down in it's parts, and code!

The Market Tides Composite Index

At each timestep t, given by streaming candles, order data, and sentiment state, the total market energy can be represented as the Market Tides Composite Index:

where:

Below is the equivalent C# implementation:

public sealed class MtciParams  
{
    public double wT = 0.5, wU = 0.35, wS = 0.15;
    public double wL = 0.6, wShrt = 0.4;
    public double OFP = 0.6, LHF = 0.4;
    public double cT = 1.25, cO = 1.0, cH = 1.0, cS = 1.0;
    public double g1 = 0.4, g2 = 0.6, gMax = 2.0;
    public double Eps = 1e-9; // ε safeguard
}

public sealed class MtciEngine  
{
    private readonly MtciParams _p;
    public MtciEngine(MtciParams p) => _p = p;

    public double Compute(double price,
                          double nrmaL, double nrtrL,
                          double nrmaS, double nrtrS,
                          double ofp, double lhf,
                          double alpha)
    {
        double T_L = Math.Tanh(_p.cT * ((nrmaL - nrtrL) / Math.Max(price, _p.Eps)));
        double T_S = Math.Tanh(_p.cT * ((nrmaS - nrtrS) / Math.Max(price, _p.Eps)));
        double T = _p.wL * T_L + _p.wShrt * T_S;

        double U = _p.OFP * ofp + _p.LHF * lhf;

        double G = 1.0 + _p.g1 * Math.Abs(ofp) + _p.g2 * Math.Max(0.0, lhf);
        G = Math.Min(G, _p.gMax);

        return _p.wT * G * T + _p.wU * U + _p.wS * alpha;
    }
}

Surface Layer (Tides)

The surface layer measures how NRMA and NRTR diverge the “height” of the market wave across long and short timeframes:

Below is the equivalent C# implementation:

public sealed class NRTR  
{
    public double K { get; }
    private double _trend;
    private double _h, _l, _rev;

    public double Reverse => _rev;
    public double Trend => _trend;

    public NRTR(double k, double initial)
    {
        K = k;
        _trend = +1;
        _h = initial;
        _l = initial;
        _rev = initial;
    }

    public double Update(double close)
    {
        if (_trend >= 0)
        {
            if (close > _h) _h = close;
            _rev = _h * (1.0 - K * 0.01);
            if (close <= _rev)
            {
                _trend = -1;
                _l = close;
                _rev = _l * (1.0 + K * 0.01);
            }
        }
        else
        {
            if (close < _l) _l = close;
            _rev = _l * (1.0 + K * 0.01);
            if (close >= _rev)
            {
                _trend = +1;
                _h = close;
                _rev = _h * (1.0 - K * 0.01);
            }
        }
        return _rev;
    }
}

public sealed class NRMA  
{
    public double K { get; }
    public int Fast { get; }
    public int Sharp { get; }
    private NRTR _nrtr;
    private double _prev;
    private readonly Queue<double> _recent = new();

    public double Value => _prev;

    public NRMA(double k, int fast, int sharp, double initial)
    {
        K = k; Fast = fast; Sharp = sharp;
        _nrtr = new NRTR(k, initial);
        _prev = initial;
    }

    public double Update(double close)
    {
        double r = _nrtr.Update(close);
        double osc = (100.0 * Math.Abs(close - r) / Math.Max(close, 1e-12)) / Math.Max(K, 1e-12);
        _recent.Enqueue(osc);
        while (_recent.Count > Math.Max(3, Fast)) _recent.Dequeue();

        double avg = 0.0;
        foreach (var v in _recent) avg += v;
        avg /= _recent.Count;

        double F = 2.0 / (1.0 + Fast);
        double rho = F * Math.Pow(avg, Sharp);

        _prev = _prev + rho * (close - _prev);
        return _prev;
    }
}

Subsurface Layer (Underflows)

The underflow layer models two invisible but powerful forces:

1. Order Flow Pressure (OFPt):

where Bd and Ad are bid/ask volumes within depth d.

2. Liquidation Heat Flux (LHFt):

These combine into:

Below is the equivalent C# implementation:

public static class Underflows  
{
    public static double Ofp(double cO, double bidsDepthSum, double asksDepthSum, double eps = 1e-9)
    {
        double num = (bidsDepthSum - asksDepthSum);
        double den = (bidsDepthSum + asksDepthSum + eps);
        return Math.Tanh(cO * (num / den));
    }

    public sealed record LiqEvent(bool IsShort, double Price, double Size, DateTime TimeUtc);

    public static double Lhf(double cH,
                             IEnumerable<LiqEvent> events,
                             double currentPrice,
                             double advVolume,
                             DateTime nowUtc,
                             double lambdaDist = 0.002,
                             double lambdaTime = 0.001,
                             double eps = 1e-9)
    {
        double up = 0.0, down = 0.0;
        foreach (var e in events)
        {
            double dist = Math.Abs(e.Price - currentPrice);
            double dtSec = Math.Max(0.0, (nowUtc - e.TimeUtc).TotalSeconds);
            double w = Math.Exp(-lambdaDist * dist) * Math.Exp(-lambdaTime * dtSec);
            if (e.IsShort && e.Price > currentPrice) up += e.Size * w;
            if (!e.IsShort && e.Price < currentPrice) down += e.Size * w;
        }

        double num = (up - down);
        double den = Math.Max(advVolume, eps);
        return Math.Tanh(cH * (num / den));
    }
}

Atmospheric Layer (Storms)

The storm layer captures exogenous sentiment energy:

where Zssst is the z-scored sentiment index from social sources (e.g., Twitter).

Below is the equivalent C# implementation:

public sealed class ZScore  
{
    private readonly int _win;
    private readonly Queue<double> _buf = new();
    private double _sum, _sum2;
    private readonly double _eps;

    public ZScore(int window = 300, double eps = 1e-9)
    { _win = window; _eps = eps; }

    public double Update(double x)
    {
        _buf.Enqueue(x);
        _sum += x;
        _sum2 += x * x;
        if (_buf.Count > _win)
        {
            double y = _buf.Dequeue();
            _sum -= y; _sum2 -= y * y;
        }

        int n = _buf.Count;
        double mu = _sum / Math.Max(1, n);
        double var = Math.Max(0.0, _sum2 / Math.Max(1, n) - mu * mu);
        double sigma = Math.Sqrt(var);
        return (x - mu) / Math.Max(_eps, sigma);
    }
}

public static class Storms  
{
    public static double Alpha(double cS, double zScore) => Math.Tanh(cS * zScore);
}

Coupling Amplifier

Just as deep currents magnify surface waves, the coupling term Gt modulates how underflows amplify tides:

Below is the equivalent C# implementation:

public static class Coupling  
{
    public static double Gain(double ofp, double lhf, double g1, double g2, double gMax = 2.0)
    {
        double g = 1.0 + g1 * Math.Abs(ofp) + g2 * Math.Max(0.0, lhf);
        return Math.Min(g, gMax);
    }
}

The roaring ocean

Once all layers are computed, we integrate them into an event / loop that runs each market tick (or candle close). This determines whether to enter, exit, or stay out based on the Market Tides Composite Index and the state of each layer!

Below is the equivalent C# implementation:

public sealed class Logic  
{
    private readonly MtciEngine _engine;
    private readonly double _entry, _exit, _halt;

    public Logic(MtciEngine engine, double entryThreshold = 0.7, double exitThreshold = 0.3, double haltThreshold = 0.85)
    {
        _engine = engine;
        _entry = entryThreshold;
        _exit = exitThreshold;
        _halt = haltThreshold;
    }

    public enum SignalType { None, Long, Short, Exit, Halt }

    public SignalType Evaluate(double price,
                               double nrmaL, double nrtrL,
                               double nrmaS, double nrtrS,
                               double ofp, double lhf,
                               double alpha)
    {
        double mtci = _engine.Compute(price, nrmaL, nrtrL, nrmaS, nrtrS, ofp, lhf, alpha);

        // Storm halt condition — too much sentiment pressure
        if (Math.Abs(alpha) >= _halt)
            return SignalType.Halt;

        bool longTrend = (nrmaL > nrtrL) && (nrmaS > nrtrS);
        bool shortTrend = (nrmaL < nrtrL) && (nrmaS < nrtrS);

        if (longTrend && mtci >= _entry) return SignalType.Long;
        if (shortTrend && mtci <= -_entry) return SignalType.Short;

        if (Math.Abs(mtci) < _exit) return SignalType.Exit;

        return SignalType.None;
    }
}

Evaluation:

// Test: called once per candle update
void OnNewCandle(Candle c)  
{
    var nrmaL = nrmaLong.Update(c.Close);
    var nrmaS = nrmaShort.Update(c.Close);
    var nrtrL = nrtrLong.Update(c.Close);
    var nrtrS = nrtrShort.Update(c.Close);

    var ofp = Underflows.Ofp(paramsOFP, totalBids, totalAsks);
    var lhf = Underflows.Lhf(paramsLHF, liquidationEvents, c.Close, advVolume, DateTime.UtcNow);
    var alpha = Storms.Alpha(paramsStorm, sentimentZ);

    var signal = logic.Evaluate(c.Close, nrmaL, nrtrL, nrmaS, nrtrS, ofp, lhf, alpha);

    Console.WriteLine($"{c.Time}: Signal={signal}");
}

ʍɐɔ ʍɐɔ ʍɐɔ

Comments