Special Relativity in Financial Modeling 1.0.0
Lorentz transforms, spacetime classification, and geodesic price paths for quantitative finance
Loading...
Searching...
No Matches
geodesic_strategy.cpp
Go to the documentation of this file.
1/// @file src/backtest/geodesic_strategy.cpp
2/// @brief Implementation of ExtendedBacktester with GEODESIC_DEVIATION strategy.
3///
4/// Extends the Backtester to support three strategy modes:
5/// RAW — classical baseline (unit position)
6/// RELATIVISTIC — Lorentz-corrected γ-scaled position (original Backtester)
7/// GEODESIC_DEVIATION — long when deviation > rolling p75 (mean-reversion)
8///
9/// The geodesic deviation strategy encodes the hypothesis:
10/// "When the market has been pulled far from its natural geodesic path,
11/// expect mean reversion back toward the geodesic."
12/// Concretely: position = 1 when deviation_i > p75 of the last W bars;
13/// otherwise position = 0 (flat, no short).
14
16#include "srfm/constants.hpp"
17
18#include <algorithm>
19#include <cmath>
20#include <numeric>
21#include <sstream>
22#include <string>
23#include <vector>
24
25namespace srfm::backtest {
26
27// ─── TripleComparison::to_string ─────────────────────────────────────────────
28
30 std::ostringstream os;
31 os << "══════════════════════════════════════════════════════════════\n";
32 os << " SRFM Strategy Comparison — Ticker: " << ticker << "\n";
33 os << "══════════════════════════════════════════════════════════════\n";
34 os << " Strategy Sharpe Sortino MDD γ-IR\n";
35 os << "──────────────────────────────────────────────────────────────\n";
36
37 auto fmt_row = [&](const std::string& name, const PerformanceMetrics& m) {
38 os << " " << name;
39 for (int i = static_cast<int>(name.size()); i < 20; ++i) os << ' ';
40 os << " " << std::fixed;
41 os.precision(4);
42 os << m.sharpe_ratio << " "
43 << m.sortino_ratio << " "
44 << m.max_drawdown << " "
45 << m.gamma_weighted_ir << "\n";
46 };
47
48 fmt_row("RAW", raw);
49 fmt_row("RELATIVISTIC", relativistic);
50 fmt_row("GEODESIC_DEV", geodesic);
51 os << "══════════════════════════════════════════════════════════════\n";
52
53 // Deltas
54 auto delta = [](double a, double b) -> std::string {
55 double d = a - b;
56 std::ostringstream s;
57 s << std::fixed;
58 s.precision(4);
59 if (d >= 0.0) s << '+';
60 s << d;
61 return s.str();
62 };
63
64 os << " Relativistic Sharpe lift: " << delta(relativistic.sharpe_ratio, raw.sharpe_ratio) << "\n";
65 os << " Geodesic Sharpe alpha: " << delta(geodesic.sharpe_ratio, raw.sharpe_ratio) << "\n";
66 os << " Relativistic MDD delta: " << delta(raw.max_drawdown, relativistic.max_drawdown) << "\n";
67 os << " Geodesic MDD delta: " << delta(raw.max_drawdown, geodesic.max_drawdown) << "\n";
68 os << "══════════════════════════════════════════════════════════════\n";
69 return os.str();
70}
71
72// ─── Construction ─────────────────────────────────────────────────────────────
73
75 BacktestConfig config,
76 std::size_t rolling_window) noexcept
77 : config_(config)
78 , base_backtester_(config)
79 , rolling_window_(rolling_window)
80{}
81
82// ─── Private: rolling_p75 ────────────────────────────────────────────────────
83
84double ExtendedBacktester::rolling_p75(
85 const std::vector<GeodesicBarData>& bars,
86 std::size_t i) const noexcept
87{
88 // Window: [max(0, i - rolling_window_ + 1), i]
89 const std::size_t start =
90 (i + 1 > rolling_window_) ? (i + 1 - rolling_window_) : 0;
91
92 std::vector<double> window;
93 window.reserve(i - start + 1);
94 for (std::size_t j = start; j <= i; ++j) {
95 double dev = bars[j].geodesic_deviation;
96 if (std::isfinite(dev)) {
97 window.push_back(dev);
98 }
99 }
100
101 if (window.empty()) {
102 return 0.0;
103 }
104
105 // Partial sort to find p75
106 std::size_t p75_idx = static_cast<std::size_t>(
107 std::ceil(0.75 * static_cast<double>(window.size())) - 1.0);
108 p75_idx = std::min(p75_idx, window.size() - 1);
109
110 std::nth_element(window.begin(),
111 window.begin() + static_cast<std::ptrdiff_t>(p75_idx),
112 window.end());
113 return window[p75_idx];
114}
115
116// ─── Private: geodesic_positions ─────────────────────────────────────────────
117
118std::vector<double> ExtendedBacktester::geodesic_positions(
119 const std::vector<GeodesicBarData>& bars) const noexcept
120{
121 const std::size_t n = bars.size();
122 std::vector<double> positions;
123 positions.reserve(n);
124
125 for (std::size_t i = 0; i < n; ++i) {
126 double dev = bars[i].geodesic_deviation;
127 double p75 = rolling_p75(bars, i);
128 // Long (position = 1) when deviation exceeds rolling p75
129 positions.push_back((std::isfinite(dev) && dev > p75) ? 1.0 : 0.0);
130 }
131
132 return positions;
133}
134
135// ─── geodesic_returns ────────────────────────────────────────────────────────
136
137std::optional<std::vector<double>> ExtendedBacktester::geodesic_returns(
138 const std::vector<GeodesicBarData>& bars,
139 const std::vector<double>& asset_returns) const noexcept
140{
141 if (bars.size() != asset_returns.size()) return std::nullopt;
142 if (bars.size() < constants::MIN_RETURN_SERIES_LENGTH) return std::nullopt;
143
144 const std::size_t n = bars.size();
145 std::vector<double> positions = geodesic_positions(bars);
146
147 std::vector<double> returns;
148 returns.reserve(n);
149 for (std::size_t i = 0; i < n; ++i) {
150 double ret = positions[i] * asset_returns[i];
151 returns.push_back(std::isfinite(ret) ? ret : 0.0);
152 }
153 return returns;
154}
155
156// ─── run_triple ──────────────────────────────────────────────────────────────
157
158std::optional<ExtendedBacktester::TripleComparison>
160 const std::vector<GeodesicBarData>& bars,
161 const std::vector<double>& asset_returns,
162 const std::string& ticker) const noexcept
163{
164 // ── Input validation ────────────────────────────────────────────────────
165 if (bars.size() != asset_returns.size()) return std::nullopt;
166 if (bars.size() < constants::MIN_RETURN_SERIES_LENGTH) return std::nullopt;
167
168 const std::size_t n = bars.size();
169
170 // ── Extract base BarData for the delegate Backtester ────────────────────
171 std::vector<BarData> base_bars;
172 base_bars.reserve(n);
173 for (const auto& b : bars) {
174 base_bars.push_back(b.base);
175 }
176
177 // ── RAW & RELATIVISTIC via base Backtester ───────────────────────────────
178 auto comparison = base_backtester_.run(base_bars, asset_returns);
179 if (!comparison.has_value()) return std::nullopt;
180
181 // ── GEODESIC_DEVIATION ───────────────────────────────────────────────────
182 auto geo_rets = geodesic_returns(bars, asset_returns);
183 if (!geo_rets.has_value()) return std::nullopt;
184
185 // Build γ factors (reuse from base comparison — same beta inputs)
186 // For the γ-weighted IR we use a unit gamma vector (geodesic strategy
187 // does not apply relativistic γ scaling).
188 std::vector<double> unit_gamma(n, 1.0);
189 std::vector<double> benchmark(n, 0.0);
190 for (std::size_t i = 0; i < n; ++i) {
191 benchmark[i] = base_bars[i].benchmark;
192 }
193
194 auto sh = PerformanceCalculator::sharpe(*geo_rets,
195 config_.risk_free_rate,
196 config_.annualisation);
197 auto so = PerformanceCalculator::sortino(*geo_rets,
198 config_.risk_free_rate,
199 config_.annualisation);
200 auto mdd = PerformanceCalculator::max_drawdown(*geo_rets);
202 benchmark,
203 unit_gamma);
204
205 if (!sh || !so || !mdd || !ir) return std::nullopt;
206
207 PerformanceMetrics geo_metrics{
208 .sharpe_ratio = *sh,
209 .sortino_ratio = *so,
210 .max_drawdown = *mdd,
211 .gamma_weighted_ir = *ir,
212 };
213
214 return TripleComparison{
215 .raw = comparison->raw,
216 .relativistic = comparison->relativistic,
217 .geodesic = geo_metrics,
218 .ticker = ticker,
219 };
220}
221
222} // namespace srfm::backtest
std::optional< BacktestComparison > run(std::span< const BarData > bars, std::span< const double > asset_returns) const noexcept
std::optional< TripleComparison > run_triple(const std::vector< GeodesicBarData > &bars, const std::vector< double > &asset_returns, const std::string &ticker="") const noexcept
std::optional< std::vector< double > > geodesic_returns(const std::vector< GeodesicBarData > &bars, const std::vector< double > &asset_returns) const noexcept
ExtendedBacktester(BacktestConfig config=BacktestConfig{}, std::size_t rolling_window=100) noexcept
static std::optional< double > max_drawdown(std::span< const double > returns) noexcept
static std::optional< double > sortino(std::span< const double > returns, double risk_free_rate=constants::DEFAULT_RISK_FREE_RATE, double annualisation=constants::ANNUALISATION_FACTOR) noexcept
static std::optional< double > sharpe(std::span< const double > returns, double risk_free_rate=constants::DEFAULT_RISK_FREE_RATE, double annualisation=constants::ANNUALISATION_FACTOR) noexcept
static std::optional< double > gamma_weighted_ir(std::span< const double > strategy_returns, std::span< const double > benchmark_returns, std::span< const double > gamma_factors) noexcept
Physical and financial constants for the SRFM system.
Extended Backtester with Geodesic Deviation Strategy — AGT-07.
static constexpr std::size_t MIN_RETURN_SERIES_LENGTH
Definition constants.hpp:49
Configuration for a backtest run.
Definition backtest.hpp:113
Three-way performance comparison across all strategy modes.
std::string to_string() const
Human-readable comparison table.
PerformanceMetrics geodesic
GEODESIC_DEVIATION strategy metrics.
PerformanceMetrics raw
RAW strategy metrics.
std::string ticker
Ticker symbol (informational)
PerformanceMetrics relativistic
RELATIVISTIC strategy metrics.
Performance metrics for a single strategy evaluation.
Definition backtest.hpp:76
double max_drawdown
Peak-to-trough fractional loss (≥ 0)
Definition backtest.hpp:79
double sharpe_ratio
(mean_ret − r_f) / σ, annualised
Definition backtest.hpp:77