Passer au contenu
Tous les documents de la bibliothèque

Choisir les prix ETF ajustés ou non pour l’analyse

Code Machine Learning for Trading

Résumé

Le document décrit le chargement des données de marché ETF, avec des filtres facultatifs sur les symboles et les dates, ainsi qu’une limite déterministe au nombre de symboles renvoyés. Le point analytique principal consiste à adapter la série de prix à la mesure étudiée : les prix ajustés conviennent aux rendements, tandis que les prix non ajustés sont nécessaires aux calculs en dollars et en nombre de titres, comme les filtres de rotation ou les commissions par action.

Le document explique pourquoi les facteurs d’ajustement qui s’annulent dans un ratio de prix peuvent fausser les valeurs libellées en dollars. L’exemple compare les cours de clôture ajustés et négociés de SPY à une date ancienne et montre que l’écart augmente avec les ajustements cumulés. Le chargeur normalise également les horodatages en dates et trie les résultats non ajustés par symbole et par heure. Le document ne décrit pas de stratégie de trading et n’évalue pas la qualité des données ; ses conseils pratiques se limitent au choix de la série adaptée à un calcul et à l’obtention des données dans le stockage local.

Idées clés

  • Les prix ajustés conviennent au calcul des rendements, car les facteurs d’ajustement communs s’annulent dans les ratios de prix.
  • Les prix non ajustés sont nécessaires pour les valeurs en dollars et le nombre de titres, notamment pour les calculs de rotation et de coût par action.
  • Les cours de clôture historiques ajustés peuvent différer sensiblement des prix réellement négociés.
  • Le chargeur accepte des filtres de symboles et de dates, et peut limiter de façon déterministe l’ensemble des symboles.

Étiquettes

Texte intégral
# loader.py


```py
"""ETF universe loader."""

import polars as pl

from data.exceptions import DataNotFoundError
from utils import ML4T_DATA_PATH
from utils.data_quality import apply_max_symbols


def list_etfs() -> list[str]:
    """List ETF symbols available in the local data store.

    Returns:
        Sorted list of ETF tickers (e.g., ``["ACWI", "AGG", ..., "XLK"]``).

    Raises:
        DataNotFoundError: If ``etfs/etf_universe.parquet`` is missing.

    Example:
        >>> list_etfs()[:3]
        ['ACWI', 'ACWX', 'AGG']
    """
    path = ML4T_DATA_PATH / "etfs" / "market" / "etf_universe.parquet"
    if not path.exists():
        raise DataNotFoundError(
            dataset_name="ETF Universe",
            path=path,
            download_script="data/etfs/market/download.py",
            readme="data/etfs/README.md",
        )
    return pl.scan_parquet(path).select("symbol").unique().collect().to_series().sort().to_list()


def load_etfs(
    symbols: list[str] | None = None,
    start_date: str | None = None,
    end_date: str | None = None,
    max_symbols: int = 0,
) -> pl.DataFrame:
    """Load ETF universe for momentum case study.

    Args:
        symbols: Optional list of symbols to filter (e.g., ["SPY", "QQQ"])
        start_date: Optional start date (YYYY-MM-DD format)
        end_date: Optional end date (YYYY-MM-DD format)
        max_symbols: Limit to N random symbols (0 = all). Seed-deterministic.

    Returns:
        DataFrame with columns: timestamp, symbol, open, high, low, close, volume
    """
    path = ML4T_DATA_PATH / "etfs" / "market" / "etf_universe.parquet"
    if not path.exists():
        raise DataNotFoundError(
            dataset_name="ETF Universe",
            path=path,
            download_script="data/etfs/market/download.py",
            readme="data/etfs/README.md",
        )

    lf = pl.scan_parquet(path)
    ts_type = lf.collect_schema()["timestamp"]

    # Apply filters lazily (parquet pushdown / row-group pruning)
    if symbols:
        lf = lf.filter(pl.col("symbol").is_in(symbols))
    if start_date:
        lit = (
            pl.lit(start_date).str.to_date()
            if ts_type == pl.Date
            else pl.lit(start_date).str.to_datetime()
        )
        lf = lf.filter(pl.col("timestamp") >= lit)
    if end_date:
        lit = (
            pl.lit(end_date).str.to_date()
            if ts_type == pl.Date
            else pl.lit(end_date).str.to_datetime()
        )
        lf = lf.filter(pl.col("timestamp") <= lit)

    # Normalize daily data to Date type (post-filter so pushdown works on raw type)
    if ts_type != pl.Date:
        lf = lf.with_columns(pl.col("timestamp").cast(pl.Date))

    return apply_max_symbols(lf.collect(), max_symbols)


def load_etfs_unadjusted(
    symbols: list[str] | None = None,
    start_date: str | None = None,
    end_date: str | None = None,
) -> pl.DataFrame:
    """Load the traded close and share count, unadjusted for splits and distributions.

    :func:`load_etfs` returns an adjusted panel, which is what a return needs: the
    adjustment divides out distributions, and dividing both ends of a ratio by the same
    factor leaves the ratio alone. A dollar amount has no such cancellation. An adjusted
    close on an early session sits well below what the fund traded at - $87.23 against
    $126.70 for SPY on 2006-01-03 - so a turnover screen or a per-share commission read
    off it is wrong by the cumulative adjustment, and wrong by more the further back it
    looks.

    Use this series for anything denominated in dollars or in shares, and
    :func:`load_etfs` for anything denominated in returns.

    Args:
        symbols: Optional list of symbols to filter (e.g., ["SPY", "QQQ"])
        start_date: Optional start date (YYYY-MM-DD format)
        end_date: Optional end date (YYYY-MM-DD format)

    Returns:
        DataFrame with columns: timestamp, symbol, close, volume

    Raises:
        DataNotFoundError: If ``etfs/etf_universe_unadjusted.parquet`` is missing.
    """
    path = ML4T_DATA_PATH / "etfs" / "market" / "etf_universe_unadjusted.parquet"
    if not path.exists():
        raise DataNotFoundError(
            dataset_name="ETF Universe (unadjusted)",
            path=path,
            download_script="data/etfs/market/download.py",
            readme="data/etfs/README.md",
        )

    lf = pl.scan_parquet(path)
    ts_type = lf.collect_schema()["timestamp"]

    if symbols:
        lf = lf.filter(pl.col("symbol").is_in(symbols))
    if start_date:
        lit = (
            pl.lit(start_date).str.to_date()
            if ts_type == pl.Date
            else pl.lit(start_date).str.to_datetime()
        )
        lf = lf.filter(pl.col("timestamp") >= lit)
    if end_date:
        lit = (
            pl.lit(end_date).str.to_date()
            if ts_type == pl.Date
            else pl.lit(end_date).str.to_datetime()
        )
        lf = lf.filter(pl.col("timestamp") <= lit)

    if ts_type != pl.Date:
        lf = lf.with_columns(pl.col("timestamp").cast(pl.Date))

    return lf.collect().sort(["symbol", "timestamp"])

```

Reproduit dans son intégralité avec attribution, conformément à la licence de la source. Licence: MIT

Ce résumé a été rédigé par l’agent de recherche de Stratmill à partir de la source originale ; il n’en est pas une copie.