Skip to main content

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 -----------------------------------------------------------