Special Relativity in Financial Modeling 1.0.0
Lorentz transforms, spacetime classification, and geodesic price paths for quantitative finance
Loading...
Searching...
No Matches
normalizer.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file include/srfm/normalizer.hpp
4/// @brief CoordinateNormalizer — rolling z-score normalizer for SpacetimeEvent.
5///
6/// # Module: Coordinate Normalizer
7///
8/// ## Responsibility
9/// Normalize the spatial coordinates of SpacetimeEvent instances using a
10/// rolling window of observed values before they are passed to
11/// SpacetimeInterval::compute.
12///
13/// ## Why This Matters
14/// Raw market coordinates differ by many orders of magnitude:
15/// - price ~100
16/// - volume ~1e6
17/// - momentum ~0.01
18///
19/// Without normalization, volume dominates ds² by ~10 orders of magnitude,
20/// making interval classification (timelike/spacelike) meaningless. After
21/// z-score normalization each coordinate contributes equally.
22///
23/// ## Formula
24/// For each spatial coordinate x, rolling window of n observations:
25/// x_norm = (x - rolling_mean(x)) / rolling_stddev(x)
26///
27/// Time coordinate passes through unchanged.
28///
29/// ## Warm-up Guard
30/// Before at least `min_samples` data points have been accumulated (default =
31/// window_size, minimum 20), the z-scores are potentially unreliable due to
32/// insufficient statistical history. warmed_up() returns false during this
33/// period, and normalize() returns a zero-valued event (all spatial coordinates
34/// set to 0.0 with time unchanged) instead of a potentially wild z-score.
35/// Callers should check warmed_up() before using the output of normalize() for
36/// trading decisions.
37///
38/// ## Edge Cases
39/// - stddev < 1e-9 (flat / constant series): returns 0.0 (no variance to normalize)
40/// - Window not yet full OR min_samples not yet reached: returns 0.0 for spatial coords
41/// - Single-sample window: mean = x, stddev = 0 → returns 0.0
42///
43/// ## Guarantees
44/// - Stateful: call in time order for correct rolling statistics
45/// - Never divides by zero
46/// - Time coordinate always unchanged
47/// - No dynamic allocation on hot path (deque amortized)
48
49#include "srfm/manifold.hpp"
50
51#include <cassert>
52#include <cstddef>
53#include <deque>
54#include <optional>
55
56namespace srfm {
57
58/// Rolling z-score normalizer for SpacetimeEvent spatial coordinates.
59///
60/// Maintains independent rolling windows for price, volume, and momentum.
61/// Each call to normalize() updates the windows and returns a z-scored event.
63public:
64 /// Minimum number of samples required before warmed_up() returns true.
65 static constexpr std::size_t DEFAULT_MIN_SAMPLES = 20;
66
67 /// Construct with a given window size and optional min_samples warm-up guard.
68 ///
69 /// # Arguments
70 /// * `window` — Number of bars to use for rolling statistics.
71 /// Minimum 1. Default: 20.
72 /// * `min_samples` — Number of samples that must be seen before warmed_up()
73 /// returns true and normalize() emits non-zero z-scores.
74 /// Defaults to max(window, DEFAULT_MIN_SAMPLES).
75 explicit CoordinateNormalizer(std::size_t window = 20,
76 std::size_t min_samples = 0) noexcept;
77
78 /// Normalize the spatial coordinates of a SpacetimeEvent.
79 ///
80 /// Updates the rolling window with the current event's coordinates, then
81 /// returns a new SpacetimeEvent where each spatial coordinate is z-scored
82 /// using the updated window statistics.
83 ///
84 /// # Returns
85 /// SpacetimeEvent with:
86 /// - time: unchanged (pass-through, no normalization)
87 /// - price: z-scored using current rolling window
88 /// - volume: z-scored using current rolling window
89 /// - momentum: z-scored using current rolling window
90 ///
91 /// If stddev < 1e-9 for a coordinate (flat series, no variance),
92 /// that coordinate is returned as 0.0.
93 [[nodiscard]] std::optional<manifold::SpacetimeEvent>
94 normalize(const manifold::SpacetimeEvent& raw) noexcept;
95
96 /// Number of samples currently in the rolling window (≤ window_size()).
97 [[nodiscard]] std::size_t size() const noexcept;
98
99 /// Configured maximum window size.
100 [[nodiscard]] std::size_t window_size() const noexcept;
101
102 /// Total number of samples seen so far (does not saturate at window_size()).
103 [[nodiscard]] std::size_t total_samples() const noexcept;
104
105 /// Returns true once at least min_samples data points have been accumulated.
106 ///
107 /// Before warmed_up() is true, normalize() returns a zero-valued event to
108 /// avoid producing wild z-scores from an insufficient statistical window.
109 [[nodiscard]] bool warmed_up() const noexcept;
110
111 /// Reset the normalizer, clearing all buffered observations.
112 void reset() noexcept;
113
114private:
115 std::size_t window_;
116 std::size_t min_samples_; ///< Warm-up threshold.
117 std::size_t total_{0}; ///< Total samples ingested (unbounded).
118 std::deque<double> price_buf_;
119 std::deque<double> volume_buf_;
120 std::deque<double> momentum_buf_;
121
122 /// Push a value into a deque, evicting the oldest if at capacity.
123 static void push(std::deque<double>& buf,
124 double value,
125 std::size_t max_size) noexcept;
126
127 /// Mean of all values in buf. Precondition: buf is non-empty.
128 static double rolling_mean(const std::deque<double>& buf) noexcept;
129
130 /// Sample std-dev (Bessel-corrected). Returns 0.0 if buf.size() < 2.
131 static double rolling_stddev(const std::deque<double>& buf,
132 double mean) noexcept;
133
134 /// Z-score `value` using the buffer's current statistics.
135 /// Returns 0.0 if stddev < FLAT_STDDEV_THRESHOLD.
136 static double zscore(double value,
137 const std::deque<double>& buf) noexcept;
138
139 static constexpr double FLAT_STDDEV_THRESHOLD = 1e-9;
140};
141
142} // namespace srfm
std::size_t total_samples() const noexcept
Total number of samples seen so far (does not saturate at window_size()).
static constexpr std::size_t DEFAULT_MIN_SAMPLES
Minimum number of samples required before warmed_up() returns true.
bool warmed_up() const noexcept
std::size_t size() const noexcept
Number of samples currently in the rolling window (≤ window_size()).
void reset() noexcept
Reset the normalizer, clearing all buffered observations.
std::size_t window_size() const noexcept
Configured maximum window size.
std::optional< manifold::SpacetimeEvent > normalize(const manifold::SpacetimeEvent &raw) noexcept
Spacetime Market Manifold — AGT-02 public API (implemented by AGT-06).