Special Relativity in Financial Modeling 1.0.0
Lorentz transforms, spacetime classification, and geodesic price paths for quantitative finance
Loading...
Searching...
No Matches
signal_processor.cpp
Go to the documentation of this file.
1/**
2 * @file signal_processor.cpp
3 * @brief SignalProcessor implementation — per-tick signal chain.
4 *
5 * See include/srfm/stream/signal_processor.hpp for the full module contract.
6 *
7 * Hot-path design
8 * ---------------
9 * The inner loop in run_loop() calls process_one() for each tick.
10 * process_one() is the pure computation kernel — no ring I/O, no threading.
11 * This separation allows unit tests to exercise the signal chain without
12 * launching threads.
13 *
14 * Per-tick allocation budget: zero.
15 * - CoordinateNormalizer uses a pre-allocated std::vector (constructed once).
16 * - BetaCalculatorFix3 uses a std::array<double,3> (stack).
17 * - LorentzTransform is stateless.
18 * - SpacetimeManifold has two doubles + one bool (stack).
19 * - StreamRelativisticSignal is a plain struct (stack).
20 *
21 * Backoff
22 * -------
23 * When in_ring_ is empty, yields (same strategy as TickIngester).
24 */
25
26#include "../../include/srfm/stream/signal_processor.hpp"
27
28#include <cmath>
29#include <thread>
30
31namespace srfm::stream {
32
33// ── SignalProcessor::start ────────────────────────────────────────────────────
34
35void SignalProcessor::start() noexcept {
36 if (running_.load(std::memory_order_acquire)) return;
37
38 stop_requested_.store(false, std::memory_order_release);
39 running_.store(true, std::memory_order_release);
40
41 thread_ = std::thread([this]() noexcept { run_loop(); });
42}
43
44// ── SignalProcessor::stop ─────────────────────────────────────────────────────
45
46void SignalProcessor::stop() noexcept {
47 stop_requested_.store(true, std::memory_order_release);
48 if (thread_.joinable()) thread_.join();
49 running_.store(false, std::memory_order_release);
50}
51
52// ── SignalProcessor::process_one ──────────────────────────────────────────────
53
56 std::int64_t bar_index) noexcept {
57 // ── Stage 1: CoordinateNormalizer ──────────────────────────────────────────
58 // Update the rolling window with the close price first so that normalise()
59 // reflects the current bar.
60 normalizer_.update(tick.close);
61 const double norm_close = normalizer_.normalise(tick.close);
62
63 // ── Stage 2: BetaCalculator ────────────────────────────────────────────────
64 beta_calc_.update(tick.close);
65 const double beta = beta_calc_.beta(); // 0.0 until warm-up
66
67 // ── Stage 3: LorentzTransform ──────────────────────────────────────────────
68 const double t = static_cast<double>(bar_index);
69 const double x = norm_close;
70 const auto ev = lorentz_.transform(t, x, beta);
71
72 // ── Stage 4: SpacetimeManifold ─────────────────────────────────────────────
73 const auto mr = manifold_.update(ev.t_prime, ev.x_prime);
74
75 // ── Assemble output ────────────────────────────────────────────────────────
76 // Final signal = γ * manifold_signal (Lorentz-scaled interval signal).
77 const double final_signal = ev.gamma * mr.signal;
78
80 out.bar = bar_index;
81 out.beta = beta;
82 out.gamma = ev.gamma;
83 out.regime = mr.regime;
84 out.signal = final_signal;
85 out.ds2 = mr.ds2;
86
87 return out;
88}
89
90// ── SignalProcessor::run_loop ─────────────────────────────────────────────────
91
92void SignalProcessor::run_loop() noexcept {
93 while (!stop_requested_.load(std::memory_order_acquire)) {
94
95 auto maybe_tick = in_ring_.pop();
96
97 if (!maybe_tick.has_value()) {
98 std::this_thread::yield();
99 continue;
100 }
101
102 ++counters_.ticks_processed;
103
104 // ── NaN/Inf propagation guard ───────────────────────────────────────
105 // Reject any tick whose signal-relevant fields are non-finite,
106 // non-positive (close) or negative (volume). This prevents NaN/Inf
107 // from propagating through the normalizer and Lorentz chain.
108 {
109 const OHLCVTick& t = *maybe_tick;
110 const bool close_ok = std::isfinite(t.close) && t.close > 0.0;
111 const bool volume_ok = std::isfinite(t.volume) && t.volume >= 0.0;
112 if (!close_ok || !volume_ok) {
113 ++counters_.ticks_rejected_invalid;
114 continue;
115 }
116 }
117
118 const auto sig = process_one(*maybe_tick, bar_counter_++);
119
120 // Track regime distribution.
121 switch (sig.regime) {
122 case Regime::TIMELIKE: ++counters_.timelike_count; break;
123 case Regime::LIGHTLIKE: ++counters_.lightlike_count; break;
124 case Regime::SPACELIKE: ++counters_.spacelike_count; break;
125 }
126
127 // Push to output ring.
128 StreamRelativisticSignal s = sig; // copy for move
129 if (!out_ring_.push(std::move(s))) {
130 ++counters_.signals_dropped_ring_full;
131 continue;
132 }
133
134 ++counters_.signals_emitted;
135 }
136
137 running_.store(false, std::memory_order_release);
138}
139
140} // namespace srfm::stream
std::optional< T > pop() noexcept
Try to dequeue one element.
bool push(T &&item) noexcept
Try to enqueue one element.
void stop() noexcept
Stop and join the processing thread. Idempotent.
void start() noexcept
Launch the processing thread. Idempotent.
StreamRelativisticSignal process_one(const OHLCVTick &tick, std::int64_t bar_index) noexcept
Process one tick synchronously and return the resulting signal.
Single OHLCV bar tick from the market data feed.
Definition tick.hpp:50
std::uint64_t timelike_count
TIMELIKE regime events.
std::uint64_t signals_emitted
Signals pushed to output ring.
std::uint64_t ticks_rejected_invalid
Dropped: NaN/Inf/non-positive field.
std::uint64_t lightlike_count
LIGHTLIKE regime events.
std::uint64_t ticks_processed
Ticks popped from input ring.
std::uint64_t spacelike_count
SPACELIKE regime events.
std::uint64_t signals_dropped_ring_full
Dropped: output ring full.
Fully-characterised relativistic signal for one processed tick.
Regime regime
Spacetime interval regime.
std::int64_t bar
Bar sequence number.
double signal
Relativistic signal value.
double ds2
Spacetime interval Δs² (diagnostic).