Special Relativity in Financial Modeling 1.0.0
Lorentz transforms, spacetime classification, and geodesic price paths for quantitative finance
Loading...
Searching...
No Matches
data_loader.cpp
Go to the documentation of this file.
1/// @file src/core/data_loader.cpp
2/// @brief CSV DataLoader for OHLCV market data — AGT-06.
3
5
6#include <algorithm>
7#include <cmath>
8#include <fstream>
9#include <sstream>
10#include <stdexcept>
11#include <string>
12
13namespace srfm::core {
14
15namespace {
16
17/// Days since 1970-01-01 for a proleptic Gregorian date (H. Hinnant's
18/// days_from_civil).
19long long days_from_civil(long long y, unsigned m, unsigned d) noexcept {
20 y -= m <= 2 ? 1 : 0;
21 const long long era = (y >= 0 ? y : y - 399) / 400;
22 const unsigned yoe = static_cast<unsigned>(y - era * 400);
23 const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
24 const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
25 return era * 146097 + static_cast<long long>(doe) - 719468;
26}
27
28/// Parse an ISO-8601 style timestamp ("2021-03-01", "2021-03-01 14:30:00",
29/// "2021-03-01T14:30:00+00:00") into Unix epoch seconds (UTC). Any UTC
30/// offset suffix is ignored. Returns nullopt if the text is not a date.
31std::optional<double> parse_iso_timestamp(const std::string& s) noexcept {
32 auto digits = [&](std::size_t pos, std::size_t n, int& out) {
33 if (pos + n > s.size()) return false;
34 int v = 0;
35 for (std::size_t i = pos; i < pos + n; ++i) {
36 if (s[i] < '0' || s[i] > '9') return false;
37 v = v * 10 + (s[i] - '0');
38 }
39 out = v;
40 return true;
41 };
42 int y = 0, mo = 0, d = 0, h = 0, mi = 0, sec = 0;
43 if (!digits(0, 4, y) || s.size() < 10 || s[4] != '-' || s[7] != '-' ||
44 !digits(5, 2, mo) || !digits(8, 2, d)) {
45 return std::nullopt;
46 }
47 if (mo < 1 || mo > 12 || d < 1 || d > 31) return std::nullopt;
48 if (s.size() > 10) {
49 if ((s[10] != ' ' && s[10] != 'T') || !digits(11, 2, h) ||
50 s.size() < 16 || s[13] != ':' || !digits(14, 2, mi)) {
51 return std::nullopt;
52 }
53 if (s.size() >= 19 && s[16] == ':' && !digits(17, 2, sec)) {
54 return std::nullopt;
55 }
56 }
57 const long long days = days_from_civil(y, static_cast<unsigned>(mo),
58 static_cast<unsigned>(d));
59 return static_cast<double>(days * 86400LL + h * 3600LL + mi * 60LL + sec);
60}
61
62} // namespace
63
64// ─── DataLoader::validate_bar ─────────────────────────────────────────────────
65
66bool DataLoader::validate_bar(const OHLCV& bar) noexcept {
67 // All fields must be finite.
68 if (!std::isfinite(bar.timestamp) ||
69 !std::isfinite(bar.open) ||
70 !std::isfinite(bar.high) ||
71 !std::isfinite(bar.low) ||
72 !std::isfinite(bar.close) ||
73 !std::isfinite(bar.volume)) {
74 return false;
75 }
76
77 // OHLC consistency.
78 if (bar.high < bar.low) return false;
79 if (bar.open > bar.high) return false;
80 if (bar.open < bar.low) return false;
81 if (bar.close > bar.high) return false;
82 if (bar.close < bar.low) return false;
83
84 // Volume is non-negative.
85 if (bar.volume < 0.0) return false;
86
87 return true;
88}
89
90// ─── DataLoader::parse_row ────────────────────────────────────────────────────
91
92std::optional<OHLCV>
93DataLoader::parse_row(const std::string& line) noexcept {
94 // Skip blank lines and comment lines.
95 if (line.empty() || line[0] == '#') {
96 return std::nullopt;
97 }
98
99 std::istringstream ss(line);
100 std::string token;
101 std::vector<double> fields;
102 fields.reserve(6);
103
104 while (std::getline(ss, token, ',')) {
105 // Trim leading/trailing whitespace.
106 const auto first = token.find_first_not_of(" \t\r\n");
107 const auto last = token.find_last_not_of(" \t\r\n");
108 if (first == std::string::npos) {
109 return std::nullopt; // empty token
110 }
111 token = token.substr(first, last - first + 1);
112
113 double val = 0.0;
114 if (fields.empty()) {
115 // The timestamp column may be numeric or an ISO-8601 date.
116 if (auto ts = parse_iso_timestamp(token)) {
117 fields.push_back(*ts);
118 continue;
119 }
120 }
121 try {
122 std::size_t pos = 0;
123 val = std::stod(token, &pos);
124 if (pos != token.size()) {
125 return std::nullopt; // trailing garbage
126 }
127 } catch (...) {
128 return std::nullopt;
129 }
130
131 if (!std::isfinite(val)) {
132 return std::nullopt;
133 }
134
135 fields.push_back(val);
136 }
137
138 if (fields.size() != 6) {
139 return std::nullopt;
140 }
141
142 OHLCV bar{
143 .timestamp = fields[0],
144 .open = fields[1],
145 .high = fields[2],
146 .low = fields[3],
147 .close = fields[4],
148 .volume = fields[5],
149 };
150
151 if (!validate_bar(bar)) {
152 return std::nullopt;
153 }
154
155 return bar;
156}
157
158// ─── DataLoader::parse_csv_string ────────────────────────────────────────────
159
160std::vector<OHLCV>
161DataLoader::parse_csv_string(const std::string& csv_content) noexcept {
162 std::vector<OHLCV> bars;
163 std::istringstream stream(csv_content);
164 std::string line;
165 bool header_skipped = false;
166
167 while (std::getline(stream, line)) {
168 // Trim carriage return.
169 if (!line.empty() && line.back() == '\r') {
170 line.pop_back();
171 }
172
173 if (!header_skipped) {
174 // First non-empty, non-comment line is the header.
175 if (!line.empty() && line[0] != '#') {
176 header_skipped = true;
177 }
178 continue;
179 }
180
181 auto bar = parse_row(line);
182 if (bar) {
183 bars.push_back(*bar);
184 }
185 }
186
187 return bars;
188}
189
190// ─── DataLoader::load_csv ────────────────────────────────────────────────────
191
192std::optional<std::vector<OHLCV>>
193DataLoader::load_csv(const std::string& filepath) noexcept {
194 std::ifstream file(filepath);
195 if (!file.is_open()) {
196 return std::nullopt;
197 }
198
199 std::string contents;
200 std::string line;
201 while (std::getline(file, line)) {
202 contents += line;
203 contents += '\n';
204 }
205
206 return parse_csv_string(contents);
207}
208
209} // namespace srfm::core
static std::optional< std::vector< OHLCV > > load_csv(const std::string &filepath) noexcept
static bool validate_bar(const OHLCV &bar) noexcept
static std::vector< OHLCV > parse_csv_string(const std::string &csv_content) noexcept
CSV data loader for OHLCV market data — AGT-06.
A single OHLCV bar of market data.
Definition engine.hpp:44