OU Horizon Spread — The Elastic Bridge
Picture an elastic bridge connecting the market's nearby movement to its more distant movement. The near end represents a short average return. The far end represents an older interval with a longer averaging span. The program watches the difference between these ends, treating that difference as a spread in horizon behavior rather than as the price spread between two independently traded assets.

The bridge is useful only if it appears to pull back toward an equilibrium. The program therefore studies how earlier spread observations relate to the observations that followed them. It fits a simple persistence relationship and estimates whether deviations tend to diminish, remain almost permanent, or alternate in a way that does not match the intended model.

If the fitted persistence is unsuitable, the bridge is considered unreliable and the program avoids opening a new position. When persistence falls inside the accepted region, the program estimates an equilibrium level, the typical size of deviations around that level, and a half life describing how quickly a displacement might diminish under the fitted assumptions.

The current spread is then compared with that estimated equilibrium. A sufficiently stretched positive displacement is interpreted as a candidate for downward normalization. A sufficiently stretched negative displacement is interpreted as a candidate for upward normalization. The strategy therefore seeks reversion in the relationship between horizons, rather than simply following a large recent price change.

The half life also influences the allowed holding period. A bridge expected to relax quickly should not justify an indefinitely patient trade. Existing positions can be closed when the spread returns near equilibrium or when the persistence estimate no longer supports the intended mean reversion interpretation. Protective price stops and profit targets remain active alongside these analytical exits.

Research diagnostics describe the expected spread at a future horizon, the dispersion of historical spread levels, and the remaining serial relationship in model innovations. These observations help reveal whether the apparent elastic behavior is being captured reasonably or whether important patterns remain unexplained.

The central limitation is that the spread itself is not a directly traded two asset portfolio. The strategy converts a horizon relationship into a directional Forex position. Even genuine normalization of the analytical spread does not guarantee a favorable movement in that position. Historical equilibrium can also shift abruptly, so the elastic bridge should be viewed as a conditional hypothesis rather than a physical law.

Code
// Independent model 02: equilibrium spread, AR(1) dynamics and OU half-life.
#include <default.c>

var OUSample[121];
var OUPrice[160];
var OUZ = 0.;
var OUHalfLife = 0.;
var OUPersistence = 0.;

int OUFitSpread()
{
    int T;
    var MeanX, MeanY, SumXX, SumXY, Intercept, Innovation, Noise, Current;
    OUZ = 0.;
    OUHalfLife = 0.;
    OUPersistence = 1.;
    for (T = 0; T < 160; T++)
    {
        if (!(OUPrice[T] > 0.))
        {
            return 0;
        }
    }
    // Contrast a near-horizon average return with a distant age-bucket rate.
    for (T = 0; T <= 120; T++)
    {
        OUSample[T] =
            log(OUPrice[T] / OUPrice[T + 2]) / 2. - log(OUPrice[T + 8] / OUPrice[T + 16]) / 8.;
    }
    MeanX = 0.;
    MeanY = 0.;
    for (T = 2; T <= 120; T++)
    {
        MeanX += OUSample[T] / 119.;
        MeanY += OUSample[T - 1] / 119.;
    }
    SumXX = 0.;
    SumXY = 0.;
    for (T = 2; T <= 120; T++)
    {
        SumXX += (OUSample[T] - MeanX) * (OUSample[T] - MeanX);
        SumXY += (OUSample[T] - MeanX) * (OUSample[T - 1] - MeanY);
    }
    if (SumXX < 1.e-20)
    {
        return 0;
    }
    OUPersistence = SumXY / SumXX;
    if (OUPersistence <= 0.05 || OUPersistence >= 0.98)
    {
        return 0;
    }
    Intercept = MeanY - OUPersistence * MeanX;
    Noise = 0.;
    for (T = 2; T <= 120; T++)
    {
        Innovation = OUSample[T - 1] - Intercept - OUPersistence * OUSample[T];
        Noise += Innovation * Innovation / 119.;
    }
    if (Noise < 1.e-22)
    {
        return 0;
    }
    OUHalfLife = -log(2.) / log(OUPersistence);
    Current = OUSample[0] - Intercept / (1. - OUPersistence);
    OUZ = Current / sqrt(Noise / (1. - OUPersistence * OUPersistence));
    if (OUHalfLife < 1. || OUHalfLife > 24. || abs(OUZ) < 1.75)
    {
        return 0;
    }
    if (OUZ < 0.)
    {
        return 1;
    }
    return -1;
}

void OUChecks()
{
    int I, A, B, Errors;
    Errors = 0;
    for (I = 0; I < 160; I++)
    {
        OUPrice[I] = 1.;
    }
    if (OUFitSpread() != 0)
    {
        Errors++;
    }
    for (I = 0; I < 160; I++)
    {
        OUPrice[I] = exp(0.004 * sin(0.28 * I) + 0.001 * sin(1.71 * I));
    }
    A = OUFitSpread();
    if (A < -1 || A > 1)
    {
        Errors++;
    }
    for (I = 0; I < 160; I++)
    {
        OUPrice[I] = 1. / OUPrice[I];
    }
    B = OUFitSpread();
    if (B != -A)
    {
        Errors++;
    }
    OUPrice[3] = -1.;
    if (OUFitSpread() != 0)
    {
        Errors++;
    }
    file_write("Log/TS02_OUHorizonSpread_test.txt", strf("4 checks; %d failures\n", Errors), 0);
}

var OUForecastAt(int Steps)
{
    int T;
    var Average;
    if (Steps < 1 || OUPersistence <= 0. || OUPersistence >= 1.)
    {
        return 0.;
    }
    Average = 0.;
    for (T = 1; T <= 120; T++)
    {
        Average += OUSample[T] / 120.;
    }
    return Average + pow(OUPersistence, Steps) * (OUSample[0] - Average);
}
var OUInnovationAutocorrelation()
{
    int T;
    var Previous, Current, Numerator, Denominator;
    Numerator = 0.;
    Denominator = 0.;
    Previous = 0.;
    for (T = 119; T >= 1; T--)
    {
        Current = OUSample[T] - OUPersistence * OUSample[T + 1];
        Numerator += Current * Previous;
        Denominator += Current * Current;
        Previous = Current;
    }
    if (Denominator < 1.e-24)
    {
        return 0.;
    }
    return Numerator / Denominator;
}
var OULevelDispersion()
{
    int T;
    var Average, SpreadVariance;
    Average = 0.;
    SpreadVariance = 0.;
    for (T = 1; T <= 120; T++)
    {
        Average += OUSample[T] / 120.;
    }
    for (T = 1; T <= 120; T++)
    {
        SpreadVariance += pow(OUSample[T] - Average, 2.) / 120.;
    }
    return sqrt(SpreadVariance);
}
void OUResearchDiagnostics()
{
    printf("\nOU half-life %.6g; forecast4 %.6g; innovation correlation %.6g; spread scale %.6g",
           OUHalfLife, OUForecastAt(4), OUInnovationAutocorrelation(), OULevelDispersion());
}

// ============================================================================
// EXTENDED REGRESSION SUITE: deterministic properties over market regimes
// These functions run ONLY under STX_SELFTEST; they never place orders.
// ============================================================================
int OUExtendedCount = 0;
int OUExtendedFailures = 0;
void OUExpect(int Passed, string Description)
{
    OUExtendedCount++;
    if (!Passed)
    {
        OUExtendedFailures++;
        file_append("Log/TS02_OUHorizonSpread_extended.txt", strf("FAIL: %s\n", Description));
    }
}

void OUCase_quiet_cycle()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(0.0005 * sin(0.17 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "quiet_cycle: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "quiet_cycle: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "quiet_cycle: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "quiet_cycle: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "quiet_cycle: missing price rejected");
}

void OUCase_mixed_cycles()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(0.003 * sin(0.23 * K) + 0.0007 * cos(0.81 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "mixed_cycles: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "mixed_cycles: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "mixed_cycles: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "mixed_cycles: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "mixed_cycles: missing price rejected");
}

void OUCase_persistent_upward()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(-0.001 * K + 0.0001 * sin(0.53 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "persistent_upward: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "persistent_upward: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "persistent_upward: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "persistent_upward: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "persistent_upward: missing price rejected");
}

void OUCase_persistent_downward()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(0.001 * K + 0.0001 * cos(0.47 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "persistent_downward: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "persistent_downward: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "persistent_downward: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "persistent_downward: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "persistent_downward: missing price rejected");
}

void OUCase_slow_curve()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(0.004 * cos(0.08 * K) + 0.0002 * sin(0.7 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "slow_curve: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "slow_curve: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "slow_curve: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "slow_curve: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "slow_curve: missing price rejected");
}

void OUCase_rough_curve()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(0.001 * sin(1.7 * K) + 0.0005 * cos(2.3 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "rough_curve: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "rough_curve: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "rough_curve: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "rough_curve: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "rough_curve: missing price rejected");
}

void OUCase_accelerating_path()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(-0.0001 * K - 0.000002 * K * K + 0.0002 * sin(0.41 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "accelerating_path: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "accelerating_path: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "accelerating_path: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "accelerating_path: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "accelerating_path: missing price rejected");
}

void OUCase_decelerating_path()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(-0.0008 * K + 0.000002 * K * K + 0.0002 * cos(0.31 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "decelerating_path: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "decelerating_path: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "decelerating_path: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "decelerating_path: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "decelerating_path: missing price rejected");
}

void OUCase_volatility_ramp()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp((0.0001 + K * 0.00001) * sin(0.37 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "volatility_ramp: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "volatility_ramp: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "volatility_ramp: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "volatility_ramp: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "volatility_ramp: missing price rejected");
}

void OUCase_volatility_decay()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(0.006 * exp(-0.02 * K) * cos(0.7 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "volatility_decay: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "volatility_decay: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "volatility_decay: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "volatility_decay: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "volatility_decay: missing price rejected");
}

void OUCase_cycle_with_drift()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(-0.0002 * K + 0.003 * sin(0.19 * K) + 0.0005 * cos(0.61 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "cycle_with_drift: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "cycle_with_drift: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "cycle_with_drift: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "cycle_with_drift: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "cycle_with_drift: missing price rejected");
}

void OUCase_near_zero_prices()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 0.0001 * exp(0.002 * sin(0.29 * K) + 0.001 * cos(0.13 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "near_zero_prices: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "near_zero_prices: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "near_zero_prices: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "near_zero_prices: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "near_zero_prices: missing price rejected");
}

void OUCase_large_price_units()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 10000.0 * exp(0.003 * sin(0.11 * K) + 0.0008 * cos(0.43 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "large_price_units: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "large_price_units: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "large_price_units: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "large_price_units: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "large_price_units: missing price rejected");
}

void OUCase_high_frequency_chop()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(0.0008 * cos(2.8 * K) + 0.001 * sin(0.23 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "high_frequency_chop: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "high_frequency_chop: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "high_frequency_chop: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "high_frequency_chop: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "high_frequency_chop: missing price rejected");
}

void OUCase_long_wave_trend()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(-0.0005 * K + 0.002 * cos(0.07 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "long_wave_trend: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "long_wave_trend: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "long_wave_trend: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "long_wave_trend: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "long_wave_trend: missing price rejected");
}

void OUCase_opposing_scales()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(0.005 * sin(0.09 * K) - 0.002 * sin(0.59 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "opposing_scales: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "opposing_scales: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "opposing_scales: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "opposing_scales: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "opposing_scales: missing price rejected");
}

void OUCase_nonlinear_curve()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(0.002 * sin(0.021 * K * K) + 0.001 * cos(0.33 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "nonlinear_curve: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "nonlinear_curve: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "nonlinear_curve: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "nonlinear_curve: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "nonlinear_curve: missing price rejected");
}

void OUCase_amplitude_modulation()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(0.002 * sin(0.3 * K) * cos(0.04 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "amplitude_modulation: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "amplitude_modulation: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "amplitude_modulation: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "amplitude_modulation: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "amplitude_modulation: missing price rejected");
}

void OUCase_decaying_event()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(0.01 * exp(-0.2 * K) + 0.0003 * sin(0.71 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "decaying_event: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "decaying_event: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "decaying_event: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "decaying_event: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "decaying_event: missing price rejected");
}

void OUCase_time_varying_frequency()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(0.002 * cos(0.004 * K * K + 0.11 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "time_varying_frequency: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "time_varying_frequency: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "time_varying_frequency: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "time_varying_frequency: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "time_varying_frequency: missing price rejected");
}

void OUCase_slow_fast_cancellation()
{
    int K, Original, Again, Scaled, Reflected;
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1.0 * exp(0.004 * sin(0.07 * K) + 0.0013 * cos(0.97 * K));
    }
    Original = OUFitSpread();
    OUExpect(Original >= -1 && Original <= 1, "slow_fast_cancellation: bounded direction");
    Again = OUFitSpread();
    OUExpect(Again == Original, "slow_fast_cancellation: repeatable estimate");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] *= 7.;
    }
    Scaled = OUFitSpread();
    OUExpect(Scaled == Original, "slow_fast_cancellation: price-unit invariance");
    for (K = 0; K < 160; K++)
    {
        OUPrice[K] = 1. / OUPrice[K];
    }
    Reflected = OUFitSpread();
    OUExpect(Reflected == -Original, "slow_fast_cancellation: directional reflection");
    OUPrice[80] = 0.;
    OUExpect(OUFitSpread() == 0, "slow_fast_cancellation: missing price rejected");
}

void OUExtendedTests()
{
    OUExtendedCount = 0;
    OUExtendedFailures = 0;
    file_write("Log/TS02_OUHorizonSpread_extended.txt",
               "TS02_OUHorizonSpread: extended properties\n", 0);
    OUCase_quiet_cycle();
    OUCase_mixed_cycles();
    OUCase_persistent_upward();
    OUCase_persistent_downward();
    OUCase_slow_curve();
    OUCase_rough_curve();
    OUCase_accelerating_path();
    OUCase_decelerating_path();
    OUCase_volatility_ramp();
    OUCase_volatility_decay();
    OUCase_cycle_with_drift();
    OUCase_near_zero_prices();
    OUCase_large_price_units();
    OUCase_high_frequency_chop();
    OUCase_long_wave_trend();
    OUCase_opposing_scales();
    OUCase_nonlinear_curve();
    OUCase_amplitude_modulation();
    OUCase_decaying_event();
    OUCase_time_varying_frequency();
    OUCase_slow_fast_cancellation();
    file_append("Log/TS02_OUHorizonSpread_extended.txt",
                strf("RESULT: %d checks; %d failures\n", OUExtendedCount, OUExtendedFailures));
}

function run()
{
#ifdef STX_SELFTEST
    if (is(INITRUN))
    {
        OUChecks();
        OUExtendedTests();
        quit("OU tests complete");
    }
    return;
#endif
    int I, OrderSide;
    var Exposure, LossUnit;
    BarPeriod = 60;
    StartDate = 20240101;
    EndDate = 20241231;
    LookBack = 161;
    Capital = 10000.;
    MaxLong = 1;
    MaxShort = 1;
    Hedge = 0;
    asset("EUR/USD");
    algo("OU02");
    if (is(EXITRUN))
    {
        OUResearchDiagnostics();
        return;
    }
    if (is(LOOKBACK))
    {
        return;
    }
    for (I = 0; I < 160; I++)
    {
        OUPrice[I] = priceClose(I + 1);
    }
    OrderSide = OUFitSpread();
    // Equilibrium arrival or loss of mean-reverting dynamics closes the trade.
    if (abs(OUZ) < 0.25 || OUPersistence >= 0.98 || OUPersistence <= 0.)
    {
        exitLong();
        exitShort();
    }
    if (OrderSide == 0 || NumOpenLong + NumOpenShort > 0)
    {
        return;
    }
    if (Equity <= Balance * 0.85 || Balance <= 0.)
    {
        return;
    }
    if (LotAmount <= 0. || PIPCost <= 0. || PIP <= 0. || MarginCost <= 0.)
    {
        return;
    }
    Stop = PIP * 35.;
    TakeProfit = PIP * 50.;
    LifeTime = max(2., min(48., OUHalfLife * 3.));
    LossUnit = PIPCost * (Stop + Spread) / PIP + Commission * LotAmount / 10000.;
    if (LossUnit <= 0.)
    {
        return;
    }
    Exposure = min(2000. / LotAmount, Balance * 0.005 / LossUnit);
    Exposure = min(Exposure, max(0., Equity - MarginVal) * 0.8 / MarginCost);
    if (Exposure < 1.)
    {
        return;
    }
    Lots = floor(Exposure);
    Risk = 0.;
    Margin = 0.;
    if (OrderSide == 1)
    {
        enterLong();
    }
    else
    {
        enterShort();
    }
}