Update app.py
Browse files
app.py
CHANGED
|
@@ -31,71 +31,89 @@ dataset_name_YfOptions_output = os.getenv("dataset_name_YfOptions_output")
|
|
| 31 |
HF_TOKEN_YfOptions = os.getenv("HF_TOKEN_YfOptions")
|
| 32 |
|
| 33 |
# Set page configuration
|
| 34 |
-
st.set_page_config(
|
| 35 |
-
page_title="Option Data Screener App",
|
| 36 |
-
page_icon="📊",
|
| 37 |
-
layout="wide"
|
| 38 |
-
)
|
| 39 |
|
| 40 |
|
| 41 |
@st.cache_data
|
| 42 |
def get_TD_DF(current_datetime):
|
| 43 |
# Load lastest TradingView DataSet from HuggingFace Dataset which is always america.csv
|
| 44 |
# download_from_hf_dataset("america.csv", "AmirTrader/TradingViewData", HF_TOKEN_YfOptions)
|
| 45 |
-
DF = load_hf_dataset(
|
|
|
|
|
|
|
| 46 |
|
| 47 |
# get ticker list by filtering only above 1 billion dollar company
|
| 48 |
# DF = pd.read_csv(f'america_2024-03-01.csv')
|
| 49 |
tickerlst = list(DF.query("`Market Capitalization`>10e9").Ticker)
|
| 50 |
-
|
| 51 |
return DF, tickerlst
|
| 52 |
|
|
|
|
| 53 |
@st.cache_data
|
| 54 |
def get_options_DF(current_datetime):
|
| 55 |
-
DF = load_hf_dataset(
|
|
|
|
|
|
|
| 56 |
return DF
|
| 57 |
|
|
|
|
| 58 |
@st.cache_data
|
| 59 |
def convert_df(df):
|
| 60 |
-
return df.to_csv().encode(
|
| 61 |
|
| 62 |
|
| 63 |
@st.cache_data
|
| 64 |
def get_options_merge(current_datetime):
|
| 65 |
-
|
| 66 |
DF, tickerlst = get_TD_DF(current_datetime)
|
| 67 |
|
| 68 |
DF_options_origin = get_options_DF(current_datetime)
|
| 69 |
-
|
| 70 |
-
DF_options_origin['Volume_OpenInterest_Ratio'] = DF_options_origin['volume'] / DF_options_origin['openInterest']
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
DF_options_merged = pd.merge(DF_options_origin, DF[TD_interestedColumns], on='Ticker', how='left')
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
# Pivot the DataFrame to separate 'Call' and 'Put' for volume
|
| 79 |
-
volume_pivot =
|
| 80 |
-
|
| 81 |
-
|
|
|
|
| 82 |
|
| 83 |
# Pivot the DataFrame to separate 'Call' and 'Put' for openInterest
|
| 84 |
-
openInterest_pivot =
|
| 85 |
-
|
|
|
|
|
|
|
| 86 |
|
| 87 |
# Merge the volume and open interest DataFrames
|
| 88 |
-
merged_df = volume_pivot.merge(
|
|
|
|
|
|
|
| 89 |
|
| 90 |
# Calculate Put/Call Volume Ratio
|
| 91 |
-
merged_df[
|
|
|
|
|
|
|
| 92 |
|
| 93 |
# Calculate Put/Call Open Interest Ratio
|
| 94 |
-
merged_df[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
|
| 96 |
-
DFtotal
|
| 97 |
|
| 98 |
-
return DFtotal, tickerlst
|
| 99 |
|
| 100 |
DF_options, tickerlst = get_options_merge(current_datetime)
|
| 101 |
|
|
@@ -103,9 +121,9 @@ DF_options, tickerlst = get_options_merge(current_datetime)
|
|
| 103 |
# Title
|
| 104 |
st.title("📊 Unusual Options Activity Dashboard")
|
| 105 |
|
| 106 |
-
st.write(f
|
| 107 |
|
| 108 |
-
st.write(f
|
| 109 |
|
| 110 |
# Display options data
|
| 111 |
st.header("Options Data")
|
|
@@ -118,32 +136,46 @@ st.sidebar.markdown("### Filter Options Data")
|
|
| 118 |
volume_range = st.sidebar.slider(
|
| 119 |
"Volume Range",
|
| 120 |
min_value=0,
|
| 121 |
-
max_value=DF_options[
|
| 122 |
-
value=(0, DF_options[
|
| 123 |
-
step=100
|
| 124 |
)
|
| 125 |
|
| 126 |
open_interest_range = st.sidebar.slider(
|
| 127 |
-
"Open Interest Range",
|
| 128 |
min_value=0,
|
| 129 |
-
max_value=DF_options[
|
| 130 |
-
value=(0, DF_options[
|
| 131 |
-
step=100
|
| 132 |
)
|
| 133 |
|
| 134 |
# Add range selector for Volume/Open Interest Ratio
|
| 135 |
vol_oi_ratio_range = st.sidebar.slider(
|
| 136 |
"Volume/Open Interest Ratio Range",
|
| 137 |
min_value=0.0,
|
| 138 |
-
max_value=np.nanmax(
|
| 139 |
-
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
)
|
| 142 |
|
| 143 |
st.sidebar.markdown("---") # Add a horizontal line as a visual separator
|
| 144 |
|
| 145 |
st.sidebar.markdown("### Filter Stock Data")
|
| 146 |
-
min_relative_volume = st.sidebar.number_input(
|
|
|
|
|
|
|
| 147 |
|
| 148 |
# Changed to range sliders
|
| 149 |
put_call_volume_range = st.sidebar.slider(
|
|
@@ -151,84 +183,97 @@ put_call_volume_range = st.sidebar.slider(
|
|
| 151 |
min_value=0.0,
|
| 152 |
max_value=10.0,
|
| 153 |
value=(0.0, 0.6),
|
| 154 |
-
step=0.1
|
| 155 |
)
|
| 156 |
|
| 157 |
put_call_oi_range = st.sidebar.slider(
|
| 158 |
-
"Put/Call OI Ratio Range",
|
| 159 |
-
min_value=0.0,
|
| 160 |
-
max_value=10.0,
|
| 161 |
-
value=(0.0, 3.0),
|
| 162 |
-
step=0.1
|
| 163 |
)
|
| 164 |
|
| 165 |
-
if st.sidebar.button("Filter"):
|
| 166 |
# Filter the dataframe with the range
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
|
| 191 |
-
|
| 192 |
-
|
| 193 |
|
| 194 |
# Download button for the DataFrame
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
st.write(f"Filtered Tickers: {filtered_df['Ticker'].unique()} ")
|
| 204 |
|
|
|
|
| 205 |
|
| 206 |
|
| 207 |
st.sidebar.markdown("---") # Add a horizontal line as a visual separator
|
| 208 |
st.sidebar.header("Ticker")
|
| 209 |
|
| 210 |
-
selectedTicker = st.sidebar.selectbox(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
|
| 212 |
if st.sidebar.button("Submit"):
|
| 213 |
st.write(f"Ticker {selectedTicker}")
|
| 214 |
# filter DF_options for selectedTicker
|
| 215 |
-
filtered_ticker_df = DF_options[DF_options[
|
| 216 |
-
#plot plotly3D scatter plot for filtered_ticker_df for volume, openInterest and impliedVolatility
|
| 217 |
-
fig = px.scatter_3d(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
fig.update_layout(
|
| 219 |
-
autosize=True,
|
| 220 |
-
width=2560,
|
| 221 |
-
height=600,
|
| 222 |
-
margin=dict(l=50, r=50, b=50, t=50)
|
| 223 |
)
|
| 224 |
-
st.plotly_chart(fig)
|
| 225 |
# Display the filtered DataFrame
|
| 226 |
-
|
| 227 |
-
|
| 228 |
|
| 229 |
st.sidebar.markdown("---") # Add a horizontal line as a visual separator
|
| 230 |
st.sidebar.header("Advanced")
|
| 231 |
-
# **Daily Change in Open Interest**
|
| 232 |
# Monitoring the increase or decrease in Open Interest compared to the previous day can indicate the inflow (rising OI) or outflow (declining OI) of capital. This factor helps assess the strength of the current trend.
|
| 233 |
st.sidebar.button("Daily Change in Open Interest ")
|
| 234 |
# contractSymbol
|
|
|
|
| 31 |
HF_TOKEN_YfOptions = os.getenv("HF_TOKEN_YfOptions")
|
| 32 |
|
| 33 |
# Set page configuration
|
| 34 |
+
st.set_page_config(page_title="Option Data Screener App", page_icon="📊", layout="wide")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
|
| 37 |
@st.cache_data
|
| 38 |
def get_TD_DF(current_datetime):
|
| 39 |
# Load lastest TradingView DataSet from HuggingFace Dataset which is always america.csv
|
| 40 |
# download_from_hf_dataset("america.csv", "AmirTrader/TradingViewData", HF_TOKEN_YfOptions)
|
| 41 |
+
DF = load_hf_dataset(
|
| 42 |
+
"america.csv", HF_TOKEN_YfOptions, dataset_name_TradingView_input
|
| 43 |
+
)
|
| 44 |
|
| 45 |
# get ticker list by filtering only above 1 billion dollar company
|
| 46 |
# DF = pd.read_csv(f'america_2024-03-01.csv')
|
| 47 |
tickerlst = list(DF.query("`Market Capitalization`>10e9").Ticker)
|
| 48 |
+
|
| 49 |
return DF, tickerlst
|
| 50 |
|
| 51 |
+
|
| 52 |
@st.cache_data
|
| 53 |
def get_options_DF(current_datetime):
|
| 54 |
+
DF = load_hf_dataset(
|
| 55 |
+
"optionchain.csv", HF_TOKEN_YfOptions, dataset_name_YfOptions_output
|
| 56 |
+
)
|
| 57 |
return DF
|
| 58 |
|
| 59 |
+
|
| 60 |
@st.cache_data
|
| 61 |
def convert_df(df):
|
| 62 |
+
return df.to_csv().encode("utf-8")
|
| 63 |
|
| 64 |
|
| 65 |
@st.cache_data
|
| 66 |
def get_options_merge(current_datetime):
|
|
|
|
| 67 |
DF, tickerlst = get_TD_DF(current_datetime)
|
| 68 |
|
| 69 |
DF_options_origin = get_options_DF(current_datetime)
|
|
|
|
|
|
|
| 70 |
|
| 71 |
+
DF_options_origin["Volume_OpenInterest_Ratio"] = (
|
| 72 |
+
DF_options_origin["volume"] / DF_options_origin["openInterest"]
|
| 73 |
+
)
|
|
|
|
| 74 |
|
| 75 |
+
# Extract ticker from contractSymbol and merge dataframes
|
| 76 |
+
DF_options_origin["Ticker"] = DF_options_origin["contractSymbol"].str.extract(
|
| 77 |
+
r"([A-Z]+)"
|
| 78 |
+
)
|
| 79 |
+
TD_interestedColumns = ["Ticker", "Market Capitalization", "Relative Volume"]
|
| 80 |
+
DF_options_merged = pd.merge(
|
| 81 |
+
DF_options_origin, DF[TD_interestedColumns], on="Ticker", how="left"
|
| 82 |
+
)
|
| 83 |
|
| 84 |
# Pivot the DataFrame to separate 'Call' and 'Put' for volume
|
| 85 |
+
volume_pivot = (
|
| 86 |
+
DF_options_merged.groupby(["Ticker", "Type"])["volume"].sum().unstack()
|
| 87 |
+
)
|
| 88 |
+
volume_pivot.columns = ["Call_Volume", "Put_Volume"]
|
| 89 |
|
| 90 |
# Pivot the DataFrame to separate 'Call' and 'Put' for openInterest
|
| 91 |
+
openInterest_pivot = (
|
| 92 |
+
DF_options_merged.groupby(["Ticker", "Type"])["openInterest"].sum().unstack()
|
| 93 |
+
)
|
| 94 |
+
openInterest_pivot.columns = ["Call_openInterest", "Put_openInterest"]
|
| 95 |
|
| 96 |
# Merge the volume and open interest DataFrames
|
| 97 |
+
merged_df = volume_pivot.merge(
|
| 98 |
+
openInterest_pivot, left_index=True, right_index=True
|
| 99 |
+
)
|
| 100 |
|
| 101 |
# Calculate Put/Call Volume Ratio
|
| 102 |
+
merged_df["Put_Call_Volume_Ratio"] = (
|
| 103 |
+
merged_df["Put_Volume"] / merged_df["Call_Volume"]
|
| 104 |
+
) # .replace(0, pd.NA)
|
| 105 |
|
| 106 |
# Calculate Put/Call Open Interest Ratio
|
| 107 |
+
merged_df["Put_Call_OI_Ratio"] = (
|
| 108 |
+
merged_df["Put_openInterest"] / merged_df["Call_openInterest"]
|
| 109 |
+
) # .replace(0, pd.NA)
|
| 110 |
+
|
| 111 |
+
DFtotal = pd.merge(
|
| 112 |
+
DF_options_merged, merged_df, left_on="Ticker", right_index=True, how="left"
|
| 113 |
+
)
|
| 114 |
|
| 115 |
+
return DFtotal, tickerlst
|
| 116 |
|
|
|
|
| 117 |
|
| 118 |
DF_options, tickerlst = get_options_merge(current_datetime)
|
| 119 |
|
|
|
|
| 121 |
# Title
|
| 122 |
st.title("📊 Unusual Options Activity Dashboard")
|
| 123 |
|
| 124 |
+
st.write(f"Number of avialable tickers: {len(tickerlst)}")
|
| 125 |
|
| 126 |
+
st.write(f"Number of options contract records: {len(DF_options)}")
|
| 127 |
|
| 128 |
# Display options data
|
| 129 |
st.header("Options Data")
|
|
|
|
| 136 |
volume_range = st.sidebar.slider(
|
| 137 |
"Volume Range",
|
| 138 |
min_value=0,
|
| 139 |
+
max_value=DF_options["volume"].max().astype(int),
|
| 140 |
+
value=(0, DF_options["volume"].max().astype(int)),
|
| 141 |
+
step=100,
|
| 142 |
)
|
| 143 |
|
| 144 |
open_interest_range = st.sidebar.slider(
|
| 145 |
+
"Open Interest Range",
|
| 146 |
min_value=0,
|
| 147 |
+
max_value=DF_options["openInterest"].max().astype(int),
|
| 148 |
+
value=(0, DF_options["openInterest"].max().astype(int)),
|
| 149 |
+
step=100,
|
| 150 |
)
|
| 151 |
|
| 152 |
# Add range selector for Volume/Open Interest Ratio
|
| 153 |
vol_oi_ratio_range = st.sidebar.slider(
|
| 154 |
"Volume/Open Interest Ratio Range",
|
| 155 |
min_value=0.0,
|
| 156 |
+
max_value=100.0, #np.nanmax(
|
| 157 |
+
# DF_options["Volume_OpenInterest_Ratio"][
|
| 158 |
+
# ~np.isinf(DF_options["Volume_OpenInterest_Ratio"])
|
| 159 |
+
# ]
|
| 160 |
+
# ),
|
| 161 |
+
value=(0.0,10.0), #(
|
| 162 |
+
# 0.5,
|
| 163 |
+
# np.nanmax(
|
| 164 |
+
# DF_options["Volume_OpenInterest_Ratio"][
|
| 165 |
+
# ~np.isinf(DF_options["Volume_OpenInterest_Ratio"])
|
| 166 |
+
# ]
|
| 167 |
+
# )
|
| 168 |
+
# / 2,
|
| 169 |
+
# ),
|
| 170 |
+
step=0.1,
|
| 171 |
)
|
| 172 |
|
| 173 |
st.sidebar.markdown("---") # Add a horizontal line as a visual separator
|
| 174 |
|
| 175 |
st.sidebar.markdown("### Filter Stock Data")
|
| 176 |
+
min_relative_volume = st.sidebar.number_input(
|
| 177 |
+
"Minimum Relative Volume", min_value=0.0, value=1.5, step=0.1
|
| 178 |
+
)
|
| 179 |
|
| 180 |
# Changed to range sliders
|
| 181 |
put_call_volume_range = st.sidebar.slider(
|
|
|
|
| 183 |
min_value=0.0,
|
| 184 |
max_value=10.0,
|
| 185 |
value=(0.0, 0.6),
|
| 186 |
+
step=0.1,
|
| 187 |
)
|
| 188 |
|
| 189 |
put_call_oi_range = st.sidebar.slider(
|
| 190 |
+
"Put/Call OI Ratio Range", min_value=0.0, max_value=10.0, value=(0.0, 3.0), step=0.1
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
)
|
| 192 |
|
| 193 |
+
# if st.sidebar.button("Filter"):
|
| 194 |
# Filter the dataframe with the range
|
| 195 |
+
filtered_df = DF_options[
|
| 196 |
+
(DF_options["volume"] >= volume_range[0])
|
| 197 |
+
& (DF_options["volume"] <= volume_range[1])
|
| 198 |
+
& (DF_options["openInterest"] >= open_interest_range[0])
|
| 199 |
+
& (DF_options["openInterest"] <= open_interest_range[1])
|
| 200 |
+
& (DF_options["Relative Volume"] >= min_relative_volume)
|
| 201 |
+
& (DF_options["Put_Call_Volume_Ratio"] >= put_call_volume_range[0])
|
| 202 |
+
& (DF_options["Put_Call_Volume_Ratio"] <= put_call_volume_range[1])
|
| 203 |
+
& (DF_options["Put_Call_OI_Ratio"] >= put_call_oi_range[0])
|
| 204 |
+
& (DF_options["Put_Call_OI_Ratio"] <= put_call_oi_range[1])
|
| 205 |
+
& (DF_options["Volume_OpenInterest_Ratio"] >= vol_oi_ratio_range[0])
|
| 206 |
+
& (DF_options["Volume_OpenInterest_Ratio"] <= vol_oi_ratio_range[1])
|
| 207 |
+
]
|
| 208 |
+
|
| 209 |
+
st.write(f"Filtered records: {len(filtered_df)} rows")
|
| 210 |
+
|
| 211 |
+
interestedColumns = [
|
| 212 |
+
"contractSymbol",
|
| 213 |
+
"volume",
|
| 214 |
+
"openInterest",
|
| 215 |
+
"impliedVolatility",
|
| 216 |
+
"Volume_OpenInterest_Ratio",
|
| 217 |
+
"Relative Volume",
|
| 218 |
+
"Put_Call_Volume_Ratio",
|
| 219 |
+
"Put_Call_OI_Ratio",
|
| 220 |
+
]
|
| 221 |
+
|
| 222 |
+
selected_columns = st.multiselect(
|
| 223 |
+
"Select columns to display",
|
| 224 |
+
options=filtered_df.columns.tolist(),
|
| 225 |
+
default=interestedColumns,
|
| 226 |
+
)
|
| 227 |
|
| 228 |
+
if selected_columns:
|
| 229 |
+
st.dataframe(filtered_df[selected_columns])
|
| 230 |
|
| 231 |
# Download button for the DataFrame
|
| 232 |
+
csv = convert_df(filtered_df)
|
| 233 |
+
st.download_button(
|
| 234 |
+
label="Download Options Data as CSV",
|
| 235 |
+
data=csv,
|
| 236 |
+
file_name=f"options_data_{current_datetime}.csv",
|
| 237 |
+
mime="text/csv",
|
| 238 |
+
)
|
|
|
|
|
|
|
| 239 |
|
| 240 |
+
st.write(f"Filtered Tickers: {filtered_df['Ticker'].unique()} ")
|
| 241 |
|
| 242 |
|
| 243 |
st.sidebar.markdown("---") # Add a horizontal line as a visual separator
|
| 244 |
st.sidebar.header("Ticker")
|
| 245 |
|
| 246 |
+
selectedTicker = st.sidebar.selectbox(
|
| 247 |
+
"Select Ticker",
|
| 248 |
+
options=tickerlst,
|
| 249 |
+
index=tickerlst.index("AAPL") if "AAPL" in tickerlst else 0,
|
| 250 |
+
)
|
| 251 |
|
| 252 |
if st.sidebar.button("Submit"):
|
| 253 |
st.write(f"Ticker {selectedTicker}")
|
| 254 |
# filter DF_options for selectedTicker
|
| 255 |
+
filtered_ticker_df = DF_options[DF_options["Ticker"] == selectedTicker]
|
| 256 |
+
# plot plotly3D scatter plot for filtered_ticker_df for volume, openInterest and impliedVolatility
|
| 257 |
+
fig = px.scatter_3d(
|
| 258 |
+
filtered_ticker_df,
|
| 259 |
+
x="volume",
|
| 260 |
+
y="openInterest",
|
| 261 |
+
z="impliedVolatility",
|
| 262 |
+
color="Type",
|
| 263 |
+
title=f"3D Scatter Plot for {selectedTicker}",
|
| 264 |
+
hover_data=["strike", "lastPrice", "mark", "daysleft", "contractSymbol", "expirationDate"],
|
| 265 |
+
color_discrete_map={"CALL": "green", "PUT": "red"}
|
| 266 |
+
)
|
| 267 |
fig.update_layout(
|
| 268 |
+
autosize=True, width=2560, height=600, margin=dict(l=50, r=50, b=50, t=50)
|
|
|
|
|
|
|
|
|
|
| 269 |
)
|
| 270 |
+
st.plotly_chart(fig) # , use_container_width=True)
|
| 271 |
# Display the filtered DataFrame
|
| 272 |
+
|
|
|
|
| 273 |
|
| 274 |
st.sidebar.markdown("---") # Add a horizontal line as a visual separator
|
| 275 |
st.sidebar.header("Advanced")
|
| 276 |
+
# **Daily Change in Open Interest**
|
| 277 |
# Monitoring the increase or decrease in Open Interest compared to the previous day can indicate the inflow (rising OI) or outflow (declining OI) of capital. This factor helps assess the strength of the current trend.
|
| 278 |
st.sidebar.button("Daily Change in Open Interest ")
|
| 279 |
# contractSymbol
|