feat(market): per-pair candle cache — instant switch-back, no re-fetch
The per-exchange chart used a single buffer keyed by the loaded pair, so bouncing between venues (Ourbit <-> NonKYC) re-fetched every switch (loading spinner each time). Add a per-pair cache: each venue's fetched candles (OHLC + derived close series) are kept keyed by "<identifier>:<BASE>/<QUOTE>". Switching back to a recently-viewed pair loads from the cache instantly — no fetch, no spinner — while entries older than 30 min re-fetch to stay fresh. Bounded to 16 entries (irrelevant for DRGX's 2 venues, but safe if it lists more). Cache is main-thread-only (populated by the worker's completion MainCb), so no races. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -687,6 +687,14 @@ private:
|
|||||||
std::string exchange_chart_key_; // "<identifier>:<BASE>/<QUOTE>" of the loaded series ("" = none)
|
std::string exchange_chart_key_; // "<identifier>:<BASE>/<QUOTE>" of the loaded series ("" = none)
|
||||||
bool exchange_chart_fetch_in_flight_ = false;
|
bool exchange_chart_fetch_in_flight_ = false;
|
||||||
std::chrono::steady_clock::time_point exchange_chart_last_fetch_{};
|
std::chrono::steady_clock::time_point exchange_chart_last_fetch_{};
|
||||||
|
// Per-pair candle cache: switching back to a recently-viewed venue loads instantly (no re-fetch)
|
||||||
|
// instead of overwriting the single active buffer. Keyed like exchange_chart_key_.
|
||||||
|
struct ExchangeChartCache {
|
||||||
|
std::vector<std::pair<std::time_t, double>> closeIntraday, closeDaily;
|
||||||
|
std::vector<data::Candle> ohlcIntraday, ohlcDaily;
|
||||||
|
std::chrono::steady_clock::time_point fetchedAt{};
|
||||||
|
};
|
||||||
|
std::unordered_map<std::string, ExchangeChartCache> exchange_chart_cache_;
|
||||||
util::AsyncTaskManager async_tasks_;
|
util::AsyncTaskManager async_tasks_;
|
||||||
bool pending_antivirus_dialog_ = false; // Show Windows Defender help dialog
|
bool pending_antivirus_dialog_ = false; // Show Windows Defender help dialog
|
||||||
|
|
||||||
|
|||||||
@@ -1806,6 +1806,19 @@ void App::refreshExchangeChart()
|
|||||||
if (key == exchange_chart_key_ && state_.market.exchange_chart_active &&
|
if (key == exchange_chart_key_ && state_.market.exchange_chart_active &&
|
||||||
std::chrono::steady_clock::now() - exchange_chart_last_fetch_ < std::chrono::minutes(30))
|
std::chrono::steady_clock::now() - exchange_chart_last_fetch_ < std::chrono::minutes(30))
|
||||||
return;
|
return;
|
||||||
|
// Cached from a recent view of this pair -> load instantly, no re-fetch (switching back is snappy).
|
||||||
|
if (auto it = exchange_chart_cache_.find(key);
|
||||||
|
it != exchange_chart_cache_.end() &&
|
||||||
|
std::chrono::steady_clock::now() - it->second.fetchedAt < std::chrono::minutes(30)) {
|
||||||
|
state_.market.exchange_chart_intraday = it->second.closeIntraday;
|
||||||
|
state_.market.exchange_chart_daily = it->second.closeDaily;
|
||||||
|
state_.market.exchange_ohlc_intraday = it->second.ohlcIntraday;
|
||||||
|
state_.market.exchange_ohlc_daily = it->second.ohlcDaily;
|
||||||
|
state_.market.exchange_chart_active = true;
|
||||||
|
exchange_chart_key_ = key;
|
||||||
|
exchange_chart_last_fetch_ = it->second.fetchedAt;
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Switching pairs: fall back to the aggregate until the new venue's candles arrive.
|
// Switching pairs: fall back to the aggregate until the new venue's candles arrive.
|
||||||
if (key != exchange_chart_key_) state_.market.exchange_chart_active = false;
|
if (key != exchange_chart_key_) state_.market.exchange_chart_active = false;
|
||||||
|
|
||||||
@@ -1824,18 +1837,25 @@ void App::refreshExchangeChart()
|
|||||||
return [this, key, ohlcIntra = std::move(ohlcIntra), ohlcDaily = std::move(ohlcDaily)]() mutable {
|
return [this, key, ohlcIntra = std::move(ohlcIntra), ohlcDaily = std::move(ohlcDaily)]() mutable {
|
||||||
exchange_chart_fetch_in_flight_ = false;
|
exchange_chart_fetch_in_flight_ = false;
|
||||||
if (!ohlcDaily.empty() || !ohlcIntra.empty()) {
|
if (!ohlcDaily.empty() || !ohlcIntra.empty()) {
|
||||||
// Derive close series (line + change%) from the OHLC (candles).
|
// Build a cache entry (OHLC + derived close series for the line/change%), publish it to
|
||||||
std::vector<std::pair<std::time_t, double>> closeIntra, closeDaily;
|
// the active buffer, and keep it so switching back to this pair later is instant.
|
||||||
closeIntra.reserve(ohlcIntra.size()); closeDaily.reserve(ohlcDaily.size());
|
ExchangeChartCache e;
|
||||||
for (const auto& c : ohlcIntra) closeIntra.emplace_back(c.time, c.close);
|
e.ohlcIntraday = std::move(ohlcIntra);
|
||||||
for (const auto& c : ohlcDaily) closeDaily.emplace_back(c.time, c.close);
|
e.ohlcDaily = std::move(ohlcDaily);
|
||||||
state_.market.exchange_chart_intraday = std::move(closeIntra);
|
e.closeIntraday.reserve(e.ohlcIntraday.size());
|
||||||
state_.market.exchange_chart_daily = std::move(closeDaily);
|
e.closeDaily.reserve(e.ohlcDaily.size());
|
||||||
state_.market.exchange_ohlc_intraday = std::move(ohlcIntra);
|
for (const auto& c : e.ohlcIntraday) e.closeIntraday.emplace_back(c.time, c.close);
|
||||||
state_.market.exchange_ohlc_daily = std::move(ohlcDaily);
|
for (const auto& c : e.ohlcDaily) e.closeDaily.emplace_back(c.time, c.close);
|
||||||
|
e.fetchedAt = std::chrono::steady_clock::now();
|
||||||
|
state_.market.exchange_chart_intraday = e.closeIntraday;
|
||||||
|
state_.market.exchange_chart_daily = e.closeDaily;
|
||||||
|
state_.market.exchange_ohlc_intraday = e.ohlcIntraday;
|
||||||
|
state_.market.exchange_ohlc_daily = e.ohlcDaily;
|
||||||
state_.market.exchange_chart_active = true;
|
state_.market.exchange_chart_active = true;
|
||||||
exchange_chart_key_ = key;
|
exchange_chart_key_ = key;
|
||||||
exchange_chart_last_fetch_ = std::chrono::steady_clock::now();
|
exchange_chart_last_fetch_ = e.fetchedAt;
|
||||||
|
if (exchange_chart_cache_.size() >= 16) exchange_chart_cache_.clear(); // bound it (many venues)
|
||||||
|
exchange_chart_cache_[key] = std::move(e);
|
||||||
} else {
|
} else {
|
||||||
// Both ranges empty -> the venue's API failed/changed; keep the aggregate.
|
// Both ranges empty -> the venue's API failed/changed; keep the aggregate.
|
||||||
state_.market.exchange_chart_active = false;
|
state_.market.exchange_chart_active = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user