boss/Scripts/BOSS/BOSSSelfTest.mq5

230 linhas
8,4 KiB
MQL5

2026-07-13 16:05:51 +00:00
//+------------------------------------------------------------------+
//| BOSSSelfTest.mq5|
//| MMQ — Muhammad Minhas Qamar |
//| www.mql5.com/en/articles/23491 |
//+------------------------------------------------------------------+
#property copyright "MMQ — Muhammad Minhas Qamar"
#property link "https://www.mql5.com/en/articles/23491"
#property description "Behavioral Self-Test"
#property version "1.00"
#property strict
#property script_show_inputs
#include <BOSS\BOSS.mqh>
//--- shape parameters shared by every synthetic series in the test.
input int WordLen = 2; // SFA coefficients kept (word = 2*WordLen letters).
input int Alphabet = 4; // letters per coefficient slot
input int Window = 24; // sliding window length
input int SeriesLen = 240; // length of each labelled/query series
input double NoiseAmp = 0.35; // noise amplitude for the robustness test
//--- three canonical shapes the classifier must tell apart. Each is
//--- built on a common grid so only the shape differs, not the scale.
//+------------------------------------------------------------------+
//| Fill 'out' with a trending series: a steady drift plus a small |
//| oscillation and optional noise. |
//+------------------------------------------------------------------+
void MakeTrend(double &out[],int len,double noise)
{
ArrayResize(out,len);
for(int i=0;i<len;i++)
out[i]=0.02*i
+0.5*MathSin(2.0*M_PI*i/Window)
+noise*(MathRand()/32767.0-0.5);
}
//+------------------------------------------------------------------+
//| Fill 'out' with a ranging series: a clean oscillation about a |
//| flat mean, no drift. |
//+------------------------------------------------------------------+
void MakeRange(double &out[],int len,double noise)
{
ArrayResize(out,len);
for(int i=0;i<len;i++)
out[i]=MathSin(2.0*M_PI*i/Window)
+noise*(MathRand()/32767.0-0.5);
}
//+------------------------------------------------------------------+
//| Fill 'out' with a volatile series: large erratic jumps with no |
//| coherent period. |
//+------------------------------------------------------------------+
void MakeVolatile(double &out[],int len,double noise)
{
ArrayResize(out,len);
double v=0.0;
for(int i=0;i<len;i++)
{
v+=1.5*(MathRand()/32767.0-0.5); // random walk
out[i]=v+noise*(MathRand()/32767.0-0.5);
}
}
//+------------------------------------------------------------------+
//| Build the MCB training set: many windows drawn from all three |
//| shapes so the learned breakpoints see the full spread. |
//+------------------------------------------------------------------+
void BuildTrainingWindows(double &windows[],int &count,int win)
{
int per=120; // windows per shape
count=per*3;
ArrayResize(windows,count*win);
double s[];
int idx=0;
for(int rep=0;rep<per;rep++)
{
MakeTrend(s,win,NoiseAmp);
for(int i=0;i<win;i++)
windows[idx*win+i]=s[i];
idx++;
MakeRange(s,win,NoiseAmp);
for(int i=0;i<win;i++)
windows[idx*win+i]=s[i];
idx++;
MakeVolatile(s,win,NoiseAmp);
for(int i=0;i<win;i++)
windows[idx*win+i]=s[i];
idx++;
}
}
//+------------------------------------------------------------------+
//| Test 1: does the classifier separate the three shapes? |
//| Train one clean example per class, then classify many fresh, |
//| independently generated series and report the accuracy. |
//+------------------------------------------------------------------+
void TestDiscrimination(CBossClassifier &clf)
{
Print("=== Test 1: discrimination (clean queries) ===");
int trials=30, correct=0;
double bd;
double s[];
string truth[3]= {"trend","range","volatile"};
for(int t=0;t<trials;t++)
for(int c=0;c<3;c++)
{
if(c==0)
MakeTrend(s,SeriesLen,NoiseAmp*0.3);
if(c==1)
MakeRange(s,SeriesLen,NoiseAmp*0.3);
if(c==2)
MakeVolatile(s,SeriesLen,NoiseAmp*0.3);
string got=clf.Classify(s,SeriesLen,bd);
if(got==truth[c])
correct++;
}
int total=trials*3;
PrintFormat(" accuracy: %d/%d = %.1f%%",correct,total,100.0*correct/total);
}
//+------------------------------------------------------------------+
//| Test 2: noise robustness - the headline claim. |
//| Classify range-shaped queries at rising noise levels and show |
//| the accuracy holds where a raw point-wise matcher would fail. |
//+------------------------------------------------------------------+
void TestNoiseRobustness(CBossClassifier &clf)
{
Print("=== Test 2: noise robustness (range queries) ===");
double levels[4]= {0.1,0.5,1.0,1.5};
double s[],bd;
for(int L=0;L<4;L++)
{
int correct=0, trials=40;
for(int t=0;t<trials;t++)
{
MakeRange(s,SeriesLen,levels[L]);
if(clf.Classify(s,SeriesLen,bd)=="range")
correct++;
}
PrintFormat(" noise=%.1f -> %d/%d correct (%.0f%%)",
levels[L],correct,trials,100.0*correct/trials);
}
}
//+------------------------------------------------------------------+
//| Test 3: vocabulary readout - the interpretability money-shot. |
//| Print the most frequent words each clean shape produces, so the |
//| reader can literally see the "dialect" of each regime. |
//+------------------------------------------------------------------+
void TestVocabulary(const CBossClassifier &clf)
{
Print("=== Test 3: vocabulary of each shape ===");
const CBossTransform *tr=clf.Transform();
if(tr==NULL)
{
Print(" no transform");
return;
}
double s[];
string names[3]= {"trend","range","volatile"};
for(int c=0;c<3;c++)
{
if(c==0)
MakeTrend(s,SeriesLen,NoiseAmp*0.3);
if(c==1)
MakeRange(s,SeriesLen,NoiseAmp*0.3);
if(c==2)
MakeVolatile(s,SeriesLen,NoiseAmp*0.3);
CBossHistogram h;
tr.Transform(s,SeriesLen,h);
//--- find the top-3 words by count (small vocab: linear scans)
string top="";
for(int pick=0;pick<3 && pick<h.Size();pick++)
{
int bestC=-1, bestI=-1;
for(int i=0;i<h.Size();i++)
{
bool taken=(StringFind(top,"["+h.WordAt(i)+"]")>=0);
if(!taken && h.CountAt(i)>bestC)
{ bestC=h.CountAt(i); bestI=i; }
}
if(bestI<0)
break;
top+=StringFormat("[%s]x%d ",h.WordAt(bestI),h.CountAt(bestI));
}
PrintFormat(" %-9s (%d distinct words): %s",names[c],h.Size(),top);
}
}
//+------------------------------------------------------------------+
//| Script entry: build the classifier, train one clean example per |
//| class, then run the three behavioural tests. |
//+------------------------------------------------------------------+
void OnStart()
{
MathSrand(20260712);
//--- learn MCB breakpoints from a broad window pool
double windows[];
int nwin;
BuildTrainingWindows(windows,nwin,Window);
CBossClassifier clf;
if(!clf.Build(WordLen,Alphabet,windows,nwin,Window))
{ Print("Build() failed - check WordLen/Alphabet/Window vs limits"); return; }
PrintFormat("classifier built: word=%d letters, alphabet=%d, window=%d",
WordLen*2,Alphabet,Window);
//--- one clean labelled prototype per class
double s[];
MakeTrend(s,SeriesLen,NoiseAmp*0.2);
clf.AddExample(s,SeriesLen,"trend");
MakeRange(s,SeriesLen,NoiseAmp*0.2);
clf.AddExample(s,SeriesLen,"range");
MakeVolatile(s,SeriesLen,NoiseAmp*0.2);
clf.AddExample(s,SeriesLen,"volatile");
PrintFormat("trained on %d prototypes",clf.TrainCount());
TestDiscrimination(clf);
TestNoiseRobustness(clf);
TestVocabulary(clf);
Print("=== self-test complete ===");
}
//+------------------------------------------------------------------+