Xinli Xiao commited on
Commit
b684a94
·
1 Parent(s): 29807b3
Files changed (4) hide show
  1. __pycache__/app.cpython-313.pyc +0 -0
  2. app.py +148 -0
  3. option.ipynb +286 -0
  4. requirements.txt +4 -0
__pycache__/app.cpython-313.pyc ADDED
Binary file (8.76 kB). View file
 
app.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ def conf_int_ind(symb: str, target_time: str | None = None) -> pd.DataFrame:
10
+ ticker = yf.Ticker(symb)
11
+ expirations = ticker.options
12
+ if not expirations:
13
+ raise ValueError(f"No option expirations found for {symb}.")
14
+
15
+ df_time = pd.to_datetime(expirations)
16
+ if target_time is None:
17
+ target_time_dt = df_time[0]
18
+ else:
19
+ target_time_dt = pd.to_datetime(target_time)
20
+ valid_times = df_time[df_time <= target_time_dt]
21
+ if valid_times.empty:
22
+ raise ValueError(
23
+ f"No expiration on or before {target_time_dt.strftime('%Y-%m-%d')} for {symb}."
24
+ )
25
+ target_time_dt = valid_times[-1]
26
+
27
+ target_time_r = target_time_dt.strftime("%Y-%m-%d")
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 = ticker.option_chain(target_time_r)
32
+ current_price = ticker.fast_info["lastPrice"]
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]
36
+ iv1p = opt_chain.puts.loc[opt_chain.puts.strike == lop, "impliedVolatility"].item()
37
+ iv2p = opt_chain.puts.loc[opt_chain.puts.strike == hip, "impliedVolatility"].item()
38
+ ivp = (iv1p + iv2p) / 2
39
+ sdp = ivp * np.sqrt(t_days / 365)
40
+
41
+ loca = opt_chain.calls.strike[opt_chain.calls.inTheMoney].iloc[-1]
42
+ hica = opt_chain.calls.strike[~opt_chain.calls.inTheMoney].iloc[0]
43
+ iv1c = opt_chain.calls.loc[opt_chain.calls.strike == loca, "impliedVolatility"].item()
44
+ iv2c = opt_chain.calls.loc[opt_chain.calls.strike == hica, "impliedVolatility"].item()
45
+ ivc = (iv1c + iv2c) / 2
46
+ sdc = ivc * np.sqrt(t_days / 365)
47
+
48
+ row = {
49
+ "symbol": [ticker.info["symbol"]],
50
+ "days": [f"{target_time_r}: ({t_days}) days"],
51
+ "-2.5%(p)": [current_price * (1 - 2 * sdp)],
52
+ "-6.5%(p)": [current_price * (1 - 1.5 * sdp)],
53
+ "-16.5%(p)": [current_price * (1 - 1 * sdp)],
54
+ "current": [current_price],
55
+ "+16.5%(c)": [current_price * (1 + 1 * sdc)],
56
+ "+6.5%(c)": [current_price * (1 + 1.5 * sdc)],
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(symb: str, target_time: str | None = None) -> pd.DataFrame:
64
+ if target_time is None:
65
+ expirations = yf.Ticker(symb).options
66
+ df_time = pd.to_datetime(expirations)
67
+ recent_count = (df_time - datetime.now() <= timedelta(days=14)).sum()
68
+ dlist = expirations[:recent_count]
69
+ if not dlist:
70
+ return conf_int_ind(symb, None)
71
+ return pd.concat([conf_int_ind(symb, d) for d in dlist])
72
+
73
+ return conf_int_ind(symb, target_time)
74
+
75
+
76
+ def conf_int(symblist: str | list[str], target_time: str | None = None) -> pd.DataFrame:
77
+ if isinstance(symblist, str):
78
+ return conf_int_duo(symblist, target_time)
79
+ if isinstance(symblist, list):
80
+ return pd.concat([conf_int_duo(symb, target_time) for symb in symblist])
81
+ raise TypeError("symblist must be a string or a list of strings.")
82
+
83
+
84
+ def parse_symbols(symbs_text: str) -> list[str]:
85
+ symbs = [item.strip().upper() for item in symbs_text.replace("\n", ",").split(",")]
86
+ symbs = [item for item in symbs if item]
87
+ if not symbs:
88
+ raise gr.Error("Provide at least one symbol.")
89
+ return symbs
90
+
91
+
92
+ def run_app(symbs_text: str, disable_target_time: bool, target_time: str) -> pd.DataFrame:
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
+ return df.reset_index()
104
+
105
+
106
+ def toggle_target_time(disable_target_time: bool):
107
+ return gr.update(interactive=not disable_target_time, value="" if disable_target_time else None)
108
+
109
+
110
+ with gr.Blocks(title="Options Confidence Interval") as demo:
111
+ gr.Markdown("## Options Confidence Interval")
112
+ gr.Markdown("Enter one or more symbols. Use a comma or a new line to separate multiple symbols.")
113
+
114
+ with gr.Row():
115
+ symbs_input = gr.Textbox(
116
+ label="symbs",
117
+ lines=3,
118
+ value="SOXL, SPY",
119
+ placeholder="SOXL, SPY",
120
+ )
121
+ with gr.Column():
122
+ disable_target_time_input = gr.Checkbox(
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",
129
+ interactive=False,
130
+ )
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,
137
+ inputs=disable_target_time_input,
138
+ outputs=target_time_input,
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
+
147
+ if __name__ == "__main__":
148
+ demo.launch()
option.ipynb ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "b8cef1f0",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "import numpy as np\n",
11
+ "import pandas as pd\n",
12
+ "import yfinance as yf\n",
13
+ "from datetime import datetime, timedelta\n",
14
+ "\n",
15
+ "\n",
16
+ "def conf_int_ind(symb, target_time=None):\n",
17
+ " ticker = yf.Ticker(symb)\n",
18
+ " expirations = ticker.options\n",
19
+ " df_time = pd.to_datetime(expirations)\n",
20
+ " if target_time is None:\n",
21
+ " target_time = df_time[0]\n",
22
+ " target_time_r = df_time[df_time <= target_time][-1].strftime(\"%Y-%m-%d\")\n",
23
+ "\n",
24
+ " time_now = datetime.now().strftime(\"%Y-%m-%d\")\n",
25
+ " T = (pd.to_datetime(target_time_r) - pd.to_datetime(time_now)).days + 1\n",
26
+ "\n",
27
+ " opt_chain = ticker.option_chain(target_time_r)\n",
28
+ " current_price = ticker.fast_info[\"lastPrice\"]\n",
29
+ "\n",
30
+ " lop = opt_chain.puts.strike[~opt_chain.puts.inTheMoney].iloc[-1]\n",
31
+ " hip = opt_chain.puts.strike[opt_chain.puts.inTheMoney].iloc[0]\n",
32
+ " iv1p = opt_chain.puts.loc[opt_chain.puts.strike == lop, \"impliedVolatility\"].item()\n",
33
+ " iv2p = opt_chain.puts.loc[opt_chain.puts.strike == hip, \"impliedVolatility\"].item()\n",
34
+ " ivp = (iv1p + iv2p) / 2\n",
35
+ " sdp = ivp * np.sqrt(T / 365)\n",
36
+ "\n",
37
+ " loca = opt_chain.calls.strike[opt_chain.calls.inTheMoney].iloc[-1]\n",
38
+ " hica = opt_chain.calls.strike[~opt_chain.calls.inTheMoney].iloc[0]\n",
39
+ " iv1c = opt_chain.calls.loc[opt_chain.calls.strike == loca, \"impliedVolatility\"].item()\n",
40
+ " iv2c = opt_chain.calls.loc[opt_chain.calls.strike == hica, \"impliedVolatility\"].item()\n",
41
+ " ivc = (iv1c + iv2c) / 2\n",
42
+ " sdc = ivc * np.sqrt(T / 365)\n",
43
+ "\n",
44
+ " row = {\n",
45
+ " \"symbol\": [f\"{ticker.info['symbol']}\"],\n",
46
+ " \"days\": [f\"{target_time_r}: ({T}) days\"],\n",
47
+ " \"-2.5%(p)\": [current_price * (1 - 2 * sdp)],\n",
48
+ " \"-6.5%(p)\": [current_price * (1 - 1.5 * sdp)],\n",
49
+ " \"-16.5%(p)\": [current_price * (1 - 1 * sdp)],\n",
50
+ " \"current\": [current_price],\n",
51
+ " \"+16.5%(c)\": [current_price * (1 + 1 * sdc)],\n",
52
+ " \"+6.5%(c)\": [current_price * (1 + 1.5 * sdc)],\n",
53
+ " \"+2.5%(c)\": [current_price * (1 + 2 * sdc)],\n",
54
+ " }\n",
55
+ "\n",
56
+ " df = pd.DataFrame(row).set_index(\"symbol\")\n",
57
+ " return df\n",
58
+ "\n",
59
+ "\n",
60
+ "def conf_int_duo(symb, target_time=None):\n",
61
+ " if target_time is None:\n",
62
+ " expirations = yf.Ticker(symb).options\n",
63
+ " df_time = pd.to_datetime(expirations)\n",
64
+ " dlist = expirations[: (df_time - datetime.now() <= timedelta(days=14)).sum()]\n",
65
+ " df = pd.concat([conf_int_ind(symb, d) for d in dlist])\n",
66
+ " else:\n",
67
+ " df = conf_int_ind(symb, target_time)\n",
68
+ " return df\n",
69
+ "\n",
70
+ "\n",
71
+ "def conf_int(symblist, target_time=None):\n",
72
+ " if isinstance(symblist, str):\n",
73
+ " df = conf_int_duo(symblist)\n",
74
+ " elif isinstance(symblist, list):\n",
75
+ " df = pd.concat([conf_int_duo(symb, target_time) for symb in symblist])\n",
76
+ " return df\n"
77
+ ]
78
+ },
79
+ {
80
+ "cell_type": "code",
81
+ "execution_count": 2,
82
+ "id": "587f982a",
83
+ "metadata": {},
84
+ "outputs": [
85
+ {
86
+ "data": {
87
+ "text/html": [
88
+ "<div>\n",
89
+ "<style scoped>\n",
90
+ " .dataframe tbody tr th:only-of-type {\n",
91
+ " vertical-align: middle;\n",
92
+ " }\n",
93
+ "\n",
94
+ " .dataframe tbody tr th {\n",
95
+ " vertical-align: top;\n",
96
+ " }\n",
97
+ "\n",
98
+ " .dataframe thead th {\n",
99
+ " text-align: right;\n",
100
+ " }\n",
101
+ "</style>\n",
102
+ "<table border=\"1\" class=\"dataframe\">\n",
103
+ " <thead>\n",
104
+ " <tr style=\"text-align: right;\">\n",
105
+ " <th></th>\n",
106
+ " <th>days</th>\n",
107
+ " <th>-2.5%(p)</th>\n",
108
+ " <th>-6.5%(p)</th>\n",
109
+ " <th>-16.5%(p)</th>\n",
110
+ " <th>current</th>\n",
111
+ " <th>+16.5%(c)</th>\n",
112
+ " <th>+6.5%(c)</th>\n",
113
+ " <th>+2.5%(c)</th>\n",
114
+ " </tr>\n",
115
+ " <tr>\n",
116
+ " <th>symbol</th>\n",
117
+ " <th></th>\n",
118
+ " <th></th>\n",
119
+ " <th></th>\n",
120
+ " <th></th>\n",
121
+ " <th></th>\n",
122
+ " <th></th>\n",
123
+ " <th></th>\n",
124
+ " <th></th>\n",
125
+ " </tr>\n",
126
+ " </thead>\n",
127
+ " <tbody>\n",
128
+ " <tr>\n",
129
+ " <th>SOXL</th>\n",
130
+ " <td>2026-03-13: (5) days</td>\n",
131
+ " <td>53.222366</td>\n",
132
+ " <td>53.246774</td>\n",
133
+ " <td>53.271183</td>\n",
134
+ " <td>53.320000</td>\n",
135
+ " <td>53.368817</td>\n",
136
+ " <td>53.393225</td>\n",
137
+ " <td>53.417633</td>\n",
138
+ " </tr>\n",
139
+ " <tr>\n",
140
+ " <th>SOXL</th>\n",
141
+ " <td>2026-03-20: (12) days</td>\n",
142
+ " <td>53.168746</td>\n",
143
+ " <td>53.206560</td>\n",
144
+ " <td>53.244373</td>\n",
145
+ " <td>53.320000</td>\n",
146
+ " <td>53.471157</td>\n",
147
+ " <td>53.546735</td>\n",
148
+ " <td>53.622313</td>\n",
149
+ " </tr>\n",
150
+ " <tr>\n",
151
+ " <th>MSFT</th>\n",
152
+ " <td>2026-03-11: (3) days</td>\n",
153
+ " <td>408.829314</td>\n",
154
+ " <td>408.974487</td>\n",
155
+ " <td>409.119659</td>\n",
156
+ " <td>409.410004</td>\n",
157
+ " <td>409.555362</td>\n",
158
+ " <td>409.628041</td>\n",
159
+ " <td>409.700720</td>\n",
160
+ " </tr>\n",
161
+ " <tr>\n",
162
+ " <th>MSFT</th>\n",
163
+ " <td>2026-03-13: (5) days</td>\n",
164
+ " <td>408.660337</td>\n",
165
+ " <td>408.847754</td>\n",
166
+ " <td>409.035170</td>\n",
167
+ " <td>409.410004</td>\n",
168
+ " <td>409.504071</td>\n",
169
+ " <td>409.551105</td>\n",
170
+ " <td>409.598139</td>\n",
171
+ " </tr>\n",
172
+ " <tr>\n",
173
+ " <th>MSFT</th>\n",
174
+ " <td>2026-03-16: (8) days</td>\n",
175
+ " <td>408.935267</td>\n",
176
+ " <td>409.053951</td>\n",
177
+ " <td>409.172635</td>\n",
178
+ " <td>409.410004</td>\n",
179
+ " <td>409.528991</td>\n",
180
+ " <td>409.588485</td>\n",
181
+ " <td>409.647978</td>\n",
182
+ " </tr>\n",
183
+ " <tr>\n",
184
+ " <th>MSFT</th>\n",
185
+ " <td>2026-03-18: (10) days</td>\n",
186
+ " <td>408.879232</td>\n",
187
+ " <td>409.011925</td>\n",
188
+ " <td>409.144618</td>\n",
189
+ " <td>409.410004</td>\n",
190
+ " <td>409.543036</td>\n",
191
+ " <td>409.609551</td>\n",
192
+ " <td>409.676067</td>\n",
193
+ " </tr>\n",
194
+ " <tr>\n",
195
+ " <th>MSFT</th>\n",
196
+ " <td>2026-03-20: (12) days</td>\n",
197
+ " <td>408.828572</td>\n",
198
+ " <td>408.973930</td>\n",
199
+ " <td>409.119288</td>\n",
200
+ " <td>409.410004</td>\n",
201
+ " <td>409.483239</td>\n",
202
+ " <td>409.519857</td>\n",
203
+ " <td>409.556475</td>\n",
204
+ " </tr>\n",
205
+ " </tbody>\n",
206
+ "</table>\n",
207
+ "</div>"
208
+ ],
209
+ "text/plain": [
210
+ " days -2.5%(p) -6.5%(p) -16.5%(p) current \\\n",
211
+ "symbol \n",
212
+ "SOXL 2026-03-13: (5) days 53.222366 53.246774 53.271183 53.320000 \n",
213
+ "SOXL 2026-03-20: (12) days 53.168746 53.206560 53.244373 53.320000 \n",
214
+ "MSFT 2026-03-11: (3) days 408.829314 408.974487 409.119659 409.410004 \n",
215
+ "MSFT 2026-03-13: (5) days 408.660337 408.847754 409.035170 409.410004 \n",
216
+ "MSFT 2026-03-16: (8) days 408.935267 409.053951 409.172635 409.410004 \n",
217
+ "MSFT 2026-03-18: (10) days 408.879232 409.011925 409.144618 409.410004 \n",
218
+ "MSFT 2026-03-20: (12) days 408.828572 408.973930 409.119288 409.410004 \n",
219
+ "\n",
220
+ " +16.5%(c) +6.5%(c) +2.5%(c) \n",
221
+ "symbol \n",
222
+ "SOXL 53.368817 53.393225 53.417633 \n",
223
+ "SOXL 53.471157 53.546735 53.622313 \n",
224
+ "MSFT 409.555362 409.628041 409.700720 \n",
225
+ "MSFT 409.504071 409.551105 409.598139 \n",
226
+ "MSFT 409.528991 409.588485 409.647978 \n",
227
+ "MSFT 409.543036 409.609551 409.676067 \n",
228
+ "MSFT 409.483239 409.519857 409.556475 "
229
+ ]
230
+ },
231
+ "execution_count": 2,
232
+ "metadata": {},
233
+ "output_type": "execute_result"
234
+ }
235
+ ],
236
+ "source": [
237
+ "symbs = [\"SOXL\", \"MSFT\"]\n",
238
+ "\n",
239
+ "df = conf_int(symbs)\n",
240
+ "df"
241
+ ]
242
+ },
243
+ {
244
+ "cell_type": "code",
245
+ "execution_count": null,
246
+ "id": "428526cd",
247
+ "metadata": {},
248
+ "outputs": [],
249
+ "source": [
250
+ "# symb = \"SOXL\"\n",
251
+ "\n",
252
+ "# ticker = yf.Ticker(symb)\n",
253
+ "# expirations = ticker.options\n",
254
+ "# target_time = expirations[0]\n",
255
+ "\n",
256
+ "\n",
257
+ "# time_now = datetime.now().strftime(\"%Y-%m-%d\")\n",
258
+ "# T = (pd.to_datetime(target_time) - pd.to_datetime(time_now)).days + 1\n",
259
+ "\n",
260
+ "# opt_chain = ticker.option_chain(target_time)\n",
261
+ "# current_price = ticker.fast_info[\"lastPrice\"]\n"
262
+ ]
263
+ }
264
+ ],
265
+ "metadata": {
266
+ "kernelspec": {
267
+ "display_name": "env",
268
+ "language": "python",
269
+ "name": "python3"
270
+ },
271
+ "language_info": {
272
+ "codemirror_mode": {
273
+ "name": "ipython",
274
+ "version": 3
275
+ },
276
+ "file_extension": ".py",
277
+ "mimetype": "text/x-python",
278
+ "name": "python",
279
+ "nbconvert_exporter": "python",
280
+ "pygments_lexer": "ipython3",
281
+ "version": "3.13.9"
282
+ }
283
+ },
284
+ "nbformat": 4,
285
+ "nbformat_minor": 5
286
+ }
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio>=5.0.0
2
+ numpy>=1.26.0
3
+ pandas>=2.2.0
4
+ yfinance>=0.2.54