Xinli Xiao commited on
Commit ·
e0cbbe9
1
Parent(s): 86c20d7
update
Browse files- __pycache__/app.cpython-313.pyc +0 -0
- app.py +92 -21
__pycache__/app.cpython-313.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
|
|
|
app.py
CHANGED
|
@@ -1,14 +1,69 @@
|
|
| 1 |
from datetime import datetime, timedelta
|
|
|
|
|
|
|
| 2 |
|
| 3 |
import gradio as gr
|
| 4 |
import numpy as np
|
| 5 |
import pandas as pd
|
| 6 |
import yfinance as yf
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
if not expirations:
|
| 13 |
raise ValueError(f"No option expirations found for {symb}.")
|
| 14 |
|
|
@@ -28,8 +83,8 @@ def conf_int_ind(symb: str, target_time: str | None = None) -> pd.DataFrame:
|
|
| 28 |
time_now = datetime.now().strftime("%Y-%m-%d")
|
| 29 |
t_days = (pd.to_datetime(target_time_r) - pd.to_datetime(time_now)).days + 1
|
| 30 |
|
| 31 |
-
opt_chain =
|
| 32 |
-
current_price =
|
| 33 |
|
| 34 |
lop = opt_chain.puts.strike[~opt_chain.puts.inTheMoney].iloc[-1]
|
| 35 |
hip = opt_chain.puts.strike[opt_chain.puts.inTheMoney].iloc[0]
|
|
@@ -57,27 +112,37 @@ def conf_int_ind(symb: str, target_time: str | None = None) -> pd.DataFrame:
|
|
| 57 |
"+2.5%(c)": [current_price * (1 + 2 * sdc)],
|
| 58 |
}
|
| 59 |
|
| 60 |
-
return pd.DataFrame(row).set_index("symbol")
|
| 61 |
|
| 62 |
|
| 63 |
-
def conf_int_duo(
|
|
|
|
|
|
|
| 64 |
if target_time is None:
|
| 65 |
-
expirations =
|
| 66 |
df_time = pd.to_datetime(expirations)
|
| 67 |
-
|
| 68 |
-
dlist =
|
| 69 |
if not dlist:
|
| 70 |
-
return conf_int_ind(symb, None)
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
-
return conf_int_ind(symb, target_time)
|
| 74 |
|
| 75 |
|
| 76 |
-
def conf_int(
|
|
|
|
|
|
|
| 77 |
if isinstance(symblist, str):
|
| 78 |
-
return conf_int_duo(symblist, target_time)
|
| 79 |
if isinstance(symblist, list):
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
| 81 |
raise TypeError("symblist must be a string or a list of strings.")
|
| 82 |
|
| 83 |
|
|
@@ -89,18 +154,19 @@ def parse_symbols(symbs_text: str) -> list[str]:
|
|
| 89 |
return symbs
|
| 90 |
|
| 91 |
|
| 92 |
-
def run_app(symbs_text: str, disable_target_time: bool, target_time: str
|
| 93 |
symbs = parse_symbols(symbs_text)
|
| 94 |
resolved_target_time = None if disable_target_time else (target_time or None)
|
| 95 |
if not disable_target_time and resolved_target_time is None:
|
| 96 |
raise gr.Error("Provide target_time in YYYY-MM-DD format, or disable it.")
|
| 97 |
|
| 98 |
try:
|
| 99 |
-
df = conf_int(symbs, resolved_target_time)
|
| 100 |
except Exception as exc:
|
| 101 |
raise gr.Error(str(exc)) from exc
|
| 102 |
|
| 103 |
-
|
|
|
|
| 104 |
|
| 105 |
|
| 106 |
def toggle_target_time(disable_target_time: bool):
|
|
@@ -123,6 +189,10 @@ with gr.Blocks(title="Options Confidence Interval") as demo:
|
|
| 123 |
label="Disable target_time to include all expiration dates within 14 days",
|
| 124 |
value=True,
|
| 125 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
target_time_input = gr.Textbox(
|
| 127 |
label="target_time",
|
| 128 |
placeholder="YYYY-MM-DD",
|
|
@@ -131,6 +201,7 @@ with gr.Blocks(title="Options Confidence Interval") as demo:
|
|
| 131 |
|
| 132 |
submit_btn = gr.Button("Run")
|
| 133 |
output_df = gr.Dataframe(label="Result")
|
|
|
|
| 134 |
|
| 135 |
disable_target_time_input.change(
|
| 136 |
toggle_target_time,
|
|
@@ -139,8 +210,8 @@ with gr.Blocks(title="Options Confidence Interval") as demo:
|
|
| 139 |
)
|
| 140 |
submit_btn.click(
|
| 141 |
run_app,
|
| 142 |
-
inputs=[symbs_input, disable_target_time_input, target_time_input],
|
| 143 |
-
outputs=output_df,
|
| 144 |
)
|
| 145 |
|
| 146 |
|
|
|
|
| 1 |
from datetime import datetime, timedelta
|
| 2 |
+
from functools import lru_cache
|
| 3 |
+
import time
|
| 4 |
|
| 5 |
import gradio as gr
|
| 6 |
import numpy as np
|
| 7 |
import pandas as pd
|
| 8 |
import yfinance as yf
|
| 9 |
|
| 10 |
+
MAX_AUTO_EXPIRATIONS = 3
|
| 11 |
+
RETRY_ATTEMPTS = 3
|
| 12 |
+
RETRY_DELAY_SECONDS = 1.5
|
| 13 |
+
option_chain_cache: dict[tuple[str, str], tuple[object, str]] = {}
|
| 14 |
+
price_cache: dict[str, tuple[float, str]] = {}
|
| 15 |
|
| 16 |
+
|
| 17 |
+
@lru_cache(maxsize=128)
|
| 18 |
+
def get_ticker(symb: str) -> yf.Ticker:
|
| 19 |
+
return yf.Ticker(symb)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@lru_cache(maxsize=128)
|
| 23 |
+
def get_expirations(symb: str) -> tuple[str, ...]:
|
| 24 |
+
return tuple(get_ticker(symb).options)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def get_option_chain_with_retry(ticker: yf.Ticker, target_time: str):
|
| 28 |
+
last_error = None
|
| 29 |
+
for attempt in range(RETRY_ATTEMPTS):
|
| 30 |
+
try:
|
| 31 |
+
return ticker.option_chain(target_time)
|
| 32 |
+
except Exception as exc:
|
| 33 |
+
last_error = exc
|
| 34 |
+
if attempt < RETRY_ATTEMPTS - 1:
|
| 35 |
+
time.sleep(RETRY_DELAY_SECONDS * (attempt + 1))
|
| 36 |
+
raise last_error
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def get_option_chain(symb: str, target_time: str, force_refresh: bool = False):
|
| 40 |
+
cache_key = (symb, target_time)
|
| 41 |
+
if not force_refresh and cache_key in option_chain_cache:
|
| 42 |
+
return option_chain_cache[cache_key]
|
| 43 |
+
|
| 44 |
+
ticker = get_ticker(symb)
|
| 45 |
+
opt_chain = get_option_chain_with_retry(ticker, target_time)
|
| 46 |
+
downloaded_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 47 |
+
option_chain_cache[cache_key] = (opt_chain, downloaded_at)
|
| 48 |
+
return opt_chain, downloaded_at
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def get_current_price(symb: str, force_refresh: bool = False) -> tuple[float, str]:
|
| 52 |
+
if not force_refresh and symb in price_cache:
|
| 53 |
+
return price_cache[symb]
|
| 54 |
+
|
| 55 |
+
ticker = get_ticker(symb)
|
| 56 |
+
current_price = ticker.fast_info["lastPrice"]
|
| 57 |
+
downloaded_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 58 |
+
price_cache[symb] = (current_price, downloaded_at)
|
| 59 |
+
return current_price, downloaded_at
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def conf_int_ind(
|
| 63 |
+
symb: str, target_time: str | None = None, force_refresh: bool = False
|
| 64 |
+
) -> tuple[pd.DataFrame, list[str]]:
|
| 65 |
+
ticker = get_ticker(symb)
|
| 66 |
+
expirations = get_expirations(symb)
|
| 67 |
if not expirations:
|
| 68 |
raise ValueError(f"No option expirations found for {symb}.")
|
| 69 |
|
|
|
|
| 83 |
time_now = datetime.now().strftime("%Y-%m-%d")
|
| 84 |
t_days = (pd.to_datetime(target_time_r) - pd.to_datetime(time_now)).days + 1
|
| 85 |
|
| 86 |
+
opt_chain, option_downloaded_at = get_option_chain(symb, target_time_r, force_refresh)
|
| 87 |
+
current_price, price_downloaded_at = get_current_price(symb, force_refresh)
|
| 88 |
|
| 89 |
lop = opt_chain.puts.strike[~opt_chain.puts.inTheMoney].iloc[-1]
|
| 90 |
hip = opt_chain.puts.strike[opt_chain.puts.inTheMoney].iloc[0]
|
|
|
|
| 112 |
"+2.5%(c)": [current_price * (1 + 2 * sdc)],
|
| 113 |
}
|
| 114 |
|
| 115 |
+
return pd.DataFrame(row).set_index("symbol"), [option_downloaded_at, price_downloaded_at]
|
| 116 |
|
| 117 |
|
| 118 |
+
def conf_int_duo(
|
| 119 |
+
symb: str, target_time: str | None = None, force_refresh: bool = False
|
| 120 |
+
) -> tuple[pd.DataFrame, list[str]]:
|
| 121 |
if target_time is None:
|
| 122 |
+
expirations = get_expirations(symb)
|
| 123 |
df_time = pd.to_datetime(expirations)
|
| 124 |
+
recent_mask = (df_time - datetime.now() <= timedelta(days=14)) & (df_time >= datetime.now())
|
| 125 |
+
dlist = list(df_time[recent_mask].strftime("%Y-%m-%d"))[:MAX_AUTO_EXPIRATIONS]
|
| 126 |
if not dlist:
|
| 127 |
+
return conf_int_ind(symb, None, force_refresh)
|
| 128 |
+
results = [conf_int_ind(symb, d, force_refresh) for d in dlist]
|
| 129 |
+
frames = [result[0] for result in results]
|
| 130 |
+
timestamps = [stamp for result in results for stamp in result[1]]
|
| 131 |
+
return pd.concat(frames), timestamps
|
| 132 |
|
| 133 |
+
return conf_int_ind(symb, target_time, force_refresh)
|
| 134 |
|
| 135 |
|
| 136 |
+
def conf_int(
|
| 137 |
+
symblist: str | list[str], target_time: str | None = None, force_refresh: bool = False
|
| 138 |
+
) -> tuple[pd.DataFrame, list[str]]:
|
| 139 |
if isinstance(symblist, str):
|
| 140 |
+
return conf_int_duo(symblist, target_time, force_refresh)
|
| 141 |
if isinstance(symblist, list):
|
| 142 |
+
results = [conf_int_duo(symb, target_time, force_refresh) for symb in symblist]
|
| 143 |
+
frames = [result[0] for result in results]
|
| 144 |
+
timestamps = [stamp for result in results for stamp in result[1]]
|
| 145 |
+
return pd.concat(frames), timestamps
|
| 146 |
raise TypeError("symblist must be a string or a list of strings.")
|
| 147 |
|
| 148 |
|
|
|
|
| 154 |
return symbs
|
| 155 |
|
| 156 |
|
| 157 |
+
def run_app(symbs_text: str, disable_target_time: bool, target_time: str, force_refresh: bool):
|
| 158 |
symbs = parse_symbols(symbs_text)
|
| 159 |
resolved_target_time = None if disable_target_time else (target_time or None)
|
| 160 |
if not disable_target_time and resolved_target_time is None:
|
| 161 |
raise gr.Error("Provide target_time in YYYY-MM-DD format, or disable it.")
|
| 162 |
|
| 163 |
try:
|
| 164 |
+
df, download_timestamps = conf_int(symbs, resolved_target_time, force_refresh)
|
| 165 |
except Exception as exc:
|
| 166 |
raise gr.Error(str(exc)) from exc
|
| 167 |
|
| 168 |
+
latest_download = max(download_timestamps) if download_timestamps else "Unknown"
|
| 169 |
+
return df.reset_index(), f"Last data download: {latest_download}", gr.update(value=False)
|
| 170 |
|
| 171 |
|
| 172 |
def toggle_target_time(disable_target_time: bool):
|
|
|
|
| 189 |
label="Disable target_time to include all expiration dates within 14 days",
|
| 190 |
value=True,
|
| 191 |
)
|
| 192 |
+
force_refresh_input = gr.Checkbox(
|
| 193 |
+
label="Force refresh",
|
| 194 |
+
value=False,
|
| 195 |
+
)
|
| 196 |
target_time_input = gr.Textbox(
|
| 197 |
label="target_time",
|
| 198 |
placeholder="YYYY-MM-DD",
|
|
|
|
| 201 |
|
| 202 |
submit_btn = gr.Button("Run")
|
| 203 |
output_df = gr.Dataframe(label="Result")
|
| 204 |
+
refresh_timestamp = gr.Textbox(label="Data download timestamp", interactive=False)
|
| 205 |
|
| 206 |
disable_target_time_input.change(
|
| 207 |
toggle_target_time,
|
|
|
|
| 210 |
)
|
| 211 |
submit_btn.click(
|
| 212 |
run_app,
|
| 213 |
+
inputs=[symbs_input, disable_target_time_input, target_time_input, force_refresh_input],
|
| 214 |
+
outputs=[output_df, refresh_timestamp, force_refresh_input],
|
| 215 |
)
|
| 216 |
|
| 217 |
|