Skip to content

NaN Convention

Oryon uses float('nan') in Python to represent missing values. In Rust, missing values are Option<f64> (None). The conversion is transparent at the PyO3 boundary, and you never deal with it explicitly.

There are exactly two sources of NaN in any output column.


Warm-up (features)

A windowed feature cannot produce a valid output until its buffer is full. The first warm_up_period bars are always NaN.

SMA(window=3)

bar 0  →  NaN    buffer not full
bar 1  →  NaN    buffer not full
bar 2  →  101.0  first valid output
bar 3  →  102.0
bar 4  →  103.0

warm_up_period = window - 1 for most features. The API Reference page for each feature lists the exact value.


Horizon (targets)

A target cannot compute its label when the forward window extends beyond the end of the dataset. The last forward_period bars are always NaN.

FutureReturn(horizon=2)  —  5 bars

bar 0  →  0.0500
bar 1  →  0.0098
bar 2  →  0.0286
bar 3  →  NaN    needs bar 5, which doesn't exist
bar 4  →  NaN    needs bar 6, which doesn't exist

Model-ready zone

Combining features and targets produces NaN at both ends. The rows in between are your training data.

       close_sma_3   close_fr_2
bar 0       NaN          0.05    ← feature warm-up
bar 1       NaN          0.0098  ← feature warm-up
bar 2      101.0         0.0286
bar 3      102.0         NaN     ← target horizon
bar 4      103.0         NaN     ← target horizon

Isolate the model-ready zone with a single dropna():

X = run_features_pipeline(fp, df)
y = run_targets_pipeline(tp, df)
dataset = pd.concat([X, y], axis=1).dropna()

NaN propagation in features

A NaN input contaminates a feature's buffer. The output stays NaN until that bar is evicted, i.e. after window consecutive valid bars have been seen. Each feature's Behavior tab documents the exact propagation rules, including edge cases specific to that feature (e.g. EMA resets entirely on NaN).