diff --git a/.gitignore b/.gitignore index cbfb3de..dc56bd6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ Thumbs.db # Logs *.log + +# Backtesting (scripts, data, results) +backtest/ diff --git a/config.py b/config.py index 3b6d867..63628e7 100755 --- a/config.py +++ b/config.py @@ -116,7 +116,7 @@ class SignalConfig: default_risk_pct: float = 1.0 default_rr_ratio: float = 2.0 atr_sl_multiplier: float = 1.5 - min_rr_ratio: float = 1.5 + min_rr_ratio: float = 1.2 @dataclass diff --git a/signal_aggregator.py b/signal_aggregator.py index 5f53078..8a9ca5b 100755 --- a/signal_aggregator.py +++ b/signal_aggregator.py @@ -309,7 +309,7 @@ class SignalAggregator: self._compute_final_signal(report) # ── 7. Trade setup for actionable signals ────────────────── - if report.signal_quality in ("A+", "A", "B"): + if report.signal_quality in ("A+", "A", "B", "C"): report.trade_setup = self._generate_trade_setup(report, pip_value) # ── 8. Reasons ───────────────────────────────────────────── @@ -366,6 +366,14 @@ class SignalAggregator: report.warnings.append(f"Upcoming event: {report.event_description}") confidence -= 15 + is_counter_trend = ( + (report.market_regime == "trending_up" and score < 0) or + (report.market_regime == "trending_down" and score > 0) + ) + if is_counter_trend: + confidence -= 15 + report.warnings.append("Counter-trend signal — reduced confidence") + if report.volatility_regime == "extreme": report.warnings.append("EXTREME volatility — wider stops needed, reduced position size") confidence -= 10 @@ -383,23 +391,23 @@ class SignalAggregator: report.warnings.append( f"NEAR EMA200 ({ema_dist_pct:.2f}%) — chop risk" ) - confidence -= 20 + confidence -= 10 elif ema_dist_pct > _hi: report.warnings.append( f"FAR FROM EMA200 ({ema_dist_pct:.2f}%) — over-extended" ) - confidence -= 20 + confidence -= 10 confidence = max(0.0, min(95.0, confidence)) # Final signal - if score >= 40 and confidence >= 65: + if score >= 35 and confidence >= 60: report.final_signal = SignalStrength.STRONG_BUY - elif score >= 15 and confidence >= 50: + elif score >= 12 and confidence >= 45: report.final_signal = SignalStrength.BUY - elif score <= -40 and confidence >= 65: + elif score <= -35 and confidence >= 60: report.final_signal = SignalStrength.STRONG_SELL - elif score <= -15 and confidence >= 50: + elif score <= -12 and confidence >= 45: report.final_signal = SignalStrength.SELL else: report.final_signal = SignalStrength.NEUTRAL @@ -409,15 +417,15 @@ class SignalAggregator: # Quality grade abs_score = abs(score) - if abs_score >= 50 and confidence >= 75 and report.mtf_agreement: + if abs_score >= 45 and confidence >= 70 and report.mtf_agreement: report.signal_quality = "A+" - elif abs_score >= 35 and confidence >= 65 and report.mtf_agreement: + elif abs_score >= 30 and confidence >= 60 and report.mtf_agreement: report.signal_quality = "A" - elif abs_score >= 25 and confidence >= 55: + elif abs_score >= 20 and confidence >= 50: report.signal_quality = "B" - elif abs_score >= 15 and confidence >= 45: + elif abs_score >= 12 and confidence >= 40: report.signal_quality = "C" - elif abs_score >= 10: + elif abs_score >= 8: report.signal_quality = "D" else: report.signal_quality = "F" @@ -446,23 +454,33 @@ class SignalAggregator: levels = report.primary_analysis.key_levels min_rr = self.config.signals.min_rr_ratio + mature_move = abs(report.final_score) > 30 + tp_mult = 1.5 if mature_move else 2.0 + tp_swing_cap = 2.5 if mature_move else 3.0 + if is_buy: swing_lows = sorted( [v for k, v in levels.items() if k.startswith("swing_low_") and v < price], reverse=True, ) if swing_lows: - sl_price = swing_lows[0] - atr * 0.5 + sl_price = swing_lows[0] - atr * 1.0 else: + sl_price = price - atr * 2.0 + if price - sl_price < atr * 1.5: sl_price = price - atr * 1.5 - if price - sl_price < atr * 1.0: - sl_price = price - atr * 1.0 swing_highs = sorted( v for k, v in levels.items() if k.startswith("swing_high_") and v > price + atr * 0.3 ) - tp1 = swing_highs[0] if len(swing_highs) >= 1 else price + atr * 2.0 - tp2 = swing_highs[1] if len(swing_highs) >= 2 else price + atr * 3.0 + if swing_highs and swing_highs[0] - price <= atr * tp_swing_cap: + tp1 = swing_highs[0] + else: + tp1 = price + atr * tp_mult + if len(swing_highs) >= 2 and swing_highs[1] - price <= atr * 5.0: + tp2 = swing_highs[1] + else: + tp2 = price + atr * (tp_mult + 1.5) sl_distance = price - sl_price direction = "BUY" @@ -472,18 +490,24 @@ class SignalAggregator: v for k, v in levels.items() if k.startswith("swing_high_") and v > price ) if swing_highs: - sl_price = swing_highs[0] + atr * 0.5 + sl_price = swing_highs[0] + atr * 1.0 else: + sl_price = price + atr * 2.0 + if sl_price - price < atr * 1.5: sl_price = price + atr * 1.5 - if sl_price - price < atr * 1.0: - sl_price = price + atr * 1.0 swing_lows = sorted( [v for k, v in levels.items() if k.startswith("swing_low_") and v < price - atr * 0.3], reverse=True, ) - tp1 = swing_lows[0] if len(swing_lows) >= 1 else price - atr * 2.0 - tp2 = swing_lows[1] if len(swing_lows) >= 2 else price - atr * 3.0 + if swing_lows and price - swing_lows[0] <= atr * tp_swing_cap: + tp1 = swing_lows[0] + else: + tp1 = price - atr * tp_mult + if len(swing_lows) >= 2 and price - swing_lows[1] <= atr * 5.0: + tp2 = swing_lows[1] + else: + tp2 = price - atr * (tp_mult + 1.5) sl_distance = sl_price - price direction = "SELL" diff --git a/telegram_bot.py b/telegram_bot.py index aec1e92..38cc340 100755 --- a/telegram_bot.py +++ b/telegram_bot.py @@ -296,7 +296,7 @@ def _format_scan_summary(reports: List[SignalReport], tf: Timeframe) -> str: ) lines.append("━━━━━━━━━━━━━━━━━━━━") - actionable = [r for r in reports if r.signal_quality in ("A+", "A", "B")] + actionable = [r for r in reports if r.signal_quality in ("A+", "A", "B", "C")] if actionable: names = ", ".join(r.display_name for r in actionable) lines.append(f"🔔 Actionable: {names}") @@ -390,7 +390,7 @@ async def _scan_timeframe( _update_level_cache(reports) summary = _format_scan_summary(reports, tf) - actionable = [r for r in reports if r.signal_quality in ("A+", "A", "B")] + actionable = [r for r in reports if r.signal_quality in ("A+", "A", "B", "C")] for chat_id in list(_autoscan_chats): try: