feat(stocks): add analyst consensus + price targets to stock analysis panel (#2926)

* feat(stocks): add analyst consensus + price targets to stock analysis panel

Shows recommendation trend (strongBuy/buy/hold/sell), price target range
(high/low/median vs current), and recent upgrade/downgrade actions with
firm names. Data from Yahoo Finance quoteSummary.

* chore: regenerate proto types and OpenAPI docs

* fix(stocks): fallback median to mean + use stock currency for price targets

* fix(stocks): drop fake $0 price targets and force refetch for pre-rollout snapshots

- Make PriceTarget high/low/mean/median/current optional in proto so partial
  Yahoo financialData payloads stop materializing as $0.00 cells in the panel.
- fetchYahooAnalystData now passes undefined (via optionalPositive) when a
  field is missing or non-positive, instead of coercing to 0.
- StockAnalysisPanel.renderPriceTarget skips Low/High cells entirely when the
  upstream value is missing and falls back to a Median + Analysts view.
- Add field-presence freshness check in stock-analysis-history: snapshots
  written before the analyst-revisions rollout (no analystConsensus and no
  priceTarget) are now classified as stale even when their generatedAt is
  inside the freshness window, so the data loader forces a live refetch.
- Tests cover undefined targets path, missing financialData path, and the
  three field-presence freshness branches.

* fix(stocks): preserve fresh snapshots on partial refetch + accept median-only targets

- loadStockAnalysis now merges still-fresh cached symbols with refetched
  live results so a partial refetch does not shrink the rendered watchlist
- renderAnalystConsensus accepts median-only price targets (not just mean)
This commit is contained in:
Elie Habib
2026-04-11 14:26:36 +04:00
committed by GitHub
parent 2ca4b7e60e
commit 889fa62849
12 changed files with 668 additions and 18 deletions

View File

@@ -78,4 +78,35 @@ message AnalyzeStockResponse {
double stop_loss = 44;
double take_profit = 45;
string engine_version = 46;
AnalystConsensus analyst_consensus = 47;
PriceTarget price_target = 48;
repeated UpgradeDowngrade recent_upgrades = 49;
}
message AnalystConsensus {
int32 strong_buy = 1;
int32 buy = 2;
int32 hold = 3;
int32 sell = 4;
int32 strong_sell = 5;
int32 total = 6;
string period = 7;
}
message PriceTarget {
optional double high = 1;
optional double low = 2;
optional double mean = 3;
optional double median = 4;
optional double current = 5;
int32 number_of_analysts = 6;
}
message UpgradeDowngrade {
string firm = 1;
string to_grade = 2;
string from_grade = 3;
string action = 4;
int64 epoch_grade_date = 5 [(sebuf.http.int64_encoding) = INT64_ENCODING_NUMBER];
}