Motivation
Ehlers-based normalization (stochastic oscillator + Fisher transform) is standard in quantitative finance and signal processing for making non-stationary timeseries stationary before feeding them into a model. Nott already supports financial datasets (ETTh, PTBXL) and has FisherTransform / InverseFisher implemented, but the actual Ehlers oscillator loops for multi-feature ([T, F]) tensors are entirely missing. Users working with multivariate timeseries have no path to this normalization.
Current State
src/data/transform/normalization/ehlers.hpp:27 ends with:
/// TODO: Implement Ehler loops for 2d timeseries
Only FisherTransform and InverseFisher are present. The normalization registry (registry.hpp) cannot expose an Ehlers option because there is nothing to register.
Proposed Change
Implement EhlersNormalize and EhlersInverse operating on [T, F] tensors:
struct EhlersOptions {
int period = 10; // rolling window for stochastic oscillator
double fisher_clamp = 0.999;
};
// Returns tensor in (-1, 1) after Fisher transform
at::Tensor EhlersNormalize(const at::Tensor& input, EhlersOptions opts = {});
// Inverts: Fisher inverse → rescale back to original range
at::Tensor EhlersInverse(const at::Tensor& normalized,
const at::Tensor& original,
EhlersOptions opts = {});
Algorithm per feature column:
- Compute rolling
min and max over opts.period steps
- Stochastic:
x = 2 * (val - min) / (max - min + eps) - 1 → clamp to (-clamp, clamp)
- Apply
FisherTransform(x)
Wire into src/data/transform/normalization/registry.hpp alongside ZScore and PowerNorm.
Acceptance Criteria
Motivation
Ehlers-based normalization (stochastic oscillator + Fisher transform) is standard in quantitative finance and signal processing for making non-stationary timeseries stationary before feeding them into a model. Nott already supports financial datasets (ETTh, PTBXL) and has
FisherTransform/InverseFisherimplemented, but the actual Ehlers oscillator loops for multi-feature ([T, F]) tensors are entirely missing. Users working with multivariate timeseries have no path to this normalization.Current State
src/data/transform/normalization/ehlers.hpp:27ends with:/// TODO: Implement Ehler loops for 2d timeseriesOnly
FisherTransformandInverseFisherare present. The normalization registry (registry.hpp) cannot expose an Ehlers option because there is nothing to register.Proposed Change
Implement
EhlersNormalizeandEhlersInverseoperating on[T, F]tensors:Algorithm per feature column:
minandmaxoveropts.periodstepsx = 2 * (val - min) / (max - min + eps) - 1→ clamp to(-clamp, clamp)FisherTransform(x)Wire into
src/data/transform/normalization/registry.hppalongsideZScoreandPowerNorm.Acceptance Criteria
EhlersNormalizehandles[T, F]tensors (per-column rolling normalization)EhlersInversecorrectly reconstructs the original scaleEhlersOptions::periodis configurableDataAPI/// TODOcomment removed