Special Relativity in Financial Modeling 1.0.0
Lorentz transforms, spacetime classification, and geodesic price paths for quantitative finance
Loading...
Searching...
No Matches
hawking.cpp
Go to the documentation of this file.
1/// @file src/hawking.cpp
2/// @brief Implementation of the Hawking Radiation analogy for price series.
3///
4/// See include/srfm/hawking.hpp for full documentation.
5
6#include "srfm/hawking.hpp"
7#include "srfm/constants.hpp"
8
9#include <algorithm>
10#include <cassert>
11#include <cmath>
12#include <fmt/core.h>
13#include <numeric>
14#include <sstream>
15
16namespace srfm::hawking {
17
18// ─── HawkingTemperature ───────────────────────────────────────────────────────
19
20std::string HawkingTemperature::to_string() const {
21 return fmt::format(
22 "HawkingTemp T_H={:+.4f} z={:+.4f} Δz={:+.4f} "
23 "μ={:.2f} σ={:.4f} horizon={}",
26 near_horizon ? "YES" : "no"
27 );
28}
29
30// ─── HawkingSignal ────────────────────────────────────────────────────────────
31
32const char* to_string(HawkingDirection d) noexcept {
33 switch (d) {
34 case HawkingDirection::Reversal: return "Reversal";
35 case HawkingDirection::Continuation: return "Continuation";
36 case HawkingDirection::Neutral: return "Neutral";
37 }
38 return "Unknown";
39}
40
41std::string HawkingSignal::to_string() const {
42 return fmt::format(
43 "HawkingSignal dir={} strength={:.4f} action={:+d} | {}",
46 action,
48 );
49}
50
51// ─── BollingerState ───────────────────────────────────────────────────────────
52
53void BollingerState::push(double price) noexcept {
54 if (window.size() == max_size) {
55 // Remove oldest.
56 const double oldest = window.front();
57 sum -= oldest;
58 sum_sq -= oldest * oldest;
59 window.erase(window.begin());
60 }
61 window.push_back(price);
62 sum += price;
63 sum_sq += price * price;
64}
65
66double BollingerState::mean() const noexcept {
67 if (window.empty()) return 0.0;
68 return sum / static_cast<double>(window.size());
69}
70
71double BollingerState::stddev() const noexcept {
72 const std::size_t n = window.size();
73 if (n < 2) return 0.0;
74 const double n_d = static_cast<double>(n);
75 const double m = sum / n_d;
76 const double var = (sum_sq / n_d) - (m * m);
77 return (var > 0.0) ? std::sqrt(var) : 0.0;
78}
79
80// ─── PriceEventHorizon ────────────────────────────────────────────────────────
81
83 : config_(config)
84{
85 state_.max_size = config.bb_window;
86}
87
88void PriceEventHorizon::reset() noexcept {
89 state_ = BollingerState{};
90 state_.max_size = config_.bb_window;
91 prev_z_ = 0.0;
92 has_prev_z_ = false;
93}
94
95std::optional<HawkingTemperature>
96PriceEventHorizon::update(double close) noexcept {
97 if (!std::isfinite(close)) return std::nullopt;
98
99 state_.push(close);
100
101 // Need a full window before emitting.
102 if (!state_.full()) return std::nullopt;
103
104 const double mu = state_.mean();
105 const double sigma = state_.stddev();
106
107 // Avoid division by zero for flat price series.
108 if (sigma < 1e-12) return std::nullopt;
109
110 const double z = (close - mu) / sigma;
111 const double delta_z = has_prev_z_ ? (z - prev_z_) : 0.0;
112
113 // Hawking temperature: T_H = z × Δz
114 // Interpretation:
115 // - z > 0 and Δz > 0: price is above mean AND accelerating → hot (reversal)
116 // - z < 0 and Δz < 0: price is below mean AND falling faster → hot (reversal)
117 // - z > 0 and Δz < 0: price above mean but decelerating → cold (continuation)
118 const double temperature = z * delta_z;
119
121 ht.temperature = temperature;
122 ht.z_score = z;
123 ht.delta_z = delta_z;
124 ht.bollinger_mean = mu;
125 ht.bollinger_std = sigma;
126 ht.near_horizon = std::fabs(z) >= config_.bb_sigma;
127
128 prev_z_ = z;
129 has_prev_z_ = true;
130
131 return ht;
132}
133
134// ─── HawkingSignalGenerator ───────────────────────────────────────────────────
135
138) noexcept
139 : horizon_(config)
140 , config_(config)
141{}
142
144 horizon_.reset();
145}
146
147HawkingDirection HawkingSignalGenerator::classify(double temperature) const noexcept {
148 if (temperature > config_.hot_threshold) return HawkingDirection::Reversal;
149 if (temperature < config_.cold_threshold) return HawkingDirection::Continuation;
151}
152
153double HawkingSignalGenerator::strength(double temperature) noexcept {
154 // Map |T_H| to [0, 1] via a sigmoid-like normalisation.
155 // |T_H| > 5 → strength ≈ 1.
156 const double abs_t = std::fabs(temperature);
157 return abs_t / (abs_t + 1.0); // monotone, bounded in [0, 1)
158}
159
160int HawkingSignalGenerator::direction_to_action(HawkingDirection d) noexcept {
161 switch (d) {
163 // Trade against the current trend (fade the extreme move).
164 // The sign of z_score tells us which direction to fade.
165 // We return -1 as a generic "fade" signal; callers should invert
166 // based on z_score sign if needed.
167 return -1;
169 return +1;
171 return 0;
172 }
173 return 0;
174}
175
176std::optional<HawkingSignal>
177HawkingSignalGenerator::update(double close) noexcept {
178 auto temp = horizon_.update(close);
179 if (!temp.has_value()) return std::nullopt;
180
181 const HawkingDirection dir = classify(temp->temperature);
182 const double str = strength(temp->temperature);
183 const int action = direction_to_action(dir);
184
185 // For REVERSAL, the action direction depends on z_score sign:
186 // if z > 0 (price above mean), we expect it to fall → sell (-1)
187 // if z < 0 (price below mean), we expect it to rise → buy (+1)
188 int final_action = action;
189 if (dir == HawkingDirection::Reversal) {
190 final_action = (temp->z_score > 0.0) ? -1 : +1;
191 } else if (dir == HawkingDirection::Continuation) {
192 // Continue in the direction of the current z_score trend.
193 final_action = (temp->z_score > 0.0) ? +1 : -1;
194 }
195
196 return HawkingSignal{
197 .temperature = *temp,
198 .direction = dir,
199 .strength = str,
200 .action = final_action,
201 };
202}
203
204// ─── HawkingBacktestResult ────────────────────────────────────────────────────
205
207 return fmt::format(
208 "HawkingBacktest | "
209 "HawkingSharpe={:.4f} TimelikeSharpe={:.4f} "
210 "SharpeImprovement={:+.4f} | "
211 "MeanTemp={:.4f} Coverage={:.1%}",
217 );
218}
219
220// ─── HawkingBacktest ──────────────────────────────────────────────────────────
221
223 PriceEventHorizon::Config hawking_cfg,
224 core::EngineConfig engine_cfg
225) noexcept
226 : signal_gen_(hawking_cfg)
227 , engine_(engine_cfg)
228 , hawking_cfg_(hawking_cfg)
229{}
230
231std::optional<HawkingBacktestResult>
232HawkingBacktest::run(std::span<const core::OHLCV> bars) const noexcept {
233 const std::size_t N = bars.size();
234 const std::size_t min_req = hawking_cfg_.bb_window + 30;
235 if (N < min_req) return std::nullopt;
236
237 // Build pipeline bars via Engine streaming.
238 std::vector<core::PipelineBar> pipeline_bars;
239 pipeline_bars.reserve(N);
240 {
241 core::Engine streaming_engine;
242 for (const auto& bar : bars) {
243 auto pb = streaming_engine.process_stream_bar(bar);
244 if (pb) pipeline_bars.push_back(*pb);
245 }
246 }
247
248 if (pipeline_bars.size() < min_req) return std::nullopt;
249 const std::size_t M = pipeline_bars.size();
250
251 // Compute returns.
252 std::vector<double> returns;
253 returns.reserve(M);
254 for (const auto& pb : pipeline_bars) returns.push_back(pb.price_return);
255
256 // Generate Hawking signals and TIMELIKE-based signals.
257 // Use a mutable local generator (not shared with member to keep run() const).
258 HawkingSignalGenerator local_gen(hawking_cfg_);
259
260 std::vector<backtest::BarData> hawking_data;
261 std::vector<backtest::BarData> timelike_data;
262 hawking_data.reserve(M);
263 timelike_data.reserve(M);
264
265 double total_temperature = 0.0;
266 std::size_t non_neutral = 0;
267 const std::size_t offset = bars.size() - M;
268
269 for (std::size_t i = 0; i < M; ++i) {
270 const auto& pb = pipeline_bars[i];
271 const double close = bars[offset + i].close;
272
273 // Hawking signal.
274 auto sig = local_gen.update(close);
275 double h_signal = sig ? static_cast<double>(sig->action) : 0.0;
276 if (sig && sig->direction != HawkingDirection::Neutral) {
277 total_temperature += std::fabs(sig->temperature.temperature);
278 ++non_neutral;
279 }
280
281 // TIMELIKE baseline: +1 if bar is TIMELIKE, else 0.
282 double t_signal = (pb.interval_type == manifold::IntervalType::Timelike) ? 1.0 : 0.0;
283
284 hawking_data.push_back({
285 .raw_signal = h_signal,
286 .beta = pb.beta,
287 .benchmark = pb.price_return,
288 });
289 timelike_data.push_back({
290 .raw_signal = t_signal,
291 .beta = pb.beta,
292 .benchmark = pb.price_return,
293 });
294 }
295
296 // Run backtests.
298 auto hawking_result = bt.run(hawking_data, returns);
299 auto timelike_result = bt.run(timelike_data, returns);
300
301 if (!hawking_result || !timelike_result) return std::nullopt;
302
304 result.hawking_metrics = hawking_result->relativistic;
305 result.timelike_metrics = timelike_result->relativistic;
306 result.mean_temperature = (non_neutral > 0)
307 ? (total_temperature / static_cast<double>(non_neutral))
308 : 0.0;
309 result.signal_coverage = static_cast<double>(non_neutral)
310 / static_cast<double>(M);
311 return result;
312}
313
314} // namespace srfm::hawking
std::optional< BacktestComparison > run(std::span< const BarData > bars, std::span< const double > asset_returns) const noexcept
Orchestrates the full relativistic signal-processing pipeline.
Definition engine.hpp:88
std::optional< PipelineBar > process_stream_bar(const OHLCV &bar) noexcept
Definition engine.cpp:84
HawkingBacktest(PriceEventHorizon::Config hawking_cfg=PriceEventHorizon::Config{}, core::EngineConfig engine_cfg=core::EngineConfig{}) noexcept
Definition hawking.cpp:222
std::optional< HawkingBacktestResult > run(std::span< const core::OHLCV > bars) const noexcept
Definition hawking.cpp:232
void reset() noexcept
Reset internal state.
Definition hawking.cpp:143
HawkingSignalGenerator(PriceEventHorizon::Config config=PriceEventHorizon::Config{}) noexcept
Definition hawking.cpp:136
std::optional< HawkingSignal > update(double close) noexcept
Definition hawking.cpp:177
void reset() noexcept
Reset the internal rolling state (e.g. at session boundaries).
Definition hawking.cpp:88
std::optional< HawkingTemperature > update(double close) noexcept
Definition hawking.cpp:96
PriceEventHorizon(Config config=Config{}) noexcept
Definition hawking.cpp:82
Physical and financial constants for the SRFM system.
Hawking Radiation Analogy — event-horizon detection for price series.
const char * to_string(HawkingDirection d) noexcept
Return a human-readable string for a HawkingDirection value.
Definition hawking.cpp:32
HawkingDirection
Trading signal derived from Hawking temperature.
Definition hawking.hpp:98
@ Reversal
T_H high → trend exhaustion → fade the move.
@ Neutral
Temperature in the normal range → no signal.
@ Continuation
T_H low → healthy trend → follow the trend.
@ Timelike
ds² < 0 — causal market movement (β < c)
double sharpe_ratio
(mean_ret − r_f) / σ, annualised
Definition backtest.hpp:77
Configuration parameters for the core engine.
Definition engine.hpp:56
Internal rolling state for Bollinger Band computation.
Definition hawking.hpp:121
std::vector< double > window
Rolling price window (oldest first).
Definition hawking.hpp:122
double stddev() const noexcept
Return the rolling standard deviation (population σ).
Definition hawking.cpp:71
double sum
Running sum for fast mean.
Definition hawking.hpp:123
double sum_sq
Running sum of squares for fast σ.
Definition hawking.hpp:124
void push(double price) noexcept
Push a new price and pop the oldest if window is full.
Definition hawking.cpp:53
double mean() const noexcept
Return the rolling mean (NaN-safe).
Definition hawking.cpp:66
Comparison of Hawking-signal strategy vs the existing TIMELIKE classifier.
Definition hawking.hpp:242
backtest::PerformanceMetrics hawking_metrics
Performance of the Hawking temperature signal strategy.
Definition hawking.hpp:244
double sharpe_improvement() const noexcept
Improvement in Sharpe: hawking − timelike.
Definition hawking.hpp:256
std::string to_string() const
Format a one-line human-readable summary.
Definition hawking.cpp:206
double signal_coverage
Fraction of bars where the Hawking signal was non-neutral.
Definition hawking.hpp:253
double mean_temperature
Mean |T_H| across all bars with a non-neutral signal.
Definition hawking.hpp:250
backtest::PerformanceMetrics timelike_metrics
Performance of the TIMELIKE (standard relativistic) strategy.
Definition hawking.hpp:247
A single bar's Hawking-derived trading signal.
Definition hawking.hpp:108
std::string to_string() const
Format a compact one-liner for logging.
Definition hawking.cpp:41
int action
+1 (buy), -1 (sell), 0 (neutral).
Definition hawking.hpp:112
HawkingDirection direction
Trading implication.
Definition hawking.hpp:110
HawkingTemperature temperature
Full temperature measurement.
Definition hawking.hpp:109
double strength
|T_H| normalized to [0, 1].
Definition hawking.hpp:111
double delta_z
Change in z-score from previous bar.
Definition hawking.hpp:86
double temperature
T_H = z × Δz.
Definition hawking.hpp:84
double bollinger_mean
Rolling μ
Definition hawking.hpp:87
std::string to_string() const
Format a compact one-liner for logging.
Definition hawking.cpp:20
bool near_horizon
|z_score| ≥ bb_sigma
Definition hawking.hpp:89
double z_score
(P − μ) / σ
Definition hawking.hpp:85
Configuration for the Bollinger Band event horizon detector.
Definition hawking.hpp:156
std::size_t bb_window
Rolling window size for Bollinger Band mean and std dev.
Definition hawking.hpp:158