#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);
}
}