Compare commits

..
Author SHA1 Message Date
zhaoli a9e4464668 finish experiment 28 (exp/28-isolate-the-seed-count-effect-on-the-ndr) 2026-08-18 15:48:50 +00:00
zhaoli ea75065fb5 exp 28: add 2-seed comparison workflow (exp28) seeded on exp26 template 2026-08-18 15:32:33 +00:00
zhaoli 06e3b16544 start experiment 28 (exp/28-isolate-the-seed-count-effect-on-the-ndr) 2026-08-18 15:26:42 +00:00
zhaoli 894ac260a6 finish experiment 26 (exp/26-test-whether-reducing-topkdropout-daily) 2026-08-18 14:05:47 +00:00
zhaoli c455000a1e exp26: compact stochastic, n_drop 1 (workflow only) 2026-08-18 12:10:29 +00:00
zhaoli 5c7b2265f4 start experiment 26 (exp/26-test-whether-reducing-topkdropout-daily) 2026-08-18 10:03:21 +00:00
zhaoli c724682f3a finish experiment 24 (exp/24-run-the-rankic-ensemble-in-mlflow-experi) 2026-08-18 09:00:11 +00:00
zhaoli 59e733d88d exp 24: add exact compact stochastic feature workflow 2026-08-18 08:03:37 +00:00
zhaoli 1fcadbfe86 start experiment 24 (exp/24-run-the-rankic-ensemble-in-mlflow-experi) 2026-08-18 08:02:27 +00:00
zhaoli a718424340 finish experiment 23 (exp/23-test-whether-the-5-day-rankic-ensemble-i) 2026-08-18 07:58:20 +00:00
zhaoli adf0bfa812 exp 23: add general stochastic feature ablation workflow 2026-08-18 07:21:33 +00:00
zhaoli b5054ccc25 start experiment 23 (exp/23-test-whether-the-5-day-rankic-ensemble-i) 2026-08-18 07:20:27 +00:00
zhaoli 3d845306fe finish experiment 22 (exp/22-re-run-experiment-16s-5-day-rankic-ensem) 2026-08-18 07:17:36 +00:00
zhaoli 1075525d6e exp 22: add validated TA SP ensemble workflow 2026-08-18 06:38:55 +00:00
zhaoli 63c1ea763e start experiment 22 (exp/22-re-run-experiment-16s-5-day-rankic-ensem) 2026-08-18 06:38:28 +00:00
zhaoli 2c2684b103 start experiment 16 (16-scheduled-algo-retrain-on-20260814-tacrd) 2026-08-15 05:14:48 +00:00
27 changed files with 244 additions and 1072 deletions
+16 -18
View File
@@ -1,29 +1,27 @@
# TradeAC custom-qlib-code snapshot (auto-generated) # TradeAC custom-qlib-code snapshot (auto-generated)
# parent repo HEAD : 125be7b96fb5975e798a0b4301eeb5809a8a181c # parent repo HEAD : ea75065fb517388e417ad0fea76acf2731fef84e
# tac-qlib/tac_qlib/contrib # tac-qlib/tac_qlib/contrib
# tac-qlib/tac_qlib/data # tac-qlib/tac_qlib/data
# per-file hashes (git hash-object): # per-file hashes (git hash-object):
1b6298c4a5652f2e863cbdc385a1014a570fcd59 tac-qlib/tac_qlib/contrib/__init__.py 1b6298c4a5652f2e863cbdc385a1014a570fcd59 tac-qlib/tac_qlib/contrib/__init__.py
b8112569f9b2537c45b6535e1a505a207878d322 tac-qlib/tac_qlib/contrib/__pycache__/__init__.cpython-312.pyc b5e28b7c0a10ab553eaa6303013a880731742378 tac-qlib/tac_qlib/contrib/__pycache__/__init__.cpython-312.pyc
c76a9f17f680e74eea766eff27f7624359749ed6 tac-qlib/tac_qlib/contrib/data/__init__.py c76a9f17f680e74eea766eff27f7624359749ed6 tac-qlib/tac_qlib/contrib/data/__init__.py
8d5333ebd2b44165c50cba639ca2d4ac3fc7cfec tac-qlib/tac_qlib/contrib/data/__pycache__/__init__.cpython-312.pyc 715ab312125a446af751b928fe41cf41b99ffcd0 tac-qlib/tac_qlib/contrib/data/__pycache__/__init__.cpython-312.pyc
18cb37c0354184c49fa2e598396d7df0634cce0f tac-qlib/tac_qlib/contrib/data/__pycache__/handler.cpython-312.pyc a82a6a236db93097d48748e541c2cf9caa4e48b5 tac-qlib/tac_qlib/contrib/data/__pycache__/handler.cpython-312.pyc
871ff1e163c29261f140c3f53d42a41e6504c779 tac-qlib/tac_qlib/contrib/data/handler.py 0dd25ef161c6e0f15eafc84886e7e1381deb38c3 tac-qlib/tac_qlib/contrib/data/handler.py
b151d139a0dcde87d74b21e7c4b729176ba5c39b tac-qlib/tac_qlib/contrib/model/__init__.py b151d139a0dcde87d74b21e7c4b729176ba5c39b tac-qlib/tac_qlib/contrib/model/__init__.py
ab958203f33a99d12c7d923b6efb435189231666 tac-qlib/tac_qlib/contrib/model/__pycache__/__init__.cpython-312.pyc 483221cf1d5bad779d3cf9505269b7fb0b3f165d tac-qlib/tac_qlib/contrib/model/__pycache__/__init__.cpython-312.pyc
7478f6b0f6de419615c02d4d92b54529f689ef04 tac-qlib/tac_qlib/contrib/model/__pycache__/rank_ensemble.cpython-312.pyc 1286dec07a861f25691b24f71ecc22d8eb3537df tac-qlib/tac_qlib/contrib/model/__pycache__/rank_ensemble.cpython-312.pyc
9f9014ddd9bce37490061312d51e8e6fe540fec4 tac-qlib/tac_qlib/contrib/model/__pycache__/rank_gbdt.cpython-312.pyc 5185764bd55dbc63d16582bfcb6adc11b7e6d342 tac-qlib/tac_qlib/contrib/model/__pycache__/rank_gbdt.cpython-312.pyc
ce77dea53f6a87c5379782709293bf8ff55b2c75 tac-qlib/tac_qlib/contrib/model/rank_ensemble.py d3f051f3a8650c42fedc7b367b966f7c74fb5789 tac-qlib/tac_qlib/contrib/model/rank_ensemble.py
ccfe7d554989aa7f3e5a2128ae663e51b2207149 tac-qlib/tac_qlib/contrib/model/rank_gbdt.py ccfe7d554989aa7f3e5a2128ae663e51b2207149 tac-qlib/tac_qlib/contrib/model/rank_gbdt.py
4afcf9058231111c412925f4c4b84e81d656db87 tac-qlib/tac_qlib/contrib/strategy/__init__.py 4afcf9058231111c412925f4c4b84e81d656db87 tac-qlib/tac_qlib/contrib/strategy/__init__.py
74e5ecbbbb20bb71fd5cd083383de4ce88476712 tac-qlib/tac_qlib/contrib/strategy/__pycache__/__init__.cpython-312.pyc 13a8e4a41ce9afde97a55756d666d3ff5cbbec0e tac-qlib/tac_qlib/contrib/strategy/__pycache__/__init__.cpython-312.pyc
afaf562aeaa12cebc8529cd916153252e7e3c38a tac-qlib/tac_qlib/contrib/strategy/__pycache__/optimal_stop.cpython-312.pyc 0bb5804a79611c66bc25988226ca0f1cdbe1a7eb tac-qlib/tac_qlib/contrib/strategy/__pycache__/optimal_stop.cpython-312.pyc
96a0a25201f0a1bb2fc2190e26228c5c0e711a79 tac-qlib/tac_qlib/contrib/strategy/hmm_risk.py
816de5d58ae23d996635d42331cf9fc8963d5dbe tac-qlib/tac_qlib/contrib/strategy/momentum_gate.py
79aaad9e39fcc740a773f4f63c512ce1086cfde0 tac-qlib/tac_qlib/contrib/strategy/optimal_stop.py 79aaad9e39fcc740a773f4f63c512ce1086cfde0 tac-qlib/tac_qlib/contrib/strategy/optimal_stop.py
92e6e90eb0cd0a25142034560f27adb6b705b1a8 tac-qlib/tac_qlib/data/__init__.py 92e6e90eb0cd0a25142034560f27adb6b705b1a8 tac-qlib/tac_qlib/data/__init__.py
0ed1ead6c1314a3f25784d453e54a15a8a04baaa tac-qlib/tac_qlib/data/__pycache__/__init__.cpython-312.pyc 3cbc0686e6f863d306d93c589a607a5bdb7201f2 tac-qlib/tac_qlib/data/__pycache__/__init__.cpython-312.pyc
9609782800944c45b78bb58eaa7b51ba1b7f8f43 tac-qlib/tac_qlib/data/__pycache__/config.cpython-312.pyc 46bcdbcc173ff3a6281fed33d10c1bcac96876b0 tac-qlib/tac_qlib/data/__pycache__/config.cpython-312.pyc
a85628d71d12cfe5b18b1c884c5d829c89594579 tac-qlib/tac_qlib/data/__pycache__/providers.cpython-312.pyc 6df4f88af22ab4c026c14ef1835f2e630cdf850c tac-qlib/tac_qlib/data/__pycache__/providers.cpython-312.pyc
686d36f6d101c547491ca866aa143aa542e17518 tac-qlib/tac_qlib/data/config.py 53c9007a928841fd3c3b08450f9a6520ce1ac091 tac-qlib/tac_qlib/data/config.py
d9f839be30026f337754a3f015425a8efdbe8e2a tac-qlib/tac_qlib/data/providers.py 8d0644f6f0d1efb94798ed444cc73e63b643459b tac-qlib/tac_qlib/data/providers.py
+22 -4
View File
@@ -64,9 +64,13 @@ def check_transform_proc(proc_l, fit_start_time, fit_end_time):
def get_common_feature_fields(lake_root=None, market="US", timeframe="1d") -> List[str]: def get_common_feature_fields(lake_root=None, market="US", timeframe="1d") -> List[str]:
"""Discover ta-lib columns present in *every* features parquet file of the lake. """Discover feature columns present in *every* feature file of the lake.
Returns sorted field names (without the ``$`` prefix). Empty if no features are persisted. Walks the `family=ta|sp` partition layout (plus any legacy flat files).
TA and SP columns are disjoint by construction, so the common set is
computed per family (columns shared by all symbol files of that family),
then the per-family results are unioned. Returns sorted field names
(without the ``$`` prefix). Empty if no features are persisted.
""" """
cfg = LakeConfig(lake_root, market) cfg = LakeConfig(lake_root, market)
feat_dir = cfg.features_dir(timeframe) feat_dir = cfg.features_dir(timeframe)
@@ -74,8 +78,9 @@ def get_common_feature_fields(lake_root=None, market="US", timeframe="1d") -> Li
return [] return []
import pyarrow.parquet as pq import pyarrow.parquet as pq
def _family_common(fam_dir: Path) -> set:
common = None common = None
for p in sorted(feat_dir.glob("symbol=*.parquet")): for p in sorted(fam_dir.glob("symbol=*.parquet")):
try: try:
cols = set(pq.read_schema(p).names) - set(NON_FEATURE_COLUMNS) cols = set(pq.read_schema(p).names) - set(NON_FEATURE_COLUMNS)
except Exception: # pragma: no cover - skip unreadable files except Exception: # pragma: no cover - skip unreadable files
@@ -83,7 +88,20 @@ def get_common_feature_fields(lake_root=None, market="US", timeframe="1d") -> Li
common = cols if common is None else (common & cols) common = cols if common is None else (common & cols)
if not common: if not common:
break break
return sorted(common) if common else [] return common or set()
common: set = set()
# family tier: features/market=*/timeframe=*/family=*/symbol=*.parquet
for fam in ("ta", "sp"):
fam_dir = feat_dir / f"family={fam}"
if fam_dir.is_dir():
common |= _family_common(fam_dir)
# legacy flat: features/market=*/timeframe=*/symbol=*.parquet
if (feat_dir / "family=ta").exists() or (feat_dir / "family=sp").exists():
pass # family layout already covered
else:
common |= _family_common(feat_dir)
return sorted(common)
class DropAllNaN(processor_module.Processor): class DropAllNaN(processor_module.Processor):
@@ -56,7 +56,6 @@ import os
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from typing import List, Optional from typing import List, Optional
import numpy as np
import pandas as pd import pandas as pd
from qlib.data.dataset import DatasetH from qlib.data.dataset import DatasetH
@@ -80,15 +79,11 @@ class RankICEnsembleLGBModel(RankICLGBModel):
forwarded. forwarded.
""" """
def __init__(self, seeds: str = "42", parallel: int = 0, weight_mode: str = "equal", **kwargs): def __init__(self, seeds: str = "42", parallel: int = 0, **kwargs):
self.seeds = [int(s.strip()) for s in str(seeds).split(",") if s.strip()] self.seeds = [int(s.strip()) for s in str(seeds).split(",") if s.strip()]
if not self.seeds: if not self.seeds:
raise ValueError("seeds must contain at least one integer") raise ValueError("seeds must contain at least one integer")
self.parallel = int(parallel) self.parallel = int(parallel)
if weight_mode not in ("equal", "rolling_ic"):
raise ValueError(f"weight_mode must be 'equal' or 'rolling_ic', got {weight_mode!r}")
self.weight_mode = weight_mode
self.rolling_ic_window = int(kwargs.pop("rolling_ic_window", 21))
# drop seed/parallel handling from the base kwargs, keep everything else # drop seed/parallel handling from the base kwargs, keep everything else
self._model_kwargs = dict(kwargs) self._model_kwargs = dict(kwargs)
super().__init__(**self._model_kwargs) super().__init__(**self._model_kwargs)
@@ -184,44 +179,11 @@ class RankICEnsembleLGBModel(RankICLGBModel):
# -------------------------------------------------------------- predict # -------------------------------------------------------------- predict
def predict(self, dataset: DatasetH, segment="test") -> pd.Series: def predict(self, dataset: DatasetH, segment="test") -> pd.Series:
"""Combine per-seed predictions. """Average the per-seed predictions over the given segment."""
``weight_mode='equal'`` (default): simple average, as before.
``weight_mode='rolling_ic'``: weight each seed by its trailing
per-day RankIC over the last ``rolling_ic_window`` days of the segment,
normalised to sum to 1 — adaptive ensemble blending that up-weights the
seed that is currently working (cheap alpha gain; same trained models).
"""
if not self._models: if not self._models:
raise ValueError("model is not fitted yet!") raise ValueError("model is not fitted yet!")
preds = [m.predict(dataset, segment=segment) for m in self._models] preds = [m.predict(dataset, segment=segment) for m in self._models]
if len(preds) == 1: if len(preds) == 1:
return preds[0] return preds[0]
frame = pd.concat(preds, axis=1) frame = pd.concat(preds, axis=1)
frame.columns = [f"seed{m.params.get('seed', i)}" for i, m in enumerate(self._models)]
if self.weight_mode == "equal":
return frame.mean(axis=1) return frame.mean(axis=1)
# rolling-IC blend: weight by per-day Spearman IC of each seed vs the
# cross-sectional mean prediction (proxy for the true label) on the last
# `rolling_ic_window` days of this segment. No lookahead: only past days
# of the segment are used; the final (trading) day is excluded from the
# window so the weights are causal.
mean_pred = frame.mean(axis=1)
dates = sorted(frame.index.get_level_values(0).unique())
win = [d for d in dates if d < dates[-1]][-self.rolling_ic_window :]
ics = {}
for col in frame.columns:
if not win:
ics[col] = 1.0
continue
sub = pd.DataFrame({"p": frame[col], "m": mean_pred})
vals = []
for d in win:
s = sub[sub.index.get_level_values(0) == d]
if len(s) >= 3 and s["p"].nunique() > 1 and s["m"].nunique() > 1:
vals.append(s["p"].rank().corr(s["m"].rank()))
ics[col] = float(np.mean(vals)) if vals else 1.0
wsum = sum(ics.values()) or len(ics)
weights = {c: v / wsum for c, v in ics.items()}
return sum(frame[c] * weights[c] for c in frame.columns)
@@ -1,138 +0,0 @@
"""TopkDropout with HMM high-volatility + drawdown-pause risk gates.
Gates NEW entries on two risk conditions (held names are never force-sold):
1. **HMM high-vol pause**: when the cross-sectional mean of ``sp_hmm_p_regime1``
(HMM high-vol regime probability) on the signal date is >= ``hmm_pause_pct``,
new buys are paused. The time-series study showed HMM high-vol probability
pulses BEFORE sharp moves (regime-change cut) — pausing new exposure at the
boundary reduces drawdown from price over-reaction.
2. **Drawdown pause**: when the account equity drawdown from its running peak
exceeds ``drawdown_pause_pct``, new buys are paused. This is the
``drawdown_pause_pct`` risk-limit expressed inside the backtest (the pure
executor-side gate is documented as not expressible in a one-shot backtest).
3. **Liquidity floor**: names whose 20-day average daily dollar volume is below
``liquidity_floor_adv`` are dropped from BUY candidates (the proven mitigant
from exp-18: $5M floor cut drawdown 7.9%->5.4% at higher IR).
Implementation: pre-filter the signal score before the base TopkDropout
decision — non-held names get score 0 when any gate fires.
Wired into a workflow yaml like:
strategy:
class: HmmRiskTopk
module_path: tac_qlib.contrib.strategy.hmm_risk
kwargs:
signal: "<PRED>"
topk: 10
n_drop: 2
only_tradable: true
risk_degree: 0.95
hmm_pause_pct: 0.70
drawdown_pause_pct: 8.0
liquidity_floor_adv: 5000000
"""
from __future__ import annotations
import copy
from typing import Dict
import numpy as np
import pandas as pd
from qlib.backtest.decision import TradeDecisionWO
from qlib.backtest.position import Position
from qlib.contrib.strategy.signal_strategy import TopkDropoutStrategy
__all__ = ["HmmRiskTopk"]
class HmmRiskTopk(TopkDropoutStrategy):
"""TopkDropoutStrategy with HMM high-vol pause + drawdown pause + liquidity floor."""
def __init__(
self,
*,
hmm_pause_pct: float = 0.70,
drawdown_pause_pct: float = 8.0,
liquidity_floor_adv: float = 0.0,
**kwargs,
):
super().__init__(**kwargs)
self.hmm_pause_pct = float(hmm_pause_pct)
self.drawdown_pause_pct = float(drawdown_pause_pct)
self.liquidity_floor_adv = float(liquidity_floor_adv)
self._peak_equity = 0.0
# ------------------------------------------------------------- gates
def _hmm_high_vol(self, pred_date) -> bool:
"""Cross-sectional mean HMM high-vol regime probability >= threshold."""
try:
from qlib.data import D
feat = D.features(D.instruments("all"), ["$sp_hmm_p_regime1"],
start_time=pred_date, end_time=pred_date)
if feat is None or len(feat) == 0:
return False
p = feat["$sp_hmm_p_regime1"].dropna()
if len(p) == 0:
return False
return float(p.mean()) >= self.hmm_pause_pct
except Exception:
return False
def _drawdown_active(self, equity: float) -> bool:
if self.drawdown_pause_pct <= 0:
return False
self._peak_equity = max(self._peak_equity, equity)
if self._peak_equity <= 0:
return False
dd = (self._peak_equity - equity) / self._peak_equity * 100.0
return dd >= self.drawdown_pause_pct
def _illiquid(self, codes, asof) -> Dict[str, bool]:
if self.liquidity_floor_adv <= 0 or not codes:
return {}
from tac_qlib.risk_limits import dollar_adv
adv = dollar_adv(codes, market="US", asof=asof, lookback=20)
return {c: adv.get(str(c).upper(), 0.0) < self.liquidity_floor_adv for c in codes}
# ------------------------------------------------------------- decision
def generate_trade_decision(self, execute_result=None):
trade_step = self.trade_calendar.get_trade_step()
trade_start_time, trade_end_time = self.trade_calendar.get_step_time(trade_step)
pred_start_time, pred_end_time = self.trade_calendar.get_step_time(trade_step, shift=1)
pred_score = self.signal.get_signal(start_time=pred_start_time, end_time=pred_end_time)
if pred_score is None:
return TradeDecisionWO([], self)
if isinstance(pred_score, pd.DataFrame):
pred_score = pred_score.iloc[:, 0]
current_temp = copy.deepcopy(self.trade_position)
assert isinstance(current_temp, Position)
held = {c for c in current_temp.get_stock_list() if abs(current_temp.get_stock_amount(c)) > 1e-6}
equity = current_temp.get_cash()
for code in held:
mark = self.trade_exchange.get_deal_price(
stock_id=code, start_time=trade_start_time, end_time=trade_end_time, direction=1
)
if mark is not None and np.isfinite(mark):
equity += abs(current_temp.get_stock_amount(code)) * mark
hmm_pause = self._hmm_high_vol(str(pd.Timestamp(pred_start_time).date()))
dd_pause = self._drawdown_active(equity)
buys_paused = hmm_pause or dd_pause
pred_score = pred_score.copy()
if buys_paused or self.liquidity_floor_adv > 0:
new_codes = [c for c in pred_score.index if c not in held]
illiquid = self._illiquid(new_codes, str(pd.Timestamp(pred_start_time).date()))
for code in new_codes:
if buys_paused or illiquid.get(code, False):
pred_score[code] = -1e9 # cannot enter today
return super().generate_trade_decision(execute_result)
@@ -1,91 +0,0 @@
"""TopkDropout with a 1-day momentum entry-confirmation gate.
Gates NEW entries on short-term momentum: a name that is not currently held
may only be bought when its trailing 1-day return is above ``min_momentum``
(Lag-1 autocorr ~ +0.45 in the time-series study => short-term momentum
continuation). Held names are never force-sold by this gate — exits stay the
pure TopkDropout rule.
Implementation: override ``generate_trade_decision`` and zero out the signal
score of any non-held name that fails the momentum check BEFORE calling the
base TopkDropout decision, so it can never be selected as a buy candidate.
This is a clean pre-filter: the rest of the strategy (top-k, n_drop, sizing,
costs) is untouched.
Wired into a workflow yaml like:
strategy:
class: MomentumGateTopk
module_path: tac_qlib.contrib.strategy.momentum_gate
kwargs:
signal: "<PRED>"
topk: 10
n_drop: 2
only_tradable: true
risk_degree: 0.95
min_momentum: 0.0
"""
from __future__ import annotations
import copy
import pandas as pd
from qlib.backtest.decision import TradeDecisionWO
from qlib.backtest.position import Position
from qlib.contrib.strategy.signal_strategy import TopkDropoutStrategy
__all__ = ["MomentumGateTopk"]
class MomentumGateTopk(TopkDropoutStrategy):
"""TopkDropoutStrategy gated on 1-day momentum for new entries."""
def __init__(self, *, min_momentum: float = 0.0, **kwargs):
super().__init__(**kwargs)
self.min_momentum = float(min_momentum)
def _momentum_ok(self, code, trade_start, trade_end) -> bool:
"""True when the trailing 1-day return is above the momentum floor."""
try:
cur = self.trade_exchange.get_deal_price(
stock_id=code, start_time=trade_start, end_time=trade_end, direction=1
)
except Exception:
return False
if cur is None or cur != cur or cur <= 0:
return False
prev_start = trade_start - pd.Timedelta(days=5)
prev_end = trade_start - pd.Timedelta(seconds=1)
prev = self.trade_exchange.get_deal_price(
stock_id=code, start_time=prev_start, end_time=prev_end, direction=0
)
if prev is None or prev != prev or prev <= 0:
return False
return (cur / prev - 1.0) >= self.min_momentum
def generate_trade_decision(self, execute_result=None):
trade_step = self.trade_calendar.get_trade_step()
trade_start_time, trade_end_time = self.trade_calendar.get_step_time(trade_step)
pred_start_time, pred_end_time = self.trade_calendar.get_step_time(trade_step, shift=1)
pred_score = self.signal.get_signal(start_time=pred_start_time, end_time=pred_end_time)
if pred_score is None:
return TradeDecisionWO([], self)
if isinstance(pred_score, pd.DataFrame):
pred_score = pred_score.iloc[:, 0]
current_temp = copy.deepcopy(self.trade_position)
assert isinstance(current_temp, Position)
held = set(current_temp.get_stock_list())
held = {c for c in held if abs(current_temp.get_stock_amount(c)) > 1e-6}
# pre-filter: zero the score of non-held names that fail momentum
pred_score = pred_score.copy()
for code in pred_score.index:
if code in held:
continue # never gate exits / re-balancing of held names
if not self._momentum_ok(code, trade_start_time, trade_end_time):
pred_score[code] = -1e9 # cannot enter today
return super().generate_trade_decision(execute_result)
+29 -2
View File
@@ -6,10 +6,11 @@ The lake is a hive-partitioned parquet store (see ``tac-engine/skills/tradeac-la
├── market=US/ ├── market=US/
│ └── timeframe=1d/ │ └── timeframe=1d/
│ └── symbol=AAPL.parquet # OHLCV bars: t, date, o, h, l, c, v, n, vw │ └── symbol=AAPL.parquet # OHLCV bars: t, date, o, h, l, c, v, n, vw
├── features/ # ta-lib indicators, wide format ├── features/ # indicators, wide format, family tier
│ └── market=US/ │ └── market=US/
│ └── timeframe=1d/ │ └── timeframe=1d/
│ └── symbol=AAPL.parquet # t, sma_5, sma_20, rsi_14, ... │ ├── family=ta/symbol=AAPL.parquet # t, sma_5, sma_20, rsi_14, ...
│ └── family=sp/symbol=AAPL.parquet # t, sp_ou_*, sp_hmm_*, ...
├── calendar.parquet # trading days per market ├── calendar.parquet # trading days per market
├── coverage.parquet # per (market,timeframe,symbol) loaded windows ├── coverage.parquet # per (market,timeframe,symbol) loaded windows
└── symbols.parquet # asset master └── symbols.parquet # asset master
@@ -107,8 +108,34 @@ class LakeConfig:
return self.lake_root / "features" / f"market={self.market}" / f"timeframe={timeframe}" return self.lake_root / "features" / f"market={self.market}" / f"timeframe={timeframe}"
def features_path(self, timeframe: str, symbol: str) -> Path: def features_path(self, timeframe: str, symbol: str) -> Path:
# Legacy flat path (no family tier). Prefer `load_features` which
# resolves the family=ta|sp partition layout.
return self.features_dir(timeframe) / f"symbol={str(symbol).upper()}.parquet" return self.features_dir(timeframe) / f"symbol={str(symbol).upper()}.parquet"
def load_features(self, timeframe: str, symbol: str) -> pd.DataFrame:
"""All feature columns for a symbol, merging the `family=ta` and
`family=sp` partitions by timestamp. Returns an empty frame when no
feature files exist (legacy flat layout falls back transparently)."""
sym = str(symbol).upper()
frames = []
for family in ("ta", "sp"):
p = self.features_dir(timeframe) / f"family={family}" / f"symbol={sym}.parquet"
if p.exists():
frames.append(pd.read_parquet(p))
if not frames:
flat = self.features_dir(timeframe) / f"symbol={sym}.parquet"
if flat.exists():
return pd.read_parquet(flat)
return pd.DataFrame()
if len(frames) == 1:
return frames[0]
merged = frames[0]
for extra in frames[1:]:
merged = merged.merge(extra, on="t", how="outer", suffixes=("", "_dup"))
for c in [c for c in merged.columns if c.endswith("_dup")]:
merged = merged.drop(columns=c)
return merged
def calendar_path(self) -> Path: def calendar_path(self) -> Path:
return self.lake_root / "calendar.parquet" return self.lake_root / "calendar.parquet"
+1 -2
View File
@@ -173,8 +173,7 @@ class LakeFeatureProvider(FeatureProvider):
def _load_feature_df(self, instrument: str, timeframe: str) -> pd.DataFrame: def _load_feature_df(self, instrument: str, timeframe: str) -> pd.DataFrame:
key = (instrument, timeframe) key = (instrument, timeframe)
if key not in self._feature_cache: if key not in self._feature_cache:
p = self.cfg.features_path(timeframe, instrument) self._feature_cache[key] = self.cfg.load_features(timeframe, instrument)
self._feature_cache[key] = pd.read_parquet(p) if p.exists() else pd.DataFrame()
return self._feature_cache[key] return self._feature_cache[key]
@staticmethod @staticmethod
-141
View File
@@ -1,141 +0,0 @@
# -----------------------------------------------------------------------------
# EXP 18 - Risk-limit control: reference model + TopkDropout baseline (A).
#
# Signal/model identical to the reference (tac-rd-rank-ensemble-isolated,
# run 0cea66d9...): RankICEnsembleLGBModel (parallel, 5 seeds) on the 50-ETF
# SP-5d panel, test 2026-01-04..2026-08-10. This workflow reproduces the
# unconstrained TopkDropout baseline net-of-cost so the risk-limited variant
# (same pred, liquidity/size/concentration caps) can be compared 1:1.
#
# The risk_limits spec itself is applied via rd_backtest / rd_strategy_targets
# (tool-level param, not a YAML key); this run records the unconstrained
# baseline that the limit A/B is measured against.
#
# Run:
# rd_run_workflow config_path=experiments/workflows/exp18-risk-limit/a_baseline.yaml \
# experiment_name=tac-rd-risk-limit
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set SP_FIELDS = "sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs:
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-risk-limit"
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
parallel: 5
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03
end_time: 2026-08-14
fit_start_time: 2016-01-04
fit_end_time: 2025-09-01
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,{{ SP_FIELDS }}"
infer_processors:
- class: DropAllNaN
kwargs: {}
- class: ProcessInf
kwargs: {}
- class: CSRankNorm
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments:
train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10]
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs:
signal: "<PRED>"
topk: 10
n_drop: 2
only_tradable: true
risk_degree: 0.95
backtest:
start_time: 2026-01-04
end_time: 2026-08-10
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "{{ UNIVERSE }}"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,136 +0,0 @@
# -----------------------------------------------------------------------------
# EXP 20 - R1: 2-seed ensemble (seeds 42,7), TopkDropout baseline.
#
# Runtime cut: 2 seeds instead of 5. Everything else identical to the reference
# (test 2026-01-04..2026-08-10, SPY, costs 5bp/15bp). Measures whether the
# 2-seed ensemble keeps the reference quality at ~2/5 the training time.
#
# Run:
# rd_run_workflow config_path=experiments/workflows/exp20-risk-limit-improve/r1_2seed.yaml \
# experiment_name=tac-rd-risk-limit
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set SP_FIELDS = "sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs:
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-risk-limit"
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
parallel: 5
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03
end_time: 2026-08-14
fit_start_time: 2016-01-04
fit_end_time: 2025-09-01
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,{{ SP_FIELDS }}"
infer_processors:
- class: DropAllNaN
kwargs: {}
- class: ProcessInf
kwargs: {}
- class: CSRankNorm
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments:
train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10]
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: MomentumGateTopk
module_path: tac_qlib.contrib.strategy.momentum_gate
kwargs:
signal: "<PRED>"
topk: 10
n_drop: 2
min_momentum: 0.0
only_tradable: true
risk_degree: 0.95
backtest:
start_time: 2026-01-04
end_time: 2026-08-10
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "{{ UNIVERSE }}"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,138 +0,0 @@
# -----------------------------------------------------------------------------
# EXP 20 - R1: 2-seed ensemble (seeds 42,7), TopkDropout baseline.
#
# Runtime cut: 2 seeds instead of 5. Everything else identical to the reference
# (test 2026-01-04..2026-08-10, SPY, costs 5bp/15bp). Measures whether the
# 2-seed ensemble keeps the reference quality at ~2/5 the training time.
#
# Run:
# rd_run_workflow config_path=experiments/workflows/exp20-risk-limit-improve/r1_2seed.yaml \
# experiment_name=tac-rd-risk-limit
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set SP_FIELDS = "sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs:
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-risk-limit"
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
parallel: 5
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03
end_time: 2026-08-14
fit_start_time: 2016-01-04
fit_end_time: 2025-09-01
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,{{ SP_FIELDS }}"
infer_processors:
- class: DropAllNaN
kwargs: {}
- class: ProcessInf
kwargs: {}
- class: CSRankNorm
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments:
train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10]
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: HmmRiskTopk
module_path: tac_qlib.contrib.strategy.hmm_risk
kwargs:
signal: "<PRED>"
topk: 10
n_drop: 2
hmm_pause_pct: 0.70
drawdown_pause_pct: 8.0
liquidity_floor_adv: 5000000
only_tradable: true
risk_degree: 0.95
backtest:
start_time: 2026-01-04
end_time: 2026-08-10
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "{{ UNIVERSE }}"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,137 +0,0 @@
# -----------------------------------------------------------------------------
# EXP 20 - R1: 2-seed ensemble (seeds 42,7), TopkDropout baseline.
#
# Runtime cut: 2 seeds instead of 5. Everything else identical to the reference
# (test 2026-01-04..2026-08-10, SPY, costs 5bp/15bp). Measures whether the
# 2-seed ensemble keeps the reference quality at ~2/5 the training time.
#
# Run:
# rd_run_workflow config_path=experiments/workflows/exp20-risk-limit-improve/r1_2seed.yaml \
# experiment_name=tac-rd-risk-limit
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set SP_FIELDS = "sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs:
lake_root: "{{ LAKE }}"
market: US
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs:
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-risk-limit"
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
weight_mode: rolling_ic
rolling_ic_window: 21
parallel: 5
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03
end_time: 2026-08-14
fit_start_time: 2016-01-04
fit_end_time: 2025-09-01
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,{{ SP_FIELDS }}"
infer_processors:
- class: DropAllNaN
kwargs: {}
- class: ProcessInf
kwargs: {}
- class: CSRankNorm
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments:
train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10]
record:
- class: SignalRecord
module_path: qlib.workflow.record_temp
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs:
signal: "<PRED>"
topk: 10
n_drop: 2
only_tradable: true
risk_degree: 0.95
backtest:
start_time: 2026-01-04
end_time: 2026-08-10
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "{{ UNIVERSE }}"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,47 +1,26 @@
# ----------------------------------------------------------------------------- # Re-run of experiment 16 with validated family=ta and family=sp lake features.
# EXP 20 - R1: 2-seed ensemble (seeds 42,7), TopkDropout baseline.
#
# Runtime cut: 2 seeds instead of 5. Everything else identical to the reference
# (test 2026-01-04..2026-08-10, SPY, costs 5bp/15bp). Measures whether the
# 2-seed ensemble keeps the reference quality at ~2/5 the training time.
#
# Run:
# rd_run_workflow config_path=experiments/workflows/exp20-risk-limit-improve/r1_2seed.yaml \
# experiment_name=tac-rd-risk-limit
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %} {%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %} {%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set SP_FIELDS = "sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %} {%- set FEATURES = "$open,$high,$low,$close,$vwap,$volume,sma_5,sma_20,ema_12,ema_26,rsi_14,macd,macd_signal,macd_hist,bb_upper,bb_middle,bb_lower,atr_14,adx_14,sp_ret,sp_ou_half_life,sp_ou_revert,sp_ou_zscore,sp_hmm_p_regime1,sp_hmm_state,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_down,sp_max_move,sp_max_up,sp_rv1,sp_rv5,sp_rv22,sp_rv_ac1,sp_rv_cv_22,sp_vol_ratio_1_22,sp_vol_ratio_5_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_rskew_5,sp_rskew_22,sp_rkurt_5,sp_rkurt_22,sp_dsv_1,sp_dsv_5,sp_dsv_22,sp_dsv_ratio_1,sp_dsv_ratio_5,sp_dsv_ratio_22,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead,sp_sig_level2_lead_lag_5,sp_sig_level2_lag_lead_5" %}
qlib_init: qlib_init:
provider_uri: "{{ LAKE }}" provider_uri: "{{ LAKE }}"
region: us region: us
expression_cache: null expression_cache: null
dataset_cache: null dataset_cache: null
calendar_provider: calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
instrument_provider: instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider: feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
exp_manager: exp_manager:
class: MLflowExpManager class: MLflowExpManager
module_path: qlib.workflow.expm module_path: qlib.workflow.expm
kwargs: kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-exp16-db-ta-sp" }
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-risk-limit"
task: task:
model: model:
@@ -62,7 +41,6 @@ task:
reg_alpha: 0.1 reg_alpha: 0.1
reg_lambda: 1.0 reg_lambda: 1.0
seeds: "42,7,2026,99,123" seeds: "42,7,2026,99,123"
parallel: 1
dataset: dataset:
class: DatasetH class: DatasetH
@@ -74,39 +52,28 @@ task:
kwargs: kwargs:
instruments: "{{ UNIVERSE }}" instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03 start_time: 2015-01-03
end_time: 2026-08-14 end_time: 2026-08-10
fit_start_time: 2016-01-04 fit_start_time: 2016-01-04
fit_end_time: 2025-09-01 fit_end_time: 2025-09-01
freq: day freq: day
lake_root: "{{ LAKE }}" lake_root: "{{ LAKE }}"
market: US market: US
label: "Ref($close,-6)/Ref($close,-1)-1" label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,{{ SP_FIELDS }}" feature_fields: "{{ FEATURES }}"
infer_processors: infer_processors:
- class: DropAllNaN - { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
kwargs: {} - { class: ProcessInf, kwargs: {} }
- class: ProcessInf - { class: CSRankNorm, kwargs: {} }
kwargs: {} - { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- class: CSRankNorm - { class: Fillna, kwargs: {} }
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments: segments:
train: [2016-01-04, 2025-09-01] train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03] valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10] test: [2026-01-04, 2026-08-10]
record: record:
- class: SignalRecord - { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
module_path: qlib.workflow.record_temp - { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord - class: PortAnaRecord
module_path: qlib.workflow.record_temp module_path: qlib.workflow.record_temp
kwargs: kwargs:
@@ -114,12 +81,7 @@ task:
strategy: strategy:
class: TopkDropoutStrategy class: TopkDropoutStrategy
module_path: qlib.contrib.strategy module_path: qlib.contrib.strategy
kwargs: kwargs: { signal: "<PRED>", topk: 10, n_drop: 2, only_tradable: true, risk_degree: 0.95 }
signal: "<PRED>"
topk: 10
n_drop: 2
only_tradable: true
risk_degree: 0.95
backtest: backtest:
start_time: 2026-01-04 start_time: 2026-01-04
end_time: 2026-08-10 end_time: 2026-08-10
@@ -1,47 +1,26 @@
# ----------------------------------------------------------------------------- # General stochastic-process feature ablation: no TA, HMM, or OU fields.
# EXP 20 - R1: 2-seed ensemble (seeds 42,7), TopkDropout baseline.
#
# Runtime cut: 2 seeds instead of 5. Everything else identical to the reference
# (test 2026-01-04..2026-08-10, SPY, costs 5bp/15bp). Measures whether the
# 2-seed ensemble keeps the reference quality at ~2/5 the training time.
#
# Run:
# rd_run_workflow config_path=experiments/workflows/exp20-risk-limit-improve/r1_2seed.yaml \
# experiment_name=tac-rd-risk-limit
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %} {%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %} {%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set SP_FIELDS = "sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %} {%- set FEATURES = "$open,$high,$low,$close,$vwap,$volume,sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_down,sp_max_move,sp_max_up,sp_rv1,sp_rv5,sp_rv22,sp_rv_ac1,sp_rv_cv_22,sp_vol_ratio_1_22,sp_vol_ratio_5_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_rskew_5,sp_rskew_22,sp_rkurt_5,sp_rkurt_22,sp_dsv_1,sp_dsv_5,sp_dsv_22,sp_dsv_ratio_1,sp_dsv_ratio_5,sp_dsv_ratio_22,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead,sp_sig_level2_lead_lag_5,sp_sig_level2_lag_lead_5" %}
qlib_init: qlib_init:
provider_uri: "{{ LAKE }}" provider_uri: "{{ LAKE }}"
region: us region: us
expression_cache: null expression_cache: null
dataset_cache: null dataset_cache: null
calendar_provider: calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
instrument_provider: instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider: feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
exp_manager: exp_manager:
class: MLflowExpManager class: MLflowExpManager
module_path: qlib.workflow.expm module_path: qlib.workflow.expm
kwargs: kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-exp22-stochastic-general" }
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-risk-limit"
task: task:
model: model:
@@ -62,7 +41,6 @@ task:
reg_alpha: 0.1 reg_alpha: 0.1
reg_lambda: 1.0 reg_lambda: 1.0
seeds: "42,7,2026,99,123" seeds: "42,7,2026,99,123"
parallel: 5
dataset: dataset:
class: DatasetH class: DatasetH
@@ -74,39 +52,28 @@ task:
kwargs: kwargs:
instruments: "{{ UNIVERSE }}" instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03 start_time: 2015-01-03
end_time: 2026-08-14 end_time: 2026-08-10
fit_start_time: 2016-01-04 fit_start_time: 2016-01-04
fit_end_time: 2025-09-01 fit_end_time: 2025-09-01
freq: day freq: day
lake_root: "{{ LAKE }}" lake_root: "{{ LAKE }}"
market: US market: US
label: "Ref($close,-6)/Ref($close,-1)-1" label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,{{ SP_FIELDS }}" feature_fields: "{{ FEATURES }}"
infer_processors: infer_processors:
- class: DropAllNaN - { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
kwargs: {} - { class: ProcessInf, kwargs: {} }
- class: ProcessInf - { class: CSRankNorm, kwargs: {} }
kwargs: {} - { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- class: CSRankNorm - { class: Fillna, kwargs: {} }
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments: segments:
train: [2016-01-04, 2025-09-01] train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03] valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10] test: [2026-01-04, 2026-08-10]
record: record:
- class: SignalRecord - { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
module_path: qlib.workflow.record_temp - { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord - class: PortAnaRecord
module_path: qlib.workflow.record_temp module_path: qlib.workflow.record_temp
kwargs: kwargs:
@@ -114,12 +81,7 @@ task:
strategy: strategy:
class: TopkDropoutStrategy class: TopkDropoutStrategy
module_path: qlib.contrib.strategy module_path: qlib.contrib.strategy
kwargs: kwargs: { signal: "<PRED>", topk: 10, n_drop: 2, only_tradable: true, risk_degree: 0.95 }
signal: "<PRED>"
topk: 10
n_drop: 2
only_tradable: true
risk_degree: 0.95
backtest: backtest:
start_time: 2026-01-04 start_time: 2026-01-04
end_time: 2026-08-10 end_time: 2026-08-10
@@ -1,47 +1,26 @@
# ----------------------------------------------------------------------------- # Exact compact stochastic feature set requested for a new run in MLflow exp 25.
# EXP 20 - R1: 2-seed ensemble (seeds 42,7), TopkDropout baseline.
#
# Runtime cut: 2 seeds instead of 5. Everything else identical to the reference
# (test 2026-01-04..2026-08-10, SPY, costs 5bp/15bp). Measures whether the
# 2-seed ensemble keeps the reference quality at ~2/5 the training time.
#
# Run:
# rd_run_workflow config_path=experiments/workflows/exp20-risk-limit-improve/r1_2seed.yaml \
# experiment_name=tac-rd-risk-limit
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %} {%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %} {%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set SP_FIELDS = "sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead,sma_3,ema_3" %} {%- set FEATURES = "$open,$high,$low,$close,$vwap,$volume,sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %}
qlib_init: qlib_init:
provider_uri: "{{ LAKE }}" provider_uri: "{{ LAKE }}"
region: us region: us
expression_cache: null expression_cache: null
dataset_cache: null dataset_cache: null
calendar_provider: calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
instrument_provider: instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider: feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
exp_manager: exp_manager:
class: MLflowExpManager class: MLflowExpManager
module_path: qlib.workflow.expm module_path: qlib.workflow.expm
kwargs: kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-exp22-stochastic-general" }
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-risk-limit"
task: task:
model: model:
@@ -62,7 +41,6 @@ task:
reg_alpha: 0.1 reg_alpha: 0.1
reg_lambda: 1.0 reg_lambda: 1.0
seeds: "42,7,2026,99,123" seeds: "42,7,2026,99,123"
parallel: 5
dataset: dataset:
class: DatasetH class: DatasetH
@@ -74,39 +52,28 @@ task:
kwargs: kwargs:
instruments: "{{ UNIVERSE }}" instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03 start_time: 2015-01-03
end_time: 2026-08-14 end_time: 2026-08-10
fit_start_time: 2016-01-04 fit_start_time: 2016-01-04
fit_end_time: 2025-09-01 fit_end_time: 2025-09-01
freq: day freq: day
lake_root: "{{ LAKE }}" lake_root: "{{ LAKE }}"
market: US market: US
label: "Ref($close,-6)/Ref($close,-1)-1" label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,{{ SP_FIELDS }}" feature_fields: "{{ FEATURES }}"
infer_processors: infer_processors:
- class: DropAllNaN - { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
kwargs: {} - { class: ProcessInf, kwargs: {} }
- class: ProcessInf - { class: CSRankNorm, kwargs: {} }
kwargs: {} - { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- class: CSRankNorm - { class: Fillna, kwargs: {} }
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments: segments:
train: [2016-01-04, 2025-09-01] train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03] valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10] test: [2026-01-04, 2026-08-10]
record: record:
- class: SignalRecord - { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
module_path: qlib.workflow.record_temp - { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord - class: PortAnaRecord
module_path: qlib.workflow.record_temp module_path: qlib.workflow.record_temp
kwargs: kwargs:
@@ -114,12 +81,7 @@ task:
strategy: strategy:
class: TopkDropoutStrategy class: TopkDropoutStrategy
module_path: qlib.contrib.strategy module_path: qlib.contrib.strategy
kwargs: kwargs: { signal: "<PRED>", topk: 10, n_drop: 2, only_tradable: true, risk_degree: 0.95 }
signal: "<PRED>"
topk: 10
n_drop: 2
only_tradable: true
risk_degree: 0.95
backtest: backtest:
start_time: 2026-01-04 start_time: 2026-01-04
end_time: 2026-08-10 end_time: 2026-08-10
+98
View File
@@ -0,0 +1,98 @@
# Compact stochastic feature set with reduced turnover: n_drop=1 instead of 2.
# Same setup as exp24 (compact baseline) but replacing the TopkDropout n_drop 2 with 1.
{%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set FEATURES = "$open,$high,$low,$close,$vwap,$volume,sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %}
qlib_init:
provider_uri: "{{ LAKE }}"
region: us
expression_cache: null
dataset_cache: null
calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: { lake_root: "{{ LAKE }}", market: US }
exp_manager:
class: MLflowExpManager
module_path: qlib.workflow.expm
kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-exp22-stochastic-general" }
task:
model:
class: RankICEnsembleLGBModel
module_path: tac_qlib.contrib.model.rank_ensemble
kwargs:
loss: mse
learning_rate: 0.02
num_leaves: 31
n_estimators: 3000
num_boost_round: 3000
early_stopping_rounds: 200
min_data_in_leaf: 20
lambda_l2: 0.5
colsample_bytree: 0.8
subsample: 0.8
subsample_freq: 1
reg_alpha: 0.1
reg_lambda: 1.0
seeds: "42,7,2026,99,123"
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: TACHandler
module_path: tac_qlib.contrib.data.handler
kwargs:
instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03
end_time: 2026-08-10
fit_start_time: 2016-01-04
fit_end_time: 2025-09-01
freq: day
lake_root: "{{ LAKE }}"
market: US
label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "{{ FEATURES }}"
infer_processors:
- { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- { class: ProcessInf, kwargs: {} }
- { class: CSRankNorm, kwargs: {} }
- { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- { class: Fillna, kwargs: {} }
segments:
train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10]
record:
- { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
- { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
- class: PortAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
config:
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs: { signal: "<PRED>", topk: 10, n_drop: 1, only_tradable: true, risk_degree: 0.95 }
backtest:
start_time: 2026-01-04
end_time: 2026-08-10
account: 1000000
benchmark: SPY
exchange_kwargs:
codes: "{{ UNIVERSE }}"
deal_price: $close
freq: day
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5.0
risk_analysis_freq: 1d
@@ -1,47 +1,28 @@
# ----------------------------------------------------------------------------- # 2-seed RankICEnsemble comparison on the compact stochastic set, n_drop=1.
# EXP 20 - R1: 2-seed ensemble (seeds 42,7), TopkDropout baseline. # Exact copy of exp26 (reference: expId=25 run=21afc6afdb674a399b59dd76c97628ce)
# # except seeds=42,7 and parallel=2. Test whether 5 seeds are needed vs 2.
# Runtime cut: 2 seeds instead of 5. Everything else identical to the reference
# (test 2026-01-04..2026-08-10, SPY, costs 5bp/15bp). Measures whether the
# 2-seed ensemble keeps the reference quality at ~2/5 the training time.
#
# Run:
# rd_run_workflow config_path=experiments/workflows/exp20-risk-limit-improve/r1_2seed.yaml \
# experiment_name=tac-rd-risk-limit
# -----------------------------------------------------------------------------
{%- set LAKE = TAC_LAKE_DIR %} {%- set LAKE = TAC_LAKE_DIR %}
{%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %} {%- set UNIVERSE = "SPY,QQQ,DIA,IWM,MDY,VTI,VOO,VEA,VWO,VT,EFA,EEM,TLT,IEF,SHY,AGG,BND,LQD,HYG,JNK,EMB,GLD,SLV,USO,UNG,DBA,DBC,XLK,XLF,XLE,XLV,XLI,XLY,XLP,XLU,XLB,XLRE,ARKK,SMH,SOXX,IBB,XBI,ITA,XAR,ICLN,TAN,FDN,IGV,ESPO,REM" %}
{%- set SP_FIELDS = "sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %} {%- set FEATURES = "$open,$high,$low,$close,$vwap,$volume,sp_ret,sp_jump_ratio,sp_jump_flag,sp_jump_tail,sp_max_move,sp_rv1,sp_rv5,sp_rv22,sp_vol_ratio_5_22,sp_vol_ratio_1_22,sp_trend_slope_5,sp_trend_slope_20,sp_trend_slope_60,sp_logp,sp_hurst_exponent,sp_sig_level1_lead,sp_sig_level1_lag,sp_sig_level2_lead_lag,sp_sig_level2_lag_lead" %}
qlib_init: qlib_init:
provider_uri: "{{ LAKE }}" provider_uri: "{{ LAKE }}"
region: us region: us
expression_cache: null expression_cache: null
dataset_cache: null dataset_cache: null
calendar_provider: calendar_provider:
class: tac_qlib.data.providers.LakeCalendarProvider class: tac_qlib.data.providers.LakeCalendarProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
instrument_provider: instrument_provider:
class: tac_qlib.data.providers.LakeInstrumentProvider class: tac_qlib.data.providers.LakeInstrumentProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US, markets: {} }
lake_root: "{{ LAKE }}"
market: US
markets: {}
feature_provider: feature_provider:
class: tac_qlib.data.providers.LakeFeatureProvider class: tac_qlib.data.providers.LakeFeatureProvider
kwargs: kwargs: { lake_root: "{{ LAKE }}", market: US }
lake_root: "{{ LAKE }}"
market: US
exp_manager: exp_manager:
class: MLflowExpManager class: MLflowExpManager
module_path: qlib.workflow.expm module_path: qlib.workflow.expm
kwargs: kwargs: { uri: "sqlite:///mlruns.db", default_exp_name: "tac-rd-exp28-2seed" }
uri: "sqlite:///{{ LAKE }}/mlruns.db"
default_exp_name: "tac-rd-risk-limit"
task: task:
model: model:
@@ -74,39 +55,28 @@ task:
kwargs: kwargs:
instruments: "{{ UNIVERSE }}" instruments: "{{ UNIVERSE }}"
start_time: 2015-01-03 start_time: 2015-01-03
end_time: 2026-08-14 end_time: 2026-08-10
fit_start_time: 2016-01-04 fit_start_time: 2016-01-04
fit_end_time: 2025-09-01 fit_end_time: 2025-09-01
freq: day freq: day
lake_root: "{{ LAKE }}" lake_root: "{{ LAKE }}"
market: US market: US
label: "Ref($close,-6)/Ref($close,-1)-1" label: "Ref($close,-6)/Ref($close,-1)-1"
feature_fields: "$open,$high,$low,$close,$vwap,$volume,{{ SP_FIELDS }}" feature_fields: "{{ FEATURES }}"
infer_processors: infer_processors:
- class: DropAllNaN - { class: DropAllNaN, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
kwargs: {} - { class: ProcessInf, kwargs: {} }
- class: ProcessInf - { class: CSRankNorm, kwargs: {} }
kwargs: {} - { class: ZScoreNorm, kwargs: { fit_start_time: "2016-01-04", fit_end_time: "2025-09-01" } }
- class: CSRankNorm - { class: Fillna, kwargs: {} }
kwargs: {}
- class: ZScoreNorm
kwargs: {}
- class: Fillna
kwargs: {}
segments: segments:
train: [2016-01-04, 2025-09-01] train: [2016-01-04, 2025-09-01]
valid: [2025-09-03, 2026-01-03] valid: [2025-09-03, 2026-01-03]
test: [2026-01-04, 2026-08-10] test: [2026-01-04, 2026-08-10]
record: record:
- class: SignalRecord - { class: SignalRecord, module_path: qlib.workflow.record_temp, kwargs: {} }
module_path: qlib.workflow.record_temp - { class: SigAnaRecord, module_path: qlib.workflow.record_temp, kwargs: { ana_long_short: true, ann_scaler: 252 } }
kwargs: {}
- class: SigAnaRecord
module_path: qlib.workflow.record_temp
kwargs:
ana_long_short: true
ann_scaler: 252
- class: PortAnaRecord - class: PortAnaRecord
module_path: qlib.workflow.record_temp module_path: qlib.workflow.record_temp
kwargs: kwargs:
@@ -114,12 +84,7 @@ task:
strategy: strategy:
class: TopkDropoutStrategy class: TopkDropoutStrategy
module_path: qlib.contrib.strategy module_path: qlib.contrib.strategy
kwargs: kwargs: { signal: "<PRED>", topk: 10, n_drop: 1, only_tradable: true, risk_degree: 0.95 }
signal: "<PRED>"
topk: 10
n_drop: 2
only_tradable: true
risk_degree: 0.95
backtest: backtest:
start_time: 2026-01-04 start_time: 2026-01-04
end_time: 2026-08-10 end_time: 2026-08-10