Files

102 lines
3.5 KiB
Python

"""Ichimoku MS/W1/D1 alignment — a faithful port of CheckAlign() in
ichimoku-ms-w1-d1-ea.mq5.
A timeframe's last *completed* bar counts as aligned only when price AND the
chikou span both sit clearly above (bullish) or clearly below (bearish) the
tenkan-sen, kijun-sen, and kumo on that timeframe.
Bar/offset conventions (mirroring the EA):
* `df` is oldest -> newest; the LAST row is the last closed bar.
* The price-side tenkan/kijun are read at the last closed bar; the cloud
there is the Senkou value computed Kijun rows earlier (the spans are
plotted Kijun bars into the future).
* The chikou value equals the close of the last closed bar, plotted Kijun
bars back; the reference candle and tenkan/kijun are read there, and the
cloud at that position another Kijun rows further back.
"""
import pandas as pd
import config
def compute_levels(df):
high = df["high"]
low = df["low"]
tenkan = (high.rolling(config.TENKAN).max() + low.rolling(config.TENKAN).min()) / 2.0
kijun = (high.rolling(config.KIJUN).max() + low.rolling(config.KIJUN).min()) / 2.0
senkou_a = (tenkan + kijun) / 2.0
senkou_b = (high.rolling(config.SENKOU_B).max() + low.rolling(config.SENKOU_B).min()) / 2.0
return tenkan, kijun, senkou_a, senkou_b
def align_last_bar(df):
"""Return 1 (bullish), -1 (bearish), or 0 (not aligned) for the last bar."""
n = len(df)
# Enough bars for every level read below: the chikou-side cloud is read
# 2*KIJUN rows before the last closed bar, and Senkou B there needs its
# own SENKOU_B-bar window before that.
if n < config.SENKOU_B + 2 * config.KIJUN + 1:
return 0
tenkan, kijun, senkou_a, senkou_b = compute_levels(df)
# last closed bar (oldest->newest: the final row)
i0 = n - 1
close_p = float(df["close"].iloc[i0])
# The Senkou spans are plotted Kijun bars into the future, so the cloud
# shown under the last closed bar is the Senkou value computed Kijun bars
# earlier (mirrors the EA's CopyBuffer(..., sh + Kijun) read).
iPriceCloud = n - 1 - config.KIJUN
above = (
close_p > tenkan.iloc[i0]
and close_p > kijun.iloc[i0]
and close_p > max(senkou_a.iloc[iPriceCloud], senkou_b.iloc[iPriceCloud])
)
below = (
close_p < tenkan.iloc[i0]
and close_p < kijun.iloc[i0]
and close_p < min(senkou_a.iloc[iPriceCloud], senkou_b.iloc[iPriceCloud])
)
if not above and not below:
return 0
# chikou position: last closed bar, Kijun bars back
i1 = n - 1 - config.KIJUN
# Cloud at the chikou's plotted position: another Kijun bars further back.
i2 = n - 1 - 2 * config.KIJUN
chik = close_p # the chikou span for the last closed bar is its close
if above:
if (
chik > df["high"].iloc[i1]
and chik > tenkan.iloc[i1]
and chik > kijun.iloc[i1]
and chik > max(senkou_a.iloc[i2], senkou_b.iloc[i2])
):
return 1
return 0
if below:
if (
chik < df["low"].iloc[i1]
and chik < tenkan.iloc[i1]
and chik < kijun.iloc[i1]
and chik < min(senkou_a.iloc[i2], senkou_b.iloc[i2])
):
return -1
return 0
return 0
def alignment_summary(df):
"""Per-timeframe direction plus the last close, for alert messages."""
n = len(df)
if n < config.KIJUN + 1:
return {"dir": 0, "close": None, "levels": {}}
dirn = align_last_bar(df)
return {"dir": dirn, "close": float(df["close"].iloc[-1]), "levels": {}}