From 8df42cd384cde7cbea480fbf8c587f6d154302f8 Mon Sep 17 00:00:00 2001 From: Harwinder Date: Wed, 6 May 2026 18:13:59 -0400 Subject: [PATCH] Fix Ichimoku look-ahead bias: lag Senkou Spans by 26 bars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Senkou Span A and B are plotted 26 periods ahead of calculation. Comparing current price to iloc[-1] cloud means comparing against the cloud 26 bars in the future — a textbook look-ahead bias that inflates signal grades in backtests. Now uses iloc[-(kijun+1)] so the cloud boundaries reflect where the cloud actually is at the current candle. TK cross (tenkan_val/kijun_val) stays at iloc[-1] — that is a current condition, not a projected one. Co-Authored-By: Claude Sonnet 4.6 --- engine.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/engine.py b/engine.py index 7fb242e..e44659d 100644 --- a/engine.py +++ b/engine.py @@ -544,11 +544,17 @@ class TechnicalEngine: tenkan_val = tenkan.iloc[-1] kijun_val = kijun.iloc[-1] - # Current (unshifted) cloud - sa = (tenkan.iloc[-1] + kijun.iloc[-1]) / 2 + # Senkou Spans are plotted 26 bars ahead of their calculation date, so + # the cloud boundaries at the *current* bar are the values computed 26 + # bars ago. Using iloc[-1] would compare price to the future cloud + # (look-ahead bias). iloc[-27] gives the correctly lagged cloud. + lag = c.ichimoku_kijun # 26 by default sb_raw = (high.rolling(c.ichimoku_senkou_b).max() + low.rolling(c.ichimoku_senkou_b).min()) / 2 - sb = sb_raw.iloc[-1] + if len(sb_raw) < lag + 1: + return + sa = (tenkan.iloc[-(lag + 1)] + kijun.iloc[-(lag + 1)]) / 2 + sb = sb_raw.iloc[-(lag + 1)] if pd.isna(sa) or pd.isna(sb) or pd.isna(tenkan_val) or pd.isna(kijun_val): return