132 lines
4.7 KiB
MQL5
132 lines
4.7 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| FFDValidation.mq5 — Export FFD values for Python cross-check |
|
|
//| Copyright 2025, Patrick M. Njoroge |
|
|
//| |
|
|
//| Run as a script. Exports close prices and FFD values to CSV. |
|
|
//| Compare the CSV output against Python's frac_diff_ffd() to |
|
|
//| verify numerical equivalence (see ffd_cross_validate.py). |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Patrick M. Njoroge"
|
|
#property link "https://www.mql5.com/en/users/patricknjoroge743"
|
|
#property version "1.11"
|
|
#property description "Exports FFD values for cross-validation with Python"
|
|
#property script_show_inputs
|
|
|
|
//--- Inputs
|
|
input double InpD = 0.4; // Differencing order d
|
|
input double InpThreshold = 1e-5; // Weight cutoff threshold
|
|
input bool InpUseLog = true; // Log-transform prices
|
|
input int InpBars = 5000; // Number of bars to export
|
|
|
|
//--- Include the computation engine from MQL5\Include\
|
|
#include "FFDEngine.mqh"
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Script program start function |
|
|
//+------------------------------------------------------------------+
|
|
void OnStart()
|
|
{
|
|
//--- Initialize engine
|
|
CFFDEngine engine;
|
|
if(!engine.Init(InpD, InpThreshold, InpUseLog))
|
|
{
|
|
Print("Validation: engine initialization failed");
|
|
return;
|
|
}
|
|
|
|
PrintFormat("Validation: requesting %d bars, engine width=%d",
|
|
InpBars, engine.GetWidth());
|
|
|
|
//--- Copy close prices
|
|
double close[];
|
|
int copied = CopyClose(_Symbol, _Period, 0, InpBars, close);
|
|
if(copied < engine.GetMinBars())
|
|
{
|
|
PrintFormat("Not enough bars: got %d, need at least %d",
|
|
copied, engine.GetMinBars());
|
|
return;
|
|
}
|
|
|
|
//--- Ensure chronological order (oldest first).
|
|
// CopyClose returns chronological by default, but if any other
|
|
// code in the terminal has set ArraySetAsSeries on this buffer
|
|
// globally, the order silently reverses and all FFD values are
|
|
// wrong. This defensive call costs nothing and prevents that.
|
|
ArraySetAsSeries(close, false);
|
|
|
|
PrintFormat("Copied %d bars", copied);
|
|
|
|
//--- Copy timestamps for reference
|
|
datetime time[];
|
|
CopyTime(_Symbol, _Period, 0, InpBars, time);
|
|
ArraySetAsSeries(time, false);
|
|
|
|
//--- Compute FFD for all bars
|
|
double ffd_buffer[];
|
|
ArrayResize(ffd_buffer, copied);
|
|
engine.ComputeBuffer(close, ffd_buffer, copied, 0);
|
|
|
|
//--- Write to CSV
|
|
string filename = StringFormat("ffd_validation_%s_%s_d%.2f.csv",
|
|
_Symbol,
|
|
EnumToString(_Period),
|
|
InpD);
|
|
|
|
int file = FileOpen(filename, FILE_WRITE | FILE_CSV, ",");
|
|
if(file == INVALID_HANDLE)
|
|
{
|
|
Print("Cannot open file: ", filename);
|
|
return;
|
|
}
|
|
|
|
//--- Header
|
|
FileWrite(file, "bar_index", "datetime", "close", "ffd");
|
|
|
|
//--- Data rows (skip EMPTY_VALUE bars)
|
|
int written = 0;
|
|
for(int i = 0; i < copied; i++)
|
|
{
|
|
if(ffd_buffer[i] != EMPTY_VALUE)
|
|
{
|
|
FileWrite(file,
|
|
IntegerToString(i),
|
|
TimeToString(time[i], TIME_DATE | TIME_SECONDS),
|
|
DoubleToString(close[i], 8),
|
|
DoubleToString(ffd_buffer[i], 12));
|
|
written++;
|
|
}
|
|
}
|
|
|
|
FileClose(file);
|
|
PrintFormat("Validation file written: %s (%d data rows out of %d bars)",
|
|
filename, written, copied);
|
|
PrintFormat("Expected %d empty bars (width) + %d data bars = %d total",
|
|
engine.GetWidth(), written, engine.GetWidth() + written);
|
|
|
|
//--- Self-check: verify the first data bar via Compute()
|
|
if(written > 0)
|
|
{
|
|
int first_bar = engine.GetWidth();
|
|
int window = engine.GetMinBars();
|
|
|
|
// Extract the first valid price window and compute independently
|
|
double check_prices[];
|
|
ArrayResize(check_prices, window);
|
|
for(int k = 0; k < window; k++)
|
|
check_prices[k] = close[k];
|
|
|
|
double check_val = engine.Compute(check_prices, window);
|
|
double buffer_val = ffd_buffer[first_bar];
|
|
double diff = MathAbs(check_val - buffer_val);
|
|
|
|
PrintFormat("Self-check (bar %d): Compute=%.12f, Buffer=%.12f, diff=%.2e",
|
|
first_bar, check_val, buffer_val, diff);
|
|
|
|
if(diff > 1e-12)
|
|
Print("WARNING: Compute() and ComputeBuffer() disagree.");
|
|
else
|
|
Print("Self-check PASSED: Compute() and ComputeBuffer() agree.");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|