From 0f547ba758fd3f01db6a24c51cf9ad3b692b70c1 Mon Sep 17 00:00:00 2001 From: Stupdi Go Date: Sat, 30 Aug 2025 22:32:21 -0500 Subject: [PATCH] Rewrote main.py --- __pycache__/utils.cpython-313.pyc | Bin 0 -> 3774 bytes main.py | 35 +++--- producers/__pycache__/sp500.cpython-313.pyc | Bin 0 -> 3769 bytes producers/sp500.py | 57 ++++++++++ rss-feed-notif/config.toml | 5 + rss-feed-notif/main.py | 0 rss-feed-notif/rss-feed.py | 23 ++++ utils.py | 112 ++++++++------------ 8 files changed, 146 insertions(+), 86 deletions(-) create mode 100644 __pycache__/utils.cpython-313.pyc create mode 100644 producers/__pycache__/sp500.cpython-313.pyc create mode 100644 rss-feed-notif/config.toml create mode 100644 rss-feed-notif/main.py create mode 100644 rss-feed-notif/rss-feed.py diff --git a/__pycache__/utils.cpython-313.pyc b/__pycache__/utils.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d01e6cf401a36af0802d2856789eb7ad6799c581 GIT binary patch literal 3774 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%0AK4i=Wp2u8e_&-`75>D) zD#rGOkAaoD-Mh(qg8mZa1$md1OfM*!T`=>zAmD$6CEy8it@Hq1D~YX;yUb^Cox|b+ zhs6U$&E@h7<+q4kQMCBZz{DBII6)g)am>(N!M&jNf`sW69Z!ocFH%yNW<(N&S-2rDy)&Ed%HYQ}t2n9)su@u(OFh?Hjp zi6}CS7x0HT8#gPHV^3Y>HXO=dq$rXm3b28JR~L7~Z1qzY#XaH zBRDA+gN##vBt2nhpTRRMJ|nZZBtN|H+6dR!W6P$sIKy?zN z6ah7(z-29Z%oK(9aCk6%vP>>YcGcYiGU}j`wyv@LOpF#UR zgUWpd!@CTUcNvUtGjQK$u)D}$cSA&Sg82moft#En-LegApP0BA4ZbLeG6ph!)?;Ke H0$Tt8$Xf{8 literal 0 HcmV?d00001 diff --git a/producers/sp500.py b/producers/sp500.py index e69de29..f5f77b3 100644 --- a/producers/sp500.py +++ b/producers/sp500.py @@ -0,0 +1,57 @@ +import asyncio +from tastytrade.dxfeed import Quote, Greeks +from datetime import date, timedelta +from tastytrade import instruments,DXLinkStreamer +from loguru import logger +from tastytrade.instruments import get_option_chain +import json +from utils import create_mqttc, load_config, parse_greek_event +market_subs_list = ["SPX"] # your subscription list, format per tastytrade docs +config = load_config() +async def get_spx_market(session): + mqttc = create_mqttc(config) + async with DXLinkStreamer(session) as streamer: + await streamer.subscribe(Quote, market_subs_list) + logger.info("Subscribed, streaming quotes... Press Ctrl+C to stop.") + try: + while True: + quote = await streamer.get_event(Quote) + mqttc.publish("SP500",str((quote.ask_price+quote.bid_price)/2)) + # or process/yield the quote as needed + except asyncio.CancelledError: + logger.info("Streaming cancelled.") + + + + + +async def stream_spx_greeks(session, days_out=30): + mqttc = create_mqttc(config) + # Fetch option chain (blocking → thread) + chain = await asyncio.to_thread(get_option_chain, session, "SPX") + + cutoff = date.today() + timedelta(days=days_out) + option_symbols = [ + opt.streamer_symbol + for exp_date, opts in chain.items() + if exp_date <= cutoff + for opt in opts + ] + + logger.info(f"Subscribing to {len(option_symbols)} SPX option contracts...") + + async with DXLinkStreamer(session) as streamer: + # Here we pass the list as a single argument (no unpacking, no loop) + await streamer.subscribe(Greeks, option_symbols) + + async for greek_event in streamer.listen(Greeks): + logger.info(f"Greek Event: {greek_event}") + mqttc.publish("SP500/oc",json.dumps(parse_greek_event(greek_event))) + +def create_IV_histogram(mqttc): + mqttc.subscribe([("SP500",0),("SP500/oc",0)]) + + + + + diff --git a/rss-feed-notif/config.toml b/rss-feed-notif/config.toml new file mode 100644 index 0000000..804a177 --- /dev/null +++ b/rss-feed-notif/config.toml @@ -0,0 +1,5 @@ +[mqtt] +user = "mqttuser" +password = "MWkWG42PX8Be5j87Q3Rk" +[rss-feeds] +nyt-feed = 'https://rss.nytimes.com/services/xml/rss/nyt/World.xml' diff --git a/rss-feed-notif/main.py b/rss-feed-notif/main.py new file mode 100644 index 0000000..e69de29 diff --git a/rss-feed-notif/rss-feed.py b/rss-feed-notif/rss-feed.py new file mode 100644 index 0000000..4b763d6 --- /dev/null +++ b/rss-feed-notif/rss-feed.py @@ -0,0 +1,23 @@ +from typing import List +import feedparser +import tomllib +import json +with open("config.toml","rb") as file: + config = tomllib.load(file) +print(config) + +previous_msgs = set() + +def read_rss_feeds(client): + for feed_name,feed_link in config["rss-feeds"].items(): + feed = feedparser.parse(feed_link) + for feed_post in feed.entries: + if feed_post.title in previous_msgs: + continue + else: + previous_msgs.add(feed_post.title) + notif_json = {"title":feed_post.title,"body":feed_post.description,"topic":"overlordian-news-192","link":feed_post.link} + client.publish("notification-pusher",json.dumps(notif_json),0) + + + diff --git a/utils.py b/utils.py index 2695913..a9c47d3 100644 --- a/utils.py +++ b/utils.py @@ -1,5 +1,10 @@ from typing import NamedTuple import tomllib +import tastytrade +import re +import paho.mqtt.client as mqtt +from datetime import datetime +from loguru import logger def dict_to_namedtuple(name, dictionary): fields = {} for key, value in dictionary.items(): @@ -15,75 +20,48 @@ def load_config(): config_dict = tomllib.load(f) config = dict_to_namedtuple("Config", config_dict) return config -import webbrowser -import requests -import threading -from flask import Flask, request -def authenticate(client_id, client_secret, scopes=None, port=5000): - """ - Opens the browser for OAuth authorization and returns token data. - - Args: - client_id (str): Your Tastytrade client ID - client_secret (str): Your Tastytrade client secret - scopes (list[str]): Optional list of scopes (default: None) - port (int): Port for local callback server (default: 5000) - - Returns: - dict: Token response JSON (access_token, refresh_token, etc.) - """ - redirect_uri = f"http://127.0.0.1:{port}/callback" - auth_url = "https://api.tastytrade.com/oauth/authorize" - token_url = "https://api.tastytrade.com/oauth/token" - - app = Flask(__name__) - auth_code_container = {} - - @app.route("/callback") - def callback(): - code = request.args.get("code") - auth_code_container["code"] = code - return "Authorization successful. You may close this window." - - # Start local server in background - def run_server(): - app.run(port=port, debug=False, use_reloader=False) - threading.Thread(target=run_server, daemon=True).start() - - # Build auth URL - params = { - "response_type": "code", - "client_id": client_id, - "redirect_uri": redirect_uri +def parse_event_symbol(symbol: str): + symbol = symbol.lstrip('.') + pattern = r"([A-Z]+)(\d{6})([CP])(\d+)" + match = re.match(pattern, symbol) + if not match: + return None + underlying, exp_str, opt_type, strike_str = match.groups() + expiration = datetime.strptime(exp_str, "%y%m%d").date() + strike = int(strike_str) / 10 + return { + "underlying": underlying, + "expiration": expiration.isoformat(), + "option_type": opt_type, + "strike": strike, } - if scopes: - params["scope"] = " ".join(scopes) +def create_mqttc(config): + mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) + mqttc.username_pw_set(username=config.mqtt.user,password=config.mqtt.password) + mqttc.connect("proliant.lan",port=1883) + logger.info("MQTT client instantiated") + return mqttc - # Open browser - webbrowser.open(f"{auth_url}?{requests.compat.urlencode(params)}") +def parse_greek_event(greek_event): + # parse symbol details + parsed_symbol = parse_event_symbol(greek_event.event_symbol) - # Wait for code - print("Waiting for authorization...") - while "code" not in auth_code_container: - pass - - # Exchange code for token - token_resp = requests.post(token_url, data={ - "grant_type": "authorization_code", - "code": auth_code_container["code"], - "redirect_uri": redirect_uri, - "client_id": client_id, - "client_secret": client_secret - }) - token_resp.raise_for_status() - - return token_resp.json() - -# Example usage -if __name__ == "__main__": - client_id = "YOUR_CLIENT_ID" - client_secret = "YOUR_CLIENT_SECRET" - token_data = authenticate(client_id, client_secret, scopes=["accounts", "orders"]) - print(token_data) + # combine everything into one dict + event_dict = { + **parsed_symbol, + "event_time": greek_event.event_time, + "event_flags": greek_event.event_flags, + "index": greek_event.index, + "time": greek_event.time, + "sequence": greek_event.sequence, + "price": float(greek_event.price), + "volatility": float(greek_event.volatility), + "delta": float(greek_event.delta), + "gamma": float(greek_event.gamma), + "theta": float(greek_event.theta), + "rho": float(greek_event.rho), + "vega": float(greek_event.vega), + } + return event_dict