59 std::span<const double> asset_returns)
const noexcept {
62 if (asset_returns.size() != bars.size())
return std::nullopt;
64 const std::size_t n = bars.size();
67 auto corrected = adjuster_.adjust(bars);
68 if (!corrected.has_value())
return std::nullopt;
71 std::vector<double> benchmark(n);
72 for (std::size_t i = 0; i < n; ++i) {
73 benchmark[i] = bars[i].benchmark;
76 const std::vector<double>& gammas = corrected->gamma_factors;
91 std::vector<double> raw_returns(n);
92 std::vector<double> adj_returns(n);
94 double gamma_sum = 0.0;
95 double max_gamma_seen = 1.0;
96 const double max_gamma_cap = config_.max_gamma;
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;
104 const double gamma_i = gammas[i];
105 const double gamma_mult = std::max(1.0, std::min(gamma_i, max_gamma_cap));
107 raw_returns[i] = raw_sign * asset_returns[i];
108 adj_returns[i] = adj_sign * gamma_mult * asset_returns[i];
110 gamma_sum += gamma_i;
111 if (gamma_mult > max_gamma_seen) {
112 max_gamma_seen = gamma_mult;
116 const double mean_gamma_val = gamma_sum /
static_cast<double>(n);
120 std::vector<double> unit_gammas(n, 1.0);
122 auto raw_metrics = compute_metrics(raw_returns, benchmark, unit_gammas);
123 if (!raw_metrics.has_value())
return std::nullopt;
125 auto adj_metrics = compute_metrics(adj_returns, benchmark, gammas);
126 if (!adj_metrics.has_value())
return std::nullopt;
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;
135 .relativistic = *adj_metrics,
136 .mean_gamma = mean_gamma_val,
137 .max_gamma_applied = max_gamma_seen,
138 .relativistic_lift = lift,