Overview
This repository provides a Backtrader Store/Broker/DataFeed integration for FYERS. It is designed for reliability and operational clarity in live trading, while remaining testable and deterministic in a local environment. It is also usable outside Backtrader as a set of small, composable adapters plus a thin orchestration layer.
What This Project Delivers
This project focuses on the parts that typically fail in production systems: normalization, resilience, and recovery.
- It provides a Store that owns adapter lifecycles and exposes poll-friendly helpers for Backtrader.
- It provides a DataFeed that can serve both historical and live market data without blocking the Backtrader loop.
- It provides a Broker that maps FYERS order and trade updates into Backtrader order lifecycle events, including partial fills and idempotency.
- It provides a SQLite historical cache (WAL) with market-aware gap detection so repeated strategy runs do not repeatedly hit FYERS history endpoints.
- It provides crash-safe runtime state persistence in a separate SQLite database so restarts can recover without double-applying fills.
How To Read The Documentation
This documentation is split by intent so that operators and developers can quickly find what they need.
docs/README.mdis the entrypoint for setup, configuration, and quickstart.docs/Architecture.mdexplains the system structure, data flows, and the decision log.docs/visuals/contains Mermaid diagrams for each major subsystem and supporting utilities/configuration.docs/fyers_mapping.mdis the canonical FYERS SDK and WebSocket mapping reference and includes sample payloads.docs/ops.mdcontains operational guidance, including restart strategies and scheduler examples.docs/tasks.mdtracks phase tasks, hard gates, and rolling to-dos.
Important Operational Assumptions (FYERS Reality)
FYERS imposes a few operational constraints that directly shape the design of this repository. These constraints are important because they explain several "fail fast" and "unknown state" behaviors in the Broker and REST adapter.
- FYERS order APIs do not support client-provided idempotency keys. This means that "retrying a place order call" can create duplicate real orders when the first call actually reached the server but the client timed out or crashed before receiving a response.
- The FYERS SDK does not consistently enforce a per-request timeout for every API call. This means that an SDK call can hang longer than expected under network or server issues unless the SDK itself exposes a timeout hook for that call.
- FYERS API sessions automatically expire at the end of each trading day, which requires a fresh login daily for live trading continuity.
- FYERS sessions can also expire immediately due to multi-device invalidation if you log into the API from a different device using the same credentials.
In this repo, these realities are handled by avoiding unsafe retries on place_order, requiring explicit reconciliation of state via Order WebSocket and REST snapshots, and treating reconciliation failures as a critical failure that should halt trading rather than "carry on with stale assumptions".
Repository Structure (Business-Friendly Map)
The top-level directories separate configuration, application code, documentation, and tests so that deployments can be audited and reproduced.
- The
src/directory contains all application code, including the FYERS Store/Broker/DataFeed implementation, adapters, persistence layers, and logging/auth helpers. - The
config/directory contains YAML configuration files that define runtime behavior such as credentials, caching paths, WebSocket behavior, logging, and rate-limit tuning. - The
tests/directory contains pytest tests. Most tests are fully mocked and do not require live FYERS connectivity. - The
docs/directory contains the documentation set. - The
logs/directory is the default sink for rotating logs and is created and managed by the logging setup. - The
secrets/directory stores runtime tokens and should never be committed. - The
tmp/directory is a scratch space used by tests and local workflows.
Example Workflows (Quick Start)
The examples under examples/ are meant to be copy-paste starting points. They show how to wire the Store, adapters, and strategy logic for common workflows.
examples/portfolio_backtest_two_symbols.pyruns a multi-stock Backtrader backtest from FYERS history (REST + cache).examples/backtest_to_live.pyruns a backtest first, then switches to live trading with the same strategy.examples/diy_backtest_no_backtrader.pyshows how to run a simple SMA backtest without Backtrader.examples/live_signal_loop.pydemonstrates a guarded live loop that drains WS data, checks triggers, and handles order WS updates.
Live examples assume you have config/state.yaml configured and FYERS_ENVIRONMENT=LIVE (or allow_live_orders: true) when you intend to place real orders.
Key Configuration (What Operators Typically Change)
This project is intentionally configuration-driven so that operational behavior can be tuned without code changes.
- The file
config/fyers_credentials.yamlcontains FYERS authentication inputs and retry/backoff tuning for auth calls. This file is sensitive and should be handled like a secret. - The file
config/cache.yamldefines the required SQLite historical cache path. This cache is treated as rebuildable, but it is valuable for performance and rate-limit protection. - The file
config/state.yamldefines the separate SQLite runtime state database used for crash-safe recovery. This path must be different from the historical cache path. - The file
config/fyers_ws.yamldefines WebSocket settings (data_type, reconnect policy, SDK logging flags, batch size, throttling, health checks). The WebSocket adapters validate this configuration and treat it as the source of truth when provided. - The file
config/fyers_rest_limits.yamldefines REST rate limits and backoff parameters used by the REST adapter to reduce 429/503 pressure. - The file
config/logging_config.yamldefines log handlers and category routing so operational errors and high-volume traffic can be separated. - The file
config/broker.yamldefines broker-specific runtime knobs such as reconcile intervals and allow lists. - The file
config/data_feed.yamldefines DataFeed drain limits so live processing remains bounded under load. - The file
config/nse_calendar.yamldefines market session and holiday rules so historical gap detection avoids pointless refetches on non-trading days. - The file
config/symbol_master_sources.yamldefines symbol master CSV sources and headers as a planned configuration surface. It is currently not consumed bysrc/and exists so future symbol master tooling can be wired without changing code.
Key Modules (What Developers Typically Touch)
The code is organized so that business logic is separated from transport, and the Backtrader-facing surface stays small and stable.
- The module
src/fyers_store/store.pyowns adapter lifecycles and exposes the polling and DB-first history helpers used by Backtrader integration. - The module
src/fyers_store/data_feed.pydrains the market WebSocket queue and normalizes messages into bars. It is designed to avoid blocking and to handle high message rates. - The module
src/fyers_store/broker.pybuilds order intents, maps FYERS order/trade updates to Backtrader order lifecycle events, and enforces fail-fast reconciliation rules. - The package
src/fyers_store/adapters/contains thin transport adapters. The REST adapter owns auth/session usage and backoff logic, while WebSocket adapters own reconnect/backoff and subscription tracking. - The package
src/fyers_store/persistence/contains the historical cache interface and the SQLite/WAL implementation. - The package
src/fyers_store/state/contains the runtime state persistence layer used for restart recovery. - The package
src/logging_module/contains logging configuration and category-based routing helpers. - The module
src/fyers/auth.pycontains FYERS auth flow helpers and client initialization.
For visual maps of these modules and their interactions, see docs/visuals/README.md.
Core Runtime Flows
Historical Data (Cache-First, Gap-Fill)
When a strategy requests historical bars, the Store reads from the local SQLite cache first. If gaps are detected in the requested range, only the missing windows are fetched from FYERS history and then upserted back into the cache. Gap detection respects market session rules so weekends and configured holidays do not cause repeated refetch attempts.
Live Market Data (Queue-Based Polling)
The market WebSocket adapter pushes messages into a queue. The DataFeed drains that queue in bounded batches with a time budget to keep the Backtrader loop responsive. Live updates are normalized into bars (including volume delta logic) and invalid or out-of-order messages are dropped deterministically.
Orders and Trades (REST Actions, WS Notifications)
Order create/modify/cancel calls are REST-only. Order and trade updates arrive through the order WebSocket and are translated into Backtrader order events. Partial fills are supported, and fills are deduped using trade identifiers so restarts do not double-apply executions.
Restart Recovery
At process start, the Broker loads persisted state from the runtime state DB and reconciles with REST snapshots before consuming WebSocket messages. If reconciliation fails, the broker fails fast to avoid trading on stale state. Operational restarts should be owned by an external scheduler, as documented in docs/ops.md.
Testing Strategy
Most tests are mocked and focus on deterministic behavior under edge cases such as partial fills, reconnect sequences, cache gap detection, and retry policy rules. A small number of integration-style tests exist, and any tests that require live FYERS connectivity should be clearly marked and safe to skip in CI environments.
Source Code Walkthrough (Module-by-Module)
This section is intentionally detailed. It walks the src/ tree in a "module-by-module" order, and for each module it answers the following questions in plain English.
- What the module does, and why it exists in the system.
- Which configuration files and environment variables it depends on.
- How you use it as part of Backtrader.
- How you use it outside Backtrader.
- Which limitations and operational caveats you should be aware of.
If you prefer diagrams instead of text, start with docs/visuals/system_overview.md and then open the per-component diagrams referenced throughout this walkthrough.
src/__init__.py (Repository Package Marker)
This file exists only to mark src/ as a Python package root for import purposes. It is intentionally empty and has no runtime behavior.
src/main.py (Sample Entry Point)
This file exists to provide a minimal "smoke test" that verifies credentials, token handling, network access, and logging setup without involving Backtrader.
- What it does: It configures logging, loads the FYERS auth configuration, creates an SDK client, calls
get_profile(), and logs the response. - Configuration dependencies: It relies on
config/fyers_credentials.yamlviafyers.auth.config_from_yaml(). It writes token material to the token path configured in that YAML (default issecrets/fyers_token.json). - Utility dependencies: It relies on
logging_module.config.setup_logging()andlogging_module.utils.get_logger(...). - How to use with Backtrader: You do not use this file with Backtrader. It is only a standalone entry point.
- How to use outside Backtrader: Run
python src/main.pyas a credential and connectivity check. - Limitations: This file does not wire the Store/Broker/DataFeed. It only validates authentication and basic API access.
src/fyers/ (FYERS Authentication and Client Helpers)
The src/fyers/ package contains the login flow and token persistence logic. It is intentionally separate from the Store and adapters so authentication can be tested and reasoned about in isolation.
src/fyers/__init__.py
This file exists only to mark src/fyers/ as a Python package. It currently does not define a public API surface and is intentionally empty.
src/fyers/auth.py
This module provides the FYERS authentication flow and a helper that returns a ready-to-use FYERS SDK client.
- What it does: It implements the multi-step FYERS login flow (OTP/TOTP + PIN + auth code + token exchange), persists the access token to disk, and reuses a valid token when possible to avoid repeated logins during the day.
- Primary public surface:
- The dataclass
FyersAuthConfigdefines the configuration schema. It is designed so YAML keys can match field names directly. - The function
config_from_yaml(...)loadsconfig/fyers_credentials.yamlintoFyersAuthConfig. - The function
get_fyers_client(config)returns a FYERS SDK client instance initialized with a usable access token (either loaded from disk or freshly generated). - Configuration dependencies:
- The default credentials file is
config/fyers_credentials.yaml. This file is sensitive and should be handled like a secret. - The environment variable
FYERS_CREDENTIALS_YAMLcan override the credentials file path when running this module directly (for example,python -m fyers.auth). - Token storage defaults to
secrets/fyers_token.jsonvia thetoken_pathfield. This file should never be committed. - Auth request timeouts and retry/backoff are controlled by the config fields
request_timeout_seconds,auth_max_retries,auth_backoff_base_seconds,auth_backoff_max_seconds, andauth_backoff_jitter_seconds. - The field
token_refresh_time_istcontrols the daily cutoff time used to decide whether a cached token should be treated as still valid. - Utility dependencies: It uses
logging_module.utils.fyers_auth_logger(...)so auth-related logs can be routed separately from trading logs. - How to use with Backtrader:
- In normal operation, you use this module indirectly. You obtain a token or client here and then pass it into the REST/WS adapters that the Store will use.
- You should treat "auth" and "trading" as separate concerns. Auth creates and refreshes the session token, while Store/Broker/DataFeed use that session to trade and stream data.
- How to use outside Backtrader:
- You can use this module directly in scripts to obtain a token and build a FYERS client for one-off operations (profile fetch, order snapshots, history pulls, and so on).
- Limitations and caveats:
- Token validity is treated as a daily concept. This aligns with FYERS sessions expiring at the end of the trading day, but you should still expect unexpected invalidation due to multi-device logins.
- This module makes network calls and should not be invoked during unit tests unless explicitly mocked.
src/logging_module/ (Logging Configuration and Category Routing)
The src/logging_module/ package exists so operational visibility is consistent across the system. It provides a single "logging setup entry point" and a category mechanism that can route different types of messages to different log files.
src/logging_module/__init__.py
This file exists only to mark src/logging_module/ as a Python package. It currently does not define a public API surface and is intentionally empty.
src/logging_module/config.py
This module is responsible for loading a YAML logging configuration and applying it exactly once per process.
- What it does: It reads
config/logging_config.yaml, validates that referenced handlers actually exist, creates any required log directories, and applies the configuration vialogging.config.dictConfig(...). - Configuration dependencies: It uses
config/logging_config.yamlby default. The default path is resolved relative to the repository, which keeps behavior consistent across local runs and deployments. - How to use with Backtrader: Call
logging_module.config.setup_logging()before wiring the Store/Broker/DataFeed so category-based routing is active from the first log line. - How to use outside Backtrader: Call
setup_logging()at the start of any script that uses the FYERS modules or adapters. - Limitations and caveats:
- If you force a reload (
force=True), root handlers are cleared so you do not get duplicated log lines. This is deliberate and is safer than "silently stacking handlers".
Related diagrams and docs:
- docs/visuals/logging.md
src/logging_module/utils.py
This module provides the "category" abstraction used by the rest of the codebase.
- What it does: It returns
logging.LoggerAdapterinstances that always carry acategoryfield, which the YAML logging configuration can route to dedicated log files. - Why it matters: Category routing lets you separate high-volume telemetry (for example, sampled WebSocket payloads) from critical failures (for example, broker reconciliation errors).
- How to use with Backtrader: Most modules already use this helper internally. You only need to call it directly when you add new modules or want a category-specific logger in your own strategy code.
- How to use outside Backtrader: Use it in scripts to keep logs consistent with the rest of the repository.
- Limitations: Categories are a convention. If you bypass the helper and log directly with the Python logger, your logs may not be routed as expected.
src/logging_module/filters.py
This module defines filters that are referenced by config/logging_config.yaml to route messages.
- What it does: It provides a generic
CategoryFilterand a specialized auth filter that detects auth/token messages so they can be routed to dedicated error logs. - How to use with Backtrader: You normally do not call this module directly. It is wired through YAML configuration.
- How to use outside Backtrader: You typically do not call this module directly, unless you are building your own logging config and want to reuse the same filters.
- Limitations: Filters rely on the
categoryfield being present, so the system expects callers to uselogging_module.utils.get_logger(...)for consistent routing.
src/fyers_store/ (Backtrader Integration Layer)
The src/fyers_store/ package contains the Store/Broker/DataFeed and the internal utilities they depend on. It is organized so that transport concerns (REST and WebSocket) are isolated under adapters/, domain normalization lives in models/, and persistence logic is split into "historical cache" versus "runtime state".
If you only remember one thing about this package, it should be this. The Store is an orchestrator, adapters transport, and the broker and feed translate between FYERS and Backtrader expectations.
Related diagrams and docs:
- docs/visuals/system_overview.md
- docs/visuals/store.md
- docs/visuals/broker.md
- docs/visuals/data_feed.md
- docs/visuals/adapters.md
- docs/visuals/historical_cache.md
- docs/visuals/state_persistence.md
src/fyers_store/__init__.py (Public API Boundary)
This file defines what external callers should treat as "stable imports" for the fyers_store package.
- What it does: It re-exports
FyersStore,FyersBroker,FyersData, and basic order enums so users can import them fromfyers_storedirectly. - How to use with Backtrader: Import the Store/Broker/DataFeed from
fyers_storeas your stable entry point rather than importing deep internal modules. - How to use outside Backtrader: You can still import the Store/Broker/DataFeed from here, but for lower-level usage you may prefer importing adapters and persistence modules directly.
- Limitations: Re-exported names are intended to remain stable, but internal module paths are allowed to change during refactors.
src/fyers_store/constants.py (Order Constants)
This module holds FYERS order constants that are used across broker and adapter payload construction.
- What it does: It defines
OrderSideandOrderTypeenums and a small label mapping that keeps intent payloads readable. - How to use with Backtrader: Use these enums when building explicit "order intent" dictionaries to pass into
FyersBroker.buy(...)orFyersBroker.sell(...). - How to use outside Backtrader: Use these enums when building raw order payloads for FYERS REST calls (or when validating payloads in your own integration).
- Limitations: These constants mirror FYERS payload conventions. If FYERS changes its API contract, these constants may need updates.
src/fyers_store/models/ (Normalized Domain Types)
The models package exists so that "what a bar looks like" is defined once and enforced at adapter boundaries.
src/fyers_store/models/__init__.py
This file re-exports NormalizedBar and bar normalization helpers for convenience. It exists so other modules can import NormalizedBar consistently without coupling to the internal file layout.
src/fyers_store/models/domain.py
This module defines the normalized OHLCV schema used across the system.
- What it does: It defines
NormalizedBar(a typed dictionary schema) andnormalize_bar(...)which validates and normalizes bars. - Why it matters: Adapters and the Store apply
normalize_bar(...)so downstream modules do not need to keep re-validating shape and types on every bar. - Configuration dependencies: This module is configuration-free.
- How to use with Backtrader: The DataFeed writes normalized bars into Backtrader line buffers. If you want to inject bars for tests, you can use
FyersData.push_bar(...)and rely on this normalization logic. - How to use outside Backtrader: If you ingest bars from another source, you can call
normalize_bar(...)as a "schema gate" before writing bars into your own database or queue. - Limitations and caveats:
- The module can require timezone-aware datetimes and can optionally validate an expected timezone offset, but it cannot decide which timezone is correct without context such as resolution and exchange session rules.
src/fyers_store/resolution.py (Resolution Canonicalization)
This module ensures that resolution tokens are handled consistently across the REST adapter, cache, and Store.
- What it does: It canonicalizes FYERS-supported resolutions (seconds, minutes, daily) and provides helpers such as
resolution_to_timedelta(...),is_daily_resolution(...), andfyers_resolution_aliases(...). - Why it matters: A single canonical form prevents duplicated cache keys, inconsistent pagination, and silent resolution drift caused by aliases such as
"1m"versus"1". - Configuration dependencies: This module is configuration-free.
- How to use with Backtrader: When you request history or wire a data feed, use FYERS-native base resolutions (for example
"1","5", or"D"). If you need synthetic timeframes (for example 15m bars), you should fetch a base resolution and resample using Backtrader. - How to use outside Backtrader: Use
canonicalize_fyers_resolution(...)as a validation layer at your boundaries so you fail fast instead of sending an invalid token to FYERS. - Limitations: This repository intentionally does not create synthetic resolutions because it would blur the boundary between "data transport" and "strategy-level transformation".
src/fyers_store/market_calendar.py (NSE Calendar for Gap Detection)
This module provides a lightweight NSE trading calendar so the historical cache can distinguish "real missing bars" from "expected no-trade time".
- What it does: It loads
config/nse_calendar.yamland provides anNseCalendarobject with helpers to check trading days, session bounds, and whether a time window contains any expected bar timestamps. - How it is used:
SQLiteHistoricalCache.detect_gaps(...)uses it to filter out gaps that fall entirely in weekends, holidays, and off-session periods, which reduces pointless REST backfills. - Configuration dependencies: It reads
config/nse_calendar.yaml. Theholiday_list_urlkey is treated as metadata and is not fetched automatically in-process. - How to use with Backtrader: You generally do not call this module directly. It exists to make the historical cache's gap detection market-aware.
- How to use outside Backtrader: If you build your own ingestion process, you can use the calendar to decide whether a missing span is actionable or expected.
- Limitations and caveats:
- If the calendar file is missing or invalid, the cache falls back to naive gap detection, which can cause extra REST history calls around weekends and holidays.
- The calendar is intentionally local-file-driven so the trading process never depends on network access for calendar updates.
src/fyers_store/persistence/ (Historical Cache)
This package provides a pluggable persistence contract for historical bars and a default SQLite/WAL implementation.
src/fyers_store/persistence/__init__.py
This file defines the public exports for the persistence package.
- What it does: It re-exports the
HistoricalCachecontract and the defaultSQLiteHistoricalCacheimplementation, along with config helpers such asresolve_cache_db_path(...). - How to use with Backtrader: You normally do not import this directly. The Store uses these exports to create the default cache when you did not inject your own.
- How to use outside Backtrader: You can import the cache implementation directly if you want cache-first history without the Store.
src/fyers_store/persistence/cache.py (Cache Contract)
This module defines the abstract contract that all historical caches must implement.
- What it does: It defines the
HistoricalCacheinterface and theGapstructure used by the Store to decide what needs to be backfilled from REST. - Why it matters: The Store can depend on "what a cache does" without coupling to "which database implementation is used".
- How to use with Backtrader: You normally do not call this contract directly, but it is a useful reference if you plan to plug in another backend.
- How to use outside Backtrader: If you implement your own historical cache (for example a shared Postgres cache), this interface defines the required behavior.
src/fyers_store/persistence/sqlite_schema.py (Cache Schema Helpers)
This module centralizes the SQLite DDL and WAL enabling logic for the historical cache.
- What it does: It defines the
barsandbar_watermarkstables, creates indexes, and enables WAL mode with sane PRAGMA tuning. - How it is used:
SQLiteHistoricalCache.initialize()calls into this module to ensure the DB is ready. - Limitations and caveats:
- This module intentionally does not act as a general migration framework. The history cache is treated as rebuildable, so schema changes are handled operationally.
src/fyers_store/persistence/sqlite_cache.py (SQLite/WAL Historical Cache)
This is the default "zero-ops" historical cache backend.
- What it does:
- It stores bars in SQLite with a primary key on (symbol, resolution, ts) so upserts are naturally idempotent.
- It stores per-context watermarks so different ingestion contexts do not block each other.
- It detects gaps and can filter out "expected gaps" using the NSE calendar when configured.
- It supports optional background maintenance such as WAL checkpointing and lightweight integrity checks.
- Configuration dependencies:
config/cache.yamlmust providedb_path. The Store will fail fast if it is missing.config/cache.yamlcan optionally provide amaintenance:section with keys such asenabled,interval_seconds,start_jitter_seconds, andwal_checkpoint_mode.config/cache.yamlcan optionally provide aconcurrency:section with keys such asbusy_timeout_msand bounded write lock retry/backoff settings.config/nse_calendar.yamlis optionally loaded to provide market-aware gap detection.- How to use with Backtrader: You usually do not instantiate this cache yourself. The Store will create it on startup if you did not inject a cache instance.
- How to use outside Backtrader: You can instantiate
SQLiteHistoricalCachedirectly if you want a cache-backed history process without the Store. - Limitations and caveats:
- SQLite supports many readers and a single concurrent writer. In multi-process deployments (multiple strategies/processes sharing the same cache DB), you should expect occasional
database is lockederrors, which this module mitigates using WAL mode,busy_timeout, and bounded retry/backoff. - The cache is treated as rebuildable. If you change the bar schema or cache layout, you should plan an operational cache reset rather than an in-place migration.
src/fyers_store/state/ (Crash-Safe Runtime State)
This package is separate from the historical cache because "historical bars" are rebuildable, while "runtime broker state" is used to prevent duplicate fill processing after crashes.
src/fyers_store/state/__init__.py
This file defines the public exports for the state package.
- What it does: It re-exports the SQLite state store types and the config loader so the Store and Broker can import them cleanly.
- How to use with Backtrader: You typically do not import this module directly. The Broker and Store handle state persistence for you.
- How to use outside Backtrader: If you build your own broker loop, importing the state store types from here is the cleanest entry point.
src/fyers_store/state/state_store.py (SQLite State Store)
This module provides the SQLite state store used by the broker for crash-safe restart recovery.
- What it does:
- It loads a state config from
config/state.yaml(with environment overrides). - It creates a versioned schema for orders, positions, trade dedupe identifiers, and WebSocket processing watermarks.
- It exposes load/upsert/delete methods so the broker can persist snapshots deterministically.
- Configuration dependencies:
config/state.yamlmust definestate_db_pathandaccount_id.- Environment overrides are supported via
FYERS_STATE_YAML,FYERS_STATE_DB_PATH, andFYERS_STATE_ACCOUNT_ID. - How to use with Backtrader: You normally do not interact with this module directly. The Store creates a per-broker state store using the configured account id and a store-managed strategy id.
- How to use outside Backtrader: You can use this module directly to store orders, positions, and trade dedupe keys if you implement your own event loop.
- Limitations and caveats:
- Schema upgrades are intentionally fail-fast. If the schema version changes, you are expected to delete the state DB (or call
clear_state()) and restart with a clean store. - The Store enforces that the state DB path must be different from the historical cache DB path to prevent accidental mixing of concerns.
src/fyers_store/adapters/ (Transport Adapters)
The adapter layer exists so "transport and retries" are isolated from "business decisions". In practice, this means the following.
- The Broker constructs order payloads and implements the order lifecycle.
- The REST adapter only transports payloads and enforces safe retry/backoff policies.
- The WebSocket adapters only manage connectivity, reconnect, subscriptions, and message queues.
src/fyers_store/adapters/__init__.py
This file re-exports the adapter interfaces and the concrete implementations so importing adapters is ergonomic and stable.
- What it does: It exports
RestAdapter,WsAdapter,MessageQueue, and the concrete FYERS SDK-backed adapter classes. - How to use with Backtrader: You typically create adapter instances and inject them into the Store.
- How to use outside Backtrader: You can import and use adapters directly without involving the Store if you want lower-level control.
src/fyers_store/adapters/interfaces.py (Adapter Contracts)
This module defines the adapter contracts used across the system.
- What it does: It defines the abstract interfaces
RestAdapterandWsAdapter, plus a minimalMessageQueueprotocol that supports non-blocking polling. - Why it matters: The Store and DataFeed can depend on these interfaces and remain stable even if the adapter implementation changes (for example, switching from FYERS SDK to a direct HTTP client).
- How to use with Backtrader: You do not usually import this module directly. It is primarily an internal boundary.
- How to use outside Backtrader: If you want to implement your own adapters (for example, a fully mocked adapter, or a broker simulator), this contract defines the minimal required behavior.
src/fyers_store/adapters/fyers_rest.py (FYERS REST Adapter)
This module provides the concrete REST adapter backed by the FYERS SDK.
- What it does:
- It owns FYERS SDK client initialization and therefore owns the REST "session lifecycle".
- It normalizes history bars and enforces a timezone policy for history boundaries (intraday UTC, daily IST by default).
- It applies a per-second and per-minute limiter for history calls, plus bounded retry/backoff for retryable failures.
- It hardens order calls by making
place_ordersingle-shot and making modify/cancel/orderbook calls retryable with bounded backoff. - Configuration dependencies:
- It reads rate limit and backoff settings from
config/fyers_rest_limits.yamlby default. - It depends on a valid FYERS SDK client or an auth config produced by
src/fyers/auth.py. - How to use with Backtrader: Provide an instance of
FyersSdkRestAdapterto aFyersStore, then let the Broker and Store call it. - How to use outside Backtrader: You can use
FyersSdkRestAdapterdirectly for history pulls and account/order snapshots. - Limitations and caveats:
place_order(...)does not retry by design because FYERS does not support client-provided idempotency keys. If it fails, you must reconcile via Order WebSocket and/ororderbook()before deciding whether to place another order.- The adapter does not guarantee per-request timeouts for SDK calls because the FYERS SDK may not expose a uniform timeout mechanism per call.
- The current
RateLimiterenforces only per-second and per-minute budgets. Theper_daysetting exists for operator visibility but is not currently enforced in-process.
src/fyers_store/adapters/fyers_ws.py (FYERS WebSocket Adapters)
This module provides queue-based WebSocket adapters for market data and order updates with explicit reconnect backoff.
- What it does:
- It validates and loads
config/fyers_ws.yamlso configuration errors are caught early and predictably. - It wraps FYERS SDK WebSocket clients in adapters that push parsed messages into an internal queue.
- It implements a reconnect loop with exponential backoff and jitter, and it deliberately disables SDK auto-reconnect so reconnect behavior is deterministic and testable.
- It implements subscription batching to respect FYERS guidance (batches of up to 100 symbols) and optional subscription cooldown to reduce subscribe/unsubscribe bursts.
- It optionally runs a health monitor thread that warns when message flow becomes stale.
- Configuration dependencies:
- It reads
config/fyers_ws.yamlviaload_ws_config(...). - The config includes shared keys (logging flags, reconnect settings) and data-socket-specific keys (batch size, queue sizing, health checks).
- How to use with Backtrader:
- Inject
FyersDataWsAdapter(market data) into the Store and letFyersDatadrain it. - Inject
FyersOrderWsAdapter(order updates) into the Store and letFyersBrokerconsume it viadispatch_order_notifications(...). - For tests and demos,
InMemoryWsAdaptercan be used to simulate WS messages deterministically. - How to use outside Backtrader: You can use the adapters in your own loop by periodically draining
adapter.get_queue()and processing the returned message mappings. - Limitations and caveats:
- WebSocket messages are queue-based. If your consumer does not drain regularly, backlogs can build and memory can grow if the queue is unbounded.
- Subscription batching and throttling are important for large symbol sets. The adapter is designed to enforce safe defaults, but your config still determines how aggressive the process is.
src/fyers_store/store.py (Store / Orchestration Layer)
The Store is the central orchestrator for adapters and persistence. It is deliberately kept thin so it is easy to audit and reason about in live trading environments.
- What it does:
- It owns adapter start/stop lifecycle and keeps explicit "started" flags to avoid double-start behavior.
- It provides a DB-first history method (
get_history) that uses the historical cache and only calls REST for missing windows. - It provides passthrough wrappers for REST calls such as
orderbook(),positions(), andfunds(). - It provides poll-friendly queue drain methods for market and order WebSocket messages (
drain_market_messages,drain_order_messages). - It supports multi-broker routing so multiple brokers (and therefore multiple strategies) can share one Store while keeping order updates isolated.
- It creates a per-broker state store (
SQLiteStateStore) usingaccount_id(from config) and a store-managedstrategy_id. - Configuration dependencies:
config/cache.yamlis required when you want historical caching. The required key isdb_path.- Optional cache tuning is read from
config/cache.yamlundermaintenance:andconcurrency:. config/state.yamlis required for broker crash recovery state (state_db_path,account_id), and the Store enforces that the state DB and history cache DB are not the same file.- The Store does not directly read
config/fyers_ws.yamlorconfig/fyers_rest_limits.yaml. Those configs are consumed by the adapters themselves. - Utility dependencies: It uses
logging_module.utils.get_logger(...), the cache exports fromfyers_store.persistence, and the state exports fromfyers_store.state. - How to use with Backtrader:
- You typically instantiate one Store per process and pass it to both your Broker and DataFeed so they share sessions, caches, and routing state.
- The Store is started indirectly by
broker.start()and/ordata.start(). You can also callstore.start()explicitly in custom wiring. - How to use outside Backtrader:
- You can treat the Store as a "connection manager" and call
start(),subscribe_market(...), anddrain_market_messages(...)in your own loop. - You can call
get_history(...)to read from cache and fill gaps from REST without running Backtrader. - Limitations and caveats:
- The classmethods
FyersStore.getdata()andFyersStore.getbroker()are scaffolding helpers. They create a fresh Store instance per call. In most live trading setups you should share a Store instance across broker and feed to avoid duplicated sessions and split routing state. - Order ownership routing relies on recognizing an order id field in messages. When a message arrives with an unknown order id (for example, when REST returns only a request id), the Store routes it to the primary broker and claims ownership to keep future updates consistent.
src/fyers_store/data_feed.py (Backtrader DataFeed)
This module provides the Backtrader-facing data feed wrapper and the queue draining logic for live market messages.
- What it does:
- It supports a historical-only mode that prefetches history from the Store and then returns bars until the queue is empty.
- It supports live mode by draining market WebSocket messages in bounded batches and normalizing them into
NormalizedBarobjects. - It includes stall detection so operators see when live feed delivery becomes stale.
- Configuration dependencies:
- It reads
config/data_feed.yamlwhen present. - It uses
stall_warning_secondsand the drain tuning keysmarket_max_messages,market_batch_size,market_drain_budget_ms, andmarket_backlog_warn_after. - Utility dependencies: It uses
logging_module.utils.get_logger(...)and the bar normalization functions fromfyers_store.models. - How to use with Backtrader:
- Use this class as the
bt.feedsdata source by wiring aFyersStoreinstance and settingdataname(symbol) andresolution. - The DataFeed is designed to avoid blocking Backtrader's
_load()loop, which is important for predictable cycle timing. - How to use outside Backtrader:
- Outside Cerebro, you will usually consume the Store's market WS queue directly rather than using a Backtrader
DataBasesubclass. - This module still provides useful test hooks such as
push_bar(...)to inject a known bar series. - Limitations and caveats:
- In live mode, SymbolUpdate messages are converted into "minimal bars" where OHLC are all equal to the last traded price. This is useful for tick-level strategies that only need last price, but it is not a true OHLC bar series.
- The drain limits are important. If your strategy is slow or you subscribe to many symbols, you should tune
market_max_messagesand related settings to avoid persistent queue backlogs.
src/fyers_store/broker.py (Backtrader Broker)
This module provides the Backtrader-facing broker wrapper with explicit reconciliation and crash recovery behaviors.
- What it does:
- It starts the Store in a safe order. It starts REST first, loads persisted state, reconciles with REST snapshots, and only then starts the order WebSocket.
- It constructs order payloads ("intents") and delegates transport to the Store and REST adapter.
- It consumes order WebSocket messages, maps them to Backtrader order lifecycle events, supports partial fills, and deduplicates fills by trade identifiers.
- It persists state snapshots (open orders, positions, trade numbers, and WS watermarks) so a restart does not double-apply fills.
- It implements a reconcile policy that is intentionally fail-fast by default. If reconciliation fails, it raises a hard exception and halts to avoid trading on stale state.
- Configuration dependencies:
- It reads
config/broker.yamlto loadreconcile_interval_secondsandallowed_product_types. - It uses the Store-provided state store, which depends on
config/state.yaml. - Utility dependencies: It uses
logging_module.utils.get_logger(...)and relies on Store adapter wiring for all external IO. - How to use with Backtrader:
- Set this broker on your
bt.Cerebroinstance so orders and account state integrate with the Backtrader lifecycle. - Ensure that order updates are drained regularly. A common pattern is to call
broker.dispatch_order_notifications(max_messages=...)via a timer so order updates are processed even when the market data feed is idle. - If you run multiple strategies, use one Store with multiple brokers and rely on the Store's order routing to isolate events to the correct broker.
- How to use outside Backtrader:
- You can use
FyersBrokeras a higher-level order state machine even without Cerebro by settingbroker.notify_orderto your own handler and callingdispatch_order_notifications(...)to process WS messages. - If you prefer lower-level control, you can skip the broker and call the Store's REST adapter directly, but then you own reconciliation and dedupe correctness.
- Limitations and caveats:
- "Unknown state" on order placement is a real operational condition. It can happen due to SDK/network timeouts, rate limits, or session expiry. Because there is no client idempotency key, the safe response is to reconcile via Order WebSocket and REST orderbook before retrying.
- The broker's reconcile failure guidance explicitly calls out daily session expiry and multi-device invalidation, because those are common causes of sudden REST snapshot failures.
- Reconciliation is a critical control point. If you disable fail-fast, you must accept the risk of operating with stale or incomplete state and you must own the operational playbook for what to do during outages.
Backtrader Usage Patterns (Practical Guidance)
This repository is intentionally conservative about "automagic" behavior. Most actions that can materially affect trading safety (such as order retries and reconciliation) are explicit. The patterns below describe how the pieces are intended to be used together.
Recommended Pattern: One Store Per Process
For most live trading deployments, you should create exactly one FyersStore per Python process and share it across:
- A single
FyersBrokerinstance, which owns reconciliation and state persistence for that process. - One or more
FyersDatainstances, which drain market data messages and present them to Backtrader.
This pattern keeps adapter lifecycles, cache usage, and order routing deterministic.
Explicit Market Subscription
Market data subscriptions are explicit because symbol sets can be large and strategies often need different symbol universes.
- The
FyersDatafeed drains messages, but it does not automatically subscribe to a symbol by itself. - You should call
store.subscribe_market([...])(or subscribe via your own adapter wiring) before expecting live bars.
Order Updates Need a Heartbeat
Backtrader runs notify_order(...) when it processes its internal broker notification queue. If your market data is quiet (or you are running an order-only strategy), you can end up with order updates waiting in the queue longer than you want.
- The broker exposes
dispatch_order_notifications(max_messages=...)so you can drain order WS updates even when there are no new market bars. - A common pattern is to call this method from a Backtrader timer (or another lightweight heartbeat) so order updates are processed regularly.
If you are running outside Cerebro, you can set broker.notify_order to your own handler and call dispatch_order_notifications(...) directly to process updates.
Failure Behavior Is Deliberately Fail-Fast
The broker reconciliation policy is designed to stop trading when the process cannot establish a correct view of account state.
- If reconciliation fails, the broker raises a
ReconcileFailureErrorby default. - Operators should treat this as a critical condition and rely on an external scheduler to restart the process after the underlying issue (session expiry, multi-device invalidation, network outage, or rate limiting) is resolved.
For operational restart strategies, see docs/ops.md.
Configuration Map (Quick Reference)
This section lists the configuration files under config/ and explains which modules use them. It is intentionally redundant with the module-by-module walkthrough so operators can answer "what do I change for X" quickly.
config/fyers_credentials.yaml
This file defines the FYERS authentication inputs used by src/fyers/auth.py. It is sensitive and should be treated like a secret.
- Used by:
src/fyers/auth.py - Typical required fields:
totp_key,username,pin,client_id,app_id,app_type,secret_key,redirect_uri - Typical operational fields:
- Token storage:
token_path,token_refresh_time_ist - Network safety:
request_timeout_seconds - Bounded retries:
auth_max_retries,auth_backoff_base_seconds,auth_backoff_max_seconds,auth_backoff_jitter_seconds - Environment override:
FYERS_CREDENTIALS_YAML(credentials file path when runningpython -m fyers.auth)
config/fyers_rest_limits.yaml
This file defines REST rate limits and backoff tuning used by the FYERS SDK-backed REST adapter.
- Used by:
src/fyers_store/adapters/fyers_rest.py - Key fields:
- Limiting:
per_second,per_minute - Backoff:
max_backoff_retries,backoff_base_seconds,backoff_cap_seconds,backoff_jitter_seconds - Note:
per_daymay appear in defaults for visibility, but the current in-process limiter enforces only per-second and per-minute budgets.
config/fyers_ws.yaml
This file defines WebSocket settings and is validated strictly by load_ws_config(...).
- Used by:
src/fyers_store/adapters/fyers_ws.py - Required fields:
log_path,write_to_file,litemode,reconnect,reconnect_retry,data_type - Optional queue sizing:
data_ws_queue_maxsize(0 means unbounded)order_ws_queue_maxsize(0 means unbounded)- Optional data-socket subscription tuning:
data_ws_batch_size(must be between 1 and 100)data_ws_subscribe_cooldown_seconds- Optional health checks:
data_ws_health_check_secondsdata_ws_stale_after_seconds- Optional adapter-level reconnect backoff:
reconnect_backoff_base_secondsreconnect_backoff_max_secondsreconnect_backoff_jitter_secondsreconnect_stable_reset_seconds
config/cache.yaml
This file defines the historical cache location and optional maintenance/concurrency knobs. The Store treats db_path as required and fails fast if it is missing.
- Used by:
src/fyers_store/store.py,src/fyers_store/persistence/sqlite_cache.py - Required fields:
db_path - Optional sections:
maintenance:(WAL checkpointing and periodic PRAGMA operations)concurrency:(busy_timeout and bounded write-lock retry/backoff settings)
config/state.yaml
This file defines the crash-safe runtime state database used by the broker.
- Used by:
src/fyers_store/state/state_store.py,src/fyers_store/store.py,src/fyers_store/broker.py - Required fields:
state_db_path,account_id - Environment overrides:
FYERS_STATE_YAML,FYERS_STATE_DB_PATH,FYERS_STATE_ACCOUNT_ID - Important rule:
state_db_pathmust not be the same file as the history cache DB path.
config/broker.yaml
This file defines broker runtime knobs that affect reconciliation and validation.
- Used by:
src/fyers_store/broker.py - Key fields:
reconcile_interval_seconds(set to 0 to disable periodic reconcile; this emits a warning)allowed_product_types(defaults toCNCandINTRADAYif not provided)
config/data_feed.yaml
This file defines live feed drain budgets and stall warning behavior.
- Used by:
src/fyers_store/data_feed.py - Key fields:
stall_warning_secondsmarket_max_messages,market_batch_size,market_drain_budget_ms,market_backlog_warn_after
config/nse_calendar.yaml
This file defines market session rules and holiday lists for market-aware gap detection.
- Used by:
src/fyers_store/market_calendar.py,src/fyers_store/persistence/sqlite_cache.py - Key fields:
timezone,session_start,session_endweekend_days(names likemon,sat, etc)holidays(a list ofYYYY-MM-DDstrings)holiday_list_url(metadata only; not fetched in-process)
config/logging_config.yaml
This file defines Python logging handlers, formatters, filters, and logger routing.
- Used by:
src/logging_module/config.py,src/logging_module/filters.py, and the entire codebase via category logging. - Operational note: If logs are missing, misrouted, or duplicated, start by verifying that all referenced handler names exist and that the configured log directories are writable.
config/symbol_master_sources.yaml
This file is currently a planned configuration surface for symbol master ingestion.
- Used by: Not consumed by
src/yet. - Why it exists: It allows symbol master sources and column mappings to be configured without changing code when the symbol master tooling is wired in.
Test Map (By Component)
The tests under tests/ are organized around failure modes and component responsibilities. This section provides a "where do I look" index so you can quickly find coverage for a behavior.
Auth and Token Handling
tests/test_fyers_auth_retries.pycovers bounded retries and backoff for auth network calls.tests/test_fyers_auth_timezones.pycovers IST-based token validity and refresh cutoff behavior.
Logging
tests/test_logging_config_setup.pycovers logging config validation and setup behavior.
Domain Normalization and Resolution
tests/test_domain.pycoversNormalizedBarvalidation and timezone enforcement behavior.tests/test_resolution.pycovers resolution canonicalization rules and supported tokens.tests/test_store_history_resolution_canonicalization.pycovers Store-level canonicalization behavior for history requests.
Market Calendar and Gap Detection
tests/test_market_calendar_gap_detection.pycovers trading-calendar-aware expected bar detection used by the cache.
Historical Cache (SQLite/WAL)
tests/test_sqlite_cache.pycovers cache inserts, reads, gap detection, watermark behavior, and concurrency-related retry behavior.tests/test_store_cache_config_concurrency.pycovers Store wiring of cache concurrency config keys.tests/test_store_history_cache.pycovers Store DB-first behavior and REST gap fill behavior.
Runtime State Persistence
tests/test_state_persistence.pycovers restart recovery behavior for orders, positions, and trade dedupe keys.
REST Adapter (Orders and History)
tests/test_fyers_rest_history.pycovers history normalization, timezone rules, and pagination/backoff behavior.tests/test_fyers_rest_orders.pycovers order call hardening, retry policy boundaries, and unknown-state error signaling.
WebSocket Adapters (Market and Order Sockets)
tests/test_fyers_ws_config.pycovers strict config validation forconfig/fyers_ws.yamland related overrides.tests/test_fyers_ws_backoff_policy.pycovers reconnect backoff policy behavior.tests/test_fyers_ws_adapter_resubscribe.pycovers resubscription behavior on reconnect and batching constraints.tests/test_fyers_ws_load.pycovers mocked load behavior at scale (subscription batching, throttling, and queue handling).tests/test_order_ws_parser.pycovers order WS message parsing and routing assumptions.
Store/Broker Integration and Order Routing
tests/test_store_queue.pycovers queue draining semantics.tests/test_store_broker_routing.pyandtests/test_store_strategy_ownership.pycover multi-broker routing and order ownership safety.tests/test_store_order_sync.pycovers REST snapshot syncing helpers.
Broker Order Lifecycle and Reconciliation
tests/test_broker_bt_order_creation.pycovers Backtrader order wrapper creation and basic lifecycle wiring.tests/test_broker_order_aliases.pycovers request-id to order-id alias behavior used in unknown-order-id scenarios.tests/test_broker_partial_fills.pycovers partial fill aggregation and trade dedupe behavior.tests/test_broker_positions_sync.pycovers positions snapshot handling and edge cases.tests/test_broker_product_type_allowlist.pycovers broker validation of product types against allow lists.tests/test_broker_reconcile_interval.pycovers reconcile interval config loading and disable behavior.tests/test_broker_reconcile_fail_fast.pycovers fail-fast behavior and error messaging.tests/test_broker_reconnect_integration.pycovers broker behavior around WS reconnect and reconcile sequences.
DataFeed Behavior
tests/test_data_feed_live.pycovers live-mode queue draining, bar normalization, and backlog/stall signaling.
Optional Integration Test
tests/test_macd_strategy.pyis an integration-style test that may require live FYERS connectivity and valid credentials, and it is expected to be skip-safe in environments without those prerequisites.