File size: 2,205 Bytes
b54319d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | import re
from typing import Dict, Any
from src.data.manifest import ExperimentManifest, create_manifest
class LLMHypothesisParser:
"""
Typed LLM Guardrail Parser:
Converts natural language user research prompts (e.g. "Test whether 20-day momentum predicts 5-day returns among sector ETFs at 5 bps cost")
into validated, deterministic Pydantic ExperimentManifest configurations.
"""
@staticmethod
def parse_hypothesis(prompt: str) -> ExperimentManifest:
prompt_lower = prompt.lower()
# Extract lookback days
lookback_match = re.search(r"(\d+)[ -]?day momentum", prompt_lower)
lookback_days = int(lookback_match.group(1)) if lookback_match else 20
# Extract holding days
holding_match = re.search(r"(\d+)[ -]?day return", prompt_lower)
holding_days = int(holding_match.group(1)) if holding_match else 5
# Extract transaction cost bps
cost_match = re.search(r"(\d+)[ -]?bps", prompt_lower)
cost_bps = float(cost_match.group(1)) if cost_match else 5.0
# Universe defaults
universe = [
"SPY", "QQQ", "IWM", "MDY",
"XLK", "XLF", "XLE", "XLV", "XLY", "XLP", "XLI", "XLB", "XLU", "XLC", "XLRE",
"SMH", "XBI", "KRE", "ITB",
"TLT", "IEF", "SHY", "LQD", "HYG", "TIP",
"GLD", "SLV", "USO", "DBA",
"EEM", "EFA", "FXI", "EWJ",
"MTUM", "USMV", "QUAL", "IWD", "IWF"
]
manifest = create_manifest(
asset_universe=universe,
lookback_days=lookback_days,
holding_days=holding_days,
transaction_cost_bps=cost_bps,
feature_config={
"raw_mom": True,
"sortino_mom": "sortino" in prompt_lower or "volatility" in prompt_lower,
"residual_mom": "residual" in prompt_lower,
"volume_z": "volume" in prompt_lower,
},
model_config={
"type": "lightgbm" if "tree" in prompt_lower or "lightgbm" in prompt_lower else "ridge",
"max_depth": 3,
"learning_rate": 0.01
}
)
return manifest
|