fix(quotes): keep negative price changes

From the Codex re-gate of the 0.3.3 fixes: the price sanity check also
applied to MSN's priceChange, so a falling stock without a previous close
got change: null. Bound a change by its size instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
This commit is contained in:
hermes 2026-10-17 05:12:11 +00:00
commit 46cf69db6e

View file

@ -33,7 +33,7 @@ pub(super) fn parse_quote(symbol: &str, quotes: &[MsnQuote]) -> Result<Quote, Id
})?;
let change = prev_close
.map(|previous| price - previous)
.or_else(|| quote.price_change.and_then(round_price));
.or_else(|| quote.price_change.and_then(round_change));
let ticker = quote
.symbol
@ -208,6 +208,11 @@ fn round_price(value: f64) -> Option<i64> {
(value.is_finite() && (0.0..=MAX_PRICE).contains(&value)).then(|| value.round() as i64)
}
/// A price change can be negative; same bound on its size.
fn round_change(value: f64) -> Option<i64> {
(value.is_finite() && value.abs() <= MAX_PRICE).then(|| value.round() as i64)
}
fn round_u64(value: Option<f64>) -> Option<u64> {
value.and_then(|number| {
if !number.is_finite() || number.is_sign_negative() {
@ -679,7 +684,7 @@ pub(super) fn parse_screener_results(raw: &RawScreenerResponse) -> Result<Vec<Qu
let prev_close = q.price_previous_close.and_then(round_price);
let change = prev_close
.map(|pc| price - pc)
.or_else(|| q.price_change.and_then(round_price));
.or_else(|| q.price_change.and_then(round_change));
let ticker = q
.symbol
.as_deref()
@ -912,6 +917,14 @@ mod tests {
assert_eq!(quote.prev_close, None);
}
#[test]
fn parse_quote_keeps_a_negative_change_without_a_previous_close() {
let raw: Vec<MsnQuote> =
serde_json::from_str(r#"[{"symbol":"BBCA","price":100.0,"priceChange":-5.0}]"#)
.unwrap();
assert_eq!(parse_quote("BBCA.JK", &raw).unwrap().change, Some(-5));
}
#[test]
fn parses_news_fixture_items() {
let raw: RawNewsFeed =