feat(market): real historical sparklines via CoinGecko market_chart
Portfolio-group sparklines previously resampled the ~24-minute in-session price
buffer, so the hour/day/week/month intervals could never fill. Fetch real
historical USD series from CoinGecko /market_chart and back each interval with
appropriately-grained data.
- MarketInfo gains two timestamped series: price_chart_intraday (days=1, 5-min)
and price_chart_daily (days=365, daily), plus a fetch timestamp.
- App::refreshMarketChart() fetches both on the RPC worker via the TLS-verifying
util::httpGetString helper, self-throttled to ~30 min (historical data moves
slowly). Gated by getFetchPrices(); triggered on connect and each price tick.
- Pure NetworkRefreshService::parseCoinGeckoMarketChart() parses {"prices":
[[ms,price],...]} into (unix-seconds, price); malformed rows skipped. Unit-tested.
- market_tab pfSparklineSeries() maps interval -> series+bucket: minute = live
buffer; hour = intraday bucketed to 1h; day/week/month = daily bucketed to
1d/7d/30d. Falls back to the in-session buffer until the fetch populates.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -458,6 +458,29 @@ std::optional<NetworkRefreshService::PriceRefreshResult> NetworkRefreshService::
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::time_t, double>> NetworkRefreshService::parseCoinGeckoMarketChart(
|
||||
const std::string& response)
|
||||
{
|
||||
std::vector<std::pair<std::time_t, double>> series;
|
||||
try {
|
||||
auto parsed = json::parse(response);
|
||||
if (!parsed.contains("prices") || !parsed["prices"].is_array()) return series;
|
||||
const auto& prices = parsed["prices"];
|
||||
series.reserve(prices.size());
|
||||
for (const auto& row : prices) {
|
||||
// Each row is [milliseconds, price]; skip anything malformed.
|
||||
if (!row.is_array() || row.size() < 2) continue;
|
||||
if (!row[0].is_number() || !row[1].is_number()) continue;
|
||||
const double ms = row[0].get<double>();
|
||||
const double price = row[1].get<double>();
|
||||
series.emplace_back(static_cast<std::time_t>(ms / 1000.0), price);
|
||||
}
|
||||
} catch (...) {
|
||||
series.clear();
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
NetworkRefreshService::PriceHttpResult NetworkRefreshService::parsePriceHttpResponse(
|
||||
const PriceHttpResponse& response,
|
||||
std::time_t fetchedAt)
|
||||
|
||||
@@ -250,6 +250,10 @@ public:
|
||||
std::time_t fetchedAt);
|
||||
static PriceHttpResult parsePriceHttpResponse(const PriceHttpResponse& response,
|
||||
std::time_t fetchedAt);
|
||||
// Parse a CoinGecko /market_chart response ({"prices":[[ms,price],...]}) into a timestamped
|
||||
// (unix seconds, USD price) series, oldest->newest. Malformed rows are skipped; on any error
|
||||
// (or a missing "prices" array) an empty vector is returned. Pure/no-I/O.
|
||||
static std::vector<std::pair<std::time_t, double>> parseCoinGeckoMarketChart(const std::string& response);
|
||||
static AddressInfo buildShieldedAddressInfo(const std::string& address,
|
||||
const nlohmann::json& validation,
|
||||
bool validationSucceeded);
|
||||
|
||||
Reference in New Issue
Block a user