MultiAgentTest v7: zero magic constants, neural orchestrator, multi-position, agent interaction fixes
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
#ifndef MARKET_DATA_MQH
|
||||
#define MARKET_DATA_MQH
|
||||
|
||||
struct MarketData {
|
||||
string symbol;
|
||||
ENUM_TIMEFRAMES timeframe;
|
||||
int count;
|
||||
double open[];
|
||||
double high[];
|
||||
double low[];
|
||||
double close[];
|
||||
double volume[];
|
||||
datetime time[];
|
||||
|
||||
MarketData() {}
|
||||
|
||||
MarketData(string sym, ENUM_TIMEFRAMES tf, int cnt) {
|
||||
symbol = sym;
|
||||
timeframe = tf;
|
||||
count = cnt;
|
||||
ResizeArrays(cnt);
|
||||
}
|
||||
|
||||
void ResizeArrays(int cnt) {
|
||||
count = cnt;
|
||||
ArrayResize(open, cnt);
|
||||
ArrayResize(high, cnt);
|
||||
ArrayResize(low, cnt);
|
||||
ArrayResize(close, cnt);
|
||||
ArrayResize(volume, cnt);
|
||||
ArrayResize(time, cnt);
|
||||
}
|
||||
|
||||
bool Fetch(int barsBack=0) {
|
||||
MqlRates rates[];
|
||||
ArrayResize(rates, count);
|
||||
ArraySetAsSeries(rates, true);
|
||||
int copied = CopyRates(symbol, timeframe, barsBack, count, rates);
|
||||
if(copied != count) {
|
||||
Print("MarketData::Fetch failed: copied ", copied, " of ", count);
|
||||
return false;
|
||||
}
|
||||
ArraySetAsSeries(open, true);
|
||||
ArraySetAsSeries(high, true);
|
||||
ArraySetAsSeries(low, true);
|
||||
ArraySetAsSeries(close, true);
|
||||
ArraySetAsSeries(volume, true);
|
||||
ArraySetAsSeries(time, true);
|
||||
for(int i=0; i<count; i++) {
|
||||
open[i] = rates[i].open;
|
||||
high[i] = rates[i].high;
|
||||
low[i] = rates[i].low;
|
||||
close[i] = rates[i].close;
|
||||
volume[i]= (double)rates[i].tick_volume;
|
||||
time[i] = rates[i].time;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
double Close(int shift=0) const {
|
||||
if(shift >= 0 && shift < count) return close[shift];
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double High(int shift=0) const {
|
||||
if(shift >= 0 && shift < count) return high[shift];
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double Low(int shift=0) const {
|
||||
if(shift >= 0 && shift < count) return low[shift];
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double TrueRange(int shift=0) const {
|
||||
if(shift < 0 || shift >= count-1) return 0;
|
||||
double hl = high[shift] - low[shift];
|
||||
double hc = MathAbs(high[shift] - close[shift+1]);
|
||||
double lc = MathAbs(low[shift] - close[shift+1]);
|
||||
return MathMax(hl, MathMax(hc, lc));
|
||||
}
|
||||
|
||||
double ATR(int period=14) const {
|
||||
if(count < period + 1) return 0;
|
||||
double sum = 0;
|
||||
for(int i=0; i<period; i++) sum += TrueRange(i);
|
||||
return sum / period;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,838 @@
|
||||
#ifndef NEURAL_NET_MQH
|
||||
#define NEURAL_NET_MQH
|
||||
#include "Statistics.mqh"
|
||||
|
||||
class CNeuralNet {
|
||||
private:
|
||||
int m_inputs;
|
||||
int m_hidden;
|
||||
int m_outputs;
|
||||
|
||||
matrix m_W1;
|
||||
vector m_b1;
|
||||
matrix m_W2;
|
||||
vector m_b2;
|
||||
|
||||
matrix m_mW1, m_vW1;
|
||||
vector m_mb1, m_vb1;
|
||||
matrix m_mW2, m_vW2;
|
||||
vector m_mb2, m_vb2;
|
||||
int m_t;
|
||||
double m_adamBeta1;
|
||||
double m_adamBeta2;
|
||||
double m_adamEps;
|
||||
double m_beta1T;
|
||||
double m_beta2T;
|
||||
|
||||
double m_l2;
|
||||
int m_epochsTrained;
|
||||
double m_lastLoss;
|
||||
bool m_initialized;
|
||||
|
||||
// HeInit per ReLU (NeuroBook §1.3)
|
||||
double HeScale(int fanIn) const {
|
||||
return MathSqrt(6.0 / MathMax(1, fanIn));
|
||||
}
|
||||
|
||||
// Dropout (NeuroBook §6.2)
|
||||
double m_dropoutRate;
|
||||
double m_dropoutMask[];
|
||||
|
||||
// BatchNorm semplificata su hidden (NeuroBook §6.3)
|
||||
// Running stats (EMA) + learnable affine
|
||||
vector m_bnGamma; // scala learnable
|
||||
vector m_bnBeta; // shift learnable
|
||||
vector m_bnRunningMean; // media mobile
|
||||
vector m_bnRunningVar; // varianza mobile
|
||||
double m_bnMomentum;
|
||||
double m_bnEps;
|
||||
|
||||
// Debug: loss history
|
||||
double m_lossHistory[];
|
||||
string m_lossCsvFn;
|
||||
|
||||
void ApplyDropout(vector &h) {
|
||||
for(int i = 0; i < h.Size(); i++) {
|
||||
m_dropoutMask[i] = ((double)MathRand() / 32767.0) > m_dropoutRate ? 1.0 : 0.0;
|
||||
h[i] *= m_dropoutMask[i];
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyBN(vector &h) {
|
||||
// Normalizza h IN PLACE: per-neuron stats (NeuroBook §6.3)
|
||||
for(int i = 0; i < h.Size(); i++) {
|
||||
double denom = MathSqrt(m_bnRunningVar[i] + m_bnEps);
|
||||
double standardized = (h[i] - m_bnRunningMean[i]) / denom;
|
||||
h[i] = m_bnGamma[i] * standardized + m_bnBeta[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Normalizza E AGGIORNA running stats (training, batch_size=1).
|
||||
// Usa running stats per normalizzare (stessa modalità di inference) per
|
||||
// evitare degenerazione con batch=1 (var=0). (NeuroBook §6.3, online BN)
|
||||
void TrainBN(const vector &h_in, vector &h_out) {
|
||||
h_out.Resize(h_in.Size());
|
||||
for(int i = 0; i < h_in.Size(); i++) {
|
||||
// Update running stats (per-neuron EMA)
|
||||
m_bnRunningMean[i] = m_bnMomentum * m_bnRunningMean[i] + (1.0 - m_bnMomentum) * h_in[i];
|
||||
double dx = h_in[i] - m_bnRunningMean[i];
|
||||
m_bnRunningVar[i] = m_bnMomentum * m_bnRunningVar[i] + (1.0 - m_bnMomentum) * dx * dx;
|
||||
// Normalize using running stats (same as ApplyBN)
|
||||
double denom = MathSqrt(m_bnRunningVar[i] + m_bnEps);
|
||||
double standardized = (h_in[i] - m_bnRunningMean[i]) / denom;
|
||||
h_out[i] = m_bnGamma[i] * standardized + m_bnBeta[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Backward attraverso BN: dh_out[i] = dh_norm[i] * gamma[i] / sqrt(runningVar[i] + eps)
|
||||
void BNBackward(vector &dh_out, const vector &h_in, const vector &dh_norm) {
|
||||
dh_out.Resize(h_in.Size());
|
||||
for(int i = 0; i < h_in.Size(); i++) {
|
||||
double denom = MathSqrt(m_bnRunningVar[i] + m_bnEps);
|
||||
dh_out[i] = dh_norm[i] * m_bnGamma[i] / denom;
|
||||
}
|
||||
}
|
||||
|
||||
double ReLU(double x) const {
|
||||
return (x > 0.0) ? x : 0.0;
|
||||
}
|
||||
|
||||
double ReLUDeriv(double x) const {
|
||||
return (x > 0.0) ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
void Softmax(vector &v) const {
|
||||
double maxVal = v[0];
|
||||
for(int i = 1; i < v.Size(); i++)
|
||||
if(v[i] > maxVal) maxVal = v[i];
|
||||
|
||||
double sum = 0.0;
|
||||
for(int i = 0; i < v.Size(); i++) {
|
||||
v[i] = MathExp(v[i] - maxVal);
|
||||
sum += v[i];
|
||||
}
|
||||
double eps = DATA_EPS(sum);
|
||||
if(sum > eps) {
|
||||
for(int i = 0; i < v.Size(); i++)
|
||||
v[i] /= sum;
|
||||
} else {
|
||||
double eq = 1.0 / v.Size();
|
||||
for(int i = 0; i < v.Size(); i++)
|
||||
v[i] = eq;
|
||||
}
|
||||
}
|
||||
|
||||
void BiasInit(vector &v) {
|
||||
for(int i = 0; i < v.Size(); i++)
|
||||
v[i] = 0.0;
|
||||
}
|
||||
|
||||
void ZeroMatrix(matrix &m) {
|
||||
for(int r = 0; r < (int)m.Rows(); r++)
|
||||
for(int c = 0; c < (int)m.Cols(); c++)
|
||||
m[r][c] = 0.0;
|
||||
}
|
||||
|
||||
void ZeroVector(vector &v) {
|
||||
for(int i = 0; i < v.Size(); i++)
|
||||
v[i] = 0.0;
|
||||
}
|
||||
|
||||
void AdamUpdate(matrix ¶m, matrix &m, matrix &v, matrix &grad, double lr) {
|
||||
for(int r = 0; r < (int)param.Rows(); r++) {
|
||||
for(int c = 0; c < (int)param.Cols(); c++) {
|
||||
double g = grad[r][c] + m_l2 * param[r][c];
|
||||
|
||||
m[r][c] = m_adamBeta1 * m[r][c] + (1.0 - m_adamBeta1) * g;
|
||||
v[r][c] = m_adamBeta2 * v[r][c] + (1.0 - m_adamBeta2) * g * g;
|
||||
|
||||
double mHat = m[r][c] / (1.0 - m_beta1T);
|
||||
double vHat = v[r][c] / (1.0 - m_beta2T);
|
||||
|
||||
param[r][c] -= lr * mHat / (MathSqrt(vHat) + m_adamEps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AdamUpdate(vector ¶m, vector &m, vector &v, vector &grad, double lr) {
|
||||
for(int i = 0; i < param.Size(); i++) {
|
||||
double g = grad[i] + m_l2 * param[i];
|
||||
|
||||
m[i] = m_adamBeta1 * m[i] + (1.0 - m_adamBeta1) * g;
|
||||
v[i] = m_adamBeta2 * v[i] + (1.0 - m_adamBeta2) * g * g;
|
||||
|
||||
double mHat = m[i] / (1.0 - m_beta1T);
|
||||
double vHat = v[i] / (1.0 - m_beta2T);
|
||||
|
||||
param[i] -= lr * mHat / (MathSqrt(vHat) + m_adamEps);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
CNeuralNet()
|
||||
: m_inputs(0), m_hidden(0), m_outputs(0),
|
||||
m_t(0), m_beta1T(1.0), m_beta2T(1.0),
|
||||
m_l2(0), m_epochsTrained(0), m_lastLoss(0.0),
|
||||
m_initialized(false),
|
||||
m_dropoutRate(0), m_bnMomentum(0), m_bnEps(0) {}
|
||||
|
||||
void Init(int inputs, int hidden, int outputs, double l2 = -1, double dropoutRate = -1) {
|
||||
m_inputs = inputs;
|
||||
m_hidden = hidden;
|
||||
m_outputs = outputs;
|
||||
|
||||
// Iperparametri dall'architettura:
|
||||
// hidden min. = 2 (un neurone non può fare computazione utile)
|
||||
// β₂/β₁ ratio = 100 (Adam originale: 0.999/0.9)
|
||||
// BN window = 5× hidden (momentum più lento per layer più grandi)
|
||||
int totalParams = inputs * hidden + hidden + hidden * outputs + outputs;
|
||||
int minHidden = MathMax(hidden, 2);
|
||||
m_l2 = 1.0 / MathMax(totalParams, 1); // L2 = 1/param (1 = nessun parametro → L2=1)
|
||||
m_dropoutRate = 1.0 / MathSqrt((double)minHidden); // Dropout: 1/sqrt(hidden)
|
||||
m_adamBeta1 = 1.0 - 1.0 / (double)minHidden; // β₁ = 1 - 1/hidden
|
||||
m_adamBeta2 = 1.0 - 1.0 / (double)MathMax(minHidden * 100, 200); // β₂ = 1 - 1/(hidden×100)
|
||||
m_adamEps = DATA_EPS(1.0); // Adam ε: machine epsilon
|
||||
m_bnMomentum = 1.0 - 1.0 / (double)MathMax(minHidden * 5, 10); // BN momentum = 1 - 1/(hidden×5)
|
||||
m_bnEps = DATA_EPS(1.0); // BN ε: machine epsilon
|
||||
|
||||
// Se chiamata esterna vuole override, usa quelli
|
||||
if(l2 >= 0) m_l2 = l2;
|
||||
if(dropoutRate >= 0) m_dropoutRate = dropoutRate;
|
||||
|
||||
m_W1.Init(inputs, hidden);
|
||||
m_b1.Init(hidden);
|
||||
m_W2.Init(hidden, outputs);
|
||||
m_b2.Init(outputs);
|
||||
|
||||
// He Init per ReLU (NeuroBook §1.3)
|
||||
{
|
||||
double scale = HeScale(inputs);
|
||||
MathSrand(GetTickCount());
|
||||
for(int r = 0; r < inputs; r++)
|
||||
for(int c = 0; c < hidden; c++)
|
||||
m_W1[r][c] = ((double)MathRand() / 32767.0 * 2.0 - 1.0) * scale;
|
||||
}
|
||||
BiasInit(m_b1);
|
||||
{
|
||||
double scale = HeScale(hidden);
|
||||
for(int r = 0; r < hidden; r++)
|
||||
for(int c = 0; c < outputs; c++)
|
||||
m_W2[r][c] = ((double)MathRand() / 32767.0 * 2.0 - 1.0) * scale;
|
||||
}
|
||||
BiasInit(m_b2);
|
||||
|
||||
m_mW1.Init(inputs, hidden); ZeroMatrix(m_mW1);
|
||||
m_vW1.Init(inputs, hidden); ZeroMatrix(m_vW1);
|
||||
m_mb1.Init(hidden); ZeroVector(m_mb1);
|
||||
m_vb1.Init(hidden); ZeroVector(m_vb1);
|
||||
m_mW2.Init(hidden, outputs); ZeroMatrix(m_mW2);
|
||||
m_vW2.Init(hidden, outputs); ZeroMatrix(m_vW2);
|
||||
m_mb2.Init(outputs); ZeroVector(m_mb2);
|
||||
m_vb2.Init(outputs); ZeroVector(m_vb2);
|
||||
|
||||
// Dropout mask
|
||||
ArrayResize(m_dropoutMask, hidden);
|
||||
|
||||
// BatchNorm (NeuroBook §6.3)
|
||||
m_bnGamma.Init(hidden);
|
||||
m_bnBeta.Init(hidden);
|
||||
m_bnRunningMean.Init(hidden);
|
||||
m_bnRunningVar.Init(hidden);
|
||||
for(int i = 0; i < hidden; i++) {
|
||||
m_bnGamma[i] = 1.0;
|
||||
m_bnBeta[i] = 0.0;
|
||||
m_bnRunningMean[i] = 0.0;
|
||||
m_bnRunningVar[i] = 1.0;
|
||||
}
|
||||
|
||||
m_t = 0;
|
||||
m_beta1T = 1.0;
|
||||
m_beta2T = 1.0;
|
||||
m_epochsTrained = 0;
|
||||
m_lastLoss = 0.0;
|
||||
m_initialized = true;
|
||||
ArrayResize(m_lossHistory, 0);
|
||||
m_lossCsvFn = "";
|
||||
}
|
||||
|
||||
void ForwardPass(vector &input, vector &output, vector &h1Cache) {
|
||||
h1Cache.Resize(m_hidden);
|
||||
for(int i = 0; i < m_hidden; i++) {
|
||||
double sum = m_b1[i];
|
||||
for(int j = 0; j < m_inputs; j++)
|
||||
sum += input[j] * m_W1[j][i];
|
||||
h1Cache[i] = ReLU(sum);
|
||||
}
|
||||
// BatchNorm inferenza: normalizza con running stats (NeuroBook §6.3)
|
||||
ApplyBN(h1Cache);
|
||||
// Dropout scaling in inferenza: scale = 1 - dropoutRate (NeuroBook §6.2)
|
||||
if(m_dropoutRate > 0) {
|
||||
for(int i = 0; i < h1Cache.Size(); i++)
|
||||
h1Cache[i] *= (1.0 - m_dropoutRate);
|
||||
}
|
||||
|
||||
output.Resize(m_outputs);
|
||||
for(int i = 0; i < m_outputs; i++) {
|
||||
double sum = m_b2[i];
|
||||
for(int j = 0; j < m_hidden; j++)
|
||||
sum += h1Cache[j] * m_W2[j][i];
|
||||
output[i] = sum;
|
||||
}
|
||||
Softmax(output);
|
||||
}
|
||||
|
||||
void Forward(vector &input, vector &output) {
|
||||
vector h1Cache;
|
||||
ForwardPass(input, output, h1Cache);
|
||||
}
|
||||
|
||||
double TrainSample(vector &input, vector &target, double lr, double weight = 1.0) {
|
||||
if(!m_initialized) return -1.0;
|
||||
|
||||
// ── Forward ──
|
||||
// Layer 1: W1*x + b1 → z1 → ReLU → h1_raw → Dropout → h1_drop → BN → h1_norm
|
||||
vector z1(m_hidden);
|
||||
vector h1_raw(m_hidden);
|
||||
for(int i = 0; i < m_hidden; i++) {
|
||||
z1[i] = m_b1[i];
|
||||
for(int j = 0; j < m_inputs; j++)
|
||||
z1[i] += input[j] * m_W1[j][i];
|
||||
h1_raw[i] = ReLU(z1[i]);
|
||||
}
|
||||
|
||||
// Dropout (NeuroBook §6.2): salva in m_dropoutMask, applica a h1_drop
|
||||
vector h1_drop(m_hidden);
|
||||
if(m_dropoutRate > 0.0) {
|
||||
for(int i = 0; i < m_hidden; i++) {
|
||||
m_dropoutMask[i] = ((double)MathRand() / 32767.0) > m_dropoutRate ? 1.0 : 0.0;
|
||||
h1_drop[i] = h1_raw[i] * m_dropoutMask[i];
|
||||
}
|
||||
} else {
|
||||
for(int i = 0; i < m_hidden; i++)
|
||||
h1_drop[i] = h1_raw[i];
|
||||
}
|
||||
|
||||
// BatchNorm (NeuroBook §6.3): normalizza h1_drop → h1_norm, aggiorna running stats
|
||||
vector h1_cache = h1_drop; // copia per backward
|
||||
vector h1_norm(m_hidden);
|
||||
TrainBN(h1_drop, h1_norm);
|
||||
|
||||
// Layer 2: W2*h1_norm + b2 → z2 → Softmax → output
|
||||
vector z2(m_outputs);
|
||||
for(int i = 0; i < m_outputs; i++) {
|
||||
z2[i] = m_b2[i];
|
||||
for(int j = 0; j < m_hidden; j++)
|
||||
z2[i] += h1_norm[j] * m_W2[j][i];
|
||||
}
|
||||
vector output(m_outputs);
|
||||
for(int i = 0; i < m_outputs; i++) output[i] = z2[i];
|
||||
Softmax(output);
|
||||
|
||||
// ── Loss: CCE pesata + L2 ──
|
||||
double loss = 0.0;
|
||||
for(int i = 0; i < m_outputs; i++) {
|
||||
double p = MathMax(DATA_EPS(output[i]), output[i]);
|
||||
loss -= weight * target[i] * MathLog(p);
|
||||
}
|
||||
loss += 0.5 * m_l2 * (L2Norm(m_W1) + L2Norm(m_W2));
|
||||
m_lastLoss = loss;
|
||||
|
||||
m_t++;
|
||||
m_beta1T *= m_adamBeta1;
|
||||
m_beta2T *= m_adamBeta2;
|
||||
|
||||
// ── Backward ──
|
||||
// dL/dz2 = weight * (output - target) (CCE+Softmax combinata, pesata)
|
||||
vector dL_dz2(m_outputs);
|
||||
for(int i = 0; i < m_outputs; i++)
|
||||
dL_dz2[i] = weight * (output[i] - target[i]);
|
||||
|
||||
// dL/dW2 = h1_norm ⊗ dL/dz2
|
||||
matrix dL_dW2(m_hidden, m_outputs);
|
||||
for(int i = 0; i < m_hidden; i++)
|
||||
for(int j = 0; j < m_outputs; j++)
|
||||
dL_dW2[i][j] = h1_norm[i] * dL_dz2[j];
|
||||
|
||||
// dL/db2 = dL/dz2
|
||||
vector dL_db2(m_outputs);
|
||||
for(int i = 0; i < m_outputs; i++)
|
||||
dL_db2[i] = dL_dz2[i];
|
||||
|
||||
// dL/dh1_norm = W2^T * dL/dz2
|
||||
vector dL_dh1_norm(m_hidden);
|
||||
for(int i = 0; i < m_hidden; i++) {
|
||||
double sum = 0.0;
|
||||
for(int j = 0; j < m_outputs; j++)
|
||||
sum += dL_dz2[j] * m_W2[i][j];
|
||||
dL_dh1_norm[i] = sum;
|
||||
}
|
||||
|
||||
// BatchNorm backward: dL/dh1_drop = gamma/sqrt(var) * dL/dh1_norm
|
||||
// + update gamma, beta
|
||||
vector dL_dh1_drop(m_hidden);
|
||||
for(int i = 0; i < m_hidden; i++) {
|
||||
double denom = MathSqrt(m_bnRunningVar[i] + m_bnEps);
|
||||
double h_std = (h1_cache[i] - m_bnRunningMean[i]) / denom;
|
||||
double gOld = m_bnGamma[i]; // salva gamma pre-update per backward pass-through
|
||||
m_bnGamma[i] -= lr * dL_dh1_norm[i] * h_std;
|
||||
m_bnBeta[i] -= lr * dL_dh1_norm[i];
|
||||
dL_dh1_drop[i] = dL_dh1_norm[i] * gOld / denom;
|
||||
}
|
||||
|
||||
// Dropout backward: applica stessa mask
|
||||
if(m_dropoutRate > 0.0) {
|
||||
for(int i = 0; i < m_hidden; i++)
|
||||
dL_dh1_drop[i] *= m_dropoutMask[i];
|
||||
}
|
||||
|
||||
// dL/dz1 = dL/dh1_drop * ReLU'(z1)
|
||||
vector dL_dz1(m_hidden);
|
||||
for(int i = 0; i < m_hidden; i++)
|
||||
dL_dz1[i] = dL_dh1_drop[i] * ReLUDeriv(z1[i]);
|
||||
|
||||
// dL/dW1 = input ⊗ dL/dz1
|
||||
matrix dL_dW1(m_inputs, m_hidden);
|
||||
for(int i = 0; i < m_inputs; i++)
|
||||
for(int j = 0; j < m_hidden; j++)
|
||||
dL_dW1[i][j] = input[i] * dL_dz1[j];
|
||||
|
||||
// dL/db1 = dL/dz1
|
||||
vector dL_db1(m_hidden);
|
||||
for(int i = 0; i < m_hidden; i++)
|
||||
dL_db1[i] = dL_dz1[i];
|
||||
|
||||
// Adam update
|
||||
AdamUpdate(m_W1, m_mW1, m_vW1, dL_dW1, lr);
|
||||
AdamUpdate(m_b1, m_mb1, m_vb1, dL_db1, lr);
|
||||
AdamUpdate(m_W2, m_mW2, m_vW2, dL_dW2, lr);
|
||||
AdamUpdate(m_b2, m_mb2, m_vb2, dL_db2, lr);
|
||||
|
||||
return loss;
|
||||
}
|
||||
|
||||
double L2Norm(matrix &m) {
|
||||
double sum = 0.0;
|
||||
for(int r = 0; r < (int)m.Rows(); r++)
|
||||
for(int c = 0; c < (int)m.Cols(); c++)
|
||||
sum += m[r][c] * m[r][c];
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Overload senza pesi (compatibilità)
|
||||
double Train(matrix &features, matrix &targets, int epochs, double lr) {
|
||||
vector empty;
|
||||
return Train(features, targets, epochs, lr, empty);
|
||||
}
|
||||
|
||||
double Train(matrix &features, matrix &targets, int epochs, double lr, vector &weights) {
|
||||
if(!m_initialized || features.Rows() == 0) return -1.0;
|
||||
|
||||
int n = (int)features.Rows();
|
||||
double avgLoss = 0.0;
|
||||
ArrayResize(m_lossHistory, epochs);
|
||||
int logEvery = MathMax(1, epochs / 10);
|
||||
|
||||
for(int epoch = 0; epoch < epochs; epoch++) {
|
||||
int indices[];
|
||||
ArrayResize(indices, n);
|
||||
for(int i = 0; i < n; i++) indices[i] = i;
|
||||
|
||||
for(int i = n - 1; i > 0; i--) {
|
||||
int j = MathRand() % (i + 1);
|
||||
int tmp = indices[i];
|
||||
indices[i] = indices[j];
|
||||
indices[j] = tmp;
|
||||
}
|
||||
|
||||
double epochLoss = 0.0;
|
||||
for(int s = 0; s < n; s++) {
|
||||
int idx = indices[s];
|
||||
vector inp = features.Row(idx);
|
||||
vector tgt = targets.Row(idx);
|
||||
double w = (weights.Size() > idx) ? weights[idx] : 1.0;
|
||||
epochLoss += TrainSample(inp, tgt, lr, w);
|
||||
}
|
||||
epochLoss /= (double)n;
|
||||
m_lossHistory[epoch] = epochLoss;
|
||||
|
||||
if(epoch == epochs - 1) avgLoss = epochLoss;
|
||||
|
||||
if(epoch == 0 || epoch == epochs - 1 || (epoch+1) % logEvery == 0) {
|
||||
Print(" Epoch ", epoch+1, "/", epochs,
|
||||
" | loss: ", StringFormat("%.6f", epochLoss),
|
||||
" | lr: ", StringFormat("%.5f", lr));
|
||||
}
|
||||
}
|
||||
|
||||
m_epochsTrained += epochs;
|
||||
m_lastLoss = avgLoss;
|
||||
return avgLoss;
|
||||
}
|
||||
|
||||
// Salva loss history in CSV nella cartella Common
|
||||
bool SaveLossCsv(string filename = "") {
|
||||
if(ArraySize(m_lossHistory) == 0) return false;
|
||||
if(filename == "") filename = "NN_LossHistory.csv";
|
||||
int fh = FileOpen(filename, FILE_TXT|FILE_WRITE|FILE_COMMON);
|
||||
if(fh == INVALID_HANDLE) return false;
|
||||
|
||||
FileWriteString(fh, "epoch,loss\r\n");
|
||||
for(int i = 0; i < ArraySize(m_lossHistory); i++) {
|
||||
FileWriteString(fh, (string)(i+1) + "," + StringFormat("%.8f", m_lossHistory[i]) + "\r\n");
|
||||
}
|
||||
FileClose(fh);
|
||||
Print("Loss history saved to ", filename, " (", ArraySize(m_lossHistory), " epochs)");
|
||||
return true;
|
||||
}
|
||||
|
||||
string LossHistorySummary() const {
|
||||
if(ArraySize(m_lossHistory) == 0) return "no history";
|
||||
double first = m_lossHistory[0];
|
||||
double last = m_lossHistory[ArraySize(m_lossHistory)-1];
|
||||
double best = first;
|
||||
int bestEpoch = 0;
|
||||
for(int i = 0; i < ArraySize(m_lossHistory); i++) {
|
||||
if(m_lossHistory[i] < best) { best = m_lossHistory[i]; bestEpoch = i; }
|
||||
}
|
||||
return StringFormat("loss: %.6f → %.6f (best: %.6f @ epoch %d, %d epochs)",
|
||||
first, last, best, bestEpoch+1, ArraySize(m_lossHistory));
|
||||
}
|
||||
|
||||
int Predict(vector &input) {
|
||||
vector output;
|
||||
Forward(input, output);
|
||||
int bestIdx = 0;
|
||||
double bestVal = output[0];
|
||||
for(int i = 1; i < m_outputs; i++) {
|
||||
if(output[i] > bestVal) {
|
||||
bestVal = output[i];
|
||||
bestIdx = i;
|
||||
}
|
||||
}
|
||||
return bestIdx;
|
||||
}
|
||||
|
||||
double GetCombinedZ(vector &input) {
|
||||
vector output;
|
||||
Forward(input, output);
|
||||
return output[0] - output[2];
|
||||
}
|
||||
|
||||
bool Save(string filename) {
|
||||
int fh = FileOpen(filename, FILE_WRITE | FILE_BIN | FILE_COMMON);
|
||||
if(fh == INVALID_HANDLE) return false;
|
||||
bool ok = Save(fh);
|
||||
FileClose(fh);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool Save(int fh) {
|
||||
if(fh == INVALID_HANDLE) return false;
|
||||
|
||||
FileWriteInteger(fh, 3);
|
||||
FileWriteInteger(fh, m_inputs);
|
||||
FileWriteInteger(fh, m_hidden);
|
||||
FileWriteInteger(fh, m_outputs);
|
||||
FileWriteInteger(fh, m_epochsTrained);
|
||||
FileWriteDouble(fh, m_lastLoss);
|
||||
FileWriteDouble(fh, m_l2);
|
||||
FileWriteDouble(fh, m_dropoutRate);
|
||||
FileWriteDouble(fh, m_bnMomentum);
|
||||
FileWriteDouble(fh, m_bnEps);
|
||||
|
||||
for(int r = 0; r < m_inputs; r++)
|
||||
for(int c = 0; c < m_hidden; c++)
|
||||
FileWriteDouble(fh, m_W1[r][c]);
|
||||
|
||||
for(int i = 0; i < m_hidden; i++)
|
||||
FileWriteDouble(fh, m_b1[i]);
|
||||
|
||||
for(int r = 0; r < m_hidden; r++)
|
||||
for(int c = 0; c < m_outputs; c++)
|
||||
FileWriteDouble(fh, m_W2[r][c]);
|
||||
|
||||
for(int i = 0; i < m_outputs; i++)
|
||||
FileWriteDouble(fh, m_b2[i]);
|
||||
|
||||
for(int r = 0; r < m_inputs; r++)
|
||||
for(int c = 0; c < m_hidden; c++) {
|
||||
FileWriteDouble(fh, m_mW1[r][c]);
|
||||
FileWriteDouble(fh, m_vW1[r][c]);
|
||||
}
|
||||
|
||||
for(int i = 0; i < m_hidden; i++) {
|
||||
FileWriteDouble(fh, m_mb1[i]);
|
||||
FileWriteDouble(fh, m_vb1[i]);
|
||||
}
|
||||
|
||||
for(int r = 0; r < m_hidden; r++)
|
||||
for(int c = 0; c < m_outputs; c++) {
|
||||
FileWriteDouble(fh, m_mW2[r][c]);
|
||||
FileWriteDouble(fh, m_vW2[r][c]);
|
||||
}
|
||||
|
||||
for(int i = 0; i < m_outputs; i++) {
|
||||
FileWriteDouble(fh, m_mb2[i]);
|
||||
FileWriteDouble(fh, m_vb2[i]);
|
||||
}
|
||||
|
||||
FileWriteDouble(fh, m_beta1T);
|
||||
FileWriteDouble(fh, m_beta2T);
|
||||
FileWriteInteger(fh, m_t);
|
||||
|
||||
// BatchNorm (v3)
|
||||
for(int i = 0; i < m_hidden; i++) {
|
||||
FileWriteDouble(fh, m_bnGamma[i]);
|
||||
FileWriteDouble(fh, m_bnBeta[i]);
|
||||
FileWriteDouble(fh, m_bnRunningMean[i]);
|
||||
FileWriteDouble(fh, m_bnRunningVar[i]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Load(string filename) {
|
||||
if(!FileIsExist(filename, FILE_COMMON)) return false;
|
||||
|
||||
int fh = FileOpen(filename, FILE_READ | FILE_BIN | FILE_COMMON);
|
||||
if(fh == INVALID_HANDLE) return false;
|
||||
bool ok = Load(fh);
|
||||
FileClose(fh);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool Load(int fh) {
|
||||
if(fh == INVALID_HANDLE) return false;
|
||||
|
||||
int version = FileReadInteger(fh);
|
||||
int inputs = FileReadInteger(fh);
|
||||
int hidden = FileReadInteger(fh);
|
||||
int outputs = FileReadInteger(fh);
|
||||
|
||||
Init(inputs, hidden, outputs);
|
||||
|
||||
m_epochsTrained = FileReadInteger(fh);
|
||||
m_lastLoss = FileReadDouble(fh);
|
||||
if(version >= 2) m_l2 = FileReadDouble(fh);
|
||||
if(version >= 3) {
|
||||
m_dropoutRate = FileReadDouble(fh);
|
||||
m_bnMomentum = FileReadDouble(fh);
|
||||
m_bnEps = FileReadDouble(fh);
|
||||
}
|
||||
|
||||
for(int r = 0; r < m_inputs; r++)
|
||||
for(int c = 0; c < m_hidden; c++)
|
||||
m_W1[r][c] = FileReadDouble(fh);
|
||||
|
||||
for(int i = 0; i < m_hidden; i++)
|
||||
m_b1[i] = FileReadDouble(fh);
|
||||
|
||||
for(int r = 0; r < m_hidden; r++)
|
||||
for(int c = 0; c < m_outputs; c++)
|
||||
m_W2[r][c] = FileReadDouble(fh);
|
||||
|
||||
for(int i = 0; i < m_outputs; i++)
|
||||
m_b2[i] = FileReadDouble(fh);
|
||||
|
||||
if(version >= 1) {
|
||||
for(int r = 0; r < m_inputs; r++)
|
||||
for(int c = 0; c < m_hidden; c++) {
|
||||
m_mW1[r][c] = FileReadDouble(fh);
|
||||
m_vW1[r][c] = FileReadDouble(fh);
|
||||
}
|
||||
|
||||
for(int i = 0; i < m_hidden; i++) {
|
||||
m_mb1[i] = FileReadDouble(fh);
|
||||
m_vb1[i] = FileReadDouble(fh);
|
||||
}
|
||||
|
||||
for(int r = 0; r < m_hidden; r++)
|
||||
for(int c = 0; c < m_outputs; c++) {
|
||||
m_mW2[r][c] = FileReadDouble(fh);
|
||||
m_vW2[r][c] = FileReadDouble(fh);
|
||||
}
|
||||
|
||||
for(int i = 0; i < m_outputs; i++) {
|
||||
m_mb2[i] = FileReadDouble(fh);
|
||||
m_vb2[i] = FileReadDouble(fh);
|
||||
}
|
||||
|
||||
if(version >= 1) {
|
||||
m_beta1T = FileReadDouble(fh);
|
||||
m_beta2T = FileReadDouble(fh);
|
||||
m_t = FileReadInteger(fh);
|
||||
}
|
||||
|
||||
// BatchNorm (v3)
|
||||
if(version >= 3) {
|
||||
m_bnGamma.Init(m_hidden);
|
||||
m_bnBeta.Init(m_hidden);
|
||||
m_bnRunningMean.Init(m_hidden);
|
||||
m_bnRunningVar.Init(m_hidden);
|
||||
for(int i = 0; i < m_hidden; i++) {
|
||||
m_bnGamma[i] = FileReadDouble(fh);
|
||||
m_bnBeta[i] = FileReadDouble(fh);
|
||||
m_bnRunningMean[i] = FileReadDouble(fh);
|
||||
m_bnRunningVar[i] = FileReadDouble(fh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsInitialized() const { return m_initialized; }
|
||||
int EpochsTrained() const { return m_epochsTrained; }
|
||||
double LastLoss() const { return m_lastLoss; }
|
||||
int Inputs() const { return m_inputs; }
|
||||
int Hidden() const { return m_hidden; }
|
||||
int Outputs() const { return m_outputs; }
|
||||
|
||||
string Info() const {
|
||||
if(!m_initialized) return "NN: uninitialized";
|
||||
return StringFormat("NN: %d→%d→%d (epoche=%d, loss=%.6f, dropout=%.2f, BN=%s)",
|
||||
m_inputs, m_hidden, m_outputs, m_epochsTrained, m_lastLoss,
|
||||
m_dropoutRate, m_initialized ? "on" : "off");
|
||||
}
|
||||
|
||||
string WeightsSummary() const {
|
||||
if(!m_initialized) return "NN: uninitialized";
|
||||
double w1Min = 1e99, w1Max = -1e99, w1Sum = 0;
|
||||
double w2Min = 1e99, w2Max = -1e99, w2Sum = 0;
|
||||
int w1Count = 0, w2Count = 0;
|
||||
for(int r = 0; r < m_inputs; r++) {
|
||||
for(int c = 0; c < m_hidden; c++) {
|
||||
double v = m_W1[r][c];
|
||||
if(v < w1Min) w1Min = v; if(v > w1Max) w1Max = v;
|
||||
w1Sum += v; w1Count++;
|
||||
}
|
||||
}
|
||||
for(int r = 0; r < m_hidden; r++) {
|
||||
for(int c = 0; c < m_outputs; c++) {
|
||||
double v = m_W2[r][c];
|
||||
if(v < w2Min) w2Min = v; if(v > w2Max) w2Max = v;
|
||||
w2Sum += v; w2Count++;
|
||||
}
|
||||
}
|
||||
return StringFormat(" W1 [%.4f, %.4f] μ=%.4f | W2 [%.4f, %.4f] μ=%.4f",
|
||||
w1Min, w1Max, (w1Count>0?w1Sum/w1Count:0),
|
||||
w2Min, w2Max, (w2Count>0?w2Sum/w2Count:0));
|
||||
}
|
||||
};
|
||||
|
||||
struct NNTrainSample {
|
||||
double features[NN_FEATURES];
|
||||
double target[NN_TARGETS];
|
||||
double weight; // peso del sample: trades con grosso impatto pesano di più
|
||||
};
|
||||
|
||||
class NNTrainBuffer {
|
||||
private:
|
||||
NNTrainSample m_samples[];
|
||||
int m_count;
|
||||
public:
|
||||
NNTrainBuffer() : m_count(0) {}
|
||||
|
||||
void Add(double &features[], double &target[], double weight = 1.0) {
|
||||
int idx = m_count;
|
||||
ArrayResize(m_samples, idx + 1);
|
||||
for(int i = 0; i < NN_FEATURES; i++) m_samples[idx].features[i] = features[i];
|
||||
for(int i = 0; i < NN_TARGETS; i++) m_samples[idx].target[i] = target[i];
|
||||
m_samples[idx].weight = weight;
|
||||
m_count++;
|
||||
}
|
||||
|
||||
int Count() const { return m_count; }
|
||||
|
||||
void ToMatrices(matrix &features, matrix &targets) {
|
||||
if(m_count == 0) return;
|
||||
features.Init(m_count, NN_FEATURES);
|
||||
targets.Init(m_count, NN_TARGETS);
|
||||
for(int i = 0; i < m_count; i++) {
|
||||
for(int j = 0; j < NN_FEATURES; j++)
|
||||
features[i][j] = m_samples[i].features[j];
|
||||
for(int j = 0; j < NN_TARGETS; j++)
|
||||
targets[i][j] = m_samples[i].target[j];
|
||||
}
|
||||
}
|
||||
|
||||
vector GetWeights() {
|
||||
vector w(m_count);
|
||||
for(int i = 0; i < m_count; i++)
|
||||
w[i] = m_samples[i].weight;
|
||||
return w;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
ArrayResize(m_samples, 0);
|
||||
m_count = 0;
|
||||
}
|
||||
|
||||
void Trim(int maxSamples) {
|
||||
if(m_count <= maxSamples) return;
|
||||
int remove = m_count - maxSamples;
|
||||
for(int i = 0; i < maxSamples; i++)
|
||||
m_samples[i] = m_samples[i + remove];
|
||||
ArrayResize(m_samples, maxSamples);
|
||||
m_count = maxSamples;
|
||||
}
|
||||
|
||||
// Dump training samples to CSV nella cartella Common per debug
|
||||
void SaveCsv(string filename = "NN_TrainingSamples.csv") {
|
||||
int fh = FileOpen(filename, FILE_TXT|FILE_WRITE|FILE_COMMON);
|
||||
if(fh == INVALID_HANDLE) return;
|
||||
FileWriteString(fh, "sample");
|
||||
for(int f = 0; f < NN_FEATURES; f++) FileWriteString(fh, ",feat_" + (string)f);
|
||||
for(int t = 0; t < NN_TARGETS; t++) FileWriteString(fh, ",target_" + (string)t);
|
||||
FileWriteString(fh, ",label\r\n");
|
||||
for(int i = 0; i < m_count; i++) {
|
||||
string line = (string)i;
|
||||
for(int f = 0; f < NN_FEATURES; f++) line += "," + StringFormat("%+.6f", m_samples[i].features[f]);
|
||||
for(int t = 0; t < NN_TARGETS; t++) line += "," + StringFormat("%.0f", m_samples[i].target[t]);
|
||||
// Label leggibile
|
||||
if(m_samples[i].target[0] == 1) line += ",BUY";
|
||||
else if(m_samples[i].target[2] == 1) line += ",SELL";
|
||||
else line += ",FLAT";
|
||||
line += "\r\n";
|
||||
FileWriteString(fh, line);
|
||||
}
|
||||
FileClose(fh);
|
||||
Print("Training samples saved to ", filename, " (", m_count, " samples)");
|
||||
}
|
||||
|
||||
// Stampa statistiche riassuntive del dataset
|
||||
void PrintStats() {
|
||||
int buyCount = 0, sellCount = 0, flatCount = 0;
|
||||
for(int i = 0; i < m_count; i++) {
|
||||
if(m_samples[i].target[0] == 1) buyCount++;
|
||||
else if(m_samples[i].target[2] == 1) sellCount++;
|
||||
else flatCount++;
|
||||
}
|
||||
Print(" Dataset: ", m_count, " samples | Buy: ", buyCount,
|
||||
" Sell: ", sellCount, " Flat: ", flatCount);
|
||||
}
|
||||
|
||||
// Media feature per classe (utile per vedere se le feature discriminano)
|
||||
void PrintFeatureStats() {
|
||||
if(m_count < 3) { Print(" Feature stats: too few samples"); return; }
|
||||
double meanFeat[NN_FEATURES] = {};
|
||||
double absMean[NN_FEATURES] = {};
|
||||
for(int i = 0; i < m_count; i++) {
|
||||
for(int f = 0; f < NN_FEATURES; f++) {
|
||||
meanFeat[f] += m_samples[i].features[f];
|
||||
absMean[f] += MathAbs(m_samples[i].features[f]);
|
||||
}
|
||||
}
|
||||
Print(" Feature means (signal strength per class):");
|
||||
for(int f = 0; f < NN_FEATURES; f++) {
|
||||
meanFeat[f] /= m_count;
|
||||
absMean[f] /= m_count;
|
||||
}
|
||||
string labels[NN_FEATURES] = {"Hurst","ADX","MA","Momentum","Consensus","Hunter","Agreement","TrendStr"};
|
||||
for(int f = 0; f < NN_FEATURES; f++) {
|
||||
Print(" [", f, "] ", labels[f], ": μ=", StringFormat("%+.4f", meanFeat[f]),
|
||||
" |avg|=", StringFormat("%.4f", absMean[f]));
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,107 @@
|
||||
#ifndef PERIOD_CALCULATOR_MQH
|
||||
#define PERIOD_CALCULATOR_MQH
|
||||
#include "MarketData.mqh"
|
||||
#include "Statistics.mqh"
|
||||
|
||||
class PeriodCalculator {
|
||||
// Soglia di correlazione: z-score ~2 per significatività approssimata
|
||||
static double CorrThreshold(int n) { return 2.0 / MathSqrt(MathMax(1, n)); }
|
||||
public:
|
||||
static int DominantCycle(const double &close[], int len, int minPeriod=8, int maxPeriod=50) {
|
||||
if(len < maxPeriod * 2) return (minPeriod + maxPeriod) / 2;
|
||||
|
||||
double mean = 0;
|
||||
for(int i=0; i<len; i++) mean += close[i];
|
||||
mean /= len;
|
||||
|
||||
double variance = 0;
|
||||
for(int i=0; i<len; i++) variance += (close[i] - mean) * (close[i] - mean);
|
||||
variance /= len;
|
||||
if(variance < DATA_EPS(mean)) return (minPeriod + maxPeriod) / 2;
|
||||
|
||||
int bestPeriod = (minPeriod + maxPeriod) / 2;
|
||||
double bestCorr = -999;
|
||||
|
||||
for(int p = minPeriod; p <= MathMin(maxPeriod, len/3); p++) {
|
||||
double cov = 0;
|
||||
int count = 0;
|
||||
for(int i=0; i<len-p; i++) {
|
||||
cov += (close[i] - mean) * (close[i+p] - mean);
|
||||
count++;
|
||||
}
|
||||
cov /= count;
|
||||
double corr = cov / variance;
|
||||
double thr = CorrThreshold(count);
|
||||
if(corr > bestCorr && corr > thr) {
|
||||
bestCorr = corr;
|
||||
bestPeriod = p;
|
||||
}
|
||||
}
|
||||
|
||||
return bestPeriod;
|
||||
}
|
||||
|
||||
// Metodo 2: Efficiency Ratio (Kaufman) - adatta a volatilità
|
||||
static int EfficiencyRatioPeriod(const double &close[], int len, int minPeriod=5, int maxPeriod=50) {
|
||||
if(len < 20) return (minPeriod + maxPeriod) / 2;
|
||||
|
||||
int lookback = MathMin(len / 3, len - 1);
|
||||
double totalMove = MathAbs(close[0] - close[lookback]);
|
||||
double noise = 0;
|
||||
for(int i=0; i<lookback; i++) noise += MathAbs(close[i] - close[i+1]);
|
||||
|
||||
double er = (noise > 0) ? totalMove / noise : 1.0;
|
||||
int period = minPeriod + (int)((maxPeriod - minPeriod) * (1.0 - er));
|
||||
return period;
|
||||
}
|
||||
|
||||
// Metodo 3: ATR-based (volatilità recente)
|
||||
static int ATRPeriod(const MarketData &data, int minPeriod=8, int maxPeriod=40) {
|
||||
if(data.count < 20) return (minPeriod + maxPeriod) / 2;
|
||||
|
||||
int lookback = MathMin(maxPeriod / 3, data.count - 1);
|
||||
lookback = MathMax(5, lookback);
|
||||
|
||||
double atr = 0;
|
||||
for(int i = 1; i <= lookback; i++) {
|
||||
double tr = MathMax(data.high[i] - data.low[i],
|
||||
MathMax(MathAbs(data.high[i] - data.close[i-1]),
|
||||
MathAbs(data.low[i] - data.close[i-1])));
|
||||
atr += tr;
|
||||
}
|
||||
atr /= lookback;
|
||||
|
||||
double avgRange = 0;
|
||||
for(int i = 0; i < lookback; i++) avgRange += data.high[i] - data.low[i];
|
||||
avgRange /= lookback;
|
||||
|
||||
double volRatio = (avgRange > 0) ? atr / avgRange : 1.0;
|
||||
int period = minPeriod + (int)((maxPeriod - minPeriod) * volRatio);
|
||||
return period;
|
||||
}
|
||||
|
||||
// Metodo combinato: pesi data-driven basati sull'accordo tra metodi
|
||||
static int AutoPeriod(const MarketData &data, int minPeriod=8, int maxPeriod=50) {
|
||||
int p1 = DominantCycle(data.close, data.count, minPeriod, maxPeriod);
|
||||
int p2 = EfficiencyRatioPeriod(data.close, data.count, minPeriod, maxPeriod);
|
||||
int p3 = ATRPeriod(data, minPeriod, maxPeriod);
|
||||
|
||||
// Pesi basati sull'accordo: il metodo più vicino alla mediana pesa di più
|
||||
double sorted[3] = { (double)p1, (double)p2, (double)p3 };
|
||||
ArraySort(sorted);
|
||||
double median = sorted[1]; // valore centrale
|
||||
|
||||
// Inverse distance weighting: distanza dalla mediana
|
||||
double d1 = MathAbs(p1 - median);
|
||||
double d2 = MathAbs(p2 - median);
|
||||
double d3 = MathAbs(p3 - median);
|
||||
double w1 = 1.0 / MathMax(0.1, d1);
|
||||
double w2 = 1.0 / MathMax(0.1, d2);
|
||||
double w3 = 1.0 / MathMax(0.1, d3);
|
||||
double sum = w1 + w2 + w3;
|
||||
|
||||
double weighted = (p1 * w1 + p2 * w2 + p3 * w3) / sum;
|
||||
return (int)MathRound(weighted);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef SIGNAL_MQH
|
||||
#define SIGNAL_MQH
|
||||
|
||||
struct FinalSignal {
|
||||
int direction; // -1, 0, +1
|
||||
double confidence; // 0.0 - 1.0 (derivato da |z_combined|)
|
||||
double zScore; // z-score combinato finale [-1, +1] via tanh
|
||||
double positionSize; // frazione di capitale suggerita
|
||||
int agreeingCount;
|
||||
int totalAgents;
|
||||
string contributingAgents;
|
||||
datetime timestamp;
|
||||
|
||||
FinalSignal()
|
||||
: direction(0), confidence(0), zScore(0), positionSize(0),
|
||||
agreeingCount(0), totalAgents(0), timestamp(0) {}
|
||||
|
||||
FinalSignal(int dir, double z)
|
||||
: direction(dir), confidence(MathAbs(z)), zScore(z),
|
||||
positionSize(MathAbs(z)), agreeingCount(0), totalAgents(0),
|
||||
timestamp(TimeCurrent()) {}
|
||||
|
||||
bool IsActionable(double minZ=0.5) const {
|
||||
return direction != 0 && MathAbs(zScore) >= minZ;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,265 @@
|
||||
#ifndef STATISTICS_MQH
|
||||
#define STATISTICS_MQH
|
||||
|
||||
// Precisione macchina double
|
||||
#define DBL_EPS 2.2204460492503131e-016
|
||||
// Soglia numerica data-scaled: |x| * DBL_EPS * 1e9 = |x| * 2.22e-7 ≈ |x| * 1e-6
|
||||
// Epsilon data-driven: sqrt(machine_epsilon) × |x|, mai sotto sqrt(machine_epsilon)
|
||||
// sqrt(DBL_EPS) è la soglia standard per confronti floating-point (Num. Recipes)
|
||||
#define DATA_EPS(x) MathMax(MathSqrt(DBL_EPS), MathAbs(x) * MathSqrt(DBL_EPS))
|
||||
|
||||
class EWMA {
|
||||
double value;
|
||||
double alpha;
|
||||
bool init;
|
||||
public:
|
||||
EWMA(double a=0.1) : alpha(a), init(false), value(0) {}
|
||||
|
||||
void SetAlpha(double a) { alpha = a; }
|
||||
double Alpha() const { return alpha; }
|
||||
|
||||
double Update(double x) {
|
||||
if(!init) { value = x; init = true; }
|
||||
else value = alpha * x + (1.0 - alpha) * value;
|
||||
return value;
|
||||
}
|
||||
|
||||
double Value() const { return init ? value : 0; }
|
||||
bool IsInit() const { return init; }
|
||||
void Reset() { init = false; value = 0; }
|
||||
|
||||
void Save(int fh) const {
|
||||
FileWriteDouble(fh, value);
|
||||
FileWriteInteger(fh, init ? 1 : 0);
|
||||
}
|
||||
void Load(int fh) {
|
||||
value = FileReadDouble(fh);
|
||||
init = FileReadInteger(fh) == 1;
|
||||
}
|
||||
};
|
||||
|
||||
class RunningStats {
|
||||
EWMA mean;
|
||||
EWMA var;
|
||||
int minSamples;
|
||||
int count;
|
||||
int freezeAfter;
|
||||
|
||||
double AdaptiveEpsilon() const {
|
||||
double m = MathAbs(mean.Value());
|
||||
return (m > 0) ? DATA_EPS(m) : 1e-15;
|
||||
}
|
||||
public:
|
||||
RunningStats(double a=0.05, int minS=20, int freeze=0)
|
||||
: mean(a), var(a), minSamples(minS), count(0), freezeAfter(freeze) {}
|
||||
|
||||
void SetFreezeAfter(int n) { freezeAfter = n; }
|
||||
bool IsFrozen() const { return freezeAfter > 0 && count >= freezeAfter; }
|
||||
|
||||
void Update(double x) {
|
||||
if(IsFrozen()) return;
|
||||
if(count == 0) {
|
||||
mean.Update(x);
|
||||
var.Update(0);
|
||||
count = 1;
|
||||
return;
|
||||
}
|
||||
double prevMean = mean.Value();
|
||||
mean.Update(x);
|
||||
double diff = x - prevMean;
|
||||
var.Update(diff * diff);
|
||||
count++;
|
||||
}
|
||||
|
||||
double ZScore(double x) {
|
||||
double m = mean.Value();
|
||||
double s = MathSqrt(var.Value());
|
||||
if(s < AdaptiveEpsilon() || count < minSamples) return 0;
|
||||
return (x - m) / s;
|
||||
}
|
||||
|
||||
double RawZScore(double x) {
|
||||
double m = mean.Value();
|
||||
double s = MathSqrt(var.Value());
|
||||
if(s < AdaptiveEpsilon()) return 0;
|
||||
return (x - m) / s;
|
||||
}
|
||||
|
||||
double Mean() const { return mean.Value(); }
|
||||
double Std() const { return MathSqrt(var.Value()); }
|
||||
bool Ready() const { return count >= minSamples; }
|
||||
void Reset() { mean.Reset(); var.Reset(); count = 0; }
|
||||
int Count() const { return count; }
|
||||
|
||||
void Save(int fh) const {
|
||||
mean.Save(fh);
|
||||
var.Save(fh);
|
||||
FileWriteInteger(fh, count);
|
||||
FileWriteInteger(fh, freezeAfter);
|
||||
}
|
||||
void Load(int fh) {
|
||||
mean.Load(fh);
|
||||
var.Load(fh);
|
||||
count = FileReadInteger(fh);
|
||||
freezeAfter = FileReadInteger(fh);
|
||||
}
|
||||
|
||||
string ToString() const {
|
||||
string s = "mean=" + StringFormat("%.5f", Mean())
|
||||
+ " std=" + StringFormat("%.5f", Std())
|
||||
+ " n=" + (string)count + "/" + (string)minSamples;
|
||||
if(IsFrozen()) s += " FROZEN";
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
class RunningCorrelation {
|
||||
double alpha;
|
||||
double meanX, meanY;
|
||||
double cov, varX, varY;
|
||||
int count;
|
||||
int minSamples;
|
||||
|
||||
double AdaptiveEpsilon() const {
|
||||
double mx = MathAbs(meanX), my = MathAbs(meanY);
|
||||
double ref = (mx + my) * 0.5;
|
||||
return (ref > 0) ? DATA_EPS(ref) : 1e-15;
|
||||
}
|
||||
public:
|
||||
RunningCorrelation(double a=0.05, int minS=10)
|
||||
: alpha(a), minSamples(minS), count(0),
|
||||
meanX(0), meanY(0), cov(0), varX(0), varY(0) {}
|
||||
|
||||
void Update(double x, double y) {
|
||||
count++;
|
||||
if(count == 1) {
|
||||
meanX = x; meanY = y;
|
||||
return;
|
||||
}
|
||||
|
||||
double dx = x - meanX;
|
||||
double dy = y - meanY;
|
||||
|
||||
meanX += alpha * dx;
|
||||
meanY += alpha * dy;
|
||||
|
||||
double dxNew = x - meanX;
|
||||
double dyNew = y - meanY;
|
||||
|
||||
cov = (1.0 - alpha) * cov + alpha * dx * dyNew;
|
||||
varX = (1.0 - alpha) * varX + alpha * dx * dxNew;
|
||||
varY = (1.0 - alpha) * varY + alpha * dy * dyNew;
|
||||
}
|
||||
|
||||
double Correlation() {
|
||||
double denom = MathSqrt(varX * varY);
|
||||
if(denom < AdaptiveEpsilon() || count < minSamples) return 0;
|
||||
double r = cov / denom;
|
||||
double maxObserved = 1.0;
|
||||
return MathMax(-maxObserved, MathMin(maxObserved, r));
|
||||
}
|
||||
|
||||
bool Ready() const { return count >= minSamples; }
|
||||
void Reset() { count = 0; meanX = meanY = cov = varX = varY = 0; }
|
||||
int Count() const { return count; }
|
||||
|
||||
void Save(int fh) const {
|
||||
FileWriteDouble(fh, meanX);
|
||||
FileWriteDouble(fh, meanY);
|
||||
FileWriteDouble(fh, cov);
|
||||
FileWriteDouble(fh, varX);
|
||||
FileWriteDouble(fh, varY);
|
||||
FileWriteInteger(fh, count);
|
||||
}
|
||||
void Load(int fh) {
|
||||
meanX = FileReadDouble(fh);
|
||||
meanY = FileReadDouble(fh);
|
||||
cov = FileReadDouble(fh);
|
||||
varX = FileReadDouble(fh);
|
||||
varY = FileReadDouble(fh);
|
||||
count = FileReadInteger(fh);
|
||||
}
|
||||
};
|
||||
|
||||
// --- Kalman Filter Normalizer con Q adattivo ---
|
||||
class KalmanNormalizer {
|
||||
double x;
|
||||
double P;
|
||||
double Q;
|
||||
double R;
|
||||
int count;
|
||||
int minSamples;
|
||||
|
||||
double AdaptiveEpsilon() const {
|
||||
double ax = MathAbs(x);
|
||||
return (ax > 0) ? DATA_EPS(ax) : 1e-15;
|
||||
}
|
||||
public:
|
||||
KalmanNormalizer(double q=0.001, double r=0.1, int minS=20)
|
||||
: x(0), P(1.0), Q(q), R(r), count(0), minSamples(minS) {}
|
||||
|
||||
void SetQ(double q) { Q = q; }
|
||||
void SetR(double r) { R = r; }
|
||||
|
||||
void Update(double obs) {
|
||||
if(count == 0) {
|
||||
x = obs;
|
||||
P = R;
|
||||
count = 1;
|
||||
return;
|
||||
}
|
||||
double innov = obs - x;
|
||||
|
||||
P += Q;
|
||||
|
||||
double K = P / (P + R);
|
||||
x += K * innov;
|
||||
P = (1.0 - K) * P;
|
||||
|
||||
double innovVar = innov * innov;
|
||||
// AdaptRate: innovVar/(R+innovVar) → [0, 1), nessun clamp
|
||||
double adaptRate = innovVar / (R + innovVar);
|
||||
Q = (1.0 - adaptRate) * Q + adaptRate * innovVar;
|
||||
double qMin = DATA_EPS(R);
|
||||
double qMax = R - DATA_EPS(R); // Q < R garantito → K < 0.5
|
||||
Q = MathMax(qMin, MathMin(qMax, Q));
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
double Mean() const { return x; }
|
||||
double Std() const { return MathSqrt(P); }
|
||||
bool Ready() const { return count >= minSamples; }
|
||||
int Count() const { return count; }
|
||||
void Reset() { x = 0; P = 1.0; count = 0; }
|
||||
|
||||
double ZScore(double obs) {
|
||||
double s = Std();
|
||||
if(s < AdaptiveEpsilon() || count < minSamples) return 0;
|
||||
return (obs - x) / s;
|
||||
}
|
||||
|
||||
double RawZScore(double obs) {
|
||||
double s = Std();
|
||||
if(s < AdaptiveEpsilon()) return 0;
|
||||
return (obs - x) / s;
|
||||
}
|
||||
|
||||
void Save(int fh) const {
|
||||
FileWriteDouble(fh, x);
|
||||
FileWriteDouble(fh, P);
|
||||
FileWriteInteger(fh, count);
|
||||
}
|
||||
void Load(int fh) {
|
||||
x = FileReadDouble(fh);
|
||||
P = FileReadDouble(fh);
|
||||
count = FileReadInteger(fh);
|
||||
}
|
||||
|
||||
string ToString() const {
|
||||
return "μ=" + StringFormat("%.5f", x)
|
||||
+ " σ=" + StringFormat("%.5f", Std())
|
||||
+ " n=" + (string)count + "/" + (string)minSamples;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
Reference in New Issue
Block a user