diff --git a/signal_aggregator.py b/signal_aggregator.py index 8a9ca5b..2af45ff 100755 --- a/signal_aggregator.py +++ b/signal_aggregator.py @@ -470,19 +470,22 @@ class SignalAggregator: if price - sl_price < atr * 1.5: sl_price = price - atr * 1.5 + sl_distance = price - sl_price + swing_highs = sorted( v for k, v in levels.items() if k.startswith("swing_high_") and v > price + atr * 0.3 ) - if swing_highs and swing_highs[0] - price <= atr * tp_swing_cap: + if (swing_highs + and swing_highs[0] - price <= atr * tp_swing_cap + and swing_highs[0] - price >= sl_distance * min_rr): tp1 = swing_highs[0] else: - tp1 = price + atr * tp_mult + tp1 = price + max(atr * tp_mult, sl_distance * min_rr) 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) + tp2 = price + max(atr * (tp_mult + 1.5), sl_distance * min_rr * 1.5) - sl_distance = price - sl_price direction = "BUY" entry, sl = price, sl_price else: @@ -496,27 +499,30 @@ class SignalAggregator: if sl_price - price < atr * 1.5: sl_price = price + atr * 1.5 + sl_distance = sl_price - price + swing_lows = sorted( [v for k, v in levels.items() if k.startswith("swing_low_") and v < price - atr * 0.3], reverse=True, ) - if swing_lows and price - swing_lows[0] <= atr * tp_swing_cap: + if (swing_lows + and price - swing_lows[0] <= atr * tp_swing_cap + and price - swing_lows[0] >= sl_distance * min_rr): tp1 = swing_lows[0] else: - tp1 = price - atr * tp_mult + tp1 = price - max(atr * tp_mult, sl_distance * min_rr) 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) + tp2 = price - max(atr * (tp_mult + 1.5), sl_distance * min_rr * 1.5) - sl_distance = sl_price - price direction = "SELL" entry, sl = price, sl_price tp1_distance = abs(tp1 - entry) rr = tp1_distance / sl_distance if sl_distance > 0 else 0 - if rr < min_rr: + if rr < min_rr - 1e-9: return None risk_pips = sl_distance / pip_value diff --git a/telegram_bot.py b/telegram_bot.py index 9b18b13..6f60013 100755 --- a/telegram_bot.py +++ b/telegram_bot.py @@ -5,23 +5,25 @@ ForexBot2 — Telegram Bot Async Telegram interface with candle-boundary auto-scanner and real-time WebSocket level-breach monitor. -Manual scan: 4 pairs × 4 timeframes via inline buttons. +Manual scan: 4 pairs × 3 timeframes (M15/H1/H4) via inline buttons. Auto-scan: Starts automatically on boot if TELEGRAM_CHAT_ID is set. /autoscan toggles for additional chats or to stop. - M5 fires at :00/:05/:10/… + 10s buffer. + M5 fires at :00/:05/:10/… + 10s buffer (data only, no signals). M15 fires at :00/:15/:30/:45 + 10s buffer. H1 fires at :00 + 10s buffer. H4 fires at :00 every 4 hours + 10s buffer. -WebSocket: Streams live ticks from TwelveData for all 4 pairs. +WebSocket: Streams live ticks from TwelveData for EUR/USD & XAU/USD + (Grow plan supports only these 2 WS symbols). When price crosses a key structure level (swing S/R, BB, EMA200) cached from the last boundary scan, triggers an immediate M15 scan and sends a Level Break Alert. 5-minute cooldown per pair prevents spam. + GBP/USD & USD/JPY are monitored via REST scans only. -Plan: TwelveData Pro (55 credits/min, 1500 WS credits). +Plan: TwelveData Grow (55 credits/min, 8 trial WS credits). Credit budget (worst case, 4-hour boundary): Unique fetches: 4 pairs × ~6 TFs = ~24 calls (rest cached) → fits in 55/min. - WebSocket: 4 symbols = 4 credits (one-time on connect). + WebSocket: 2 symbols = 2 credits (one-time on connect). Level breach scans: ~3 REST calls each, ~10/hour worst case. Usage: @@ -84,8 +86,6 @@ CORRELATION_GROUPS = { _WS_TO_PAIR = { "EUR/USD": EURUSD, - "GBP/USD": GBPUSD, - "USD/JPY": USDJPY, "XAU/USD": XAUUSD, } _WS_SYMBOLS = list(_WS_TO_PAIR.keys()) @@ -97,19 +97,15 @@ _LEVEL_KEYS = ( ) SCAN_BUTTONS = [ - {"cb": "scan_eurusd_m5", "symbol": EURUSD, "tf": Timeframe.M5, "label": "EUR/USD 5M"}, {"cb": "scan_eurusd_m15", "symbol": EURUSD, "tf": Timeframe.M15, "label": "EUR/USD 15M"}, {"cb": "scan_eurusd_h1", "symbol": EURUSD, "tf": Timeframe.H1, "label": "EUR/USD 1H"}, {"cb": "scan_eurusd_h4", "symbol": EURUSD, "tf": Timeframe.H4, "label": "EUR/USD 4H"}, - {"cb": "scan_usdjpy_m5", "symbol": USDJPY, "tf": Timeframe.M5, "label": "USD/JPY 5M"}, {"cb": "scan_usdjpy_m15", "symbol": USDJPY, "tf": Timeframe.M15, "label": "USD/JPY 15M"}, {"cb": "scan_usdjpy_h1", "symbol": USDJPY, "tf": Timeframe.H1, "label": "USD/JPY 1H"}, {"cb": "scan_usdjpy_h4", "symbol": USDJPY, "tf": Timeframe.H4, "label": "USD/JPY 4H"}, - {"cb": "scan_gbpusd_m5", "symbol": GBPUSD, "tf": Timeframe.M5, "label": "GBP/USD 5M"}, {"cb": "scan_gbpusd_m15", "symbol": GBPUSD, "tf": Timeframe.M15, "label": "GBP/USD 15M"}, {"cb": "scan_gbpusd_h1", "symbol": GBPUSD, "tf": Timeframe.H1, "label": "GBP/USD 1H"}, {"cb": "scan_gbpusd_h4", "symbol": GBPUSD, "tf": Timeframe.H4, "label": "GBP/USD 4H"}, - {"cb": "scan_xauusd_m5", "symbol": XAUUSD, "tf": Timeframe.M5, "label": "XAU/USD 5M"}, {"cb": "scan_xauusd_m15", "symbol": XAUUSD, "tf": Timeframe.M15, "label": "XAU/USD 15M"}, {"cb": "scan_xauusd_h1", "symbol": XAUUSD, "tf": Timeframe.H1, "label": "XAU/USD 1H"}, {"cb": "scan_xauusd_h4", "symbol": XAUUSD, "tf": Timeframe.H4, "label": "XAU/USD 4H"}, @@ -390,6 +386,10 @@ async def _scan_timeframe( _update_level_cache(reports) + if tf == Timeframe.M5: + logger.info("M5 scan: data analysis only — no Telegram broadcast") + return + actionable = [r for r in reports if r.signal_quality in ("A+", "A", "B", "C")] if not actionable: @@ -720,7 +720,14 @@ async def cb_scan(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: # ── Boot-time auto-start ────────────────────────────────────────────── async def _post_init(app: Application) -> None: - """Start the candle-boundary scheduler on boot if CHAT_ID is set.""" + """Register command menu and start scheduler on boot.""" + await app.bot.set_my_commands([ + ("start", "Show scan buttons"), + ("autoscan", "Toggle auto-scan on/off"), + ("status", "Bot status, WS connection, live prices"), + ("help", "All commands"), + ]) + if not CHAT_ID: logger.info("TELEGRAM_CHAT_ID not set — auto-scan disabled on boot. " "Use /autoscan in chat to enable.") diff --git a/ws_twelvedata.py b/ws_twelvedata.py index a931111..cfc59c0 100755 --- a/ws_twelvedata.py +++ b/ws_twelvedata.py @@ -8,7 +8,7 @@ from datetime import datetime, timezone API_KEY = "f563b22ffdfd4924955decbe1a8c8701" WS_URL = f"wss://ws.twelvedata.com/v1/quotes/price?apikey={API_KEY}" -SYMBOLS = ["EUR/USD", "GBP/USD", "USD/JPY", "XAU/USD"] +SYMBOLS = ["EUR/USD", "XAU/USD"] async def stream_ticks():