Token Management

Token & API Management

token_refresher.py ← standalone systemd service, checks every 5 minutes ├── TokenManager.refresh_if_due() ← renew when missing, expired, ≥23h old, or <4h from expiry ├── Try POST /v2/RenewToken → extends active token (happy path) ├── Retry failures every 5 minutes ← after 3 consecutive failures, Telegram asks operator to check Dhan └── If expired → full regeneration ├── TOTP via pyotp.TOTP(DHAN_TOTP_SECRET).now() ├── POST login with PIN + TOTP ├── Write token health to runtime_state:dhan_token ├── Update .env and os.environ as compatibility fallback └── Telegram confirmation includes method and expiry
ArbitrageBot scheduler ← every 5 minutes └── TokenManager.reload_from_store() └── DhanClient.refresh_client() ← only when SQLite token changed

The dashboard reads /api/token/health for expiry, source, last update, and status. The API never returns the token value.

Unified token store (Phase A1). Dhan token state now also lives in the brokers Postgres schema: the live token + expiry are written Fernet-encrypted into brokers.broker_connections, and every refresh attempt is appended to brokers.broker_token_events. The generalized token_refresher.py drives this via TokenService.refresh_due() (one provider per broker); the renew/regenerate flow above is unchanged. When no broker_connections row exists, the refresher and the bot fall back to the legacy .env + SQLite path, so .env remains the bootstrap/fallback. See Brokers.
Rate Limits Option chain: OPTION_CHAIN_COOLDOWN = 3.5 s between calls. Order placement: ORDER_DELAY_SEC = 0.3 s between orders (< 10 OPS limit). LTP batch: LTP_COOLDOWN = 1.0 s minimum gap between serialized ticker_data calls. A host-wide file lock coordinates the bot and dashboard processes. Dashboard WebSocket connections share one cached LTP snapshot and refresh it every WS_TICK_INTERVAL seconds (default 15) so multiple open tabs do not create concurrent marketfeed bursts.