Special Relativity in Financial Modeling 1.0.0
Lorentz transforms, spacetime classification, and geodesic price paths for quantitative finance
Loading...
Searching...
No Matches
backtester.cpp
Go to the documentation of this file.
1/// @file src/backtest/backtester.cpp
2/// @brief Implementation of the Backtester class.
3///
4/// The Backtester orchestrates:
5/// 1. Lorentz signal correction via LorentzSignalAdjuster
6/// 2. Strategy return construction (sign-following rule)
7/// 3. Performance metric evaluation for both raw and relativistic strategies
8/// 4. Side-by-side comparison via BacktestComparison
9
10#include "srfm/backtest.hpp"
11#include "srfm/constants.hpp"
12
13#include <cmath>
14#include <span>
15#include <vector>
16
17namespace srfm::backtest {
18
19// ─── Backtester ───────────────────────────────────────────────────────────────
20
22 : config_(config)
23 , adjuster_(config.effective_mass) {}
24
25std::optional<LorentzCorrectedSeries>
26Backtester::apply_corrections(std::span<const BarData> bars) const noexcept {
27 return adjuster_.adjust(bars);
28}
29
30std::optional<PerformanceMetrics>
31Backtester::compute_metrics(std::span<const double> returns,
32 std::span<const double> benchmark_returns,
33 std::span<const double> gamma_factors) const noexcept {
35 returns, config_.risk_free_rate, config_.annualisation);
36 if (!sh.has_value()) return std::nullopt;
37
39 returns, config_.risk_free_rate, config_.annualisation);
40 if (!so.has_value()) return std::nullopt;
41
42 auto mdd = PerformanceCalculator::max_drawdown(returns);
43 if (!mdd.has_value()) return std::nullopt;
44
46 returns, benchmark_returns, gamma_factors);
47 if (!ir.has_value()) return std::nullopt;
48
49 return PerformanceMetrics{
50 .sharpe_ratio = *sh,
51 .sortino_ratio = *so,
52 .max_drawdown = *mdd,
53 .gamma_weighted_ir = *ir,
54 };
55}
56
57std::optional<BacktestComparison>
58Backtester::run(std::span<const BarData> bars,
59 std::span<const double> asset_returns) const noexcept {
60 // ── Input validation ────────────────────────────────────────────────────
61 if (bars.size() < constants::MIN_RETURN_SERIES_LENGTH) return std::nullopt;
62 if (asset_returns.size() != bars.size()) return std::nullopt;
63
64 const std::size_t n = bars.size();
65
66 // ── Step 1: Compute Lorentz-corrected signals ───────────────────────────
67 auto corrected = adjuster_.adjust(bars);
68 if (!corrected.has_value()) return std::nullopt;
69
70 // ── Step 2: Extract benchmark returns and gamma factors ─────────────────
71 std::vector<double> benchmark(n);
72 for (std::size_t i = 0; i < n; ++i) {
73 benchmark[i] = bars[i].benchmark;
74 }
75
76 const std::vector<double>& gammas = corrected->gamma_factors;
77
78 // ── Step 3: Construct strategy returns ──────────────────────────────────
79 //
80 // Raw strategy (unit position):
81 // return_t = sign(raw_signal_t) × asset_return_t
82 //
83 // Relativistic strategy (γ-magnitude-weighted position):
84 // position_t = sign(adj_signal_t) × clamp(γ_t, 1.0, max_gamma)
85 // return_t = position_t × asset_return_t
86 //
87 // In high-velocity markets (γ > 1) the relativistic strategy scales up
88 // its position, up to max_gamma times the base unit size. The raw
89 // strategy always uses unit position size regardless of market velocity.
90 // This produces measurably different Sharpe/Sortino/MDD between the two.
91 std::vector<double> raw_returns(n);
92 std::vector<double> adj_returns(n);
93
94 double gamma_sum = 0.0;
95 double max_gamma_seen = 1.0;
96 const double max_gamma_cap = config_.max_gamma;
97
98 for (std::size_t i = 0; i < n; ++i) {
99 const double raw_sign = (bars[i].raw_signal >= 0.0) ? 1.0 : -1.0;
100 const double adj_sign = (corrected->adjusted_signals[i] >= 0.0) ? 1.0 : -1.0;
101
102 // γ multiplier: clamp to [1.0, max_gamma_cap] so position is always
103 // at least unit size and never more than max_gamma_cap times unit.
104 const double gamma_i = gammas[i];
105 const double gamma_mult = std::max(1.0, std::min(gamma_i, max_gamma_cap));
106
107 raw_returns[i] = raw_sign * asset_returns[i];
108 adj_returns[i] = adj_sign * gamma_mult * asset_returns[i];
109
110 gamma_sum += gamma_i;
111 if (gamma_mult > max_gamma_seen) {
112 max_gamma_seen = gamma_mult;
113 }
114 }
115
116 const double mean_gamma_val = gamma_sum / static_cast<double>(n);
117
118 // ── Step 4: Compute metrics for both strategies ──────────────────────────
119 // For γ-weighted IR: raw strategy uses uniform γ=1; adjusted uses actual γ.
120 std::vector<double> unit_gammas(n, 1.0);
121
122 auto raw_metrics = compute_metrics(raw_returns, benchmark, unit_gammas);
123 if (!raw_metrics.has_value()) return std::nullopt;
124
125 auto adj_metrics = compute_metrics(adj_returns, benchmark, gammas);
126 if (!adj_metrics.has_value()) return std::nullopt;
127
128 // relativistic_lift = IR_γ_rel / IR_γ_raw (ratio, not difference)
129 const double raw_ir = raw_metrics->gamma_weighted_ir;
130 const double rel_ir = adj_metrics->gamma_weighted_ir;
131 const double lift = (std::abs(raw_ir) > 1e-12) ? (rel_ir / raw_ir) : 0.0;
132
133 return BacktestComparison{
134 .raw = *raw_metrics,
135 .relativistic = *adj_metrics,
136 .mean_gamma = mean_gamma_val,
137 .max_gamma_applied = max_gamma_seen,
138 .relativistic_lift = lift,
139 };
140}
141
142} // namespace srfm::backtest
Relativistic Backtester — AGT-05 public API.
std::optional< BacktestComparison > run(std::span< const BarData > bars, std::span< const double > asset_returns) const noexcept
Backtester(BacktestConfig config=BacktestConfig{})
Construct with configuration.
std::optional< LorentzCorrectedSeries > apply_corrections(std::span< const BarData > bars) const 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.
static constexpr std::size_t MIN_RETURN_SERIES_LENGTH
Definition constants.hpp:49
Side-by-side comparison of raw vs relativistic strategy metrics.
Definition backtest.hpp:87
PerformanceMetrics raw
Metrics from unmodified (unit-position) signals.
Definition backtest.hpp:88
Configuration for a backtest run.
Definition backtest.hpp:113
Performance metrics for a single strategy evaluation.
Definition backtest.hpp:76
double sharpe_ratio
(mean_ret − r_f) / σ, annualised
Definition backtest.hpp:77