mosood commited on
Commit
bef5232
Β·
verified Β·
1 Parent(s): f562f35

Create bbb.py

Browse files
Files changed (1) hide show
  1. bbb.py +428 -0
bbb.py ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # main.py - Dynamic Stock Input + Test Alert Dashboard
2
+ import yfinance as yf
3
+ import pandas as pd
4
+ import ta
5
+ import requests
6
+ import time
7
+ import re
8
+ from datetime import datetime
9
+ from flask import Flask, request, render_template_string, flash, get_flashed_messages
10
+ from threading import Thread
11
+ import logging
12
+
13
+ # === CONFIGURE LOGGING ===
14
+ logging.basicConfig(level=logging.INFO,
15
+ format='%(asctime)s [%(levelname)s] %(message)s',
16
+ datefmt='%Y-%m-%d %H:%M:%S')
17
+
18
+ # === CONFIGURATION ===
19
+ DISCORD_WEBHOOK_URL = "https://discord.com/api/webhooks/1402083180785176637/hm5Efv4z3-OwCJpTJWQB78bvUixMIItPKiPdsUCjmmakr4MXOk_thRk5aBdn3uswIGVV" # ← REPLACE
20
+ SECRET_KEY = "bigly#Redis#NodJSforWin@GOLangSucks" # ← CHANGE THIS! Use a strong key
21
+ ATR_PERIOD = 7
22
+ RSI_PERIOD = 7
23
+ SUPER_TREND_FACTOR = 3
24
+ INTERVAL = "5m"
25
+
26
+ # πŸ“ Runtime Data
27
+ monitored_stocks = ["SPY", "QQQ"] # Default list
28
+ alert_history = []
29
+
30
+ # πŸ” Thread Locks
31
+ import threading
32
+
33
+ stocks_lock = threading.Lock()
34
+ history_lock = threading.Lock()
35
+
36
+
37
+ # === Validate Stock Symbol ===
38
+ def is_valid_symbol(symbol):
39
+ """Allow only 1–5 uppercase letters (e.g., AAPL, MSFT)"""
40
+ return bool(re.fullmatch(r"^[A-Z]{1,5}$", symbol.strip()))
41
+
42
+
43
+ # === Is Market Open? (US: 9:30 AM - 4:00 PM EST) ===
44
+ def is_market_open():
45
+ from pytz import timezone
46
+ from datetime import time as dt_time
47
+ est = timezone('US/Eastern')
48
+ now = datetime.now(est)
49
+
50
+ if now.weekday() >= 5:
51
+ return False
52
+
53
+ market_open_time = dt_time(9, 30)
54
+ market_close_time = dt_time(16, 0)
55
+ return market_open_time <= now.time() <= market_close_time
56
+
57
+
58
+ # === SuperTrend Calculation ===
59
+ def calculate_supertrend(df, factor=SUPER_TREND_FACTOR, period=ATR_PERIOD):
60
+ data = df.copy()
61
+ hl2 = (data['high'] + data['low']) / 2
62
+ atr = ta.volatility.average_true_range(data['high'], data['low'], data['close'], window=period)
63
+ data['upper_band'] = hl2 + (factor * atr)
64
+ data['lower_band'] = hl2 - (factor * atr)
65
+ data['supertrend_trend'] = 1
66
+ for i in range(1, len(data)):
67
+ prev_trend = data['supertrend_trend'].iloc[i - 1]
68
+ curr_close = data['close'].iloc[i]
69
+ if prev_trend == 1:
70
+ if curr_close < data['lower_band'].iloc[i - 1]:
71
+ data.loc[data.index[i], 'supertrend_trend'] = -1
72
+ else:
73
+ data.loc[data.index[i], 'lower_band'] = max(data['lower_band'].iloc[i], data['lower_band'].iloc[i - 1])
74
+ else:
75
+ if curr_close > data['upper_band'].iloc[i - 1]:
76
+ data.loc[data.index[i], 'supertrend_trend'] = 1
77
+ else:
78
+ data.loc[data.index[i], 'upper_band'] = min(data['upper_band'].iloc[i], data['upper_band'].iloc[i - 1])
79
+
80
+ data.ffill(inplace=True)
81
+ return data
82
+
83
+
84
+ # === Send to Discord ===
85
+ def send_discord_alert(symbol, signal, price, rsi):
86
+ if not DISCORD_WEBHOOK_URL:
87
+ logging.warning("Discord webhook URL not set. Skipping alert.")
88
+ return
89
+ color = 0x00ff00 if signal == "BUY" else 0xff0000
90
+ embed = {
91
+ "title": f"{signal} Signal",
92
+ "description": f"**{symbol}**",
93
+ "color": color,
94
+ "fields": [
95
+ {"name": "Price", "value": f"${price:.2f}", "inline": True},
96
+ {"name": f"RSI ({RSI_PERIOD})", "value": f"{rsi:.2f}", "inline": True},
97
+ {"name": "Interval", "value": INTERVAL, "inline": True},
98
+ ],
99
+ "timestamp": datetime.utcnow().isoformat(),
100
+ "footer": {"text": "SuperTrend ATR+RSI Bot"}
101
+ }
102
+ try:
103
+ response = requests.post(DISCORD_WEBHOOK_URL, json={"embeds": [embed]})
104
+ if response.status_code == 204:
105
+ logging.info(f"βœ… {signal} alert sent for {symbol} at ${price:.2f}")
106
+ else:
107
+ logging.error(f"❌ Discord API error: {response.status_code} - {response.text}")
108
+ except Exception as e:
109
+ logging.error(f"❌ Failed to send alert: {e}")
110
+
111
+
112
+ # === Send Test Message ===
113
+ def send_test_alert():
114
+ if not DISCORD_WEBHOOK_URL:
115
+ logging.error("❌ Test alert failed: Discord Webhook URL is not set.")
116
+ return False
117
+ embed = {
118
+ "title": "βœ… Test Alert",
119
+ "description": "**Your bot is working perfectly!**",
120
+ "color": 0x3498db,
121
+ "fields": [
122
+ {"name": "Status", "value": "All systems operational", "inline": False},
123
+ {"name": "Time", "value": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "inline": False},
124
+ ],
125
+ "footer": {"text": "SuperTrend Signal Bot"}
126
+ }
127
+ try:
128
+ response = requests.post(DISCORD_WEBHOOK_URL, json={"embeds": [embed]})
129
+ return response.status_code == 204
130
+ except Exception as e:
131
+ logging.error(f"❌ Test alert failed: {e}")
132
+ return False
133
+
134
+
135
+ # === Check One Stock ===
136
+ def check_stock(symbol):
137
+ try:
138
+ if not is_market_open():
139
+ return
140
+
141
+ ticker = yf.Ticker(symbol)
142
+ data = ticker.history(period="2d", interval=INTERVAL)
143
+ data.columns = data.columns.str.lower()
144
+
145
+ if data.empty or len(data) < RSI_PERIOD + 10:
146
+ logging.warning(f"Insufficient data for {symbol} on {INTERVAL} interval. Skipping.")
147
+ return
148
+
149
+ rsi_indicator = ta.momentum.RSIIndicator(data['close'], window=RSI_PERIOD)
150
+ data['rsi'] = rsi_indicator.rsi()
151
+ data = calculate_supertrend(data, factor=SUPER_TREND_FACTOR, period=ATR_PERIOD)
152
+
153
+ data.dropna(inplace=True)
154
+ if len(data) < 2:
155
+ logging.warning(f"Not enough data for {symbol} after indicator calculation.")
156
+ return
157
+
158
+ latest = data.iloc[-1]
159
+ previous = data.iloc[-2]
160
+ current_trend = latest['supertrend_trend']
161
+ prev_trend = previous['supertrend_trend']
162
+ price = latest['close']
163
+ rsi_val = latest['rsi']
164
+
165
+ # ADDED THIS LINE TO PROVIDE A LIVE HEARTBEAT FOR EACH STOCK
166
+ logging.info(f"{symbol}: Price=${price:.2f}, RSI={rsi_val:.2f}, Trend={int(current_trend)}")
167
+
168
+ signal = None
169
+ if current_trend == 1 and prev_trend == -1:
170
+ signal = "BUY"
171
+ elif current_trend == -1 and prev_trend == 1:
172
+ signal = "SELL"
173
+
174
+ if signal:
175
+ # --- TREND FILTER LOGIC ---
176
+ logging.info(f"Potential {signal} for {symbol}. Checking 4-hour trend filter...")
177
+ long_term_data = ticker.history(period="1mo", interval="4h")
178
+ long_term_data.columns = long_term_data.columns.str.lower()
179
+
180
+ if len(long_term_data) < 50:
181
+ logging.warning(f"Not enough 4h data for {symbol} to apply trend filter. Allowing signal.")
182
+ else:
183
+ long_term_data['ma50'] = long_term_data['close'].rolling(window=50).mean()
184
+ last_long_term_price = long_term_data['close'].iloc[-1]
185
+ last_ma_value = long_term_data['ma50'].iloc[-1]
186
+
187
+ if signal == "BUY" and last_long_term_price < last_ma_value:
188
+ logging.info(f"BUY signal for {symbol} blocked by 4-hour trend filter (price is below MA).")
189
+ return
190
+
191
+ if signal == "SELL" and last_long_term_price > last_ma_value:
192
+ logging.info(f"SELL signal for {symbol} blocked by 4-hour trend filter (price is above MA).")
193
+ return
194
+
195
+ logging.info(f"βœ… Signal for {symbol} passed trend filter. Sending alert.")
196
+ # --- END OF TREND FILTER LOGIC ---
197
+
198
+ alert = {
199
+ "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
200
+ "symbol": symbol,
201
+ "signal": signal,
202
+ "price": f"${price:.2f}",
203
+ "rsi": f"{rsi_val:.2f}",
204
+ "interval": INTERVAL
205
+ }
206
+ with history_lock:
207
+ alert_history.insert(0, alert)
208
+ if len(alert_history) > 50:
209
+ alert_history.pop()
210
+ send_discord_alert(symbol, signal, price, rsi_val)
211
+
212
+ except Exception as e:
213
+ logging.error(f"πŸ’₯ Error processing {symbol}: {e}")
214
+
215
+
216
+ # === Scanner Loop ===
217
+ def run_scanner():
218
+ logging.info("πŸš€ Scanner started...")
219
+ while True:
220
+ with stocks_lock:
221
+ current_stocks = monitored_stocks.copy()
222
+
223
+ if not current_stocks:
224
+ logging.info("No stocks to monitor. Waiting...")
225
+ elif not is_market_open():
226
+ logging.info("Market is currently closed. Scanner is sleeping.")
227
+ else:
228
+ logging.info(f"Market is open. Checking {len(current_stocks)} stocks...")
229
+ for symbol in current_stocks:
230
+ check_stock(symbol)
231
+ logging.info("Finished checking all stocks for this interval.")
232
+
233
+ time.sleep(300)
234
+
235
+
236
+ # === FLASK WEB APP (HTML is unchanged) ===
237
+ app = Flask(__name__)
238
+ app.secret_key = SECRET_KEY
239
+
240
+ HTML_TEMPLATE = """
241
+ <!DOCTYPE html>
242
+ <html lang="en">
243
+ <head>
244
+ <meta charset="UTF-8" />
245
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
246
+ <title>Stock Signal Dashboard</title>
247
+ <style>
248
+ body { font-family: Arial, sans-serif; margin: 20px; background: #f4f6f9; color: #333; }
249
+ h1, h2 { color: #2c3e50; }
250
+ .container { max-width: 1000px; margin: 0 auto; }
251
+ .section { background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); margin-bottom: 20px; }
252
+ input[type="text"], input[type="password"] { width: calc(100% - 22px); padding: 10px; margin: 5px 0 10px 0; border: 1px solid #ddd; border-radius: 4px; }
253
+ button { padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; color: white; font-weight: bold; }
254
+ .btn-add { background: #27ae60; } .btn-add:hover { background: #2ecc71; }
255
+ .btn-remove { background: #e74c3c; padding: 5px 10px; font-size: 0.9em; } .btn-remove:hover { background: #c0392b; }
256
+ .btn-test { background: #9b59b6; } .btn-test:hover { background: #8e44ad; }
257
+ table { width: 100%; border-collapse: collapse; margin-top: 15px; }
258
+ th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
259
+ th { background-color: #34495e; color: white; }
260
+ tr:nth-child(even) { background-color: #f9f9f9; }
261
+ tr:hover { background-color: #f1f1f1; }
262
+ .buy { color: #27ae60; font-weight: bold; }
263
+ .sell { color: #c0392b; font-weight: bold; }
264
+ .empty { color: #7f8c8d; font-style: italic; text-align: center; padding: 20px; }
265
+ .flash { padding: 15px; margin-bottom: 20px; border-radius: 4px; border: 1px solid transparent; }
266
+ .flash.success { background: #d4edda; color: #155724; border-color: #c3e6cb; }
267
+ .flash.error { background: #f8d7da; color: #721c24; border-color: #f5c6cb; }
268
+ .flash.info { background: #d1ecf1; color: #0c5460; border-color: #bee5eb; }
269
+ .footer { margin-top: 30px; color: #7f8c8d; font-size: 0.9em; text-align: center; }
270
+ .form-inline { display: flex; align-items: center; gap: 10px; }
271
+ .form-inline input[type="password"] { width: 120px; margin: 0; }
272
+ </style>
273
+ </head>
274
+ <body>
275
+ <div class="container">
276
+ <h1>πŸ“ˆ SuperTrend ATR+RSI Dashboard</h1>
277
+ {% with messages = get_flashed_messages(with_categories=true) %}
278
+ {% if messages %}
279
+ {% for category, msg in messages %}
280
+ <div class="flash {{ category }}">{{ msg }}</div>
281
+ {% endfor %}
282
+ {% endif %}
283
+ {% endwith %}
284
+ <div class="section">
285
+ <h2>βž• Add Stock to Watchlist</h2>
286
+ <form method="POST" action="/add_stock">
287
+ <input type="text" name="symbol" placeholder="Enter symbol (e.g., AAPL)" required autocomplete="off" />
288
+ <input type="password" name="key" placeholder="Secret Key" required />
289
+ <button type="submit" class="btn-add">Add Stock</button>
290
+ </form>
291
+ </div>
292
+ <div class="section">
293
+ <h2>πŸ“‹ Monitored Stocks</h2>
294
+ {% if stocks %}
295
+ <table>
296
+ <thead><tr><th>Symbol</th><th>Action</th></tr></thead>
297
+ <tbody>
298
+ {% for symbol in stocks %}
299
+ <tr>
300
+ <td><strong>{{ symbol }}</strong></td>
301
+ <td>
302
+ <form method="POST" action="/remove_stock" class="form-inline">
303
+ <input type="hidden" name="symbol" value="{{ symbol }}" />
304
+ <input type="password" name="key" placeholder="Key" required />
305
+ <button type="submit" class="btn-remove">Remove</button>
306
+ </form>
307
+ </td>
308
+ </tr>
309
+ {% endfor %}
310
+ </tbody>
311
+ </table>
312
+ {% else %}
313
+ <p class="empty">🚫 No stocks are being monitored. Add one above to get started!</p>
314
+ {% endif %}
315
+ </div>
316
+ <div class="section">
317
+ <h2>πŸ§ͺ Test Discord Webhook</h2>
318
+ <form method="POST" action="/test_discord">
319
+ <input type="password" name="key" placeholder="Secret Key" required />
320
+ <button type="submit" class="btn-test">πŸ“€ Send Test Message</button>
321
+ </form>
322
+ </div>
323
+ <div class="section">
324
+ <h2>πŸ”” Recent Alert History</h2>
325
+ {% if alerts %}
326
+ <table>
327
+ <thead><tr><th>Time</th><th>Symbol</th><th>Signal</th><th>Price</th><th>RSI</th><th>Interval</th></tr></thead>
328
+ <tbody>
329
+ {% for alert in alerts %}
330
+ <tr>
331
+ <td>{{ alert.time }}</td>
332
+ <td><strong>{{ alert.symbol }}</strong></td>
333
+ <td class="{{ alert.signal.lower() }}">{{ alert.signal }}</td>
334
+ <td>{{ alert.price }}</td>
335
+ <td>{{ alert.rsi }}</td>
336
+ <td>{{ alert.interval }}</td>
337
+ </tr>
338
+ {% endfor %}
339
+ </tbody>
340
+ </table>
341
+ {% else %}
342
+ <p class="empty">No alerts generated yet. Waiting for a signal...</p>
343
+ {% endif %}
344
+ </div>
345
+ <div class="footer">
346
+ Bot status as of: {{ now }} | Scan Interval: {{ interval }}
347
+ </div>
348
+ </div>
349
+ </body>
350
+ </html>
351
+ """
352
+
353
+
354
+ @app.route('/', methods=['GET'])
355
+ def home():
356
+ with stocks_lock:
357
+ stocks = sorted(monitored_stocks.copy())
358
+ with history_lock:
359
+ alerts = alert_history.copy()
360
+ now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
361
+ return render_template_string(HTML_TEMPLATE,
362
+ stocks=stocks,
363
+ alerts=alerts,
364
+ now=now,
365
+ interval=INTERVAL)
366
+
367
+
368
+ @app.route('/add_stock', methods=['POST'])
369
+ def add_stock():
370
+ symbol = request.form.get('symbol', '').strip().upper()
371
+ key = request.form.get('key', '').strip()
372
+ if key != SECRET_KEY:
373
+ flash("πŸ”’ Access denied: Invalid secret key.", "error")
374
+ elif not symbol:
375
+ flash("⚠️ Please enter a stock symbol.", "error")
376
+ elif not is_valid_symbol(symbol):
377
+ flash(f"❌ Invalid symbol: '{symbol}'. Use 1–5 uppercase letters.", "error")
378
+ else:
379
+ with stocks_lock:
380
+ if symbol in monitored_stocks:
381
+ flash(f"🟑 '{symbol}' is already being monitored.", "info")
382
+ else:
383
+ monitored_stocks.append(symbol)
384
+ flash(f"βœ… Successfully added '{symbol}' to the watchlist!", "success")
385
+ logging.info(f"βž• User added stock: {symbol}")
386
+ return home()
387
+
388
+
389
+ @app.route('/remove_stock', methods=['POST'])
390
+ def remove_stock():
391
+ symbol = request.form.get('symbol', '').strip().upper()
392
+ key = request.form.get('key', '').strip()
393
+ if key != SECRET_KEY:
394
+ flash("πŸ”’ Access denied: Invalid secret key.", "error")
395
+ else:
396
+ with stocks_lock:
397
+ if symbol not in monitored_stocks:
398
+ flash(f"πŸ” '{symbol}' is not on the list.", "info")
399
+ else:
400
+ monitored_stocks.remove(symbol)
401
+ flash(f"πŸ—‘οΈ Removed '{symbol}' from monitoring.", "success")
402
+ logging.info(f"βž– User removed stock: {symbol}")
403
+ return home()
404
+
405
+
406
+ @app.route('/test_discord', methods=['POST'])
407
+ def test_discord():
408
+ key = request.form.get('key', '').strip()
409
+ if key != SECRET_KEY:
410
+ flash("πŸ”’ Access denied: Invalid secret key.", "error")
411
+ else:
412
+ success = send_test_alert()
413
+ if success:
414
+ flash("βœ… Test message sent to Discord! Check your server.", "success")
415
+ else:
416
+ flash("❌ Failed to send test message. Check your Discord Webhook URL.", "error")
417
+ return home()
418
+
419
+
420
+ def start():
421
+ scanner_thread = Thread(target=run_scanner, daemon=True)
422
+ scanner_thread.start()
423
+ logging.info("🌐 Starting Flask web server...")
424
+ app.run(host='0.0.0.0', port=8080)
425
+
426
+
427
+ if __name__ == '__main__':
428
+ start()