diff --git a/app/cron_discord_bot.py b/app/cron_discord_bot.py index fa48833..f38d468 100644 --- a/app/cron_discord_bot.py +++ b/app/cron_discord_bot.py @@ -1,27 +1,31 @@ import requests import time import orjson -from datetime import datetime, timedelta, timezone +from datetime import datetime, timedelta, timezone, date +import pytz import os from dotenv import load_dotenv load_dotenv() + +ny_tz = pytz.timezone("America/New_York") + today = datetime.utcnow().date() now = datetime.now(timezone.utc) +now_ny = datetime.now(ny_tz) N_minutes_ago = now - timedelta(minutes=30) -WEBHOOK_URL = os.getenv("DISCORD_DARK_POOL_WEBHOOK") +DARK_POOL_WEBHOOK_URL = os.getenv("DISCORD_DARK_POOL_WEBHOOK") +OPTIONS_FLOW_WEBHOOK_URL = os.getenv("DISCORD_OPTIONS_FLOW_WEBHOOK") + def save_json(data): directory = "json/discord" - try: - os.makedirs(directory, exist_ok=True) - with open(directory+"/dark_pool.json", 'wb') as file: - file.write(orjson.dumps(data)) - except Exception as e: - print(f"An error occurred while saving data: {e}") - + os.makedirs(directory, exist_ok=True) + with open(directory+"/dark_pool.json", 'wb') as file: + file.write(orjson.dumps(data)) + def format_number(num, decimal=False): """Abbreviate large numbers with B/M suffix and format appropriately""" @@ -56,7 +60,7 @@ def format_number(num, decimal=False): return f"{num:,.0f}" # Format smaller numbers with commas def dark_pool_flow(): - + try: with open(f"json/discord/dark_pool.json", "r") as file: seen_list = orjson.loads(file.read()) @@ -118,7 +122,7 @@ def dark_pool_flow(): "embeds": [embed] } - response = requests.post(WEBHOOK_URL, json=payload) + response = requests.post(DARK_POOL_WEBHOOK_URL, json=payload) if response.status_code in (200, 204): seen_list.append({'date': result['date'], 'trackingID': result['trackingID']}) @@ -133,5 +137,126 @@ def dark_pool_flow(): else: print("Dark pool already sent!") + +def options_flow(): + now_ny = datetime.now(ny_tz) + N_minutes_ago = now_ny - timedelta(minutes=5) + today = now_ny.date() + + # Load seen entries + try: + with open("json/discord/options_flow.json", "rb") as file: + seen_list = orjson.loads(file.read()) + today_iso = today.isoformat() + seen_list = [item for item in seen_list if item['date'] == today_iso] + except FileNotFoundError: + seen_list = [] + except Exception as e: + print(f"Error loading seen list: {e}") + seen_list = [] + + # Load current data + try: + with open("json/options-flow/feed/data.json", "rb") as file: + res_list = orjson.loads(file.read()) + except Exception as e: + print(f"Error loading data.json: {e}") + res_list = [] + + # Process and filter entries + filtered = [] + for item in res_list: + try: + # Validate required fields + if float(item.get('cost_basis', 0)) < 1E5: + continue + + item_date = datetime.fromisoformat(item['date']).date() + if item_date != today: + continue + + item_time = datetime.strptime(item['time'], "%H:%M:%S").time() + item_dt = ny_tz.localize(datetime.combine(item_date, item_time)) + if item_dt < N_minutes_ago: + filtered.append(item) + except Exception as e: + print(f"Error processing item {item.get('id')}: {e}") + continue + + if not filtered: + print("No recent valid entries found") + return + + # Find best order + best_order = max(filtered, key=lambda x: float(x['cost_basis'])) + result = { + k: best_order[k] for k in [ + 'date', 'sentiment', 'option_activity_type', 'ticker', 'id', + 'strike_price', 'date_expiration', 'size', 'cost_basis', + 'execution_estimate', 'volume', 'open_interest', 'put_call' + ] + } + + # Check duplicates + seen_ids = {item['id'] for item in seen_list} + if result['id'] in seen_ids: + print("Options Flow data already sent!") + return + + # Prepare message + try: + symbol = result['ticker'] + size = format_number(result['size']) + premium = format_number(result['cost_basis']) + strike = result['strike_price'] + side = result['execution_estimate'] + volume = format_number(result['volume']) + open_interest = format_number(result['open_interest']) + put_call = result['put_call'].replace('Calls', 'Call').replace('Puts', 'Put') + option_activity_type = result['option_activity_type'] + sentiment = result['sentiment'] + + date_expiration = datetime.strptime(result['date_expiration'], "%Y-%m-%d").strftime("%d/%m/%Y") + message_timestamp = int(now_ny.timestamp()) + + color = 0x39FF14 if sentiment == 'Bullish' else 0xFF0000 if sentiment == 'Bearish' else 0xFFA500 + + embed = { + "color": color, + "thumbnail": {"url": "https://stocknear.com/pwa-64x64.png"}, + "description": f"{put_call} {option_activity_type} ({sentiment})", + "fields": [ + {"name": "Symbol", "value": symbol, "inline": True}, + {"name": "Strike", "value": str(strike), "inline": True}, + {"name": "Expiration", "value": date_expiration, "inline": True}, + {"name": "Call/Put", "value": put_call, "inline": True}, + {"name": "Side", "value": str(side), "inline": True}, + {"name": "Size", "value": str(size), "inline": True}, + {"name": "Premium", "value": f"${premium}", "inline": True}, + {"name": "Volume", "value": str(volume), "inline": True}, + {"name": "OI", "value": str(open_interest), "inline": True}, + {"name": f"Data by Stocknear - - Delayed by 5 min", + "value": "", "inline": False} + ], + "footer": {"text": ""} + } + + payload = {"content": "", "embeds": [embed]} + + # Send to Discord + response = requests.post(OPTIONS_FLOW_WEBHOOK_URL, json=payload) + response.raise_for_status() + + # Update seen list + seen_list.append({'date': result['date'], 'id': result['id']}) + with open("json/discord/options_flow.json", "wb") as file: + file.write(orjson.dumps(seen_list)) + print("Embed sent successfully!") + + except Exception as e: + print(f"Error sending message: {e}") + + if __name__ == "__main__": + options_flow() dark_pool_flow() \ No newline at end of file