add recent earnings to discord bot
This commit is contained in:
parent
1001cf4381
commit
9d093e45b8
@ -1,11 +1,15 @@
|
||||
import requests
|
||||
import time
|
||||
import orjson
|
||||
import aiohttp
|
||||
import aiofiles
|
||||
from datetime import datetime, timedelta, timezone, date
|
||||
import pytz
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
import asyncio
|
||||
import sqlite3
|
||||
import hashlib
|
||||
|
||||
load_dotenv()
|
||||
|
||||
@ -18,7 +22,10 @@ N_minutes_ago = now - timedelta(minutes=30)
|
||||
|
||||
DARK_POOL_WEBHOOK_URL = os.getenv("DISCORD_DARK_POOL_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):
|
||||
directory = "json/discord"
|
||||
@ -26,7 +33,6 @@ def save_json(data):
|
||||
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"""
|
||||
# Handle scientific notation formats like 5E6
|
||||
@ -59,6 +65,130 @@ def format_number(num, decimal=False):
|
||||
else:
|
||||
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():
|
||||
|
||||
try:
|
||||
@ -70,7 +200,7 @@ def dark_pool_flow():
|
||||
|
||||
with open(f"json/dark-pool/feed/data.json", "r") as file:
|
||||
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:
|
||||
@ -101,7 +231,7 @@ def dark_pool_flow():
|
||||
message_timestamp = int((datetime.now() - timedelta(minutes=0)).timestamp())
|
||||
|
||||
embed = {
|
||||
"color": 0xC475FD, # Green color from original embed
|
||||
"color": 0xC475FD,
|
||||
"thumbnail": {"url": "https://stocknear.com/pwa-64x64.png"},
|
||||
"title": "Dark Pool Order",
|
||||
"fields": [
|
||||
@ -256,7 +386,114 @@ def options_flow():
|
||||
except Exception as 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()
|
||||
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)
|
||||
@ -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_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')
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user