Term Structure Covariation Strategy
The strategy can be understood as a sequence of lenses, filters, and gates. Each stage transforms ordinary Forex prices into a structured picture of market movement, then decides whether that picture permits a trade.

The observation window
A moving window collects closing prices from completed candles. The prices are arranged from oldest to newest, preventing unfinished bars from influencing the analysis. Logarithmic price changes describe movement proportionally rather than through raw price differences.

The maturity lenses
The strategy views each price change through several synthetic maturity lenses. Shorter and longer maturity intervals receive different amplification weights.

These lenses do not observe separate financial instruments. They reshape the same Forex return into several related channels, creating an artificial maturity structure rather than measuring an actual bond yield curve.

The noise filter
The analyzer measures the combined strength of each observation across those channels. It sorts these measurements and uses the quieter portion of the sample to estimate a preliminary background scale.

An adjustable truncation setting converts that scale into a boundary. Ordinary observations remain inside the boundary, while unusually large movements fall outside it and become potential jumps.

The two market portraits
The strategy constructs two covariation portraits.

The first includes every observation and represents total measured movement.

The second excludes observations beyond the truncation boundary and represents movement after large shocks have been filtered out.

Both portraits retain the original sample length when averaging. Filtering therefore removes contributions without increasing the weight of the remaining observations.

The factor prism
A Jacobi decomposition separates the filtered portrait into principal directions and their relative strengths.

The strongest direction becomes the first factor. Additional factors are counted until their combined contribution reaches the configured explanation target.

The analyzer also calculates a matrix magnitude called the Hilbert Schmidt norm. Despite its volatility label in the original implementation, this quantity measures covariance magnitude, not conventional annualized volatility.

The trading compass
The first factor passes through a maturity weighted scoring rule. A sufficiently positive score requests a purchase. A sufficiently negative score requests a sale. Scores inside the decision boundary remain neutral.

An optional setting adjusts that boundary according to the covariance magnitude.

The protection gates
Before entry, the strategy checks drawdown, factor count, underlying movement, existing positions, and signal spacing.

An opposing position must close before reversal. Order size follows either a fixed Forex volume or a stop based risk budget, subject to available margin.

Stop loss, take profit, and stepped trailing protection manage exits.

The essential warning
Every synthetic channel originates from the same return stream. Consequently, the unregularized structure contains only one independent direction. Its orientation does not establish economic buying or selling meaning. The preserved trading compass is therefore experimental rather than a validated forecast.


Code
#include <default.c>
#ifndef TERM_STRUCTURE_LIB_H
#define TERM_STRUCTURE_LIB_H

// Zorro 32-bit lite-C. Singleton analyzer; all matrices are row-major.
// Forex spot proxy, NOT observations of a bond term structure.
#define TS_MAX_MATURITIES 16
#define TS_MAX_PAIRS 15
#define TS_MATRIX_SIZE 225
#define TS_MAX_PRICES 2048
#define TS_RETURN_SIZE 30705

int TS_Maturities[TS_MAX_MATURITIES];
int TS_MaturityCount = 0;
int TS_Lookback = 250;
int TS_TimeSteps = 0;
int TS_Pairs = 0;
int TS_Initialized = 0;
int TS_NumFactors = 0;
int TS_JumpCount = 0;
int TS_KeptCount = 0;
int TS_Debug = 0;
int TS_DataDriven = 1;
int TS_Regularize = 1;
var TS_Alpha = 3.;
var TS_VarianceTarget = 0.85;
var TS_PreliminaryVariance = 0.;
var TS_Threshold = 0.;
var TS_IntegratedVolatility = 0.;
var TS_RealizedTrace = 0.;
var TS_Returns[TS_RETURN_SIZE];
var TS_Norms[TS_MAX_PRICES];
var TS_SortedNorms[TS_MAX_PRICES];
var TS_LogPrices[TS_MAX_PRICES];
var TS_Realized[TS_MATRIX_SIZE];
var TS_Truncated[TS_MATRIX_SIZE];
var TS_Eigenvalues[TS_MAX_PAIRS];
var TS_ExplainedVariance[TS_MAX_PAIRS];
var TS_Eigenvectors[TS_MATRIX_SIZE];
var TS_WorkMatrix[TS_MATRIX_SIZE];
var TS_RawEigenvalues[TS_MAX_PAIRS];

void TS_ResetResults()
{
  TS_Initialized = 0;
  TS_NumFactors = 0;
  TS_TimeSteps = 0;
  TS_Pairs = 0;
  TS_JumpCount = 0;
  TS_KeptCount = 0;
  TS_PreliminaryVariance = 0.;
  TS_Threshold = 0.;
  TS_IntegratedVolatility = 0.;
  TS_RealizedTrace = 0.;
}

int TS_SetMaturities(int* Tenors, int TenorCount)
{
  int K;
  TS_ResetResults();
  if(TenorCount < 2 || TenorCount > TS_MAX_MATURITIES) return 0;
  for(K = 0; K < TenorCount; K++) {
    if(Tenors[K] <= 0) return 0;
    if(K > 0 && Tenors[K] <= Tenors[K-1]) return 0;
  }
  for(K = 0; K < TenorCount; K++) TS_Maturities[K] = Tenors[K];
  TS_MaturityCount = TenorCount;
  return 1;
}

var TS_MatrixTrace(vars MatrixData, int Dimension)
{
  int K;
  var Total = 0.;
  for(K = 0; K < Dimension; K++) Total += MatrixData[K*Dimension+K];
  return Total;
}

var TS_FrobeniusNorm(vars MatrixData, int Dimension)
{
  int K;
  var Total = 0.;
  for(K = 0; K < Dimension*Dimension; K++)
    Total += MatrixData[K]*MatrixData[K];
  return sqrt(Total);
}

// Symmetric Jacobi solver: columns of Vectors are orthonormal eigenvectors.
// Unlike the source, non-convergence is a failure, not a successful result.
int TS_EigenDecomposition(vars MatrixData, int Dimension,
  vars Values, vars Vectors)
{
  int I, J, K, Iteration, P, Q, MaxIndex, Converged;
  var Scale, Tolerance, Largest, App, Aqq, Apq;
  var Theta, Rotation, Cosine, Sine, Aip, Aiq, Vip, Viq, Temp;
  if(Dimension < 1 || Dimension > TS_MAX_PAIRS) return 0;
  Scale = 0.;
  for(I = 0; I < Dimension*Dimension; I++) {
    TS_WorkMatrix[I] = MatrixData[I];
    Vectors[I] = 0.;
    Scale = max(Scale,abs(MatrixData[I]));
  }
  for(I = 0; I < Dimension; I++) Vectors[I*Dimension+I] = 1.;
  Tolerance = max(1.e-30,Scale*1.e-12);
  Converged = 0;
  for(Iteration = 0; Iteration < 150*Dimension*Dimension; Iteration++) {
    Largest = 0.;
    P = 0;
    Q = 0;
    for(I = 0; I < Dimension; I++) {
      for(J = I+1; J < Dimension; J++) {
        Temp = abs(TS_WorkMatrix[I*Dimension+J]);
        if(Temp > Largest) { Largest = Temp; P = I; Q = J; }
      }
    }
    if(Largest <= Tolerance) { Converged = 1; break; }
    App = TS_WorkMatrix[P*Dimension+P];
    Aqq = TS_WorkMatrix[Q*Dimension+Q];
    Apq = TS_WorkMatrix[P*Dimension+Q];
    Theta = (Aqq-App)/(2.*Apq);
    // Stable equivalent to sign(theta)/(abs(theta)+sqrt(1+theta^2)).
    if(abs(Theta) > 1.e100) Rotation = 0.5/Theta;
    else {
      Rotation = 1./(abs(Theta)+sqrt(1.+Theta*Theta));
      if(Theta < 0.) Rotation = -Rotation;
    }
    Cosine = 1./sqrt(1.+Rotation*Rotation);
    Sine = Rotation*Cosine;
    TS_WorkMatrix[P*Dimension+P] = App-Rotation*Apq;
    TS_WorkMatrix[Q*Dimension+Q] = Aqq+Rotation*Apq;
    TS_WorkMatrix[P*Dimension+Q] = 0.;
    TS_WorkMatrix[Q*Dimension+P] = 0.;
    for(I = 0; I < Dimension; I++) {
      if(I != P && I != Q) {
        Aip = TS_WorkMatrix[I*Dimension+P];
        Aiq = TS_WorkMatrix[I*Dimension+Q];
        TS_WorkMatrix[I*Dimension+P] = Cosine*Aip-Sine*Aiq;
        TS_WorkMatrix[P*Dimension+I] = TS_WorkMatrix[I*Dimension+P];
        TS_WorkMatrix[I*Dimension+Q] = Sine*Aip+Cosine*Aiq;
        TS_WorkMatrix[Q*Dimension+I] = TS_WorkMatrix[I*Dimension+Q];
      }
      Vip = Vectors[I*Dimension+P];
      Viq = Vectors[I*Dimension+Q];
      Vectors[I*Dimension+P] = Cosine*Vip-Sine*Viq;
      Vectors[I*Dimension+Q] = Sine*Vip+Cosine*Viq;
    }
  }
  if(!Converged) return 0;
  for(I = 0; I < Dimension; I++) Values[I] = TS_WorkMatrix[I*Dimension+I];
  for(I = 0; I < Dimension-1; I++) {
    MaxIndex = I;
    for(J = I+1; J < Dimension; J++)
      if(abs(Values[J]) > abs(Values[MaxIndex])) MaxIndex = J;
    if(MaxIndex != I) {
      Temp = Values[I]; Values[I] = Values[MaxIndex]; Values[MaxIndex] = Temp;
      for(K = 0; K < Dimension; K++) {
        Temp = Vectors[K*Dimension+I];
        Vectors[K*Dimension+I] = Vectors[K*Dimension+MaxIndex];
        Vectors[K*Dimension+MaxIndex] = Temp;
      }
    }
  }
  return 1;
}

int TS_ComputePreliminaryEstimate()
{
  int N, J, K, ValidSamples, CutoffIndex;
  var Temporary, Quantile, TraceSum;
  for(N = 0; N < TS_TimeSteps; N++) TS_SortedNorms[N] = TS_Norms[N];
  // Insertion sort: no dependency on MQL ArraySort or C qsort callbacks.
  for(N = 1; N < TS_TimeSteps; N++) {
    Temporary = TS_SortedNorms[N];
    K = N-1;
    while(K >= 0) {
      if(TS_SortedNorms[K] <= Temporary) break;
      TS_SortedNorms[K+1] = TS_SortedNorms[K];
      K--;
    }
    TS_SortedNorms[K+1] = Temporary;
  }
  CutoffIndex = (int)(TS_TimeSteps*0.75);
  Quantile = TS_SortedNorms[CutoffIndex];
  ValidSamples = 0;
  TraceSum = 0.;
  // Only the trace is needed; equivalent to constructing prelimCov.
  for(N = 0; N < TS_TimeSteps; N++) {
    if(TS_Norms[N] <= Quantile) {
      ValidSamples++;
      for(J = 0; J < TS_Pairs; J++) {
        Temporary = TS_Returns[N*TS_Pairs+J];
        TraceSum += Temporary*Temporary;
      }
    }
  }
  TS_PreliminaryVariance = 0.;
  if(ValidSamples > 0)
    TS_PreliminaryVariance = TraceSum/(ValidSamples*TS_Pairs);
  return TS_PreliminaryVariance > 1.e-15;
}

// Prices must be oldest -> newest. Uses only the last TS_Lookback prices.
int TS_ComputeCovariations(vars Prices, int PriceCount)
{
  int I, J, N, Offset, PreliminaryOK;
  var LogReturn, Amplification, SumSquares, Product, Total, Cumulative;
  TS_ResetResults();
  if(TS_Lookback < 2 || TS_Lookback > TS_MAX_PRICES) return 0;
  if(PriceCount < TS_Lookback || TS_MaturityCount < 2) return 0;
  if(TS_Alpha <= 0. || TS_VarianceTarget <= 0. || TS_VarianceTarget > 1.) return 0;
  Offset = PriceCount-TS_Lookback;
  for(I = 0; I < TS_Lookback; I++) {
    // Comparisons also reject NaN; upper bound prevents overflow inputs.
    if(!(Prices[Offset+I] > 0.) || Prices[Offset+I] > 1.e100) return 0;
    TS_LogPrices[I] = log(Prices[Offset+I]);
  }
  TS_TimeSteps = TS_Lookback-1;
  TS_Pairs = TS_MaturityCount-1;
  for(N = 0; N < TS_TimeSteps; N++) {
    LogReturn = TS_LogPrices[N+1]-TS_LogPrices[N];
    SumSquares = 0.;
    for(J = 0; J < TS_Pairs; J++) {
      Amplification = sqrt((TS_Maturities[J+1]-TS_Maturities[J])/30.)*(J+1)*2.;
      Product = LogReturn*Amplification;
      TS_Returns[N*TS_Pairs+J] = Product;
      SumSquares += Product*Product;
    }
    TS_Norms[N] = sqrt(SumSquares);
  }
  PreliminaryOK = TS_ComputePreliminaryEstimate();
  TS_Threshold = TS_Alpha*sqrt(TS_PreliminaryVariance);
  if(TS_DataDriven && PreliminaryOK) TS_Threshold *= sqrt(TS_Pairs);
  // When DataDriven=0 use alpha*sqrt(preliminaryVariance), in norm units.
  for(N = 0; N < TS_TimeSteps; N++) {
    if(TS_Norms[N] < TS_Threshold) TS_KeptCount++;
    if(TS_Norms[N] > TS_Threshold) TS_JumpCount++;
  }
  for(I = 0; I < TS_Pairs; I++) {
    for(J = I; J < TS_Pairs; J++) {
      Total = 0.;
      Cumulative = 0.;
      for(N = 0; N < TS_TimeSteps; N++) {
        Product = TS_Returns[N*TS_Pairs+I]*TS_Returns[N*TS_Pairs+J];
        Total += Product;
        if(TS_Norms[N] < TS_Threshold) Cumulative += Product;
      }
      TS_Realized[I*TS_Pairs+J] = Total/TS_TimeSteps;
      TS_Realized[J*TS_Pairs+I] = Total/TS_TimeSteps;
      TS_Truncated[I*TS_Pairs+J] = Cumulative/TS_TimeSteps;
      TS_Truncated[J*TS_Pairs+I] = Cumulative/TS_TimeSteps;
    }
  }
  TS_RealizedTrace = TS_MatrixTrace(TS_Realized,TS_Pairs);
  if(TS_Regularize && TS_MatrixTrace(TS_Truncated,TS_Pairs) < 1.e-12)
    for(I = 0; I < TS_Pairs; I++) TS_Truncated[I*TS_Pairs+I] += 1.e-9;
  if(!TS_EigenDecomposition(TS_Truncated,TS_Pairs,
    TS_RawEigenvalues,TS_Eigenvectors)) return 0;
  TS_IntegratedVolatility = TS_FrobeniusNorm(TS_Truncated,TS_Pairs);
  Total = 0.;
  for(I = 0; I < TS_Pairs; I++) Total += abs(TS_RawEigenvalues[I]);
  if(Total < 1.e-15) return 0;
  Cumulative = 0.;
  for(I = 0; I < TS_Pairs; I++) {
    TS_Eigenvalues[I] = abs(TS_RawEigenvalues[I])/Total;
    Cumulative += TS_Eigenvalues[I];
    TS_ExplainedVariance[I] = Cumulative;
    if(TS_NumFactors == 0 && Cumulative+1.e-14 >= TS_VarianceTarget)
      TS_NumFactors = I+1;
  }
  if(TS_NumFactors == 0) TS_NumFactors = TS_Pairs;
  TS_Initialized = 1;
  if(TS_Debug)
    printf("\nTS: HS=%.10g factors=%d kept=%d/%d jumps=%d threshold=%.8g",
      TS_IntegratedVolatility,TS_NumFactors,TS_KeptCount,TS_TimeSteps,
      TS_JumpCount,TS_Threshold);
  return 1;
}

var TS_GetIntegratedVolatility()
{
  if(!TS_Initialized) return 0.;
  return TS_IntegratedVolatility;
}

int TS_GetNumberOfFactors()
{
  if(!TS_Initialized) return 0;
  return TS_NumFactors;
}

int TS_GetEigenvalues(vars Destination, int Capacity)
{
  int K;
  if(!TS_Initialized || Capacity < TS_Pairs) return 0;
  for(K = 0; K < TS_Pairs; K++) Destination[K] = TS_Eigenvalues[K];
  return TS_Pairs;
}

int TS_GetFactorLoadings(int FactorIndex, vars Destination, int Capacity)
{
  int K;
  if(!TS_Initialized || FactorIndex < 0 || FactorIndex >= TS_Pairs) return 0;
  if(Capacity < TS_Pairs) return 0;
  for(K = 0; K < TS_Pairs; K++)
    Destination[K] = TS_Eigenvectors[K*TS_Pairs+FactorIndex];
  return TS_Pairs;
}

var TS_GetExplainedVariance(int FactorCount)
{
  if(!TS_Initialized || FactorCount <= 0 || FactorCount > TS_Pairs) return 0.;
  return TS_ExplainedVariance[FactorCount-1];
}

// Returns count; -1 = invalid/uninitialized/insufficient output capacity.
// Indices are zero-based return indices in the oldest -> newest window.
int TS_DetectJumps(vars Indices, vars Sizes, int Capacity)
{
  int N, K;
  if(!TS_Initialized || Capacity < TS_JumpCount) return -1;
  K = 0;
  for(N = 0; N < TS_TimeSteps; N++) {
    if(TS_Norms[N] > TS_Threshold) {
      Indices[K] = N;
      Sizes[K] = TS_Norms[N];
      K++;
    }
  }
  return K;
}

void TS_PrintDiagnostics()
{
  int K;
  if(!TS_Initialized) { printf("\nTS: analyzer not initialized"); return; }
  printf("\nTERM STRUCTURE SPOT-PROXY DIAGNOSTICS");
  printf("\nSteps=%d pairs=%d HS=%.10g factors=%d target=%.1f%%",
    TS_TimeSteps,TS_Pairs,TS_IntegratedVolatility,TS_NumFactors,
    TS_VarianceTarget*100.);
  printf("\nPreliminary=%.10g threshold=%.10g jumps=%d excluded=%d",
    TS_PreliminaryVariance,TS_Threshold,TS_JumpCount,TS_TimeSteps-TS_KeptCount);
  for(K = 0; K < TS_Pairs; K++)
    printf("\n  lambda[%d]=%.5f%% cumulative=%.5f%%",K,
      TS_Eigenvalues[K]*100.,TS_ExplainedVariance[K]*100.);
}
#endif

// ===== End TermStructureLib.h =====

// Configuration. Forex maturity proxy is preserved from the supplied EA.
string InpAsset = "EUR/USD";
int InpBarPeriod = 60;
int InpStartDate = 20240101;
int InpEndDate = 20241231;
int InpMaturity1 = 30;
int InpMaturity2 = 90;
int InpMaturity3 = 180;
int InpMaturity4 = 360;
int InpLookbackBars = 100;
var InpTruncLevel = 3.;
var InpVarianceThreshold = 0.85;
int InpUseDataDrivenTruncation = 1;
int InpRegularizeSmallTrace = 1;
var InpSignalThreshold = 0.000001;
int InpUseVolatilityScaling = 0;
var InpVolatilityScaleFactor = 3.;
int InpMinBarsBetweenSignals = 1;
int InpMinFactorsRequired = 1;
var InpLotSize = 0.1; // MT5 standard Forex lots, 100000 units each.
int InpUseAdaptiveLots = 0;
var InpRiskPercent = 2.;
var InpInitialCapital = 10000.;
var InpMaxDrawdownPercent = 15.;
var InpStopLossPips = 50.;
var InpTakeProfitPips = 125.;
var InpRiskRewardRatio = 2.5;
int InpUseTrailingStop = 1;
var InpTrailingStopPips = 40.;
var InpTrailingStepPips = 10.;
// Zorro plots replace MT5 objects; native trade markers show fills/exits.
int InpShowPanel = 1;
int InpShowSignals = 1;
int InpShowDebugLogs = 0;
int InpShowDiagnostics = 0;
int InpBuyColor = GREEN;
int InpSellColor = RED;

var TS_InputPrices[TS_MAX_PRICES];
var TS_FirstFactor[TS_MAX_PAIRS];
int TS_ConfigMaturities[4];
int TS_LastTradeBar = -1000000;
int TS_TotalSignals = 0;
int TS_WinningTrades = 0;
int TS_LosingTrades = 0;
var TS_CurrentDrawdown = 0.;
var TS_SlopeFactor = 0.;

int TS_Configure()
{
  if(InpLookbackBars < 2 || InpLookbackBars > TS_MAX_PRICES) return 0;
  if(InpTruncLevel <= 0. || InpVarianceThreshold <= 0. || InpVarianceThreshold > 1.) return 0;
  if(InpLotSize <= 0. || InpInitialCapital <= 0. || InpBarPeriod <= 0) return 0;
  if(InpRiskPercent <= 0. || InpRiskPercent > 100. || InpRiskRewardRatio <= 0.) return 0;
  if(InpMinFactorsRequired < 1 || InpMinFactorsRequired > 3) return 0;
  if(InpMinBarsBetweenSignals < 0 || InpSignalThreshold <= 0.) return 0;
  if(InpMaxDrawdownPercent < 0. || InpVolatilityScaleFactor < 0.) return 0;
  if(InpUseTrailingStop && (InpTrailingStopPips <= 0. || InpTrailingStepPips < 0.)) return 0;
  TS_ConfigMaturities[0] = InpMaturity1;
  TS_ConfigMaturities[1] = InpMaturity2;
  TS_ConfigMaturities[2] = InpMaturity3;
  TS_ConfigMaturities[3] = InpMaturity4;
  if(!TS_SetMaturities(TS_ConfigMaturities,4)) return 0;
  TS_Lookback = InpLookbackBars;
  TS_Alpha = InpTruncLevel;
  TS_VarianceTarget = InpVarianceThreshold;
  TS_DataDriven = InpUseDataDrivenTruncation;
  TS_Regularize = InpRegularizeSmallTrace;
  TS_Debug = InpShowDebugLogs;
  TS_LastTradeBar = -1000000;
  TS_TotalSignals = 0;
  TS_WinningTrades = 0;
  TS_LosingTrades = 0;
  return 1;
}

// Called by Zorro for price updates and closures, including SL/TP exits.
int TS_ManageTrade()
{
  var ExitPrice, Candidate, Distance, StepDistance;
  if(TradeIsClosed) {
    if(TradeProfit > 0.) TS_WinningTrades++;
    if(TradeProfit < 0.) TS_LosingTrades++;
    return 0;
  }
  if(!TradeIsOpen || !InpUseTrailingStop || InpStopLossPips <= 0.) return 0;
  if(TradeStopLimit == 0.) return 0;
  Distance = InpTrailingStopPips*PIP;
  StepDistance = InpTrailingStepPips*PIP;
  ExitPrice = TradePriceClose;
  if(TradeIsLong) {
    // Zorro prices are ask; long exits use bid, short exits use ask.
    ExitPrice -= Spread;
    Candidate = roundto(ExitPrice-Distance,PIP/10.);
    if(ExitPrice > TradePriceOpen+Distance && Candidate > TradeStopLimit+StepDistance)
      TradeStopLimit = Candidate;
  } else {
    Candidate = roundto(ExitPrice+Distance,PIP/10.);
    if(ExitPrice < TradePriceOpen-Distance && Candidate < TradeStopLimit-StepDistance)
      TradeStopLimit = Candidate;
  }
  return 0;
}

int TS_UpdateAnalysis()
{
  int K;
  // Only completed candles, newest at index N-1. No MQL series-flag reversal.
  for(K = 0; K < InpLookbackBars; K++)
    TS_InputPrices[K] = priceClose(InpLookbackBars-K);
  return TS_ComputeCovariations(TS_InputPrices,InpLookbackBars);
}

var TS_CalculateDrawdown()
{
  if(Balance <= 0.) return 0.;
  return max(0.,100.*(Balance-Equity)/Balance);
}

int TS_CheckTradingConditions()
{
  TS_CurrentDrawdown = TS_CalculateDrawdown();
  if(TS_CurrentDrawdown > InpMaxDrawdownPercent) return 0;
  if(TS_GetNumberOfFactors() < InpMinFactorsRequired) return 0;
  // Prevent regularization from manufacturing a tradable flat-price signal.
  if(TS_RealizedTrace <= 1.e-20) return 0;
  if(TS_GetIntegratedVolatility() < 1.e-10) return 0;
  return 1;
}

int TS_GenerateSignal()
{
  int K, Count;
  var WeightSum, Threshold;
  Count = TS_GetFactorLoadings(0,TS_FirstFactor,TS_MAX_PAIRS);
  if(Count < 2) return 0;
  TS_SlopeFactor = 0.;
  WeightSum = 0.;
  for(K = 0; K < Count; K++) {
    TS_SlopeFactor += TS_FirstFactor[K]*(K+1.);
    WeightSum += K+1.;
  }
  TS_SlopeFactor /= WeightSum;
  Threshold = InpSignalThreshold;
  if(InpUseVolatilityScaling)
    Threshold = max(Threshold,TS_GetIntegratedVolatility()*InpVolatilityScaleFactor);
  if(InpShowDebugLogs)
    printf("\nTS slope=%.8g threshold=%.8g",TS_SlopeFactor,Threshold);
  // Compatibility only: eigenvector signs are not economic directions.
  if(TS_SlopeFactor > Threshold) return 1;
  if(TS_SlopeFactor < -Threshold) return -1;
  return 0;
}

int TS_ShouldTakeSignal(int Direction)
{
  if(Direction > 0 && NumOpenLong > 0) return 0;
  if(Direction < 0 && NumOpenShort > 0) return 0;
  // The source permits reversal immediately, bypassing the cooldown.
  if(Direction > 0 && NumOpenShort > 0) return 1;
  if(Direction < 0 && NumOpenLong > 0) return 1;
  return Bar-TS_LastTradeBar >= InpMinBarsBetweenSignals;
}

int TS_ExecuteTrade(int Direction)
{
  int OrderLots;
  var StopDistance, ProfitDistance, Available, RiskPerLot, LotCount;
  TRADE* Entered;
  if(Direction > 0) {
    if(NumOpenShort > 0) exitShort();
    if(NumOpenShort > 0) return 0; // Do not hedge a pending/failed reversal.
    if(NumOpenLong > 0) return 0;
  } else {
    if(NumOpenLong > 0) exitLong();
    if(NumOpenLong > 0) return 0;
    if(NumOpenShort > 0) return 0;
  }
  if(PIP <= 0. || LotAmount <= 0. || MarginCost <= 0.) return 0;
  StopDistance = InpStopLossPips*PIP;
  if(InpStopLossPips <= 0.)
    StopDistance = max(TS_GetIntegratedVolatility()*3000.*PIP/10.,30.*PIP);
  ProfitDistance = InpTakeProfitPips*PIP;
  if(InpTakeProfitPips <= 0.) ProfitDistance = StopDistance*InpRiskRewardRatio;
  LotCount = floor(InpLotSize*100000./LotAmount+1.e-9);
  if(InpUseAdaptiveLots) {
    if(PIPCost <= 0.) return 0;
    // Use the ACTUAL stop (plus spread/commission), not a different proxy SL.
    RiskPerLot = (StopDistance+Spread)/PIP*PIPCost+Commission*LotAmount/10000.;
    if(RiskPerLot <= 0.) return 0;
    LotCount = floor(max(0.,Balance)*InpRiskPercent/100./RiskPerLot);
  }
  Available = max(0.,Equity-MarginVal)*0.8;
  LotCount = min(LotCount,floor(Available/MarginCost));
  // Never round UP to the minimum lot if it breaches risk or margin caps.
  if(LotCount < 1. || LotCount > 2000000000.) return 0;
  OrderLots = (int)LotCount;
  Stop = StopDistance;
  TakeProfit = ProfitDistance;
  Trail = 0.; // Exact pip-step trailing implemented in the TMF, not TrailStep.
  TrailStep = 0.;
  Margin = 0.;
  Risk = 0.;
  Lots = OrderLots;
  if(Direction > 0) Entered = enterLong(TS_ManageTrade);
  else Entered = enterShort(TS_ManageTrade);
  if(!Entered) return 0;
  // Market orders only. Do not treat an unfilled/rejected order as a fill.
  if(!(Entered->flags & TR_OPEN)) return 0;
  TS_LastTradeBar = Bar;
  if(InpShowSignals) {
    if(Direction > 0) plot("TS Buy",priceClose(0),MAIN|TRIANGLE,InpBuyColor);
    else plot("TS Sell",priceClose(0),MAIN|TRIANGLE,InpSellColor);
  }
  return 1;
}

void TS_UpdateDisplay()
{
  if(!InpShowPanel) return;
  // HS is a covariance norm, not a volatility or basis-point quote.
  plot("TS HS covariance norm",TS_GetIntegratedVolatility(),NEW,BLUE);
  plot("TS factors",TS_GetNumberOfFactors(),NEW,BLACK);
  plot("TS PC1 variance %",TS_Eigenvalues[0]*100.,NEW,BLUE);
  plot("TS drawdown %",TS_CurrentDrawdown,NEW,RED);
}

// Deterministic in-engine tests; invoked with -d TS_SELFTEST, never orders.
// ===== Embedded TermStructureSelfTest.h =====
#ifndef TERM_STRUCTURE_SELF_TEST_H
#define TERM_STRUCTURE_SELF_TEST_H
int TS_TestFailures = 0;
int TS_TestCount = 0;
var TS_TestMatrix[9];
var TS_TestVectors[9];
var TS_TestValues[3];
var TS_TestPrices[100];
var TS_TestJumpIndices[100];
var TS_TestJumpSizes[100];

void TS_TestAssert(int Passed, string Description)
{
  TS_TestCount++;
  if(!Passed) {
    TS_TestFailures++;
    printf("\nFAIL: %s",Description);
    file_append("Log/TermStructureSingleFileSelfTest.txt",strf("FAIL: %s\n",Description));
  }
}

void TS_RunSelfTests()
{
  int I, J, K, OK, Count;
  var Residual, Left, Right, Dot, Expected, Cumulative;
  TS_TestCount = 0;
  TS_TestFailures = 0;
  file_write("Log/TermStructureSingleFileSelfTest.txt","32-bit Zorro lite-C self-tests\n",0);
  TS_TestAssert(sizeof(int) == 4,"32-bit integer");
  TS_TestAssert(sizeof(void*) == 4,"32-bit host pointer");
  for(I = 0; I < 9; I++) TS_TestMatrix[I] = 0.;
  TS_TestMatrix[0] = 2.; TS_TestMatrix[1] = 1.;
  TS_TestMatrix[3] = 1.; TS_TestMatrix[4] = 2.; TS_TestMatrix[8] = 0.5;
  OK = TS_EigenDecomposition(TS_TestMatrix,3,TS_TestValues,TS_TestVectors);
  TS_TestAssert(OK,"Jacobi converges");
  TS_TestAssert(abs(TS_TestValues[0]-3.) < 1.e-10,"largest eigenvalue = 3");
  TS_TestAssert(abs(TS_TestValues[1]-1.) < 1.e-10,"second eigenvalue = 1");
  Residual = 0.;
  for(K = 0; K < 3; K++) {
    for(I = 0; I < 3; I++) {
      Left = 0.;
      for(J = 0; J < 3; J++) Left += TS_TestMatrix[I*3+J]*TS_TestVectors[J*3+K];
      Right = TS_TestValues[K]*TS_TestVectors[I*3+K];
      Residual = max(Residual,abs(Left-Right));
    }
  }
  TS_TestAssert(Residual < 1.e-10,"A*v=lambda*v residual");
  for(I = 0; I < 3; I++) {
    for(J = 0; J < 3; J++) {
      Dot = 0.;
      for(K = 0; K < 3; K++) Dot += TS_TestVectors[K*3+I]*TS_TestVectors[K*3+J];
      Expected = 0.;
      if(I == J) Expected = 1.;
      TS_TestAssert(abs(Dot-Expected) < 1.e-10,"eigenvector orthonormality");
    }
  }
  // Small-scale covariance must not be accepted as already diagonal.
  for(I = 0; I < 9; I++) TS_TestMatrix[I] *= 1.e-14;
  TS_TestAssert(TS_EigenDecomposition(TS_TestMatrix,3,TS_TestValues,TS_TestVectors),
    "Jacobi small-scale convergence");
  TS_TestAssert(abs(TS_TestValues[0]/3.e-14-1.) < 1.e-10,"relative Jacobi tolerance");
  TS_TestAssert(TS_Configure(),"default configuration");
  TS_Lookback = 100;
  TS_Regularize = 0;
  for(I = 0; I < 100; I++) TS_TestPrices[I] = exp(0.001*I);
  TS_TestAssert(TS_ComputeCovariations(TS_TestPrices,100),"spot proxy analysis");
  file_append("Log/TermStructureSingleFileSelfTest.txt",
    strf("MEASURED: trace=%.15g HS=%.15g C00=%.15g\n",
      TS_RealizedTrace,TS_IntegratedVolatility,TS_Truncated[0]));
  TS_TestAssert(TS_TimeSteps == 99 && TS_Pairs == 3,"sample dimensions");
  // Amplification squared: 8,48,216. Trace = 272*0.001^2.
  // lite-C numeric literals can enter arithmetic at float precision.
  TS_TestAssert(abs(TS_RealizedTrace/0.000272-1.) < 1.e-6,"realized covariance normalization");
  TS_TestAssert(abs(TS_Truncated[0]-0.000008) < 1.e-12,"truncated covariance normalization");
  TS_TestAssert(TS_NumFactors == 1 && TS_Eigenvalues[0] > 0.999999,"rank-one spot proxy");
  TS_TestAssert(abs(TS_IntegratedVolatility/0.000272-1.) < 1.e-6,"Hilbert-Schmidt norm");
  Cumulative = 0.;
  for(I = 0; I < 3; I++) {
    Cumulative += TS_Eigenvalues[I];
    TS_TestAssert(abs(TS_GetExplainedVariance(I+1)-Cumulative) < 1.e-12,
      "all cumulative explained variances initialized");
  }
  TS_TestAssert(TS_DetectJumps(TS_TestJumpIndices,TS_TestJumpSizes,100) == 0,
    "no jumps in constant small returns");
  for(I = 50; I < 100; I++) TS_TestPrices[I] *= exp(0.1);
  TS_TestAssert(TS_ComputeCovariations(TS_TestPrices,100),"jump sample analysis");
  Count = TS_DetectJumps(TS_TestJumpIndices,TS_TestJumpSizes,100);
  TS_TestAssert(Count == 1 && TS_TestJumpIndices[0] == 49.,"jump index and count");
  TS_TestAssert(TS_KeptCount == 98,"jump removed from truncated estimate");
  TS_TestAssert(TS_DetectJumps(TS_TestJumpIndices,TS_TestJumpSizes,0) == -1,
    "jump output capacity guard");
  TS_TestAssert(TS_GetEigenvalues(TS_TestValues,2) == 0,"eigenvalue capacity guard");
  TS_TestPrices[99] = 0.;
  TS_TestAssert(!TS_ComputeCovariations(TS_TestPrices,100) && !TS_Initialized,
    "invalid prices clear stale results");
  TS_TestAssert(!TS_ComputeCovariations(TS_TestPrices,99),"insufficient samples");
  for(I = 0; I < 100; I++) TS_TestPrices[I] = 1.;
  TS_Regularize = 1;
  TS_TestAssert(TS_ComputeCovariations(TS_TestPrices,100),"flat-data regularization compatibility");
  TS_TestAssert(TS_RealizedTrace == 0.,"flat-data guard trace");
  TS_TestAssert(!TS_CheckTradingConditions(),"flat data cannot trade after regularization");
  TS_ConfigMaturities[1] = 30;
  TS_TestAssert(!TS_SetMaturities(TS_ConfigMaturities,4),"duplicate maturities rejected");
  printf("\nTS SELFTEST: %d checks, %d failures",TS_TestCount,TS_TestFailures);
  file_append("Log/TermStructureSingleFileSelfTest.txt",
    strf("RESULT: %d checks, %d failures\n",TS_TestCount,TS_TestFailures));
}
#endif

// ===== End TermStructureSelfTest.h =====

function run()
{
#ifdef TS_SELFTEST
  if(is(INITRUN)) {
    TS_RunSelfTests();
    quit("TS self-test complete; see Log/TermStructureSingleFileSelfTest.txt");
  }
  return;
#endif
  BarPeriod = InpBarPeriod;
  StartDate = InpStartDate;
  EndDate = InpEndDate;
  LookBack = InpLookbackBars+1;
  Capital = InpInitialCapital;
  Hedge = 0;
  MaxLong = 1;
  MaxShort = 1;
  set(TICKS);
  if(is(INITRUN)) {
    if(!TS_Configure()) { quit("Invalid TS configuration"); return; }
    printf("\nTermStructureCovariation: spot proxy; factor-sign signal is experimental.");
  }
  if(!asset(InpAsset)) return;
  algo("TS");
  if(is(EXITRUN)) {
    printf("\nTS summary: signals=%d wins=%d losses=%d",
      TS_TotalSignals,TS_WinningTrades,TS_LosingTrades);
    return;
  }
  if(is(LOOKBACK)) return;
  if(!TS_UpdateAnalysis()) return;
  TS_CurrentDrawdown = TS_CalculateDrawdown();
  TS_UpdateDisplay();
  if(!TS_CheckTradingConditions()) return;
  int Direction = TS_GenerateSignal();
  if(Direction != 0 && TS_ShouldTakeSignal(Direction)) {
    TS_TotalSignals++;
    if(InpShowDiagnostics) TS_PrintDiagnostics();
    TS_ExecuteTrade(Direction);
  }
}

Last edited by TipmyPip; 2 hours ago.