Skip to main content

digdigdig3_station/
builder.rs

1use std::path::PathBuf;
2use std::time::Duration;
3
4use crate::{PersistenceConfig, Result, Station};
5
6/// Fluent builder for [`Station`].
7#[derive(Debug)]
8pub struct StationBuilder {
9    pub(crate) storage_root: PathBuf,
10    pub(crate) persistence: PersistenceConfig,
11    pub(crate) warm_start: usize,
12    pub(crate) gap_heal: crate::GapHealConfig,
13    pub(crate) unsubscribe_grace: Duration,
14    /// Whether to issue a one-shot REST `get_orderbook` seed on first subscribe
15    /// to an `Orderbook` or `OrderbookDelta` stream. Default: `false`.
16    pub(crate) orderbook_rest_seed: bool,
17    /// Depth passed to `get_orderbook` when `orderbook_rest_seed` is `true`.
18    /// Default: `1000`.
19    pub(crate) orderbook_seed_depth: usize,
20}
21
22impl Default for StationBuilder {
23    fn default() -> Self {
24        let env_root = std::env::var_os("DIG3_STORAGE_ROOT").map(PathBuf::from);
25        Self {
26            storage_root: env_root.unwrap_or_else(|| PathBuf::from("./dig3_storage")),
27            persistence: PersistenceConfig::default(),
28            warm_start: 500,
29            gap_heal: crate::GapHealConfig::default(),
30            unsubscribe_grace: Duration::ZERO,
31            orderbook_rest_seed: false,
32            orderbook_seed_depth: 1000,
33        }
34    }
35}
36
37impl StationBuilder {
38    pub fn new() -> Self { Self::default() }
39
40    /// Override the root directory under which all Station-managed artefacts
41    /// (trades, bars, snapshots, indexes) are written.
42    pub fn storage_root(mut self, root: impl Into<PathBuf>) -> Self {
43        self.storage_root = root.into();
44        self
45    }
46
47    /// Configure trade/bar/snapshot persistence.
48    pub fn persistence(mut self, cfg: PersistenceConfig) -> Self {
49        self.persistence = cfg;
50        self
51    }
52
53    /// Number of most-recent points to emit from disk on subscribe BEFORE live
54    /// stream takes over. 0 disables warm-start. Acts as the in-memory
55    /// series capacity too.
56    ///
57    /// Default: `500` (≈5 minutes of history for most warm-seedable kinds at
58    /// typical event rates; gives derived bars a dense enough trade window to
59    /// bootstrap without waiting for live events).
60    pub fn warm_start(mut self, n: usize) -> Self {
61        self.warm_start = n;
62        self
63    }
64
65    /// Configure proactive gap-heal: when a live event arrives whose timestamp
66    /// jumps further than the configured threshold past the last seen event,
67    /// the station REST-backfills the missing window before emitting the live
68    /// event. Off by default.
69    ///
70    /// On wasm32 the REST pull uses browser fetch; it succeeds for the 9
71    /// proxy-override venues (Binance/Bybit/OKX/Bitget/Bitstamp/Coinbase/Kraken/
72    /// Deribit/HTX) and silently returns an empty window for other venues until
73    /// their REST CORS proxies are wired (Wave 4-C).
74    pub fn gap_heal(mut self, cfg: crate::GapHealConfig) -> Self {
75        self.gap_heal = cfg;
76        self
77    }
78
79    /// How long to keep a WS forwarder alive after its last consumer drops.
80    /// During this window a new subscription for the same key reuses the live
81    /// connection — no reconnect, no REST re-seed.
82    ///
83    /// Default: `Duration::ZERO` (immediate drop, current behaviour).
84    ///
85    /// Typical UI value: `Duration::from_secs(30)` so a quick symbol-flip
86    /// (BTC → ETH → BTC) stays on the same socket.
87    pub fn unsubscribe_grace(mut self, grace: Duration) -> Self {
88        self.unsubscribe_grace = grace;
89        self
90    }
91
92    /// On first subscribe to an [`Stream::Orderbook`] or [`Stream::OrderbookDelta`]
93    /// stream for a `(exchange, symbol, account)` key, also issue a one-shot REST
94    /// `get_orderbook` call so the live book is seeded with up to `depth` levels
95    /// before WS deltas start applying.
96    ///
97    /// Default: `false` (WS-only — matches pre-0.3.13 behaviour).
98    ///
99    /// On failure (REST not connected, exchange returns error, empty snapshot)
100    /// the station continues with WS-only and logs a warning. Never aborts the
101    /// subscribe.
102    pub fn orderbook_rest_seed(mut self, enable: bool) -> Self {
103        self.orderbook_rest_seed = enable;
104        self
105    }
106
107    /// Depth of the REST seed snapshot. Only meaningful when
108    /// [`Self::orderbook_rest_seed`] is `true`.
109    ///
110    /// Passed directly to `get_orderbook(symbol, Some(depth as u16), account)`.
111    /// Clamped to `1..=u16::MAX` internally.
112    ///
113    /// Default: `1000`.
114    pub fn orderbook_seed_depth(mut self, depth: usize) -> Self {
115        self.orderbook_seed_depth = depth;
116        self
117    }
118
119    pub async fn build(self) -> Result<Station> {
120        Station::from_builder(self).await
121    }
122}