Skip to content

Commit 3d2ac54

Browse files
1.6
1 parent 484c877 commit 3d2ac54

File tree

1 file changed

+97
-83
lines changed

1 file changed

+97
-83
lines changed

OptionsMonitor.py

Lines changed: 97 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,27 @@
66
from datetime import datetime, time, timedelta
77
import pytz
88
import math
9+
import time as time_module
10+
import threading
911

1012
class OptionsMonitor:
1113
def __init__(self, root):
1214
self.root = root
13-
self.root.title("Options Monitor - Ver 1.5")
15+
self.root.title("Options Monitor - Ver 1.6")
1416
self.data_file = r"C:\ProgramData\ShadowWhisperer\OptionsMonitor\data.csv"
1517
self.data = self.load_data()
16-
self.price_cache = {} # Cache yfinance prices
17-
self.sort_reverse = {} # Track sort direction
18-
self.last_market_status = None # Cache market status
19-
self.refresh_interval = None # Track refresh interval
20-
self.last_interval = "15 Mins" # Default Autoupdate time
21-
self.last_updated_label = None # Reference to last updated label
22-
self.DevMode = 0
23-
self.current_sort_col = None # Track current sorted column
24-
self.current_sort_reverse = False # Track current sort direction
18+
self.price_cache = {}
19+
self.sort_reverse = {}
20+
self.last_market_status = None
21+
self.refresh_interval = None
22+
self.last_interval = "15 Mins"
23+
self.last_updated_label = None
24+
self.DevMode = 0 #Dev Mode
25+
self.current_sort_col = None
26+
self.current_sort_reverse = False
2527
self.setup_gui()
28+
self.populate_treeview()
29+
threading.Thread(target=self.fetch_initial_prices, daemon=True).start()
2630

2731
def load_data(self):
2832
os.makedirs(os.path.dirname(self.data_file), exist_ok=True)
@@ -88,7 +92,6 @@ def setup_gui(self):
8892
self.status_frame.grid(row=2, column=0, sticky="ew")
8993

9094
self.update_market_status()
91-
self.populate_treeview()
9295
self.schedule_refresh()
9396

9497
def is_market_open(self):
@@ -147,11 +150,20 @@ def refresh_if_open():
147150
def add_option(self):
148151
add_window = tk.Toplevel(self.root)
149152
add_window.title("Add")
150-
add_window.geometry("220x210")
151153
add_window.resizable(False, False)
152154

155+
root_x = self.root.winfo_x()
156+
root_y = self.root.winfo_y()
157+
root_width = self.root.winfo_width()
158+
root_height = self.root.winfo_height()
159+
window_width = 210
160+
window_height = 192
161+
x = root_x + (root_width - window_width) // 2
162+
y = root_y + (root_height - window_height) // 2
163+
add_window.geometry(f"{window_width}x{window_height}+{x}+{y}")
164+
153165
form_frame = tk.Frame(add_window)
154-
form_frame.pack(pady=2, padx=1)
166+
form_frame.pack(pady=4, padx=1)
155167

156168
tk.Label(form_frame, text="Ticker").grid(row=0, column=0, sticky="e", padx=4, pady=4)
157169
ticker_entry = tk.Entry(form_frame)
@@ -178,7 +190,7 @@ def add_option(self):
178190

179191
tk.Button(add_window, text="Add Option", command=lambda: self._submit_option(
180192
ticker_entry, call_put_var, contracts_entry, strike_entry, close_date_entry, add_window), width=10
181-
).pack(pady=1)
193+
).pack(pady=3)
182194

183195
def _submit_option(self, ticker_entry, call_put_var, contracts_entry, strike_entry, close_date_entry, window):
184196
ticker = ticker_entry.get().upper()
@@ -202,36 +214,58 @@ def _submit_option(self, ticker_entry, call_put_var, contracts_entry, strike_ent
202214
else:
203215
messagebox.showerror("Error", "Invalid Ticker.")
204216

217+
def fetch_initial_prices(self):
218+
unique_tickers = {row[0] for row in self.data if len(row) == 5}
219+
for ticker in unique_tickers:
220+
if ticker not in self.price_cache or self.price_cache[ticker] in ["Checking", "?"]:
221+
self.price_cache[ticker] = "Checking"
222+
for attempt in range(3):
223+
try:
224+
yf_ticker = yf.Ticker(ticker)
225+
quote = yf_ticker.get_info().get('regularMarketPrice', None)
226+
if quote is None or (isinstance(quote, float) and math.isnan(quote)):
227+
history = yf_ticker.history(period="1d")
228+
quote = round(history["Close"].iloc[-1], 2) if not history.empty and not math.isnan(history["Close"].iloc[-1]) else None
229+
self.price_cache[ticker] = quote if quote is not None and not math.isnan(quote) else "?"
230+
if self.DevMode == 1:
231+
print(f"[{datetime.now().strftime('%H:%M:%S')}] Lookup {ticker}: {'Success' if quote is not None and not math.isnan(quote) else 'Failed'}, Price: {quote if quote is not None and not math.isnan(quote) else 'N/A'}")
232+
break
233+
except Exception as e:
234+
if self.DevMode == 1:
235+
print(f"[{datetime.now().strftime('%H:%M:%S')}] Lookup {ticker}: Failed, Error: {str(e)}")
236+
if attempt < 2:
237+
time_module.sleep(1)
238+
self.price_cache[ticker] = "?"
239+
self.root.after(0, self.populate_treeview)
240+
205241
def populate_treeview(self):
206242
for item in self.tree.get_children():
207243
self.tree.delete(item)
208244
unique_tickers = {row[0] for row in self.data if len(row) == 5}
209245
for ticker in unique_tickers:
210246
if ticker not in self.price_cache:
211-
try:
212-
yf_ticker = yf.Ticker(ticker)
213-
quote = yf_ticker.get_info().get('regularMarketPrice', None)
214-
if quote is None:
215-
history = yf_ticker.history(period="1d")
216-
quote = round(history["Close"].iloc[-1], 2) if not history.empty else None
217-
self.price_cache[ticker] = quote if quote is not None else float('nan')
218-
except Exception:
219-
self.price_cache[ticker] = float('nan')
247+
self.price_cache[ticker] = "Checking"
220248
for index, row in enumerate(self.data):
221249
if len(row) != 5:
222250
continue
223-
current_price = self.price_cache.get(row[0], float('nan'))
251+
current_price = self.price_cache.get(row[0], "Checking")
224252
option = row[2]
225253
strike_price = row[4]
226254
contracts = row[3]
227-
outcome = self.calculate_outcome(option, current_price, strike_price) if not math.isnan(current_price) else ""
228-
diff = current_price - strike_price if not math.isnan(current_price) else float('nan')
229-
diff_fmt = f"+{round(diff, 2):.2f}" if not math.isnan(diff) and diff > 0 else f"-{round(abs(diff), 2):.2f}" if not math.isnan(diff) and diff < 0 else ""
255+
outcome = ""
256+
diff = float('nan')
257+
diff_fmt = ""
258+
value = ""
259+
value_fmt = ""
260+
if current_price not in ["Checking", "?"]:
261+
outcome = self.calculate_outcome(option, current_price, strike_price) if not math.isnan(current_price) else ""
262+
diff = current_price - strike_price if not math.isnan(current_price) else float('nan')
263+
diff_fmt = f"+{round(diff, 2):.2f}" if not math.isnan(diff) and diff > 0 else f"-{round(abs(diff), 2):.2f}" if not math.isnan(diff) and diff < 0 else ""
264+
value = round(diff * (contracts * 100), 2) if outcome and not math.isnan(diff) else ""
265+
value_fmt = f"{int(value):,}" if value else ""
230266
diff_tag = 'green_diff' if not math.isnan(diff) and diff > 0 else 'red_diff' if not math.isnan(diff) and diff < 0 else ''
231-
value = round(diff * (contracts * 100), 2) if outcome and not math.isnan(diff) else ""
232-
value_fmt = f"{int(value)}" if value else ""
233267
strike_price_fmt = int(strike_price) if strike_price == int(strike_price) else round(strike_price, 2)
234-
current_price_fmt = int(current_price) if not math.isnan(current_price) and current_price == int(current_price) else round(current_price, 2) if not math.isnan(current_price) else ""
268+
current_price_fmt = current_price if current_price in ["Checking", "?"] else (int(current_price) if not math.isnan(current_price) and current_price == int(current_price) else round(current_price, 2))
235269
tag = 'redrow' if outcome else ('oddrow' if index % 2 else 'evenrow')
236270
item = self.tree.insert("", "end", tags=(tag, f"list_index_{index}"), values=(
237271
row[0], row[1], row[2], row[3], strike_price_fmt, current_price_fmt, diff_fmt, outcome, value_fmt
@@ -242,61 +276,35 @@ def populate_treeview(self):
242276
self.last_updated_label.config(text=f"{datetime.now().strftime('%H:%M:%S')}")
243277

244278
def calculate_outcome(self, call_put, current_price, strike_price):
245-
if call_put == "Call":
246-
return "Sell" if current_price > strike_price else ""
247-
else:
248-
return "Purchase" if current_price < strike_price else ""
279+
if call_put == "Put":
280+
return "Purchase" if strike_price > current_price else ""
281+
else: # Call
282+
return "Sell" if strike_price < current_price else ""
249283

250284
def refresh_data(self):
251285
unique_tickers = {row[0] for row in self.data if len(row) == 5}
252286
for ticker in unique_tickers:
253287
if ticker not in self.price_cache or self.is_market_open():
254-
try:
255-
yf_ticker = yf.Ticker(ticker)
256-
quote = yf_ticker.get_info().get('regularMarketPrice', None)
257-
if quote is None:
258-
history = yf_ticker.history(period="1d")
259-
quote = round(history["Close"].iloc[-1], 2) if not history.empty else None
260-
self.price_cache[ticker] = quote if quote is not None else float('nan')
261-
if self.DevMode == 1:
262-
print(f"[{datetime.now().strftime('%H:%M:%S')}] Lookup {ticker}: {'Success' if quote is not None else 'Failed'}, Price: {quote if quote is not None else 'N/A'}")
263-
except Exception as e:
264-
self.price_cache[ticker] = float('nan')
265-
if self.DevMode == 1:
266-
print(f"[{datetime.now().strftime('%H:%M:%S')}] Lookup {ticker}: Failed, Error: {str(e)}")
267-
268-
for item in self.tree.get_children():
269-
tags = self.tree.item(item, "tags")
270-
list_index = None
271-
for tag in tags:
272-
if tag.startswith("list_index_"):
273-
list_index = int(tag.replace("list_index_", ""))
274-
break
275-
if list_index is not None and list_index < len(self.data) and len(self.data[list_index]) == 5:
276-
row = self.data[list_index]
277-
current_price = self.price_cache.get(row[0], float('nan'))
278-
option = row[2]
279-
strike_price = row[4]
280-
contracts = row[3]
281-
outcome = self.calculate_outcome(option, current_price, strike_price) if not math.isnan(current_price) else ""
282-
diff = current_price - strike_price if not math.isnan(current_price) else float('nan')
283-
diff_fmt = f"+{round(diff, 2):.2f}" if not math.isnan(diff) and diff > 0 else f"-{round(abs(diff), 2):.2f}" if not math.isnan(diff) and diff < 0 else ""
284-
diff_tag = 'green_diff' if not math.isnan(diff) and diff > 0 else 'red_diff' if not math.isnan(diff) and diff < 0 else ''
285-
value = round(diff * (contracts * 100), 2) if outcome and not math.isnan(diff) else ""
286-
value_fmt = f"{int(value)}" if value else ""
287-
strike_price_fmt = int(strike_price) if strike_price == int(strike_price) else round(strike_price, 2)
288-
current_price_fmt = int(current_price) if not math.isnan(current_price) and current_price == int(current_price) else round(current_price, 2) if not math.isnan(current_price) else ""
289-
tag = 'redrow' if outcome else ('oddrow' if int(self.tree.index(item)) % 2 else 'evenrow')
290-
tags = [t for t in tags if t.startswith('list_index_') or t in ('oddrow', 'evenrow', 'redrow')]
291-
tags.append(tag)
292-
self.tree.item(item, tags=tuple(tags))
293-
self.tree.item(item, values=(
294-
row[0], row[1], row[2], row[3], strike_price_fmt, current_price_fmt, diff_fmt, outcome, value_fmt
295-
))
296-
if diff_tag:
297-
self.tree.set(item, "Diff", diff_fmt)
288+
for attempt in range(3):
289+
try:
290+
yf_ticker = yf.Ticker(ticker)
291+
quote = yf_ticker.get_info().get('regularMarketPrice', None)
292+
if quote is None or (isinstance(quote, float) and math.isnan(quote)):
293+
history = yf_ticker.history(period="1d")
294+
quote = round(history["Close"].iloc[-1], 2) if not history.empty and not math.isnan(history["Close"].iloc[-1]) else None
295+
self.price_cache[ticker] = quote if quote is not None and not math.isnan(quote) else "?"
296+
if self.DevMode == 1:
297+
print(f"[{datetime.now().strftime('%H:%M:%S')}] Lookup {ticker}: {'Success' if quote is not None and not math.isnan(quote) else 'Failed'}, Price: {quote if quote is not None and not math.isnan(quote) else 'N/A'}")
298+
break
299+
except Exception as e:
300+
if self.DevMode == 1:
301+
print(f"[{datetime.now().strftime('%H:%M:%S')}] Lookup {ticker}: Failed, Error: {str(e)}")
302+
if attempt < 2:
303+
time_module.sleep(1)
304+
self.price_cache[ticker] = "?"
305+
self.root.after(0, self.populate_treeview)
298306

299-
self.last_updated_label.config(text=f"{datetime.now().strftime('%H:%M:%S')}")
307+
self.root.after(0, self.populate_treeview)
300308

301309
def remove_selected(self):
302310
selected = self.tree.selection()
@@ -344,7 +352,7 @@ def sort_column(self, col):
344352
def get_sort_key(item):
345353
if len(item) != 5:
346354
return (float('inf'), "")
347-
value = item[col_index] if col_index < 5 else "" # Only use stored values for columns 0-4
355+
value = item[col_index] if col_index < 5 else ""
348356
ticker = item[0]
349357
if col == "Ends":
350358
try:
@@ -356,21 +364,26 @@ def get_sort_key(item):
356364
elif col == "Diff":
357365
current_price = self.price_cache.get(ticker, float('nan'))
358366
strike_price = item[4]
359-
if not math.isnan(current_price):
367+
if current_price not in ["Checking", "?"] and not math.isnan(current_price):
360368
diff = current_price - strike_price
361369
return (diff, ticker)
362370
return (float('inf'), ticker)
363371
elif col == "Current":
364372
current_price = self.price_cache.get(ticker, float('nan'))
365-
return (current_price, ticker) if not math.isnan(current_price) else (float('inf'), ticker)
373+
return (current_price, ticker) if current_price not in ["Checking", "?"] and not math.isnan(current_price) else (float('inf'), ticker)
366374
elif col == "Value":
367375
current_price = self.price_cache.get(ticker, float('nan'))
368376
strike_price = item[4]
369377
contracts = item[3]
370-
if not math.isnan(current_price) and self.calculate_outcome(item[2], current_price, strike_price):
378+
if current_price not in ["Checking", "?"] and not math.isnan(current_price) and self.calculate_outcome(item[2], current_price, strike_price):
371379
value = (current_price - strike_price) * (contracts * 100)
372380
return (value, ticker)
373381
return (float('inf'), ticker)
382+
elif col == "Outcome":
383+
current_price = self.price_cache.get(ticker, float('nan'))
384+
outcome = self.calculate_outcome(item[2], current_price, item[4]) if current_price not in ["Checking", "?"] and not math.isnan(current_price) else ""
385+
priority = 1 if outcome == "Purchase" else 2 if outcome == "Sell" else 3
386+
return (priority, ticker)
374387
elif col in ["Contracts", "Strike"]:
375388
return (value, ticker)
376389
else:
@@ -450,5 +463,6 @@ def save_edit(event=None):
450463
if __name__ == "__main__":
451464
root = tk.Tk()
452465
root.geometry("620x300")
466+
root.resizable(True, True)
453467
app = OptionsMonitor(root)
454468
root.mainloop()

0 commit comments

Comments
 (0)