Source code for openbasket.sources.tickerinside_source
"""TickerInside holdings.
TickerInside publishes the holdings of US ETFs under CC BY 4.0; every row it
supplies carries its citation in ``source_ref``. Equity funds read from the
issuer's daily file (``source_layer == "issuer"``) are keyed by ticker, not
ISIN. Other layers are rebuilt from N-PORT, which this package reads
directly, so only the issuer layer is used here.
"""
from __future__ import annotations
import logging
from dataclasses import dataclass
from ..throttle import THROTTLE
logger = logging.getLogger(__name__)
[docs]
@dataclass
class TickerInsideFund:
ticker: str
name: str | None
layer: str | None
holdings_kind: str | None
as_of: str | None
holdings: dict[str, float]
cite_as: str | None
page_url: str | None = None
SITE = "https://tickerinside.com"
[docs]
def issuer_tickers() -> set[str] | None:
"""Tickers TickerInside reads from the issuer's own file, or ``None`` if the list cannot be read.
One read of ``funds.json`` saves a request per ticker it does not cover,
which for a universe run is most of SEC's fund tickers.
"""
import tickerinside as ti
THROTTLE.wait(SITE)
try:
return {r["ticker"].upper() for r in ti.funds(layer="issuer").data}
except ti.TickerInsideError as exc:
logger.warning("TickerInside fund list: %s", exc)
return None
[docs]
def fetch_fund(ticker: str) -> TickerInsideFund | None:
"""Fetch one fund, or ``None`` when TickerInside does not cover it."""
import tickerinside as ti
THROTTLE.wait(SITE)
try:
d = ti.fund(ticker).data
except ti.NotCovered:
return None
except ti.TickerInsideError as exc:
logger.warning("TickerInside %s: %s", ticker, exc)
return None
holdings = d.get("holdings") or {}
return TickerInsideFund(
ticker=d.get("ticker", ticker),
name=d.get("name"),
layer=d.get("source_layer"),
holdings_kind=d.get("holdings_kind"),
as_of=d.get("holdings_as_of") or d.get("as_of"),
holdings={str(k): float(v) for k, v in holdings.items() if v is not None},
cite_as=d.get("cite_as"),
page_url=d.get("page_url"),
)