94 lines
3.1 KiB
Python
94 lines
3.1 KiB
Python
from __future__ import annotations
|
|
from dataclasses import dataclass
|
|
import math
|
|
import numpy as np
|
|
import pandas as pd
|
|
from config import CFG
|
|
# zakładam, że masz te funkcje gdzieś u siebie:
|
|
# from indicators import ema, rsi, atr, macd, adx_val
|
|
from indicators import ema, rsi, atr, macd, adx_val
|
|
|
|
Signal = str # "BUY" | "SELL" | "NONE"
|
|
|
|
@dataclass
|
|
class Decision:
|
|
signal: Signal
|
|
sl: float | None
|
|
tp: float | None
|
|
rpu: float # risk per unit (odległość od SL)
|
|
|
|
def evaluate_signal(df: pd.DataFrame) -> Decision:
|
|
"""
|
|
Warunki (poluzowane / opcjonalne):
|
|
- (opcjonalnie) kierunek EMA200 (trend filter),
|
|
- ADX >= CFG.adx_min,
|
|
- ATR >= CFG.atr_min_frac_price * price,
|
|
- RSI nieprzeciążone (BUY < rsi_buy_max, SELL > rsi_sell_min),
|
|
- (opcjonalnie) MACD zgodny z kierunkiem.
|
|
Wyjście: SL = k_ATR, TP = RR * R (R = dystans do SL).
|
|
"""
|
|
if df is None or len(df) < CFG.history_min_bars:
|
|
return Decision("NONE", None, None, 0.0)
|
|
|
|
# Bezpieczne pobranie kolumn
|
|
cols = {c.lower(): c for c in df.columns}
|
|
C = pd.to_numeric(df[cols.get("close","Close")], errors="coerce").to_numpy(float)
|
|
H = pd.to_numeric(df[cols.get("high","High")], errors="coerce").to_numpy(float)
|
|
L = pd.to_numeric(df[cols.get("low","Low")], errors="coerce").to_numpy(float)
|
|
|
|
if len(C) == 0 or np.isnan(C[-1]):
|
|
return Decision("NONE", None, None, 0.0)
|
|
|
|
# wskaźniki
|
|
ema200 = ema(C, 200)
|
|
rsi14 = rsi(C, CFG.rsi_len)
|
|
atr14 = atr(H, L, C, 14)
|
|
macd_line, macd_sig, macd_hist = macd(C, 12, 26, 9)
|
|
adx14, pdi, mdi = adx_val(H, L, C, 14)
|
|
|
|
c = float(C[-1])
|
|
a = float(atr14[-1]) if math.isfinite(atr14[-1]) else 0.0
|
|
|
|
if not (math.isfinite(c) and math.isfinite(a)) or a <= 0:
|
|
return Decision("NONE", None, None, 0.0)
|
|
|
|
# filtry bazowe
|
|
adx_ok = float(adx14[-1]) >= CFG.adx_min
|
|
atr_ok = (a / max(1e-9, c)) >= CFG.atr_min_frac_price
|
|
|
|
# trend (opcjonalny)
|
|
trend_ok_buy = True if not CFG.require_trend else (c > float(ema200[-1]))
|
|
trend_ok_sell = True if not CFG.require_trend else (c < float(ema200[-1]))
|
|
|
|
# RSI „nieprzeciążone”
|
|
rsi_val = float(rsi14[-1])
|
|
rsi_ok_buy = rsi_val <= CFG.rsi_buy_max
|
|
rsi_ok_sell = rsi_val >= CFG.rsi_sell_min
|
|
|
|
# MACD (opcjonalny)
|
|
if CFG.use_macd_filter:
|
|
macd_buy = (macd_line[-1] > macd_sig[-1] and macd_hist[-1] > 0)
|
|
macd_sell = (macd_line[-1] < macd_sig[-1] and macd_hist[-1] < 0)
|
|
else:
|
|
macd_buy = macd_sell = True
|
|
|
|
signal: Signal = "NONE"
|
|
sl = tp = None
|
|
rpu = 0.0
|
|
|
|
if adx_ok and atr_ok:
|
|
if trend_ok_buy and rsi_ok_buy and macd_buy:
|
|
signal = "BUY"
|
|
sl = c - CFG.sl_atr_mult * a
|
|
risk = c - sl
|
|
tp = c + CFG.tp_rr * risk
|
|
rpu = max(1e-9, risk)
|
|
elif CFG.allow_short and trend_ok_sell and rsi_ok_sell and macd_sell:
|
|
signal = "SELL"
|
|
sl = c + CFG.sl_atr_mult * a
|
|
risk = sl - c
|
|
tp = c - CFG.tp_rr * risk
|
|
rpu = max(1e-9, risk)
|
|
|
|
return Decision(signal, sl, tp, rpu)
|