feat(portfolio): persisted portfolio entries + pure sum helpers

Foundation for the Market tab's configurable portfolio (custom labels tied to
groups of addresses):

- Settings gains a PortfolioEntry {label, addresses[]} list, persisted in
  settings.json as a JSON array-of-objects (same mechanism as lite_servers_),
  with get/setPortfolioEntries().
- data/portfolio.h (header-only, ImGui-free): SumPortfolioBalance() sums a
  group's DRGX balance against the live per-address list (unknown addresses
  contribute 0), plus PortfolioEntryContains/Add/Remove group helpers.
- Unit-tested (testPortfolioHelpers): empty/known/unknown-address sums, and the
  add/remove/contains membership semantics.

UI (editor dialog, Market card rework, Overview right-click) comes next.
Full-node + lite build clean; ctest green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 22:32:53 -05:00
parent 3a9ff5f021
commit ecc0043559
4 changed files with 137 additions and 0 deletions

View File

@@ -294,6 +294,19 @@ bool Settings::load(const std::string& path)
for (const auto& w : j["saved_pool_workers"])
if (w.is_string()) saved_pool_workers_.push_back(w.get<std::string>());
}
if (j.contains("portfolio_entries") && j["portfolio_entries"].is_array()) {
portfolio_entries_.clear();
for (const auto& e : j["portfolio_entries"]) {
if (!e.is_object()) continue;
PortfolioEntry entry;
if (e.contains("label") && e["label"].is_string())
entry.label = e["label"].get<std::string>();
if (e.contains("addresses") && e["addresses"].is_array())
for (const auto& a : e["addresses"])
if (a.is_string()) entry.addresses.push_back(a.get<std::string>());
if (!entry.label.empty()) portfolio_entries_.push_back(std::move(entry));
}
}
if (j.contains("font_scale") && j["font_scale"].is_number())
font_scale_ = std::max(1.0f, std::min(1.5f, j["font_scale"].get<float>()));
if (j.contains("window_width") && j["window_width"].is_number_integer())
@@ -437,6 +450,14 @@ bool Settings::save(const std::string& path)
j["saved_pool_workers"] = json::array();
for (const auto& w : saved_pool_workers_)
j["saved_pool_workers"].push_back(w);
j["portfolio_entries"] = json::array();
for (const auto& e : portfolio_entries_) {
json entry;
entry["label"] = e.label;
entry["addresses"] = json::array();
for (const auto& a : e.addresses) entry["addresses"].push_back(a);
j["portfolio_entries"].push_back(std::move(entry));
}
j["font_scale"] = font_scale_;
j["settings_version"] = std::string(DRAGONX_VERSION);
if (window_width_ > 0 && window_height_ > 0) {