الانتقال إلى المحتوى
جميع مستندات المكتبة

تصميم إعداد بحث متنوع متعدد الأصول لعقود CME الآجلة

الكود Machine Learning for Trading

الملخص

يحدد هذا الإعداد كونًا أسبوعيًا لبحوث العقود الآجلة يشمل مؤشرات الأسهم والسندات الحكومية والطاقة والمعادن والعملات والزراعة والثروة الحيوانية. ويحدد تسوية يوم الجمعة كلقطة اتخاذ القرار وافتتاح يوم الاثنين كنقطة التنفيذ. ويرتب إطار الاستراتيجية المنتجات باستخدام العائد من الاحتفاظ أو الزخم، ويدعم مراكز الشراء والبيع على المكشوف بأحجام متساوية المخاطر أو اسمية. وتتغير افتراضات التكاليف حسب سيولة المنتج، وتشمل العمولات وفروق الأسعار وانزلاق ترحيل العقود.

ويحدد الإعداد أيضًا نوافذ السمات للزخم والتقلب والعائد من الاحتفاظ والاتجاه ومقاييس ذات صلة؛ ورتبًا مئينية ومركبات ضمن كل تاريخ؛ وخيارات تخصيص المحفظة؛ وحساسية التكاليف؛ وضوابط مخاطر للإيقاف والإيقاف المتحرك والزمن. ويستبعد جلستين لأن ملفات البورصة المفقودة تترك أكوان المنتجات غير مكتملة، ما يجعل الرتب المقطعية غير قابلة للمقارنة. ويوثق الإعداد قيود التنفيذ العملية، مثل العقود التي تُتداول بأعداد صحيحة والهامش على مستوى المنتج وتكاليف التداول وحدود إعادة التوازن. وهو يحدد إطارًا للتجارب بدلًا من عرض أداء الاستراتيجية، لذا لا تثبت خيارات المعلمات أن أي إشارة أو أسلوب تخصيص مربح.

الأفكار الرئيسية

  • يجمع الكون منتجات عقود 30 CME الآجلة عبر سبعة قطاعات سوقية واسعة.
  • تُقيّم الإشارات وفق جدول أسبوعي باستخدام أسعار التسوية، ويتأخر التنفيذ حتى افتتاح يوم الاثنين التالي.
  • تشكل رتب العائد من الاحتفاظ والزخم الأساس المعلن للمراكز الطويلة والقصيرة، مع إمكان اختلاف التحجيم بحسب المخاطر أو القيمة الاسمية.
  • تُستبعد جلسات المنتجات غير المكتملة لأن الرتب المئينية لكون جزئي لن تكون قابلة للمقارنة.
  • يوضح الإعداد التكاليف والهامش وضرورة تداول العقود بأعداد صحيحة وأساليب التخصيص وطبقات ضبط المخاطر، لكنه لا يقدم دليلًا على الأداء.

الوسوم

النص الكامل
# setup.yaml


```yaml
strategy_id: cme_futures
setup_version: v1

# 30 CME products grouped by sector. Tickers are CME root symbols.
universe:
  product_groups:
    equity_index:
      - ES   # E-mini S&P 500
      - NQ   # E-mini Nasdaq-100
      - YM   # E-mini Dow Jones Industrial Average
      - RTY  # E-mini Russell 2000
    treasuries:
      - ZN   # 10-year T-Note
      - ZB   # 30-year T-Bond
      - ZF   # 5-year T-Note
      - ZT   # 2-year T-Note
    energy:
      - CL   # WTI Crude Oil
      - NG   # Henry Hub Natural Gas
      - HO   # NY Harbor ULSD (heating oil)
      - RB   # RBOB Gasoline
    metals:
      - GC   # Gold
      - SI   # Silver
      - HG   # Copper
      - PL   # Platinum
    currencies:
      - 6E   # Euro / US Dollar
      - 6J   # Japanese Yen / US Dollar
      - 6B   # British Pound / US Dollar
      - 6A   # Australian Dollar / US Dollar
      - 6C   # Canadian Dollar / US Dollar
      - 6S   # Swiss Franc / US Dollar
    agriculture:
      - ZC   # Corn
      - ZS   # Soybeans
      - ZW   # Wheat (Chicago SRW)
      - ZM   # Soybean Meal
      - ZL   # Soybean Oil
    livestock:
      - LE   # Live Cattle
      - HE   # Lean Hogs
      - GF   # Feeder Cattle
  n_products: 30
  # Two sessions where one clearing venue's settlement file is absent. On each, the
  # missing products are missing at every tenor, and the two missing sets partition the
  # universe exactly: 2020-02-28 carries no CBOT, NYMEX or COMEX product (YM, the four
  # treasuries, five grains, four energy, four metals) and 2014-06-13 carries no CME
  # product (three equity index, six currencies, three livestock). Neighbouring sessions
  # trade normally, so this is a missing file rather than a thin market. Eleven of the
  # levels in stage 03 are carried as a percentile within the decision date, and a
  # percentile taken over one venue's products is not comparable with one taken over
  # thirty, so both dates are dropped rather than ranked against a partial universe.
  excluded_sessions:
    - 2020-02-28
    - 2014-06-13

decision:
  cadence: weekly_friday_close
  snapshot: settlement_price
  execution_delay: monday_open

# Engine-level execution defaults. Single source of truth — Ch16-19 notebooks
# read these via get_backtest_config(); never declare a local INITIAL_CASH
# or share_type constant. Changing values here invalidates every existing
# backtest_hash for this case study (cash + share_type are spec-hash inputs).
#
# ``initial_cash`` is sized so the full 30-product CME universe can fill
# positions under integer-share rules across the top_k_grid (5, 10) in
# long-short mode. The binding constraint is the high-notional equity-
# index e-minis: at top_k=10 long-short the per-position dollar budget
# is 5% × cash, and integer-rounding zeros out any product whose notional
# exceeds that budget. ES holdout notional ≈ $347k and NQ ≈ $513k drive
# the floor: $10M (per-position $500k) clears ES in the holdout and NQ
# in the validation window. NQ in the very-late holdout (Dec 2025,
# notional $513k) still integer-rounds out for that one product at
# top_k=10 LS — a residual integer-share footnote documented in the
# README "Margin model" section.
#
# Per-product initial+maintenance margin (futures_specs.yaml::
# {initial,maintenance}_margin_pct → ContractSpec.margin_pct) is wired
# through Engine.from_config(contract_specs=...); the engine auto-
# populates the broker's margin_pct_schedule so the margin draw scales
# with each bar's notional rather than the account-wide 50% default.
# allow_leverage is set in config/backtest/base.yaml.
#
# ``allocator_lookback`` is the bars-of-underlying-price window applied
# uniformly to every moment-based allocator (inverse_vol, risk_parity, hrp,
# mvo_ledoit_wolf). CME daily settlement bars → 63 ≈ 3 months. Keeps
# allocators comparable.
execution:
  initial_cash: 10_000_000       # sized for full 30-product universe at top_k_grid
  share_type: integer            # Futures contracts are integer
  allocator_lookback: 63         # 3 months of daily CME settlement bars

mapping:
  class: long_short_carry_rank
  position_state_space: long_short
  entry_logic: rank_by_carry_or_momentum
  sizing: equal_risk_or_notional

costs:
  class: material
  components: [commission, spread, roll_slippage]
  commission_per_contract: 2.0
  spread_ticks:
    liquid: 1
    illiquid: 2
  # Products charged the illiquid spread: live cattle, feeder cattle and soybean oil
  # quote wider than the rest of the universe. Everything else pays the liquid one tick.
  illiquid_products: [LE, GF, ZL]

backtest:
  rebalance:
    # A rebalance is skipped when the per-asset weight change is below
    # min_weight_change AND the resulting trade notional is below
    # min_trade_value. The benchmark profile disables thresholds so that
    # full-universe equal-weight (1/N per asset) rebalances at all.
    default:
      min_weight_change: 0.005
      min_trade_value: 100.0
    benchmark:
      min_weight_change: 0.0
      min_trade_value: 0.0
  sweep:
    # Iteration controls per stage. ``signal: 0`` means "all predictions";
    # downstream stages take the top-N from the upstream stage's rank-1.
    # Notebooks read these via get_top_n_predictions(case_study, stage).
    top_n_predictions:
      signal: 0                 # all signal predictions (eq-weight baseline)
      allocation: 10            # top-10 model configs by equal-weight baseline Sharpe
      cost_sensitivity: 1       # top-1 of {signal+allocation} per label
      risk_overlay: 1           # top-1 of {signal+allocation} per label
    # Skip MVO/HRP when allocator runtime is the bottleneck (intraday CSes).
    expensive_allocators_skip: false
    # All others (equal_weight, score_weighted, inverse_vol) take the cheap path.
    # Ch16 signal-stage selection. Long-short equal-weight top-k on both
    # label horizons; long-short is enforced at the backtest config level
    # (allow_short_selling=true).
    # 30-product universe with long_short=True: top_k=20 selects 20 longs ∪
    # 20 shorts (40 positions) from 30 products → degenerate (collapses to
    # ~zero trades). Cap at 10 = floor(30/3) to keep selections non-degenerate.
    top_k_grid:
      fwd_ret_5d:  [5, 10]
      fwd_ret_21d: [5, 10]
    # Ch17 portfolio: reuses top_k_grid above and sweeps over allocators.
    # Moment-based allocators (IV/RP/HRP/MVO_LW) all use the CS-level
    # ``execution.allocator_lookback`` — no per-allocator vol_window /
    # lookback fields. No max-weight cap.
    # No equal_weight entry. Equal weight IS the Chapter 16 baseline, and `stage` is not
    # part of `backtest_hash`, so an equal-weight run at the allocation stage is a no-op
    # reweight that hashes identically to its baseline parent - whichever writes first
    # claims the row and the other is lost. Measured in this case study's own pre-rebuild
    # store: 48 rows carrying `stage='signal'` with `allocation.method='equal_weight'`,
    # and zero `allocation`/`equal_weight` rows, which downstream tooling then reported
    # as both missing and unexpected trials. The baseline is already in the pool that the
    # later stages select over.
    allocators:
      - {name: score_weighted,  method: score_weighted}
      - {name: inverse_vol,     method: inverse_vol}
      - {name: risk_parity,     method: risk_parity}
      - {name: mvo_ledoit_wolf, method: mvo_ledoit_wolf}
      - {name: hrp,             method: hrp}
      - {name: conformal_weighted, method: conformal_weighted}
    # Ch18 cost sensitivity (bps; commission + exchange fees + slippage
    # combined).
    cost_grid_bps: [0, 1, 2, 3, 5, 7, 10, 15, 20, 30, 50]
    # Ch19 risk overlays.
    risk_controls:
      position:
        - {name: stop_loss_3pct,  type: stop_loss,     threshold: 0.03}
        - {name: stop_loss_5pct,  type: stop_loss,     threshold: 0.05}
        - {name: stop_loss_10pct, type: stop_loss,     threshold: 0.10}
        - {name: stop_loss_15pct, type: stop_loss,     threshold: 0.15}
        - {name: trailing_1pct,   type: trailing_stop, threshold: 0.01}
        - {name: trailing_2pct,   type: trailing_stop, threshold: 0.02}
        - {name: trailing_3pct,   type: trailing_stop, threshold: 0.03}
        - {name: trailing_5pct,   type: trailing_stop, threshold: 0.05}
        - {name: trailing_10pct,  type: trailing_stop, threshold: 0.10}
        - {name: trailing_15pct,  type: trailing_stop, threshold: 0.15}
        - {name: trailing_20pct,  type: trailing_stop, threshold: 0.20}
        - {name: time_exit_10,    type: time_exit,     bars: 10}
        - {name: time_exit_20,    type: time_exit,     bars: 20}
        - {name: time_exit_40,    type: time_exit,     bars: 40}

# The feature specification for 03_financial_features. Every window the notebook
# uses is declared here and read from here, and each family carries the ten fields
# the register renders: what it reads, how far back, with what delay, and what it
# is claimed to do. ``role`` separates a signal — something the carry thesis says
# ranks products against each other — from a state variable describing the
# environment a ranking is formed in. Windows are CME settlement sessions.
features:
  windows:
    momentum: [21, 63, 126, 252]     # each carries a risk-adjusted twin
    short_return: 5                  # the weekly return, no Sharpe twin
    volatility: [21, 63, 126]
    skip_recent: 21                  # skip-month momentum runs t-252 to t-21
    skip_start: 252
    carry_smoothing: 21
    carry_zscore: [63, 126]
    carry_momentum: [5, 21]
    trend_sign: [63, 126, 252]
    moving_average: [21, 50, 200]
    high: 252                        # dist_from_52w_high
    low: 126                         # dist_from_6m_low
    rsi: 14
    yang_zhang: 21
    variance_ratio: {horizon: 5, window: 63}
  # Source column -> the name its within-date percentile is written under. The
  # names predate this register and later stages select by them, so the mapping
  # is explicit rather than a suffix rule.
  ranked:
    ret_21d: mom_rank_21d
    ret_63d: mom_rank_63d
    ret_126d: mom_rank_126d
    ret_252d: mom_rank_252d
    sharpe_63d: sharpe_rank_63d
    sharpe_126d: sharpe_rank_126d
    sharpe_252d: sharpe_rank_252d
    vol_21d: vol_rank
    rsi_14: rsi_rank
    carry_pct: carry_rank
    curve_curvature_norm: curvature_rank
  composites:
    momentum_composite: [mom_rank_63d, mom_rank_126d, mom_rank_252d]
    sharpe_composite: [sharpe_rank_63d, sharpe_rank_126d, sharpe_rank_252d]
  thresholds:
    carry_regime_band: 0.01          # carry, on the x12 scale, separating the two regimes from flat
    signal_long: 60                  # percentile of the carry-momentum composite
    signal_short: 40
    roll_proximity_day: 25           # calendar day from which the roll week is flagged
  # Planting, harvest, heating and cooling cycles are documented for these two
  # sectors and not for the financial ones.
  seasonal_sectors: [agriculture, energy]
  families:
    - name: term structure
      pattern: carry_pct|carry_21d|carry_momentum_*|carry_zscore_*|carry_regime_num|curve_curvature_norm|curvature_21d
      role: signal
      hypothesis: A product whose curve is in backwardation earns the roll the shape implies
      inputs: unadjusted front, second and third settlement prices
      lookback: 146
      lag: 0
      frame: term structure
      representation: front-to-next spread on a x12 scale, its smoothed level, z-score and change
      failure_mode: the spread inverts around a supply shock faster than the smoothing follows
    - name: momentum
      pattern: ret_*d|skip_month_mom|mom_accel_*|ts_mom_*
      role: signal
      hypothesis: Trends in futures persist over horizons of a quarter to a year
      inputs: roll-adjusted settlement price
      lookback: 252
      lag: 0
      frame: time series
      representation: simple return, its sign, and differences between horizons
      failure_mode: reverses over the most recent month, which skip-month momentum drops
    - name: risk-adjusted momentum
      pattern: sharpe_[0-9]*d
      role: signal
      hypothesis: A trend earned with less dispersion repeats more reliably than a raw one
      inputs: log return
      lookback: 252
      lag: 0
      frame: time series
      representation: mean log return over its own dispersion, annualized
      failure_mode: unbounded as dispersion approaches zero
    - name: volatility and regime
      pattern: vol_[0-9]*d|vol_ratio_*|vol_yz_*|vr_*d
      role: state
      hypothesis: Dispersion and the trending-or-reverting regime set what a ranking can earn
      inputs: OHLC settlement bars, log return
      lookback: 126
      lag: 0
      frame: time series
      representation: annualized standard deviation, ratios of windows, variance ratio
      failure_mode: close-to-close misses the overnight gap Yang-Zhang is here to catch
    - name: trend and range
      pattern: rsi_[0-9]*|ma_ratio_*|dist_from_*
      role: signal
      hypothesis: Price against its own recent path separates a trend from an exhausted one
      inputs: roll-adjusted settlement price
      lookback: 252
      lag: 0
      frame: time series
      representation: bounded oscillator, ratio to a moving average or a rolling extreme
      failure_mode: saturates in a sustained trend, where every product reads the same
    - name: calendar and season
      pattern: month_*|day_of_year_norm|quarter|roll_proximity|is_seasonal_sector
      role: state
      hypothesis: Agricultural and energy curves move with planting, harvest and heating cycles
      inputs: the session date and the product's sector
      lookback: 1
      lag: 0
      frame: calendar
      representation: circular month encoding, quarter, roll-week and sector flags
      failure_mode: says when in the year it is and never which product to prefer
    - name: cross-sectional position
      pattern: '*_rank|mom_rank_*|sharpe_rank_*|carry_rank_sector'
      role: signal
      hypothesis: Only relative standing is tradable in a long-short cross-sectional strategy
      inputs: the levels this register's other families produce
      lookback: 252
      lag: 0
      frame: cross-section
      representation: percentile within the decision date and contract position
      failure_mode: discards the level the state families carry instead
    - name: composite
      pattern: momentum_composite|sharpe_composite|carry_mom_composite|carry_mom_interaction|risk_adj_score|ls_signal
      role: signal
      hypothesis: Carry and momentum are separate premia, so their agreement is itself information
      inputs: the percentiles of the cross-sectional family
      lookback: 252
      lag: 0
      frame: cross-section
      representation: mean of percentiles, their product, and a banded discretization
      failure_mode: hides which of the two components moved the score

# What `04_model_based_features` decides, declared here for the same reason the
# feature windows above are: an estimation window is part of a fitted feature's
# definition, so it belongs where the definition lives rather than inside the
# notebook that runs it. Every count is in trading sessions.
#
# A fitted feature is bounded by the schedule below and not by a walk-forward
# period. The parameters behind a value are estimated from sessions strictly
# before it, refreshed on the cadence given, and the same value comes out
# whichever period later selects the row - so the artifact carries no fold column.
#
# The spectral features in section C.2 have no entry here. A Fourier transform of
# a window estimates nothing, so it has no estimation window to declare; its
# lookback is a lookback like the ones under `features.windows`.
model_based:
  arima:
    # Sessions of a product's own carry z-score spent before the first forecast.
    # A trading year, so the first order is chosen from a full year of the series
    # rather than from a few weeks of it. The walk was already causal at this
    # cadence before the schedule was declared here; what moved is where the two
    # numbers live.
    burnin: 252
    # How often the weights are re-estimated. Monthly.
    refit_every: 21
    # (p, d, q), declared rather than selected per window.
    #
    # This replaced AutoARIMA's stepwise search, and the measurement that decided it is
    # worth stating because it is the reason rather than the justification. Sampling
    # eight refit cutoffs on each of the 30 eligible products, 240 searches in all,
    # AutoARIMA returned 34 distinct orders and NO product held a single order across
    # its own walk. The modal choice (2,0,1) took 11.7% of searches and (2,0,2) 10.4%;
    # the best any product managed was 6 of its 8 cutoffs on one order. An order that
    # changes almost every month on an expanding window of the same series is the
    # information criterion moving with the sample, not structure being found, and it
    # makes "the ARIMA carry forecast" a different quantity in every block.
    #
    # (2,0,1) is the modal selection and the more parsimonious of the two that are
    # indistinguishable from each other.
    #
    # d=0 is by construction, not by search: carry_zscore is already a rolling z-score
    # clipped to +/-5, so it is stationary before this model sees it. 83% of the searches
    # agreed (199 of 240 chose d=0); the 17% that differenced it were over-differencing a
    # bounded series, which is a known failure of automatic selection rather than a
    # property of carry.
    order: [2, 0, 1]
  hmm:
    # Backwardation and contango.
    n_states: 2
    # Sessions of the book-wide carry average before the regime chain is fitted.
    # Two years. A two-state chain has to see both states switch several times
    # before its transition matrix means anything, and this is one market-level
    # series rather than a panel, so the burn-in is paid once for the whole
    # notebook rather than once per product.
    #
    # The average itself begins 2011-04-27, after the carry smoothing and z-score
    # windows, and the oldest period trains from the first session of the panel -
    # so this burn-in comes out of that period's training window rather than
    # ahead of it. The first regime value lands 2013-04-09: 504 of the 1,978
    # sessions in that period's training window, and no evaluation session, since
    # the earliest evaluation window opens 2019-01-03. The walk makes 44
    # estimates before the holdout freezes it.
    burnin: 504
    # How often the chain is re-estimated. A quarter, as in etfs and fx_pairs:
    # regime parameters are the slowest-moving thing this notebook fits.
    refit_every: 63

evaluation:
  n_splits: 5
  train_size: 8Y
  val_size: 1Y
  holdout_start: '2024-01-01'
  holdout_end: '2025-12-31'
  calendar: CME
  periods_per_year: 252  # CME 5d/wk

labels:
  primary: fwd_ret_5d
  buffer: 5D
  variants:
    - fwd_ret_21d
  variant_buffers:
    fwd_ret_21d: 21D
  # Vectorized-backtest thinning step per label: number of schedule slots
  # to advance per trade so holding periods don't overlap. Authored from
  # (schedule cadence, label horizon); add an entry here for any new label.
  rebalance_step:
    fwd_ret_5d: 1   # weekly_friday schedule, 5d horizon <= 7d gap
    fwd_ret_21d: 3  # weekly_friday schedule, 21d horizon -> ceil(21/7) = 3

modeling:
  gbm:
    libraries: [lightgbm]
    preset: default
    device: cpu
    # LightGBM's own CPU default. 63 is the GPU default and was carried over with the
    # device when these runs moved off the GPU, so every CPU fit was quantizing the design
    # matrix into a quarter of the bins the library would have used. Coarser bins are
    # faster and lose split points; the reader running this on a CPU gets what the
    # documentation describes.
    max_bin: 255
  latent_factors:
    persistent_entities: true
    # Declared, not inferred. Without this key case_study.py:93 falls back to
    # preferred_latent_device(), which is `cuda` where torch sees a GPU and `cpu` where it
    # does not. device enters computation.runtime and computation.numerical_runtime, both
    # inside the hashed computation, so the same declared configuration resolves a different
    # training identity on a GPU host than on a CPU one and each publishes under the same
    # population name. 10b is neural and takes this; 10a overrides it to cpu, because
    # PCAModel is numpy and scipy with no device argument and recording cuda there would
    # describe a computation that did not happen.
    device: cuda
    model_kwargs:
      sdf:
        checkpoint_epochs: [256, 512, 768, 1024]  # conditional-relative; publishes global 256..1280
        beta_checkpoint_epochs: [256]
        beta_default_checkpoint: 256

causal:
  treatment: carry_pct
  # Bars the treatment's own construction window spans, which is what the placebo block has
  # to cover: permuting carry_pct in blocks shorter than this destroys the serial
  # dependence the refutation exists to preserve, and the resulting p-value reads like a
  # refutation without being one. Declared here rather than inferred, because guessing which
  # element of a window list a column was built from puts a wrong number behind a right-looking
  # one. Derived from the construction, not chosen:
  #
  # `(c0_price - c1_price) / c0_price * 12` in 04_model_based_features, from two prices at
  # the same timestamp. Nothing rolls, so the construction window spans one bar, which is what
  # this field declares. It is not a claim that the column is serially independent: carry_pct is
  # highly persistent, and 12_model_analysis measures that profile. Where the construction window
  # is the shorter of the two scales, as it is here, the label buffer is what sizes the block -
  # `block_size = max(label_buffer, treatment_window)`.
  treatment_window: 1
  confounders: [vol_21d, momentum_composite, carry_rank]
  method: walk_forward_dml

```

يُعرض النص كاملًا مع نسبه إلى مصدره وفقًا لترخيصه. الترخيص: MIT

أعدّ وكيل الأبحاث في Stratmill هذا الملخص استنادًا إلى المصدر الأصلي؛ وهو ليس نسخة منه.