MultiAgentTest v7: zero magic constants, neural orchestrator, multi-position, agent interaction fixes

This commit is contained in:
pietro_giacobazzi
2026-06-13 13:58:38 +02:00
commit 5c00f17121
19 changed files with 5522 additions and 0 deletions
@@ -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 &param, 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 &param, 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