{"record":{"id":"edac9b43118c2d9b","repo":"HKUDS/Vibe-Trading","slug":"delay-requires-n-1-lookahead-ban-edac9b","errorCode":null,"errorMessage":"delay requires n >= 1 (lookahead ban)","messagePattern":"delay requires n >= 1 \\(lookahead ban\\)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/factors/zoo/alpha101/alpha_030.py","lineNumber":62,"sourceCode":"    'extras_required': [],\n    'requires_sector': False,\n    'universe': ['equity_us', 'equity_in', 'equity_kr'],\n    'frequency': ['1D'],\n    'decay_horizon': 5,\n    'min_warmup_bars': 20,\n    'notes': '',\n}\n\n\ndef _rolling_sum(df: pd.DataFrame, n: int) -> pd.DataFrame:\n    \"\"\"Rolling window sum; warmup -> NaN.\"\"\"\n    return df.rolling(window=n, min_periods=n).sum()\n\n\ndef _delay(df: pd.DataFrame, n: int) -> pd.DataFrame:\n    \"\"\"Backward shift by n (lookahead-safe; n>=1 required).\"\"\"\n    if n < 1:\n        raise ValueError(\"delay requires n >= 1 (lookahead ban)\")\n    return df.shift(n)\n\n\ndef compute(panel: dict) -> pd.DataFrame:\n    \"\"\"Compute the alpha on the OHLCV+ panel and return a wide DataFrame.\"\"\"\n    close = panel[\"close\"]\n    volume = panel[\"volume\"]\n\n\n    # Helper aliases (local closures keep the file standalone & purity-safe).\n    rolling_sum = _rolling_sum\n    delay = _delay\n    s = np.sign(close - delay(close, 1)) + np.sign(delay(close, 1) - delay(close, 2)) + np.sign(delay(close, 2) - delay(close, 3))\n    out = safe_div((1.0 - rank(s)) * rolling_sum(volume, 5), rolling_sum(volume, 20))\n    return out\n","sourceCodeStart":44,"sourceCodeEnd":78,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/factors/zoo/alpha101/alpha_030.py#L44-L78","documentation":"Shared helper _delay in alpha_030.py guards against lookahead bias by refusing n < 1: delay of 0 would use the current bar's value, leaking future information into the alpha computation. It raises ValueError before calling df.shift(n). Any caller passing 0 or a negative window triggers it immediately.","triggerScenarios":"compute(panel) in alpha_030 ultimately calls _delay(close, n) with n <= 0 — e.g. a parameterized lookahead/delay constant misconfigured as 0, or an expression like _delay(df, n - 1) evaluated with n = 1.","commonSituations":"Copying the WorldQuant Alpha101 formula (#30 uses a delay/decay chain) and translating 'delay(x, 0)' literally; editing window constants; generating alphas from config where the delay field is optional and defaults to 0.","solutions":["Open agent/src/factors/zoo/alpha101/alpha_030.py, find every _delay(...) call site and confirm each literal is >= 1","If the formula truly wants the current bar, use the operand directly (e.g. `close`) instead of _delay(close, 0)","If n comes from config, validate/coerce it: n = max(1, int(n)) or reject the config early with a clear message","Re-run the alpha's test to confirm no other windows in the chain were corrupted"],"exampleFix":"// before\npart = _delay(close, 0) * _decay(rank(close), 5)\n// after\npart = close * _decay(rank(close), 5)","handlingStrategy":"validation","validationCode":"def safe_delay(df, n):\n    n = int(n)\n    if n < 1:\n        raise ValueError(f\"delay window must be >= 1, got {n}\")\n    return df.shift(n)\n\n# or, if 'no lag' is a valid config value:\nn = n if n >= 1 else None\nout = df if n is None else safe_delay(df, n)","typeGuard":"def is_valid_delay(n: int) -> bool:\n    return isinstance(n, int) and not isinstance(n, bool) and n >= 1","tryCatchPattern":null,"preventionTips":["Never call _delay with 0 — use the operand directly for current-bar access","Validate config-driven lag parameters (>= 1) before running the alpha zoo","Pin lag constants in unit tests so refactors can't silently change them"],"tags":["pandas","alpha101","lookahead-bias","input-validation"],"backgroundTag":"invalid-window-argument","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}