add recent earnings to discord bot

This commit is contained in:
MuslemRahimi 2025-04-11 17:12:37 +02:00
parent 1001cf4381
commit 9d093e45b8
2 changed files with 244 additions and 7 deletions

View File

@ -1,11 +1,15 @@
import requests import requests
import time import time
import orjson import orjson
import aiohttp
import aiofiles
from datetime import datetime, timedelta, timezone, date from datetime import datetime, timedelta, timezone, date
import pytz import pytz
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
import asyncio
import sqlite3
import hashlib
load_dotenv() load_dotenv()
@ -18,7 +22,10 @@ N_minutes_ago = now - timedelta(minutes=30)
DARK_POOL_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") OPTIONS_FLOW_WEBHOOK_URL = os.getenv("DISCORD_OPTIONS_FLOW_WEBHOOK")
RECENT_EARNINGS_WEBHOOK_URL = os.getenv("DISCORD_RECENT_EARNINGS_WEBHOOK")
BENZINGA_API_KEY = os.getenv('BENZINGA_API_KEY')
headers = {"accept": "application/json"}
def save_json(data): def save_json(data):
directory = "json/discord" directory = "json/discord"
@ -26,7 +33,6 @@ def save_json(data):
with open(directory+"/dark_pool.json", 'wb') as file: with open(directory+"/dark_pool.json", 'wb') as file:
file.write(orjson.dumps(data)) file.write(orjson.dumps(data))
def format_number(num, decimal=False): def format_number(num, decimal=False):
"""Abbreviate large numbers with B/M suffix and format appropriately""" """Abbreviate large numbers with B/M suffix and format appropriately"""
# Handle scientific notation formats like 5E6 # Handle scientific notation formats like 5E6
@ -59,6 +65,130 @@ def format_number(num, decimal=False):
else: else:
return f"{num:,.0f}" # Format smaller numbers with commas return f"{num:,.0f}" # Format smaller numbers with commas
def abbreviate_number(n):
"""
Abbreviate a number to a readable format.
E.g., 1500 -> '1.5K', 2300000 -> '2.3M'
"""
if n is None:
return "N/A"
abs_n = abs(n)
if abs_n < 1000:
return str(n)
elif abs_n < 1_000_000:
return f"{n/1000:.1f}K"
elif abs_n < 1_000_000_000:
return f"{n/1_000_000:.1f}M"
else:
return f"{n/1_000_000_000:.1f}B"
def remove_duplicates(elements):
seen = set()
unique_elements = []
for element in elements:
if element['symbol'] not in seen:
seen.add(element['symbol'])
unique_elements.append(element)
return unique_elements
def weekday():
today = datetime.today()
if today.weekday() >= 5: # 5 = Saturday, 6 = Sunday
yesterday = today - timedelta(2)
else:
yesterday = today - timedelta(1)
return yesterday.strftime('%Y-%m-%d')
async def get_recent_earnings(session):
today = datetime.today().strftime('%Y-%m-%d')
yesterday = weekday()
url = "https://api.benzinga.com/api/v2.1/calendar/earnings"
res_list = []
importance_list = ["1","2","3","4","5"]
for importance in importance_list:
querystring = {
"token": BENZINGA_API_KEY,
"parameters[importance]": importance,
"parameters[date_from]": yesterday,
"parameters[date_to]": today,
"parameters[date_sort]": "date"
}
try:
async with session.get(url, params=querystring, headers=headers) as response:
res = orjson.loads(await response.text())['earnings']
for item in res:
try:
symbol = item['ticker']
name = item['name']
time = item['time']
date = item['date']
updated = int(item['updated']) # Convert to integer for proper comparison
# Convert numeric fields, handling empty strings
eps_prior = float(item['eps_prior']) if item['eps_prior'] != '' else None
eps_surprise = float(item['eps_surprise']) if item['eps_surprise'] != '' else None
eps = float(item['eps']) if item['eps'] != '' else 0
revenue_prior = float(item['revenue_prior']) if item['revenue_prior'] != '' else None
revenue_surprise = float(item['revenue_surprise']) if item['revenue_surprise'] != '' else None
revenue = float(item['revenue']) if item['revenue'] != '' else None
if (symbol in stock_symbols and
revenue is not None and
revenue_prior is not None and
eps_prior is not None and
eps is not None and
revenue_surprise is not None and
eps_surprise is not None):
with open(f"json/quote/{symbol}.json","r") as file:
quote_data = orjson.loads(file.read())
market_cap = quote_data.get('marketCap',0)
price = quote_data.get('price',0)
changes_percentage = quote_data.get('changesPercentage',0)
res_list.append({
'symbol': symbol,
'name': name,
'time': time,
'date': date,
'marketCap': market_cap,
'epsPrior': eps_prior,
'epsSurprise': eps_surprise,
'eps': eps,
'revenuePrior': revenue_prior,
'revenueSurprise': revenue_surprise,
'revenue': revenue,
'price': price,
'changesPercentage': changes_percentage,
'updated': updated
})
except Exception as e:
print('Recent Earnings:', e)
pass
except Exception as e:
print('API Request Error:', e)
pass
# Remove duplicates
res_list = remove_duplicates(res_list)
# Sort first by the most recent 'updated' timestamp, then by market cap
res_list.sort(key=lambda x: (-x['updated'], -x['marketCap']))
# Remove market cap before returning and limit to top 10
res_list = [{k: v for k, v in d.items() if k not in ['updated']} for d in res_list]
return res_list[:10]
def dark_pool_flow(): def dark_pool_flow():
try: try:
@ -70,7 +200,7 @@ def dark_pool_flow():
with open(f"json/dark-pool/feed/data.json", "r") as file: with open(f"json/dark-pool/feed/data.json", "r") as file:
res_list = orjson.loads(file.read()) res_list = orjson.loads(file.read())
res_list = [item for item in res_list if float(item['premium']) >= 30E6 and datetime.fromisoformat(item['date']).date() == today] res_list = [item for item in res_list if float(item['premium']) >= 100E6 and datetime.fromisoformat(item['date']).date() == today]
if res_list: if res_list:
@ -101,7 +231,7 @@ def dark_pool_flow():
message_timestamp = int((datetime.now() - timedelta(minutes=0)).timestamp()) message_timestamp = int((datetime.now() - timedelta(minutes=0)).timestamp())
embed = { embed = {
"color": 0xC475FD, # Green color from original embed "color": 0xC475FD,
"thumbnail": {"url": "https://stocknear.com/pwa-64x64.png"}, "thumbnail": {"url": "https://stocknear.com/pwa-64x64.png"},
"title": "Dark Pool Order", "title": "Dark Pool Order",
"fields": [ "fields": [
@ -256,7 +386,114 @@ def options_flow():
except Exception as e: except Exception as e:
print(f"Error sending message: {e}") print(f"Error sending message: {e}")
async def recent_earnings_message():
if __name__ == "__main__": try:
with open(f"json/discord/recent_earnings.json", "r") as file:
seen_list = orjson.loads(file.read())
seen_list = [item for item in seen_list if datetime.fromisoformat(item['date']).date() == today]
except:
seen_list = []
async with aiohttp.ClientSession() as session:
res_list = await get_recent_earnings(session)
for item in res_list:
unique_str = f"{item['date']}-{item['symbol']}-{item['revenue']}-{item['eps']}"
item['id'] = hashlib.md5(unique_str.encode()).hexdigest()
if res_list:
if seen_list:
seen_ids = {item['id'] for item in seen_list}
else:
seen_ids = {}
for item in res_list:
try:
if item != None and item['id'] not in seen_ids and item['marketCap'] > 100E9:
symbol = item['symbol']
price = item['price']
changes_percentage = round(item['changesPercentage'],2)
revenue = abbreviate_number(item['revenue'])
revenue_surprise = abbreviate_number(item.get("revenueSurprise", 0))
eps = item['eps']
eps_surprise = item.get("epsSurprise", 0)
revenue_surprise_text = "exceeds" if item["revenueSurprise"] > 0 else "misses"
eps_surprise_text = "exceeds" if eps_surprise > 0 else "misses"
market_cap = abbreviate_number(item['marketCap'])
revenue_yoy_change = (item["revenue"] / item["revenuePrior"] - 1) * 100
revenue_yoy_direction = "decline" if (item["revenue"] / item["revenuePrior"] - 1) < 0 else "growth"
eps_yoy_change = (item["eps"] / item["epsPrior"] - 1) * 100
eps_yoy_direction = "decline" if (item["eps"] / item["epsPrior"] - 1) < 0 else "growth"
message_timestamp = int((datetime.now() - timedelta(minutes=0)).timestamp())
embed = {
"color": 0xC475FD,
"thumbnail": {"url": "https://stocknear.com/pwa-64x64.png"},
"title": "Earnings Surprise",
"fields": [
{"name": "Symbol", "value": symbol, "inline": True},
{"name": "", "value": "", "inline": True},
{"name": "Market Cap", "value": market_cap, "inline": True},
{"name": "Price", "value": str(price), "inline": True},
{"name": "", "value": "", "inline": True},
{"name": "% Change", "value": str(changes_percentage)+"%", "inline": True},
{"name": "", "value": "", "inline": False},
{"name": f"Revenue of {revenue} {revenue_surprise_text} estimates by {revenue_surprise}, with {revenue_yoy_change:.2f}% YoY {revenue_yoy_direction}.", "value": "", "inline": False},
{"name": f"EPS of {eps} {eps_surprise_text} estimates by {eps_surprise}, with {eps_yoy_change:.2f}% YoY {eps_yoy_direction}.", "value": "", "inline": False},
{"name": f"Data by Stocknear - <t:{message_timestamp}:R>", "value": "", "inline": False},
],
"footer": {"text": ""}
}
payload = {
"content": "",
"embeds": [embed]
}
response = requests.post(RECENT_EARNINGS_WEBHOOK_URL, json=payload)
if response.status_code in (200, 204):
seen_list.append({'date': item['date'], 'id': item['id'], 'symbol': symbol})
print("Embed sent successfully!")
else:
print(f"Failed to send embed. Status code: {response.status_code}")
print("Response content:", response.text)
else:
print("Earnings already sent!")
except Exception as e:
print(e)
try:
with open("json/discord/recent_earnings.json","wb") as file:
file.write(orjson.dumps(seen_list))
except:
pass
async def main():
options_flow() options_flow()
dark_pool_flow() dark_pool_flow()
await recent_earnings_message()
try:
con = sqlite3.connect('stocks.db')
cursor = con.cursor()
cursor.execute("PRAGMA journal_mode = wal")
cursor.execute("SELECT DISTINCT symbol FROM stocks")
stock_symbols = [row[0] for row in cursor.fetchall()]
con.close()
asyncio.run(main())
except Exception as e:
print(e)

View File

@ -427,7 +427,7 @@ schedule.every(5).minutes.do(run_threaded, run_push_notifications).tag('push_not
schedule.every(5).minutes.do(run_threaded, run_market_flow).tag('market_flow_job') schedule.every(5).minutes.do(run_threaded, run_market_flow).tag('market_flow_job')
schedule.every(5).minutes.do(run_threaded, run_list).tag('stock_list_job') schedule.every(5).minutes.do(run_threaded, run_list).tag('stock_list_job')
schedule.every(5).minutes.do(run_threaded, run_discord_bot).tag('discord_bot') schedule.every(2).minutes.do(run_threaded, run_discord_bot).tag('discord_bot')