# strategies.py from __future__ import annotations import numpy as np import pandas as pd # ===== PRESET ===== BUY_TH = 0.55 SELL_TH = -0.55 REQUIRE_TREND_CONFLUENCE = True # EMA50 vs EMA200 musi się zgadzać z kierunkiem USE_ADX_IN_FILTER = True # używaj ADX w filtrze kierunku TREND_FILTER_ADX_MIN = 18.0 MTF_CONFLICT_DAMP = 0.6 # konflikt 1m vs 15m – osłab wynik # ===== FILTRY DODATKOWE ===== SESSION_FILTER = True SESS_UTC_START = 6 # handluj tylko 06:00–21:00 UTC (LON+NY) SESS_UTC_END = 21 VOL_FILTER = True MIN_ATR_PCT = 0.0005 # min ATR/price (0.05%) MAX_ATR_PCT = 0.02 # max ATR/price (2%) – unikaj paniki OVEREXT_COEF = 1.2 # nie wchodź, jeśli |close-EMA20| > 1.2*ATR def _resample_ohlc(df: pd.DataFrame, rule: str = "15T") -> pd.DataFrame: o = {"open":"first","high":"max","low":"min","close":"last"} return df[["open","high","low","close"]].resample(rule).agg(o).dropna(how="any") def _safe(df: pd.DataFrame, col: str, default=None): return float(df[col].iloc[-1]) if col in df.columns else (float(default) if default is not None else None) def _ema(series: pd.Series, span: int) -> float: return float(series.ewm(span=span, adjust=False).mean().iloc[-1]) def _atr_pct(df: pd.DataFrame, n: int = 14) -> float: if "atr" in df.columns: atr = float(df["atr"].iloc[-1]) else: h, l, c = df["high"], df["low"], df["close"] prev_c = c.shift(1) tr = pd.concat([h - l, (h - prev_c).abs(), (l - prev_c).abs()], axis=1).max(axis=1) atr = float(tr.ewm(span=n, adjust=False).mean().iloc[-1]) price = float(df["close"].iloc[-1]) return 0.0 if price <= 0 else atr / price def _in_session(df: pd.DataFrame) -> bool: ts = pd.Timestamp(df.index[-1]) try: hour = ts.tz_convert("UTC").hour if ts.tzinfo else ts.hour except Exception: hour = ts.hour return SESS_UTC_START <= hour <= SESS_UTC_END def _trend_score_1m(df: pd.DataFrame) -> float: s = 0.0 ema20 = _safe(df, "ema20", _ema(df["close"], 20)) ema50 = _safe(df, "ema50", _ema(df["close"], 50)) s += 0.5 if ema20 > ema50 else -0.5 macd = _safe(df, "macd", _ema(df["close"],12) - _ema(df["close"],26)) macd_sig = _safe(df, "macd_sig", macd) s += 0.3 if macd > macd_sig else -0.3 adx = _safe(df, "adx", None) if adx is not None: if adx >= 18: s += 0.2 elif adx <= 12: s -= 0.1 c = float(df["close"].iloc[-1]) st = _safe(df, "supertrend", c) s += 0.2 if c > st else -0.2 up = _safe(df, "bb_up", c + 1e9); dn = _safe(df, "bb_dn", c - 1e9) if c > up: s += 0.2 if c < dn: s -= 0.2 rsi = _safe(df, "rsi14", 50.0) if rsi >= 70: s -= 0.2 if rsi <= 30: s += 0.2 return s def _trend_score_15m(df_1m: pd.DataFrame) -> float: try: htf = _resample_ohlc(df_1m, "15T") if len(htf) < 30: return 0.0 close = htf["close"] ema20 = close.ewm(span=20, adjust=False).mean().iloc[-1] ema50 = close.ewm(span=50, adjust=False).mean().iloc[-1] macd_line = close.ewm(span=12, adjust=False).mean().iloc[-1] - close.ewm(span=26, adjust=False).mean().iloc[-1] macd_sig = pd.Series(close).ewm(span=9, adjust=False).mean().iloc[-1] s = (0.6 if ema20 > ema50 else -0.6) + (0.4 if macd_line > macd_sig else -0.4) return float(s) except Exception: return 0.0 def _hysteresis_map(score: float) -> int: if score >= BUY_TH: return 1 if score <= SELL_TH: return -1 return 0 def get_signal(df: pd.DataFrame) -> int: if len(df) < 200: return 0 # Filtry ex-ante if SESSION_FILTER and not _in_session(df): return 0 atrp = _atr_pct(df) if VOL_FILTER and not (MIN_ATR_PCT <= atrp <= MAX_ATR_PCT): return 0 s1 = _trend_score_1m(df) s15 = _trend_score_15m(df) score = 0.7 * s1 + 0.3 * s15 if (s1 > 0 and s15 < 0) or (s1 < 0 and s15 > 0): score *= MTF_CONFLICT_DAMP sig = _hysteresis_map(score) # Nie handluj pod prąd + nie gonić świecy if sig != 0 and REQUIRE_TREND_CONFLUENCE: ema50 = _safe(df, "ema50", _ema(df["close"], 50)) ema200 = _safe(df, "ema200", _ema(df["close"], 200)) adx = _safe(df, "adx", None) long_ok = (ema50 > ema200) and (not USE_ADX_IN_FILTER or adx is None or adx >= TREND_FILTER_ADX_MIN) short_ok = (ema50 < ema200) and (not USE_ADX_IN_FILTER or adx is None or adx >= TREND_FILTER_ADX_MIN) if sig == 1 and not long_ok: sig = 0 if sig == -1 and not short_ok: sig = 0 # za daleko od EMA20 -> poczekaj na pullback if sig != 0: ema20 = _safe(df, "ema20", _ema(df["close"], 20)) c = float(df["close"].iloc[-1]) # przy braku atr w df liczymy atrp już powyżej atr = atrp * c if c > 0 else 0.0 if atr > 0.0 and abs(c - ema20) > OVEREXT_COEF * atr: sig = 0 return int(sig)