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:
@@ -42,6 +42,7 @@
|
||||
#include "ui/notifications.h"
|
||||
#include "default_banlist_embedded.h"
|
||||
#include "util/amount_format.h"
|
||||
#include "util/http_download.h"
|
||||
#include "util/platform.h"
|
||||
#include "util/perf_log.h"
|
||||
#include "util/i18n.h"
|
||||
@@ -1621,6 +1622,46 @@ void App::refreshMarketData()
|
||||
{
|
||||
refreshPrice();
|
||||
refreshExchanges();
|
||||
refreshMarketChart();
|
||||
}
|
||||
|
||||
void App::refreshMarketChart()
|
||||
{
|
||||
if (!settings_ || !settings_->getFetchPrices()) return;
|
||||
if (!worker_) return;
|
||||
if (chart_fetch_in_flight_) return;
|
||||
// Historical data moves slowly — refresh at most every 30 minutes after the first fetch.
|
||||
if (state_.market.chart_loaded &&
|
||||
std::chrono::steady_clock::now() - state_.market.chart_last_fetch_time < std::chrono::minutes(30))
|
||||
return;
|
||||
chart_fetch_in_flight_ = true;
|
||||
|
||||
worker_->post([this]() -> rpc::RPCWorker::MainCb {
|
||||
// Two ranges give real granularity across every sparkline interval:
|
||||
// days=1 -> 5-minute samples (backs the "hour" interval)
|
||||
// days=365 -> daily samples (backs "day"/"week"/"month")
|
||||
// httpGetString verifies TLS and returns "" on any failure (parses to an empty series).
|
||||
std::string intradayBody = util::httpGetString(
|
||||
"https://api.coingecko.com/api/v3/coins/dragonx-2/market_chart?vs_currency=usd&days=1",
|
||||
"[market-chart]");
|
||||
std::string dailyBody = util::httpGetString(
|
||||
"https://api.coingecko.com/api/v3/coins/dragonx-2/market_chart?vs_currency=usd&days=365",
|
||||
"[market-chart]");
|
||||
auto intraday = NetworkRefreshService::parseCoinGeckoMarketChart(intradayBody);
|
||||
auto daily = NetworkRefreshService::parseCoinGeckoMarketChart(dailyBody);
|
||||
|
||||
return [this, intraday = std::move(intraday), daily = std::move(daily)]() mutable {
|
||||
chart_fetch_in_flight_ = false;
|
||||
const bool any = !intraday.empty() || !daily.empty();
|
||||
if (!intraday.empty()) state_.market.price_chart_intraday = std::move(intraday);
|
||||
if (!daily.empty()) state_.market.price_chart_daily = std::move(daily);
|
||||
if (any) {
|
||||
state_.market.chart_loaded = true;
|
||||
state_.market.chart_last_fetch_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
// On total failure, leave chart_loaded=false so the next tick retries.
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
void App::refreshExchanges()
|
||||
|
||||
Reference in New Issue
Block a user