Single-binary self-hosted market watcher for stocks, ETFs, indexes, and futures: live charts, key stats, fundamentals, SEC filings, and SSE streaming.
axumdockerfinancerustself-hostedsqlitestocksvite
1//! S&P 500 membership, used to restrict the dashboard's market-movers lists to
2//! recognizable large-cap names rather than the whole-market micro-caps Yahoo's
3//! predefined screeners return (a user request: "market movers has a lot of
4//! companies I've literally never heard of").
5//!
6//! The constituent list lives in `universe/sp500.txt` (one ticker per line, in
7//! Yahoo symbology so `BRK.B` is `BRK-B`; `#` comments and blank lines allowed)
8//! and is embedded at compile time, so there is no runtime file IO and it ships
9//! inside the binary. Refresh by editing that file and redeploying (the file's
10//! header documents the one-liner that regenerates it).
11
12use std::collections::HashSet;
13use std::sync::LazyLock;
14
15/// The raw constituent list, embedded at build time.
16const SP500_RAW: &str = include_str!("../universe/sp500.txt");
17
18/// The membership set, parsed once on first use. Tickers are uppercased so the
19/// lookup is case-insensitive; blank lines and `#` comments are skipped.
20static SP500: LazyLock<HashSet<String>> = LazyLock::new(|| {
21 SP500_RAW
22 .lines()
23 .map(str::trim)
24 .filter(|l| !l.is_empty() && !l.starts_with('#'))
25 .map(str::to_uppercase)
26 .collect()
27});
28
29/// Whether `ticker` is an S&P 500 constituent (case-insensitive).
30pub fn is_member(ticker: &str) -> bool {
31 SP500.contains(&ticker.to_uppercase())
32}
33
34/// How many constituents are loaded — for a boot log / sanity check.
35pub fn count() -> usize {
36 SP500.len()
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42
43 #[test]
44 fn loads_a_full_roster() {
45 // The S&P 500 hovers around 500-505 names; guard against an empty or
46 // truncated embed (e.g. a botched refresh).
47 assert!(count() >= 490, "expected ~500 constituents, got {}", count());
48 }
49
50 #[test]
51 fn known_members_and_non_members() {
52 assert!(is_member("AAPL"));
53 assert!(is_member("aapl"), "lookup is case-insensitive");
54 assert!(is_member("BRK-B"), "dotted tickers stored in Yahoo dash form");
55 assert!(is_member("JPM"));
56 // A real ticker that is not in the index, and obvious junk.
57 assert!(!is_member("SPCX"), "a delisted micro-cap fund is not a member");
58 assert!(!is_member("NOTATICKER"));
59 }
60}