# main.py - Dynamic Stock Input + Test Alert Dashboard import yfinance as yf import pandas as pd import ta import requests import time import re from datetime import datetime from flask import Flask, request, render_template_string, flash, get_flashed_messages from threading import Thread import logging # === CONFIGURE LOGGING === logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S') # === CONFIGURATION === DISCORD_WEBHOOK_URL = "https://discord.com/api/webhooks/1402083180785176637/hm5Efv4z3-OwCJpTJWQB78bvUixMIItPKiPdsUCjmmakr4MXOk_thRk5aBdn3uswIGVV" # โ† REPLACE SECRET_KEY = "bigly#Redis#NodJSforWin@GOLangSucks" # โ† CHANGE THIS! Use a strong key ATR_PERIOD = 7 RSI_PERIOD = 7 SUPER_TREND_FACTOR = 3 INTERVAL = "5m" # ๐Ÿ“ Runtime Data monitored_stocks = ["SPY", "QQQ"] # Default list alert_history = [] # ๐Ÿ” Thread Locks import threading stocks_lock = threading.Lock() history_lock = threading.Lock() # === Validate Stock Symbol === def is_valid_symbol(symbol): """Allow only 1โ€“5 uppercase letters (e.g., AAPL, MSFT)""" return bool(re.fullmatch(r"^[A-Z]{1,5}$", symbol.strip())) # === Is Market Open? (US: 9:30 AM - 4:00 PM EST) === def is_market_open(): from pytz import timezone from datetime import time as dt_time est = timezone('US/Eastern') now = datetime.now(est) if now.weekday() >= 5: return False market_open_time = dt_time(9, 30) market_close_time = dt_time(16, 0) return market_open_time <= now.time() <= market_close_time # === SuperTrend Calculation === def calculate_supertrend(df, factor=SUPER_TREND_FACTOR, period=ATR_PERIOD): data = df.copy() hl2 = (data['high'] + data['low']) / 2 atr = ta.volatility.average_true_range(data['high'], data['low'], data['close'], window=period) data['upper_band'] = hl2 + (factor * atr) data['lower_band'] = hl2 - (factor * atr) data['supertrend_trend'] = 1 for i in range(1, len(data)): prev_trend = data['supertrend_trend'].iloc[i - 1] curr_close = data['close'].iloc[i] if prev_trend == 1: if curr_close < data['lower_band'].iloc[i - 1]: data.loc[data.index[i], 'supertrend_trend'] = -1 else: data.loc[data.index[i], 'lower_band'] = max(data['lower_band'].iloc[i], data['lower_band'].iloc[i - 1]) else: if curr_close > data['upper_band'].iloc[i - 1]: data.loc[data.index[i], 'supertrend_trend'] = 1 else: data.loc[data.index[i], 'upper_band'] = min(data['upper_band'].iloc[i], data['upper_band'].iloc[i - 1]) data.ffill(inplace=True) return data # === Send to Discord === def send_discord_alert(symbol, signal, price, rsi): if not DISCORD_WEBHOOK_URL: logging.warning("Discord webhook URL not set. Skipping alert.") return color = 0x00ff00 if signal == "BUY" else 0xff0000 embed = { "title": f"{signal} Signal", "description": f"**{symbol}**", "color": color, "fields": [ {"name": "Price", "value": f"${price:.2f}", "inline": True}, {"name": f"RSI ({RSI_PERIOD})", "value": f"{rsi:.2f}", "inline": True}, {"name": "Interval", "value": INTERVAL, "inline": True}, ], "timestamp": datetime.utcnow().isoformat(), "footer": {"text": "SuperTrend ATR+RSI Bot"} } try: response = requests.post(DISCORD_WEBHOOK_URL, json={"embeds": [embed]}) if response.status_code == 204: logging.info(f"โœ… {signal} alert sent for {symbol} at ${price:.2f}") else: logging.error(f"โŒ Discord API error: {response.status_code} - {response.text}") except Exception as e: logging.error(f"โŒ Failed to send alert: {e}") # === Send Test Message === def send_test_alert(): if not DISCORD_WEBHOOK_URL: logging.error("โŒ Test alert failed: Discord Webhook URL is not set.") return False embed = { "title": "โœ… Test Alert", "description": "**Your bot is working perfectly!**", "color": 0x3498db, "fields": [ {"name": "Status", "value": "All systems operational", "inline": False}, {"name": "Time", "value": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "inline": False}, ], "footer": {"text": "SuperTrend Signal Bot"} } try: response = requests.post(DISCORD_WEBHOOK_URL, json={"embeds": [embed]}) return response.status_code == 204 except Exception as e: logging.error(f"โŒ Test alert failed: {e}") return False # === Check One Stock === def check_stock(symbol): try: if not is_market_open(): return ticker = yf.Ticker(symbol) data = ticker.history(period="2d", interval=INTERVAL) data.columns = data.columns.str.lower() if data.empty or len(data) < RSI_PERIOD + 10: logging.warning(f"Insufficient data for {symbol} on {INTERVAL} interval. Skipping.") return rsi_indicator = ta.momentum.RSIIndicator(data['close'], window=RSI_PERIOD) data['rsi'] = rsi_indicator.rsi() data = calculate_supertrend(data, factor=SUPER_TREND_FACTOR, period=ATR_PERIOD) data.dropna(inplace=True) if len(data) < 2: logging.warning(f"Not enough data for {symbol} after indicator calculation.") return latest = data.iloc[-1] previous = data.iloc[-2] current_trend = latest['supertrend_trend'] prev_trend = previous['supertrend_trend'] price = latest['close'] rsi_val = latest['rsi'] # ADDED THIS LINE TO PROVIDE A LIVE HEARTBEAT FOR EACH STOCK logging.info(f"{symbol}: Price=${price:.2f}, RSI={rsi_val:.2f}, Trend={int(current_trend)}") signal = None if current_trend == 1 and prev_trend == -1: signal = "BUY" elif current_trend == -1 and prev_trend == 1: signal = "SELL" if signal: # --- TREND FILTER LOGIC --- logging.info(f"Potential {signal} for {symbol}. Checking 4-hour trend filter...") long_term_data = ticker.history(period="1mo", interval="4h") long_term_data.columns = long_term_data.columns.str.lower() if len(long_term_data) < 50: logging.warning(f"Not enough 4h data for {symbol} to apply trend filter. Allowing signal.") else: long_term_data['ma50'] = long_term_data['close'].rolling(window=50).mean() last_long_term_price = long_term_data['close'].iloc[-1] last_ma_value = long_term_data['ma50'].iloc[-1] if signal == "BUY" and last_long_term_price < last_ma_value: logging.info(f"BUY signal for {symbol} blocked by 4-hour trend filter (price is below MA).") return if signal == "SELL" and last_long_term_price > last_ma_value: logging.info(f"SELL signal for {symbol} blocked by 4-hour trend filter (price is above MA).") return logging.info(f"โœ… Signal for {symbol} passed trend filter. Sending alert.") # --- END OF TREND FILTER LOGIC --- alert = { "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "symbol": symbol, "signal": signal, "price": f"${price:.2f}", "rsi": f"{rsi_val:.2f}", "interval": INTERVAL } with history_lock: alert_history.insert(0, alert) if len(alert_history) > 50: alert_history.pop() send_discord_alert(symbol, signal, price, rsi_val) except Exception as e: logging.error(f"๐Ÿ’ฅ Error processing {symbol}: {e}") # === Scanner Loop === def run_scanner(): logging.info("๐Ÿš€ Scanner started...") while True: with stocks_lock: current_stocks = monitored_stocks.copy() if not current_stocks: logging.info("No stocks to monitor. Waiting...") elif not is_market_open(): logging.info("Market is currently closed. Scanner is sleeping.") else: logging.info(f"Market is open. Checking {len(current_stocks)} stocks...") for symbol in current_stocks: check_stock(symbol) logging.info("Finished checking all stocks for this interval.") time.sleep(300) # === FLASK WEB APP (HTML is unchanged) === app = Flask(__name__) app.secret_key = SECRET_KEY HTML_TEMPLATE = """ Stock Signal Dashboard

๐Ÿ“ˆ SuperTrend ATR+RSI Dashboard

{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, msg in messages %}
{{ msg }}
{% endfor %} {% endif %} {% endwith %}

โž• Add Stock to Watchlist

๐Ÿ“‹ Monitored Stocks

{% if stocks %} {% for symbol in stocks %} {% endfor %}
SymbolAction
{{ symbol }}
{% else %}

๐Ÿšซ No stocks are being monitored. Add one above to get started!

{% endif %}

๐Ÿงช Test Discord Webhook

๐Ÿ”” Recent Alert History

{% if alerts %} {% for alert in alerts %} {% endfor %}
TimeSymbolSignalPriceRSIInterval
{{ alert.time }} {{ alert.symbol }} {{ alert.signal }} {{ alert.price }} {{ alert.rsi }} {{ alert.interval }}
{% else %}

No alerts generated yet. Waiting for a signal...

{% endif %}
""" @app.route('/', methods=['GET']) def home(): with stocks_lock: stocks = sorted(monitored_stocks.copy()) with history_lock: alerts = alert_history.copy() now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") return render_template_string(HTML_TEMPLATE, stocks=stocks, alerts=alerts, now=now, interval=INTERVAL) @app.route('/add_stock', methods=['POST']) def add_stock(): symbol = request.form.get('symbol', '').strip().upper() key = request.form.get('key', '').strip() if key != SECRET_KEY: flash("๐Ÿ”’ Access denied: Invalid secret key.", "error") elif not symbol: flash("โš ๏ธ Please enter a stock symbol.", "error") elif not is_valid_symbol(symbol): flash(f"โŒ Invalid symbol: '{symbol}'. Use 1โ€“5 uppercase letters.", "error") else: with stocks_lock: if symbol in monitored_stocks: flash(f"๐ŸŸก '{symbol}' is already being monitored.", "info") else: monitored_stocks.append(symbol) flash(f"โœ… Successfully added '{symbol}' to the watchlist!", "success") logging.info(f"โž• User added stock: {symbol}") return home() @app.route('/remove_stock', methods=['POST']) def remove_stock(): symbol = request.form.get('symbol', '').strip().upper() key = request.form.get('key', '').strip() if key != SECRET_KEY: flash("๐Ÿ”’ Access denied: Invalid secret key.", "error") else: with stocks_lock: if symbol not in monitored_stocks: flash(f"๐Ÿ” '{symbol}' is not on the list.", "info") else: monitored_stocks.remove(symbol) flash(f"๐Ÿ—‘๏ธ Removed '{symbol}' from monitoring.", "success") logging.info(f"โž– User removed stock: {symbol}") return home() @app.route('/test_discord', methods=['POST']) def test_discord(): key = request.form.get('key', '').strip() if key != SECRET_KEY: flash("๐Ÿ”’ Access denied: Invalid secret key.", "error") else: success = send_test_alert() if success: flash("โœ… Test message sent to Discord! Check your server.", "success") else: flash("โŒ Failed to send test message. Check your Discord Webhook URL.", "error") return home() def start(): scanner_thread = Thread(target=run_scanner, daemon=True) scanner_thread.start() logging.info("๐ŸŒ Starting Flask web server...") app.run(host='0.0.0.0', port=8080) if __name__ == '__main__': start()