19long long days_from_civil(
long long y,
unsigned m,
unsigned d)
noexcept {
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;
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;
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');
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)) {
47 if (mo < 1 || mo > 12 || d < 1 || d > 31)
return std::nullopt;
49 if ((s[10] !=
' ' && s[10] !=
'T') || !digits(11, 2, h) ||
50 s.size() < 16 || s[13] !=
':' || !digits(14, 2, mi)) {
53 if (s.size() >= 19 && s[16] ==
':' && !digits(17, 2, sec)) {
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);
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)) {
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;
85 if (bar.volume < 0.0)
return false;
93DataLoader::parse_row(
const std::string& line)
noexcept {
95 if (line.empty() || line[0] ==
'#') {
99 std::istringstream ss(line);
101 std::vector<double> fields;
104 while (std::getline(ss, token,
',')) {
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) {
111 token = token.substr(first, last - first + 1);
114 if (fields.empty()) {
116 if (
auto ts = parse_iso_timestamp(token)) {
117 fields.push_back(*ts);
123 val = std::stod(token, &pos);
124 if (pos != token.size()) {
131 if (!std::isfinite(val)) {
135 fields.push_back(val);
138 if (fields.size() != 6) {
143 .timestamp = fields[0],
151 if (!validate_bar(bar)) {
162 std::vector<OHLCV> bars;
163 std::istringstream stream(csv_content);
165 bool header_skipped =
false;
167 while (std::getline(stream, line)) {
169 if (!line.empty() && line.back() ==
'\r') {
173 if (!header_skipped) {
175 if (!line.empty() && line[0] !=
'#') {
176 header_skipped =
true;
181 auto bar = parse_row(line);
183 bars.push_back(*bar);
192std::optional<std::vector<OHLCV>>
194 std::ifstream file(filepath);
195 if (!file.is_open()) {
199 std::string contents;
201 while (std::getline(file, line)) {
206 return parse_csv_string(contents);