40namespace fs = std::filesystem;
44static std::vector<std::string>
split_csv(
const std::string& line) {
45 std::vector<std::string> fields;
47 std::istringstream ss(line);
48 while (std::getline(ss, field,
',')) {
49 auto start = field.find_first_not_of(
" \t\r\n");
50 auto end = field.find_last_not_of(
" \t\r\n");
51 if (start != std::string::npos) {
52 fields.push_back(field.substr(start, end - start + 1));
60static std::optional<double>
safe_double(
const std::string& s) {
61 if (s.empty())
return std::nullopt;
64 double v = std::stod(s, &pos);
65 if (pos != s.size())
return std::nullopt;
66 if (!std::isfinite(v))
return std::nullopt;
73static int find_col(
const std::vector<std::string>& hdr,
const std::string& name) {
74 for (
int i = 0; i < static_cast<int>(hdr.size()); ++i) {
75 std::string h = hdr[
static_cast<std::size_t
>(i)];
76 std::transform(h.begin(), h.end(), h.begin(), ::tolower);
77 if (h == name)
return i;
93static std::optional<std::vector<RegimeRow>>
load_regime_csv(
const std::string& path,
95 std::ifstream file(path);
96 if (!file.is_open()) {
97 err =
"Cannot open: " + path;
102 if (!std::getline(file, line)) {
103 err =
"Empty file: " + path;
108 int col_idx =
find_col(hdr,
"bar_index");
109 int col_type =
find_col(hdr,
"interval_type");
110 int col_ret =
find_col(hdr,
"next_bar_abs_return");
111 int col_sret =
find_col(hdr,
"next_bar_return");
112 int col_beta =
find_col(hdr,
"beta");
113 int col_geo =
find_col(hdr,
"geodesic_deviation");
115 if (col_type < 0 || col_ret < 0) {
116 err =
"CSV missing required columns interval_type / next_bar_abs_return";
120 std::vector<RegimeRow> rows;
121 std::size_t auto_idx = 0;
122 while (std::getline(file, line)) {
123 if (line.empty())
continue;
125 if (
static_cast<int>(fields.size()) <= col_type)
continue;
127 auto maybe_ret =
safe_double(fields[
static_cast<std::size_t
>(col_ret)]);
128 if (!maybe_ret)
continue;
132 ?
static_cast<std::size_t
>(
133 safe_double(fields[
static_cast<std::size_t
>(col_idx)]).value_or(
134 static_cast<double>(auto_idx)))
136 row.
interval_type = fields[
static_cast<std::size_t
>(col_type)];
139 ?
safe_double(fields[
static_cast<std::size_t
>(col_sret)]).value_or(0.0)
141 row.
beta = (col_beta >= 0)
142 ?
safe_double(fields[
static_cast<std::size_t
>(col_beta)]).value_or(0.0)
145 ?
safe_double(fields[
static_cast<std::size_t
>(col_geo)]).value_or(0.0)
160static std::pair<std::vector<srfm::backtest::GeodesicBarData>, std::vector<double>>
162 std::vector<srfm::backtest::GeodesicBarData> bars;
163 std::vector<double> asset_returns;
164 bars.reserve(rows.size());
165 asset_returns.reserve(rows.size());
167 for (
const auto& row : rows) {
169 double raw_signal = (row.interval_type ==
"Timelike") ? 1.0 : -1.0;
180 .geodesic_deviation = row.geodesic_deviation,
188 double asset_ret = (row.next_bar_return != 0.0 || row.next_bar_abs_return == 0.0)
189 ? row.next_bar_return
190 : row.next_bar_abs_return;
191 asset_returns.push_back(asset_ret);
194 return {bars, asset_returns};
200 const std::string& ticker,
202 const std::string& output_dir,
205 fs::create_directories(output_dir);
206 std::string safe_ticker = ticker;
207 std::replace(safe_ticker.begin(), safe_ticker.end(),
'-',
'_');
209 std::string out_path = output_dir +
"/" + safe_ticker +
"_backtest.csv";
210 std::ofstream out(out_path);
211 if (!out.is_open()) {
212 err =
"Cannot open output: " + out_path;
216 out <<
"ticker,strategy,sharpe,sortino,max_drawdown\n";
217 auto write_row = [&](
const std::string& strategy,
219 out << ticker <<
"," << strategy <<
",";
222 out << m.sharpe_ratio <<
","
223 << m.sortino_ratio <<
","
224 << m.max_drawdown <<
"\n";
227 write_row(
"RAW", result.
raw);
229 write_row(
"GEODESIC_DEVIATION", result.
geodesic);
231 std::cout <<
"[" << ticker <<
"] Backtest CSV written to " << out_path <<
"\n";
243static std::optional<Args>
parse_args(
int argc,
char* argv[]) {
245 for (
int i = 1; i < argc - 1; ++i) {
246 std::string key(argv[i]);
247 std::string val(argv[i + 1]);
248 if (key ==
"--input") {
250 }
else if (key ==
"--output-dir") {
252 }
else if (key ==
"--ticker") {
263int main(
int argc,
char* argv[]) {
265 if (!maybe_args.has_value()) {
266 std::cerr <<
"Usage: backtest_runner --input <regime_csv> "
267 "--output-dir <dir> [--ticker <name>]\n";
270 const Args& args = *maybe_args;
273 std::string load_err;
275 if (!maybe_rows.has_value()) {
276 std::cerr <<
"[FATAL] " << load_err <<
"\n";
279 std::vector<RegimeRow> rows = std::move(*maybe_rows);
281 std::cout <<
"[" << args.
ticker <<
"] Loaded " << rows.size() <<
" regime rows\n";
284 std::cerr <<
"[FATAL] Too few rows (" << rows.size()
290 auto [bars, asset_returns] =
build_bars(rows);
298 auto maybe_result = backtester.
run_triple(bars, asset_returns, args.
ticker);
299 if (!maybe_result.has_value()) {
300 std::cerr <<
"[FATAL] Backtest failed for " << args.
ticker
301 <<
" — check data quality and minimum series length\n";
306 std::cout << maybe_result->to_string();
309 std::string write_err;
311 std::cerr <<
"[FATAL] " << write_err <<
"\n";
Relativistic Backtester — AGT-05 public API.
int main(int argc, char *argv[])
static std::optional< std::vector< RegimeRow > > load_regime_csv(const std::string &path, std::string &err)
static std::pair< std::vector< srfm::backtest::GeodesicBarData >, std::vector< double > > build_bars(const std::vector< RegimeRow > &rows)
static int find_col(const std::vector< std::string > &hdr, const std::string &name)
static std::vector< std::string > split_csv(const std::string &line)
static bool write_backtest_csv(const std::string &ticker, const srfm::backtest::ExtendedBacktester::TripleComparison &result, const std::string &output_dir, std::string &err)
static std::optional< double > safe_double(const std::string &s)
static std::optional< Args > parse_args(int argc, char *argv[])
std::optional< TripleComparison > run_triple(const std::vector< GeodesicBarData > &bars, const std::vector< double > &asset_returns, const std::string &ticker="") const 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
static constexpr double BETA_MAX_SAFE
double geodesic_deviation
std::string interval_type
double next_bar_return
Signed return (may be 0 if column absent)
double next_bar_abs_return
Configuration for a backtest run.
A single time-bar of backtester input.
double raw_signal
Strategy signal before relativistic correction.
Three-way performance comparison across all strategy modes.
PerformanceMetrics geodesic
GEODESIC_DEVIATION strategy metrics.
PerformanceMetrics raw
RAW strategy metrics.
PerformanceMetrics relativistic
RELATIVISTIC strategy metrics.
BarData base
Standard bar: raw_signal, beta, benchmark.