From 5efed1cd839ba7d567321c6e939cbfc13269f622 Mon Sep 17 00:00:00 2001 From: Marc Blatter Date: Mon, 27 Jul 2026 16:43:35 +0200 Subject: [PATCH] v0.5.1: RSI + Bollinger Bands HYBRID - Confidence filter (58%+ Win-Rate target) --- src/main_ml.py | 77 +++++++++++++++++++++++++++++++++----------- src/web_dashboard.py | 6 ++-- 2 files changed, 61 insertions(+), 22 deletions(-) diff --git a/src/main_ml.py b/src/main_ml.py index df3f7b5..8385392 100644 --- a/src/main_ml.py +++ b/src/main_ml.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Trading Bot v0.5 - Bollinger Bands Entry Signals (60%+ Win-Rate target)""" +"""Trading Bot v0.5.1 - RSI + Bollinger Bands HYBRID (Confidence Filter)""" import os, json, time, logging, sqlite3 from datetime import datetime from dotenv import load_dotenv @@ -25,8 +25,10 @@ STOP_LOSS_PCT = -0.008 CYCLE_SEC = 60 BB_PERIOD = 20 BB_STD_DEV = 2.0 +RSI_PERIOD = 14 +RSI_THRESHOLD = 35 -class TradingBotV05: +class TradingBotV051: def __init__(self): self.client = Client(API_KEY, API_SECRET) self.price_history = {sym: [] for sym in SYMBOLS} @@ -57,7 +59,29 @@ class TradingBotV05: except Exception as e: logger.warning(f"Recovery failed: {e}") - logger.info("[v0.5 INIT] Bollinger Bands Entry Signals (60%+ WR target)") + logger.info("[v0.5.1 INIT] RSI + Bollinger Bands HYBRID (Confidence Filter)") + + def calculate_rsi(self, prices): + """Calculate RSI (14-period standard)""" + if len(prices) < RSI_PERIOD + 1: + return None + + recent = prices[-RSI_PERIOD-1:] + deltas = [recent[i+1] - recent[i] for i in range(len(recent)-1)] + + gains = [d if d > 0 else 0 for d in deltas] + losses = [abs(d) if d < 0 else 0 for d in deltas] + + avg_gain = sum(gains) / RSI_PERIOD + avg_loss = sum(losses) / RSI_PERIOD + + if avg_loss == 0: + return 100.0 if avg_gain > 0 else 0.0 + + rs = avg_gain / avg_loss + rsi = 100 - (100 / (1 + rs)) + + return rsi def calculate_bollinger_bands(self, prices): """Calculate 20-EMA +/- 2*StdDev""" @@ -81,9 +105,13 @@ class TradingBotV05: return ema, upper_band, lower_band - def is_bollinger_breakout(self, symbol): - """Buy when price rebounds from lower band (crosses from below to above)""" - if len(self.price_history[symbol]) < BB_PERIOD + 1: + def is_hybrid_buy_signal(self, symbol): + """ + HYBRID Signal: Buy ONLY when BOTH conditions met: + 1. Price rebounds from lower Bollinger Band (BB Breakout) + 2. RSI < 35 (Oversolod confirmation) + """ + if len(self.price_history[symbol]) < max(BB_PERIOD + 1, RSI_PERIOD + 1): return False prices = self.price_history[symbol] @@ -95,13 +123,24 @@ class TradingBotV05: current_price = prices[-1] prev_price = prices[-2] - # Signal: Price was below lower band, now above lower band - breakout = (prev_price < lower and current_price > lower) + # Signal 1: Bollinger Breakout + bb_breakout = (prev_price < lower and current_price > lower) - if breakout: - logger.info(f"[SIGNAL-BB] {symbol} Bollinger Breakout (EMA={ema:.2f}, Lower={lower:.2f})") + # Signal 2: RSI Oversold + rsi = self.calculate_rsi(prices) + rsi_oversold = (rsi is not None and rsi < RSI_THRESHOLD) - return breakout + # HYBRID: Both must be true + hybrid_signal = bb_breakout and rsi_oversold + + if hybrid_signal: + logger.info(f"[SIGNAL-HYBRID] {symbol} RSI={rsi:.1f} + BB-Breakout (EMA={ema:.2f}, Lower={lower:.2f})") + elif bb_breakout and not rsi_oversold: + logger.debug(f"[FILTERED] {symbol} BB-Breakout but RSI={rsi:.1f} (need <{RSI_THRESHOLD})") + elif rsi_oversold and not bb_breakout: + logger.debug(f"[FILTERED] {symbol} RSI={rsi:.1f} but no BB-Breakout") + + return hybrid_signal def get_fresh_balance(self): try: @@ -132,7 +171,7 @@ class TradingBotV05: self.portfolio_value = portfolio_value self.max_trade_usdt = portfolio_value * MAX_POSITION_PCT - logger.info(f"[v0.5] USDT={usdt_available:.2f} | Portfolio={portfolio_value:.2f} | Max={self.max_trade_usdt:.2f}") + logger.info(f"[v0.5.1] USDT={usdt_available:.2f} | Portfolio={portfolio_value:.2f} | Max={self.max_trade_usdt:.2f}") return usdt_available, portfolio_value except: return 0, 0 @@ -192,7 +231,7 @@ class TradingBotV05: 'entry_time': datetime.now().isoformat() } - logger.info(f"[BUY-v0.5] {symbol} {qty} @ {price} (BB Breakout)") + logger.info(f"[BUY-v0.5.1] {symbol} {qty} @ {price} (RSI+BB HYBRID)") return order except: return None @@ -264,10 +303,10 @@ class TradingBotV05: if len(self.price_history[symbol]) > 100: self.price_history[symbol].pop(0) - # Find BEST Bollinger Bands signal + # Find HYBRID signal (RSI + BB both true) best_signal = None for symbol in SYMBOLS: - if symbol not in self.active_trades and self.is_bollinger_breakout(symbol): + if symbol not in self.active_trades and self.is_hybrid_buy_signal(symbol): best_signal = symbol break @@ -285,7 +324,7 @@ class TradingBotV05: 'portfolio_value': round(portfolio_val, 2), 'max_trade_usdt': round(self.max_trade_usdt, 2), 'timestamp': datetime.now().isoformat(), - 'version': 'v0.5-bollinger-bands' + 'version': 'v0.5.1-rsi-bb-hybrid' }, f) os.replace(temp, '/home/marc/bot-deploy/active_trades.json') except: @@ -294,18 +333,18 @@ class TradingBotV05: # Save P&L self.save_pnl_to_db(portfolio_val, usdt_free) - logger.info(f"[CYCLE-END] Trades={len(self.active_trades)} | Portfolio={portfolio_val:.2f} [v0.5]") + logger.info(f"[CYCLE-END] Trades={len(self.active_trades)} | Portfolio={portfolio_val:.2f} [v0.5.1]") logger.info("="*70) if __name__ == '__main__': import sys - bot = TradingBotV05() + bot = TradingBotV051() if len(sys.argv) > 1 and sys.argv[1] == '--once': bot.run_cycle() else: - logger.info("[v0.5 START] Trading Bot with Bollinger Bands signals...") + logger.info("[v0.5.1 START] Trading Bot with RSI + Bollinger Bands HYBRID signals...") while True: try: bot.run_cycle() diff --git a/src/web_dashboard.py b/src/web_dashboard.py index 0c9b895..7091fd3 100644 --- a/src/web_dashboard.py +++ b/src/web_dashboard.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Trading Bot Dashboard v0.5 (Bollinger Bands) - Auto-load 1-Day chart on page load""" +"""Trading Bot Dashboard v0.5.1 (RSI + Bollinger Bands HYBRID) - Auto-load 1-Day chart on page load""" import sqlite3 from fastapi import FastAPI from fastapi.responses import HTMLResponse @@ -110,7 +110,7 @@ async def dashboard(): -Trading Bot v0.5 +Trading Bot v0.5.1