74 const std::size_t n = bars.size();
77 std::size_t timelike_count = 0;
78 std::size_t lightlike_count = 0;
79 std::size_t spacelike_count = 0;
82 std::vector<double> timelike_returns;
83 std::vector<double> spacelike_returns;
84 timelike_returns.reserve(n);
85 spacelike_returns.reserve(n);
88 std::vector<double> always_in_rets(n);
89 std::vector<double> timelike_rets(n, 0.0);
90 std::vector<double> timelike_rel_rets(n, 0.0);
91 std::vector<double> benchmark_rets(n);
94 std::vector<double> unit_gammas(n, 1.0);
95 std::vector<double> timelike_gammas(n, 1.0);
97 for (std::size_t i = 0; i < n; ++i) {
98 const auto& bar = bars[i];
99 const double ret = bar.asset_return;
100 const double raw_sign = (bar.base.raw_signal >= 0.0) ? 1.0 : -1.0;
102 benchmark_rets[i] = bar.base.benchmark;
105 double gamma_i = 1.0;
110 const double gamma_capped =
111 std::max(1.0, std::min(gamma_i, config_.max_gamma));
114 const bool is_timelike = bar.ds2 < -LIGHTLIKE_EPSILON;
115 const bool is_lightlike = std::abs(bar.ds2) <= LIGHTLIKE_EPSILON;
116 const bool is_spacelike = bar.ds2 > LIGHTLIKE_EPSILON;
120 timelike_returns.push_back(ret);
121 }
else if (is_lightlike) {
123 }
else if (is_spacelike) {
125 spacelike_returns.push_back(ret);
129 always_in_rets[i] = raw_sign * ret;
133 timelike_rets[i] = raw_sign * ret;
134 timelike_rel_rets[i] = raw_sign * gamma_capped * ret;
135 timelike_gammas[i] = gamma_capped;
141 auto variance_of = [](
const std::vector<double>& v) ->
double {
142 if (v.size() < 2)
return 0.0;
144 std::accumulate(v.begin(), v.end(), 0.0) /
static_cast<double>(v.size());
147 const double d = x - mean;
150 return sq_sum /
static_cast<double>(v.size() - 1);
153 const double tv = variance_of(timelike_returns);
154 const double sv = variance_of(spacelike_returns);
158 auto calc_metrics = [&](
159 std::span<const double> rets,
160 std::span<const double> bench,
161 std::span<const double> gammas
162 ) -> std::optional<PerformanceMetrics> {
164 rets, config_.risk_free_rate, config_.annualisation);
165 if (!sh)
return std::nullopt;
168 rets, config_.risk_free_rate, config_.annualisation);
169 if (!so)
return std::nullopt;
172 if (!mdd)
return std::nullopt;
175 if (!ir)
return std::nullopt;
179 .sortino_ratio = *so,
180 .max_drawdown = *mdd,
181 .gamma_weighted_ir = *ir,
185 auto always_metrics = calc_metrics(always_in_rets, benchmark_rets, unit_gammas);
186 if (!always_metrics)
return std::nullopt;
188 auto timelike_metrics = calc_metrics(timelike_rets, benchmark_rets, timelike_gammas);
189 if (!timelike_metrics)
return std::nullopt;
191 auto timelike_rel_metrics = calc_metrics(timelike_rel_rets, benchmark_rets, timelike_gammas);
192 if (!timelike_rel_metrics)
return std::nullopt;
194 const double nd =
static_cast<double>(n);
197 .timelike_only = *timelike_metrics,
198 .timelike_relativistic = *timelike_rel_metrics,
199 .timelike_fraction =
static_cast<double>(timelike_count) / nd,
200 .spacelike_fraction =
static_cast<double>(spacelike_count) / nd,
201 .lightlike_fraction =
static_cast<double>(lightlike_count) / nd,
202 .timelike_variance = tv,
203 .spacelike_variance = sv,