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:
2026-07-03 14:50:17 -05:00
parent db212a81c6
commit c1d5f79502
8 changed files with 144 additions and 4 deletions

View File

@@ -8,6 +8,8 @@
#include <vector>
#include <cstdint>
#include <chrono>
#include <ctime>
#include <utility>
#include "exchange_info.h"
@@ -159,9 +161,19 @@ struct MarketInfo {
bool price_loading = false;
std::string price_error;
// Price history for chart
// Live in-session price history: ~1 sample/minute, capped at MAX_HISTORY samples.
// Backs the main chart and the "minute" portfolio-sparkline interval.
std::vector<double> price_history;
static constexpr int MAX_HISTORY = 24; // 24 hours
static constexpr int MAX_HISTORY = 24; // 24 samples (~24 minutes at the 60s refresh)
// Historical USD price series fetched from CoinGecko market_chart, so the portfolio-group
// sparklines can show real hour/day/week/month trends instead of resampling the ~24-minute
// in-session buffer. Timestamped (unix seconds), oldest->newest; empty until the first fetch.
// Refreshed on a slow cadence (~30 min) since historical data moves slowly.
std::vector<std::pair<std::time_t, double>> price_chart_intraday; // ~24h @ 5-minute granularity
std::vector<std::pair<std::time_t, double>> price_chart_daily; // ~1yr @ daily granularity
std::chrono::steady_clock::time_point chart_last_fetch_time{};
bool chart_loaded = false;
// Exchanges/pairs fetched live from CoinGecko (empty until fetched; the Market tab
// falls back to data::getExchangeRegistry() while empty).