Source code for dccd.application.service_factory
"""Service-object factory.
Central place that wires together all exchange adapters so that both the CLI
and the API import from one location. Adding a new exchange means editing only
this file.
"""
from __future__ import annotations
import pathlib
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from dccd.application.config import AppConfig
from dccd.sources.registry import SourceRegistry
from dccd.storage.coverage_sqlite import CoverageStore
from dccd.storage.parquet import ParquetStore
from dccd.storage.remote import RemoteStorage
from dccd.storage.runs_sqlite import RunsStore
__all__ = [
"build_registry",
"build_store",
"build_runs_store",
"build_remote",
"build_coverage_store",
]
[docs]
def build_registry() -> "SourceRegistry":
"""Return a :class:`~dccd.sources.registry.SourceRegistry` with all adapters registered.
Returns
-------
SourceRegistry
"""
from dccd.sources.binance import BinanceSource
from dccd.sources.bitfinex import BitfinexSource
from dccd.sources.bitmex import BitMEXSource
from dccd.sources.bybit import BybitSource
from dccd.sources.coinbase import CoinbaseSource
from dccd.sources.kraken import KrakenSource
from dccd.sources.okx import OKXSource
from dccd.sources.registry import SourceRegistry
reg = SourceRegistry()
reg.register("binance", BinanceSource())
reg.register("coinbase", CoinbaseSource())
reg.register("kraken", KrakenSource())
reg.register("bybit", BybitSource())
reg.register("okx", OKXSource())
reg.register("bitfinex", BitfinexSource())
reg.register("bitmex", BitMEXSource())
return reg
[docs]
def build_store(data_path: str | pathlib.Path) -> "ParquetStore":
"""Return a :class:`~dccd.storage.parquet.ParquetStore` for *data_path*.
Parameters
----------
data_path : str or Path
Returns
-------
ParquetStore
"""
from dccd.storage.parquet import ParquetStore
return ParquetStore(data_path)
[docs]
def build_runs_store(data_path: str | pathlib.Path) -> "RunsStore":
"""Return a :class:`~dccd.storage.runs_sqlite.RunsStore` inside *data_path*.
The database lives at ``{data_path}/.dccd/runs.db``.
Parameters
----------
data_path : str or Path
Returns
-------
RunsStore
"""
from dccd.storage.runs_sqlite import RunsStore
return RunsStore(pathlib.Path(data_path) / ".dccd" / "runs.db")
[docs]
def build_coverage_store(data_path: str | pathlib.Path) -> "CoverageStore":
"""Return a :class:`~dccd.storage.coverage_sqlite.CoverageStore`.
The database lives at ``{data_path}/.dccd/coverage.db`` — the manifest that
lets local data be dropped without forcing a re-download on the next
backfill.
Parameters
----------
data_path : str or Path
Returns
-------
CoverageStore
"""
from dccd.storage.coverage_sqlite import CoverageStore
return CoverageStore(pathlib.Path(data_path) / ".dccd" / "coverage.db")
[docs]
def build_remote(cfg: "AppConfig") -> "RemoteStorage | None":
"""Return a :class:`~dccd.storage.remote.RemoteStorage`, or ``None``.
Returns ``None`` when no rclone remotes are configured (``storage.remotes``
empty) — there is nothing to sync, so the daemon skips the sync loop. The
local root is ``settings.data_path`` (the canonical store root used by
:func:`build_store`).
Parameters
----------
cfg : AppConfig
Returns
-------
RemoteStorage or None
"""
if not cfg.storage.remotes:
return None
from dccd.storage.remote import RemoteStorage
return RemoteStorage(
cfg.settings.data_path,
[r.model_dump() for r in cfg.storage.remotes],
)