1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/// Default number of days to look back when fetching historical data
pub const DAYS_TO_BACK_LOOK: i64 = 10;
/// Maximum number of consecutive errors before forcing a cooldown
pub const MAX_CONSECUTIVE_ERRORS: u32 = 3;
/// Cooldown time in seconds when hitting max errors (5 minutes)
pub const ERROR_COOLDOWN_SECONDS: u64 = 300;
/// Default sleep time in hours if not specified in environment (24 hours)
pub const DEFAULT_SLEEP_TIME: u64 = 24;
/// Default page size for API requests
pub const DEFAULT_PAGE_SIZE: u32 = 50;
/// Default maximum number of retries for transient HTTP failures when the
/// `MAX_RETRY_COUNT` environment variable is unset.
///
/// This default is intentionally finite: unbounded retry was removed in
/// PR #26 and must never be reintroduced. "No retry count configured" now
/// means "use this finite default", never "retry forever".
pub const DEFAULT_MAX_RETRIES: u32 = 3;
/// Default base delay in seconds between retries when the `RETRY_DELAY_SECS`
/// environment variable is unset. Used as the base for exponential backoff.
pub const DEFAULT_RETRY_DELAY_SECS: u64 = 10;
/// Maximum backoff delay in seconds. Exponential backoff (`base * 2^attempt`)
/// saturates at this cap — the cap is the policy, so rounding down to it is
/// intentional and documented.
pub const MAX_RETRY_DELAY_SECS: u64 = 60;
/// Compatibility retry cap for the deprecated "infinite" retry constructors
/// ([`crate::model::retry::RetryConfig::infinite`] and
/// [`crate::model::retry::RetryConfig::with_delay`]). Unbounded retry is banned
/// (PR #26); these constructors now clamp to this large-but-finite value so
/// existing callers keep compiling while never looping forever.
pub const DEPRECATED_INFINITE_RETRY_CAP: u32 = 1000;
/// Base delay in milliseconds used for proximity-based delays in the rate limiter
/// This value is used to calculate wait times when approaching rate limits
pub const BASE_DELAY_MS: u64 = 1000;
/// Additional safety buffer in milliseconds added to wait times
/// This provides extra margin to ensure rate limits are not exceeded
pub const SAFETY_BUFFER_MS: u64 = 1000;
/// User agent string used in HTTP requests to identify this client to the IG
/// Markets API.
///
/// The version is derived from `CARGO_PKG_VERSION` at compile time so it can
/// never drift from the crate version and mislead server-side diagnostics.
pub const USER_AGENT: &str = concat!;
/// Conservative per-app trading request budget, in requests per second,
/// enforced by the rate limiter for order / position mutations
/// (`positions/otc`, `workingorders/otc`).
///
/// IG applies account-level bans for trading rate violations and the published
/// per-app trading limit is roughly one request per second, so this budget is
/// fixed independently of the configured non-trading budget: a permissive
/// `RateLimiterConfig` can never loosen it.
pub const TRADING_RATE_LIMIT_PER_SECOND: u32 = 1;
/// Conservative per-app historical-price request budget, in requests per second,
/// enforced by the rate limiter for endpoints under `prices/`.
///
/// Historical price fetches also draw down a weekly data-point allowance (default
/// 10,000 points), so the per-second rate is kept low to avoid exhausting the
/// allowance in bursts. Like the trading budget, it is fixed independently of the
/// configured non-trading budget.
pub const HISTORICAL_RATE_LIMIT_PER_SECOND: u32 = 1;
/// Burst capacity for the derived trading and historical rate-limit buckets.
///
/// Kept at one so the derived per-second budgets admit no burst beyond a single
/// in-flight request, matching IG's strict trading / historical limits.
pub const TRADING_HISTORICAL_BURST_SIZE: u32 = 1;
/// Fallback replenishment budget (requests per period) used when
/// [`crate::application::config::RateLimiterConfig::max_requests`] is configured
/// as zero, which is structurally invalid.
///
/// One request per configured period is the safe floor and avoids a
/// divide-by-zero when computing the per-cell replenishment interval.
pub const FALLBACK_RATE_LIMIT_MAX_REQUESTS: u32 = 1;
/// Fallback burst capacity used when
/// [`crate::application::config::RateLimiterConfig::burst_size`] is configured as
/// zero. Preserves the historical default of allowing a small burst.
pub const DEFAULT_RATE_LIMIT_BURST_SIZE: u32 = 10;
/// A constant representing the default sell level for orders.
///
/// This value is set to `0.0` by default and can be used to indicate an initial or
/// baseline sell level for order-related computations or configurations.
pub const DEFAULT_ORDER_SELL_LEVEL: f64 = 0.0;
/// A constant representing the default buy level for orders.
///
/// This value is set as a `f64` and determines the default threshold for the buy level in an order system.
/// Developers can use this constant to ensure uniformity and consistency when working with order buy levels
/// across the application.
pub const DEFAULT_ORDER_BUY_LEVEL: f64 = 10000.0;
/// Sentinel value used for `account_id` when `IG_ACCOUNT_ID` is not configured.
///
/// This is not a real IG account id: it signals "no account was explicitly
/// configured, use whatever account the session lands on". Auth flows must not
/// attempt to switch to this value.
pub const DEFAULT_ACCOUNT_ID: &str = "default_account_id";
/// Credential-less placeholder used for `DATABASE_URL` when it is not configured.
///
/// Persistence is optional, so an unset `DATABASE_URL` is not fatal at config
/// construction; this placeholder lets `Config::new` produce a value while
/// deliberately carrying NO username or password. It is not a working
/// connection string — any component that actually needs Postgres must fail
/// when it tries to connect. Never hard-code real credentials here.
pub const DEFAULT_DATABASE_URL: &str = "postgres://localhost/ig";
/// Lifetime of an IG API v2 (CST / X-SECURITY-TOKEN) session, in seconds
/// (21600 = 6 hours).
///
/// This is the single source of truth for the v2 session lifetime: the session
/// `expires_at` is derived from `created_at + V2_SESSION_LIFETIME_SECS`, staying
/// consistent with how [`crate::model::auth::V2Response::is_expired`] computes
/// expiry. It replaces the previously duplicated `now + 3600 * 6` / `21600`
/// literals.
pub const V2_SESSION_LIFETIME_SECS: u64 = 21600;
/// Proactive-refresh safety margin for API v2 (CST / X-SECURITY-TOKEN) sessions,
/// in seconds (5 minutes).
///
/// v2 sessions live for [`V2_SESSION_LIFETIME_SECS`] (~6 hours), so a 5-minute
/// lead time before expiry is ample without churning tokens. The *same* margin
/// is used both to decide a refresh is due and to actually perform it, so the
/// proactive refresh window is consistent (see
/// [`crate::application::auth::Auth::get_session`]).
pub const PROACTIVE_REFRESH_MARGIN_V2_SECS: u64 = 300;
/// Proactive-refresh safety margin for API v3 (OAuth) sessions, in seconds.
///
/// v3 access tokens are short-lived (~60 seconds), so the v2 margin (300s) would
/// keep them permanently "about to expire" and trigger a login on every call. A
/// small 10-second margin is sized relative to the ~60s token lifetime: it still
/// refreshes ahead of expiry without spinning. The *same* margin is used both to
/// decide a refresh is due and to perform it.
pub const PROACTIVE_REFRESH_MARGIN_V3_SECS: u64 = 10;