सामग्री पर जाएं
लाइब्रेरी के सभी दस्तावेज़

लुक-अहेड के बिना लागत-सक्षम इक्विटी यूनिवर्स बनाना

कोड Machine Learning for Trading

सारांश

यह दस्तावेज़ अनुमानित राउंड-ट्रिप ट्रेडिंग लागत का उपयोग करके सत्यापन और होल्डआउट बैकटेस्ट के लिए इक्विटी का स्थिर समूह चुनने का तरीका बताता है। प्रॉक्सी प्रति शेयर अनुमानित लागत को औसत शेयर मूल्य के अनुपात में लेकर उसमें माध्यिका हाफ-स्प्रेड का दोगुना जोड़ती है; दोनों आधार अंक में व्यक्त हैं। इस प्रॉक्सी के अनुसार सिंबलों की रैंकिंग की जाती है और हर विभाजन के लिए सबसे कम लागत वाले नाम चुने जाते हैं।

लुक-अहेड पक्षपात घटाने के लिए सत्यापन सूची में सत्यापन विंडो से पहले समाप्त होने वाले कोट बार इस्तेमाल होते हैं, जबकि होल्डआउट सूची में होल्डआउट शुरू होने से पहले की तारीखों तक सीमित तरलता प्रोफ़ाइल ली जाती है। सूची निर्माता दोनों सूचियों में साझा नामों की संख्या भी बताता है, जिससे यूनिवर्स स्थिरता का एक सरल माप मिलता है। ये सूचियाँ रोलिंग चयन के बजाय स्नैपशॉट हैं, इसलिए बाद के तरलता बदलावों के अनुसार नहीं ढलतीं। दस्तावेज़ सूचियाँ बनाने का तरीका बताता है, लेकिन मापी गई लागत, ओवरलैप परिणाम या प्रॉक्सी द्वारा वास्तविक निष्पादन लागत का अनुमान लगाने का प्रमाण नहीं देता।

मुख्य विचार

  • प्रति-शेयर लागत और कोट किए गए स्प्रेड को मिलाने वाली राउंड-ट्रिप लागत प्रॉक्सी के आधार पर इक्विटी की रैंकिंग करें।
  • हर विभाजन का यूनिवर्स केवल उस विभाजन के शुरू होने से पहले उपलब्ध तरलता जानकारी से बनाएँ।
  • हर रीबैलेंस पर पुनर्गणना करने के बजाय प्रत्येक विभाजन के लिए चुना गया यूनिवर्स स्थिर रखें।
  • सदस्यता कितनी स्थिर है, यह बताने के लिए सत्यापन और होल्डआउट सूचियों की तुलना करें।
  • लागत प्रॉक्सी एक अनुमान है और वास्तविक ट्रेडिंग लागत स्थापित नहीं करती।

टैग

पूरा पाठ
# _build_cost_feasible_universe.py


```py
"""Provenance: builder for the frozen, per-split cost-feasible universe.

Run from the repo root (``uv run python
case_studies/nasdaq100_microstructure/_build_cost_feasible_universe.py``).

The canonical lists live in ``config/setup.yaml::universe.cost_feasible.
{validation,holdout}`` and are consumed by the backtest pipeline via
``strategy.signal.universe_filter='cost_feasible'`` (see
``case_studies/utils/backtest_runner.py::_apply_cost_feasible_filter``). This
script documents HOW those lists were produced so they can be regenerated.

The universe is a frozen snapshot per split (profiled with no look-ahead),
NOT full-sample and NOT per-rebalance rolling:

  - validation universe := top-50 by round-trip-cost proxy, computed on quote
    bars STRICTLY BEFORE the validation window start (2020-01-01 -> 2020-06-30,
    the first expanding-window training block). No validation-period liquidity
    leaks into the universe pick.
  - holdout universe    := top-50 from the pre-holdout liquidity_profile.parquet,
    which 01_feasibility_analysis.py restricts to < HOLDOUT_START (2021-07-01).
    Causal at the holdout boundary.

Round-trip cost proxy: 2*(per_share/mean_price)*1e4 + 2*median_half_spread_bps.
Prints the val/holdout overlap so stability can be documented.
"""

from __future__ import annotations

import json
from pathlib import Path

import polars as pl
import yaml

from data import load_nasdaq100_bars
from utils.paths import get_case_study_dir

CS = "nasdaq100_microstructure"
CASE_DIR = get_case_study_dir(CS)
SETUP = yaml.safe_load((CASE_DIR / "config/setup.yaml").open())
PER_SHARE_USD = float(SETUP["costs"]["per_share"])
UNIVERSE = sorted(SETUP["universe"]["symbols"])

START_DATE = "2020-01-01"
VALIDATION_START = "2020-06-30"
HOLDOUT_START = str(SETUP["evaluation"]["holdout_start"])  # 2021-07-01

TOP_N = 50
OUT = Path(__file__).parent


def build_profile(start: str, end: str) -> pl.DataFrame:
    qb = load_nasdaq100_bars(start_date=start, end_date=end, include_quotes=True, symbols=UNIVERSE)
    qb = (
        qb.with_columns(
            mid=(pl.col("bid_close") + pl.col("ask_close")) / 2,
            raw_spread=pl.col("ask_close") - pl.col("bid_close"),
        )
        .filter(
            pl.col("bid_close").is_not_null()
            & pl.col("ask_close").is_not_null()
            & (pl.col("bid_close") > 0)
            & (pl.col("ask_close") >= pl.col("bid_close"))
        )
        .with_columns(half_spread_bps=(pl.col("raw_spread") / 2 / pl.col("mid") * 1e4))
    )
    return (
        qb.group_by("symbol")
        .agg(
            n_bars=pl.len(),
            median_half_spread_bps=pl.col("half_spread_bps").median(),
            mean_price=pl.col("close").mean(),
        )
        .with_columns(
            rt_cost_bps=2 * (PER_SHARE_USD / pl.col("mean_price")) * 1e4
            + 2 * pl.col("median_half_spread_bps"),
        )
        .sort("rt_cost_bps")
    )


def top50_from_existing() -> list[str]:
    lp = pl.read_parquet(CASE_DIR / "liquidity_profile.parquet").select(
        ["symbol", "median_half_spread_bps", "mean_price"]
    )
    lp = lp.with_columns(
        rt_cost_bps=2 * (PER_SHARE_USD / pl.col("mean_price")) * 1e4
        + 2 * pl.col("median_half_spread_bps")
    )
    return lp.sort("rt_cost_bps").head(TOP_N)["symbol"].to_list()


def main() -> None:
    print(f"per_share=${PER_SHARE_USD}  top_n={TOP_N}", flush=True)
    val_prof = build_profile(START_DATE, VALIDATION_START)
    val_univ = val_prof.head(TOP_N)["symbol"].to_list()
    ho_univ = top50_from_existing()
    val_set, ho_set = set(val_univ), set(ho_univ)
    print(f"OVERLAP: {len(val_set & ho_set)}/{TOP_N} symbols common", flush=True)
    (OUT / "universe_validation.json").write_text(json.dumps(val_univ, indent=2))
    (OUT / "universe_holdout.json").write_text(json.dumps(ho_univ, indent=2))
    print(
        "Wrote universe_{validation,holdout}.json — copy into "
        "config/setup.yaml::universe.cost_feasible",
        flush=True,
    )


if __name__ == "__main__":
    main()

```

स्रोत के लाइसेंस के तहत श्रेय सहित पूरा पाठ दिखाया गया है। लाइसेंस: MIT

यह सारांश मूल स्रोत के आधार पर Stratmill के शोध एजेंट ने लिखा है; यह स्रोत की प्रति नहीं है।