pomelo_fmp/config.rs
1//! Sync configuration and result summary.
2
3use std::time::Duration;
4
5pub use pomelo_http::WriteMode;
6
7/// Knobs for one [`sync`] run.
8pub struct SyncConfig {
9 /// Inclusive date bounds, packed `YYYYMMDD` (as everywhere else in the engine).
10 pub from: i32,
11 pub to: i32,
12 /// Also fetch annual fundamentals → `fundamentals/{SYM}.csv.gz`.
13 pub include_fundamentals: bool,
14 /// Also fetch company sector → `tracked/universe.csv.gz`.
15 pub include_industry: bool,
16 /// Also compute the six snapshot-factor panels (`piotroski_score`,
17 /// `altman_z`, `fcf_yield`, `pe_industry_pctile`, `analyst_upside_pct`,
18 /// `consensus_rating`) → `panels/{name}.csv.gz`. Current-snapshot factors
19 /// for universe screening (see [`super::snapshot`]); `pe_industry_pctile`
20 /// ranks P/E within an industry cohort drawn from this run's symbols.
21 pub include_snapshot_factors: bool,
22 /// Skip ETFs / mutual & closed-end funds (default on) — keep only individual
23 /// stocks. Classified from the profile endpoint's `isEtf` / `isFund`.
24 pub skip_non_stocks: bool,
25 /// Skip symbols whose company market cap is below this, in **USD**
26 /// (`0.0` = off). Read from the profile endpoint's `marketCap`. The CLI
27 /// accepts unit suffixes (`1b`, `500m`) via [`parse_market_cap`].
28 pub min_market_cap: f64,
29 /// Max requests per minute (`0` = no throttle). FMP imposes a per-plan rate
30 /// limit; set this to your plan's ceiling. Starter-class keys are commonly
31 /// ~300/min — verify against your own plan.
32 pub rate_limit_per_min: u32,
33 /// Retries per request on a retryable error before giving up on the symbol.
34 pub max_retries: u32,
35 /// Base backoff **duration**; the Nth retry waits `base * 2^(N-1)` — e.g. a
36 /// 2-second base gives 2s, 4s, 8s, 16s. `Duration::ZERO` disables the sleep
37 /// (used by tests).
38 pub backoff_base: Duration,
39 /// How to treat an already-present tree.
40 pub mode: WriteMode,
41}
42
43impl Default for SyncConfig {
44 fn default() -> Self {
45 SyncConfig {
46 from: 20000101,
47 to: 99991231,
48 include_fundamentals: false,
49 include_industry: false,
50 include_snapshot_factors: false,
51 skip_non_stocks: true,
52 min_market_cap: 0.0,
53 rate_limit_per_min: 300,
54 max_retries: 4,
55 backoff_base: Duration::from_secs(2),
56 mode: WriteMode::Overwrite,
57 }
58 }
59}
60
61impl pomelo_http::RetrySettings for SyncConfig {
62 fn rate_limit_per_min(&self) -> u32 {
63 self.rate_limit_per_min
64 }
65 fn max_retries(&self) -> u32 {
66 self.max_retries
67 }
68 fn backoff_base(&self) -> Duration {
69 self.backoff_base
70 }
71}
72
73/// What a [`sync`] run produced.
74#[derive(Debug, Default)]
75pub struct SyncSummary {
76 /// Symbols whose price file was (re)written.
77 pub symbols_written: usize,
78 /// Symbols skipped because they already existed (`--resume`).
79 pub symbols_skipped: usize,
80 /// Symbols screened out by the ETF/fund or market-cap filters.
81 pub symbols_filtered: usize,
82 /// Total price rows written across all symbols.
83 pub price_rows: usize,
84 /// Symbols with fundamentals written.
85 pub fundamentals_written: usize,
86 /// Whether the industry snapshot was written.
87 pub industry_written: bool,
88 /// Number of `panels/{name}.csv.gz` snapshot-factor panels written.
89 pub snapshot_factor_panels: usize,
90 /// Per-symbol hard failures (symbol, redacted message). A failure on one
91 /// symbol does not abort the batch.
92 pub failures: Vec<(String, String)>,
93}
94
95// ---- date helpers -----------------------------------------------------------