feat(market): per-exchange price chart from each venue's own candle API
The market chart was hardwired to CoinGecko's cross-exchange USD aggregate (vs_currency=usd), so selecting a trading pair only changed the "Trade" link — never the chart or price. Now the SELECTED exchange drives both. CoinGecko gives us which venues list DRGX (the ticker `market.identifier`) but not their APIs, so add data/exchange_candles.h: a hand-maintained map from that identifier to each venue's public candle endpoint, with two adapters verified against the live APIs — Ourbit (MEXC-style /api/v3/klines, array format) and NonKYC (TradingView-UDF /market/candles, `bars`). Unmapped venues return no URL, so the chart falls back to the CoinGecko aggregate and nothing regresses. - App::refreshExchangeChart() resolves the selected pair, fetches its intraday (5-min) + daily candles via the TLS-verified httpGetString on the worker, and stores them on MarketInfo (exchange_chart_intraday/daily + exchange_chart_active). Re-fetches on pair change; self-throttled to ~30 min otherwise; falls back to aggregate on any failure. - chartSeries() draws the per-exchange series when active (portfolio sparklines stay on the aggregate). The hero shows the selected venue's real price (converted_last) — Ourbit vs NonKYC differ ~7%. parseCoinGeckoTickers now captures the identifier + per-exchange USD price (previously discarded). Adapters unit-tested against real captured responses (URL builder + both parsers + the chartSeries switch) and validated against the full live feeds (Ourbit 20d, NonKYC 370d, ascending, correct closes). Build + ctest + hygiene green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1240,10 +1240,17 @@ static void mktDrawPriceHero(const MktCtx& cx)
|
||||
float cx0 = cardMin.x + Layout::spacingLg();
|
||||
float cy = cardMin.y + Layout::spacingLg();
|
||||
|
||||
if (market.price_usd > 0) {
|
||||
// Prefer the SELECTED exchange's own current price (it differs per venue — Ourbit vs NonKYC can be
|
||||
// ~7% apart); fall back to the CoinGecko cross-exchange aggregate when the venue price is unknown.
|
||||
double heroPrice = market.price_usd;
|
||||
if (!currentExchange.pairs.empty() && s_mkt.pairIdx < (int)currentExchange.pairs.size()
|
||||
&& currentExchange.pairs[s_mkt.pairIdx].lastUsd > 0.0)
|
||||
heroPrice = currentExchange.pairs[s_mkt.pairIdx].lastUsd;
|
||||
|
||||
if (heroPrice > 0) {
|
||||
// ---- HERO PRICE (large, prominent) ----
|
||||
ImFont* h3 = Type().h3();
|
||||
std::string priceStr = FormatPrice(market.price_usd);
|
||||
std::string priceStr = FormatPrice(heroPrice);
|
||||
ImU32 priceCol = Success();
|
||||
DrawTextShadow(dl, h3, h3->LegacySize, ImVec2(cx0, cy), priceCol, priceStr.c_str());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user