Upload 2 files
Browse files
app.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# streamlit
|
| 2 |
+
|
| 3 |
+
import streamlit as st
|
| 4 |
+
import pandas as pd
|
| 5 |
+
import numpy as np
|
| 6 |
+
import plotly.express as px
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
import time
|
| 10 |
+
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
from datetime import datetime
|
| 13 |
+
|
| 14 |
+
from utils import upload_to_hf_dataset, download_from_hf_dataset, load_hf_dataset
|
| 15 |
+
|
| 16 |
+
# Get current date and time
|
| 17 |
+
# current_datetime = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
| 18 |
+
current_datetime = datetime.now().strftime("%Y-%m-%d")
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# Load environment variables from .env file
|
| 22 |
+
load_dotenv()
|
| 23 |
+
|
| 24 |
+
# Get the name of the HuggingFace dataset for TradingView to read from
|
| 25 |
+
dataset_name_TradingView_input = os.getenv("dataset_name_TradingView_input")
|
| 26 |
+
|
| 27 |
+
# Get the name of the HuggingFace dataset for YfOptions to export
|
| 28 |
+
dataset_name_YfOptions_output = os.getenv("dataset_name_YfOptions_output")
|
| 29 |
+
|
| 30 |
+
# Get the Hugging Face API token from the environment; either set in .env file or in the environment directly in GitHub
|
| 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("america.csv", HF_TOKEN_YfOptions, dataset_name_TradingView_input)
|
| 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 |
+
# tickerlist = ['INDO', 'TSLA', 'AAPL', 'MSFT', 'GOOGL', 'AMZN', 'NFLX', 'META', 'NVDA', 'AMD', 'INTC', 'IBM', 'CSCO', 'ORCL', 'QCOM', 'TXN', 'AVGO', 'ADBE', 'CRM', 'NFLX', 'PYPL', 'SNAP']
|
| 51 |
+
return DF, tickerlst
|
| 52 |
+
|
| 53 |
+
@st.cache_data
|
| 54 |
+
def get_options_DF(current_datetime):
|
| 55 |
+
DF = load_hf_dataset("optionchain.csv", HF_TOKEN_YfOptions, dataset_name_YfOptions_output)
|
| 56 |
+
return DF
|
| 57 |
+
|
| 58 |
+
@st.cache_data
|
| 59 |
+
def convert_df(df):
|
| 60 |
+
return df.to_csv().encode('utf-8')
|
| 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 |
+
# Extract ticker from contractSymbol and merge dataframes
|
| 73 |
+
DF_options_origin['Ticker'] = DF_options_origin['contractSymbol'].str.extract(r'([A-Z]+)')
|
| 74 |
+
TD_interestedColumns = ['Ticker', 'Market Capitalization', 'Relative Volume']
|
| 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 = DF_options_merged.groupby(['Ticker', 'Type'])['volume'].sum().unstack()
|
| 80 |
+
volume_pivot.columns = ['Call_Volume', 'Put_Volume']
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# Pivot the DataFrame to separate 'Call' and 'Put' for openInterest
|
| 84 |
+
openInterest_pivot = DF_options_merged.groupby(['Ticker', 'Type'])['openInterest'].sum().unstack()
|
| 85 |
+
openInterest_pivot.columns = ['Call_openInterest', 'Put_openInterest']
|
| 86 |
+
|
| 87 |
+
# Merge the volume and open interest DataFrames
|
| 88 |
+
merged_df = volume_pivot.merge(openInterest_pivot, left_index=True, right_index=True)
|
| 89 |
+
|
| 90 |
+
# Calculate Put/Call Volume Ratio
|
| 91 |
+
merged_df['Put_Call_Volume_Ratio'] = merged_df['Put_Volume'] / merged_df['Call_Volume'] #.replace(0, pd.NA)
|
| 92 |
+
|
| 93 |
+
# Calculate Put/Call Open Interest Ratio
|
| 94 |
+
merged_df['Put_Call_OI_Ratio'] = merged_df['Put_openInterest'] / merged_df['Call_openInterest'] #.replace(0, pd.NA)
|
| 95 |
+
|
| 96 |
+
DFtotal = pd.merge(DF_options_merged, merged_df, left_on='Ticker', right_index=True, how='left')
|
| 97 |
+
|
| 98 |
+
return DFtotal, tickerlst
|
| 99 |
+
|
| 100 |
+
DF_options, tickerlst = get_options_merge(current_datetime)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
# Title
|
| 108 |
+
st.title("📊 Options Data Dashboard")
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
st.write(f'Number of avialable tickers: {len(tickerlst)}')
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
st.write(f'Number of options records: {len(DF_options)}')
|
| 116 |
+
|
| 117 |
+
# Display options data
|
| 118 |
+
st.header("Options Data")
|
| 119 |
+
|
| 120 |
+
# Sidebar
|
| 121 |
+
st.sidebar.header("Controls")
|
| 122 |
+
st.sidebar.markdown("### Filter Options Data")
|
| 123 |
+
# Add volume and open interest filters in sidebar
|
| 124 |
+
min_volume = st.sidebar.number_input("Minimum Volume", min_value=0, value=100)
|
| 125 |
+
min_open_interest = st.sidebar.number_input("Minimum Open Interest", min_value=0, value=100)
|
| 126 |
+
min_vol_oi_ratio = st.sidebar.number_input("Minimum Volume/Open Interest Ratio", min_value=0.0, value=0.5, step=0.1)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
st.sidebar.markdown("---") # Add a horizontal line as a visual separator
|
| 130 |
+
|
| 131 |
+
st.sidebar.markdown("### Filter Stock Data")
|
| 132 |
+
min_relative_volume = st.sidebar.number_input("Minimum Relative Volume", min_value=0.0, value=1.5, step=0.1)
|
| 133 |
+
min_put_call_volume = st.sidebar.number_input("Minimum Put/Call Volume Ratio", min_value=0.0, value=0.0, step=0.1)
|
| 134 |
+
min_put_call_oi = st.sidebar.number_input("Minimum Put/Call OI Ratio", min_value=0.0, value=0.0, step=0.1)
|
| 135 |
+
|
| 136 |
+
# Filter the dataframe
|
| 137 |
+
filtered_df = DF_options[
|
| 138 |
+
(DF_options['volume'] >= min_volume) &
|
| 139 |
+
(DF_options['openInterest'] >= min_open_interest) &
|
| 140 |
+
(DF_options['Relative Volume'] >= min_relative_volume) &
|
| 141 |
+
(DF_options['Put_Call_Volume_Ratio'] >= min_put_call_volume) &
|
| 142 |
+
(DF_options['Put_Call_OI_Ratio'] >= min_put_call_oi) &
|
| 143 |
+
(DF_options['Volume_OpenInterest_Ratio'] >= min_vol_oi_ratio)
|
| 144 |
+
]
|
| 145 |
+
|
| 146 |
+
st.write(f"Filtered records: {len(filtered_df)} rows")
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
interestedColumns = ['contractSymbol' , 'volume', 'openInterest', 'impliedVolatility', 'Volume_OpenInterest_Ratio' , 'Relative Volume' , 'Put_Call_Volume_Ratio' , 'Put_Call_OI_Ratio' , ]
|
| 150 |
+
|
| 151 |
+
selected_columns = st.multiselect(
|
| 152 |
+
"Select columns to display",
|
| 153 |
+
options=filtered_df.columns.tolist(),
|
| 154 |
+
default=interestedColumns
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
if selected_columns:
|
| 158 |
+
st.dataframe(filtered_df[selected_columns])
|
| 159 |
+
|
| 160 |
+
# Download button for the DataFrame
|
| 161 |
+
csv = convert_df(filtered_df)
|
| 162 |
+
st.download_button(
|
| 163 |
+
label="Download Options Data as CSV",
|
| 164 |
+
data=csv,
|
| 165 |
+
file_name=f'options_data_{current_datetime}.csv',
|
| 166 |
+
mime='text/csv',
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
st.write(f"Filtered Tickers: {filtered_df['Ticker'].unique()} ")
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
st.sidebar.markdown("---") # Add a horizontal line as a visual separator
|
| 175 |
+
st.sidebar.header("Advanced")
|
| 176 |
+
# **Daily Change in Open Interest**
|
| 177 |
+
# 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.
|
| 178 |
+
st.sidebar.button("Daily Change in Open Interest ")
|
| 179 |
+
# contractSymbol
|
| 180 |
+
# lastTradeDate
|
| 181 |
+
# strike
|
| 182 |
+
# lastPrice
|
| 183 |
+
# bid
|
| 184 |
+
# ask
|
| 185 |
+
# change
|
| 186 |
+
# percentChange
|
| 187 |
+
# volume
|
| 188 |
+
# openInterest
|
| 189 |
+
# impliedVolatility
|
| 190 |
+
# inTheMoney
|
| 191 |
+
# contractSize
|
| 192 |
+
# currency
|
| 193 |
+
# Type
|
| 194 |
+
# expirationDate
|
| 195 |
+
# daysleft
|
| 196 |
+
# mark
|
| 197 |
+
# pricepercent
|
| 198 |
+
# pricepercentstrike
|
| 199 |
+
# interinsicvalue
|
| 200 |
+
# interinsicvalue%
|
| 201 |
+
# timevalue
|
| 202 |
+
# timevalue%
|
| 203 |
+
# breakevenprice
|
| 204 |
+
|
| 205 |
+
# # Create sample data
|
| 206 |
+
# np.random.seed(42)
|
| 207 |
+
# data = pd.DataFrame({
|
| 208 |
+
# 'x': np.random.randn(100),
|
| 209 |
+
# 'y': np.random.randn(100),
|
| 210 |
+
# 'category': np.random.choice(['A', 'B', 'C'], 100)
|
| 211 |
+
# })
|
| 212 |
+
|
| 213 |
+
# # Create two columns
|
| 214 |
+
# col1, col2 = st.columns(2)
|
| 215 |
+
|
| 216 |
+
# # First column - Scatter plot
|
| 217 |
+
# with col1:
|
| 218 |
+
# st.subheader("Scatter Plot")
|
| 219 |
+
# fig = px.scatter(data, x='x', y='y', color='category')
|
| 220 |
+
# st.plotly_chart(fig, use_container_width=True)
|
| 221 |
+
|
| 222 |
+
# # Second column - Bar chart
|
| 223 |
+
# with col2:
|
| 224 |
+
# st.subheader("Bar Chart")
|
| 225 |
+
# category_counts = data['category'].value_counts()
|
| 226 |
+
# fig = px.bar(x=category_counts.index, y=category_counts.values)
|
| 227 |
+
# st.plotly_chart(fig, use_container_width=True)
|
| 228 |
+
|
| 229 |
+
# # Add a checkbox
|
| 230 |
+
# if st.checkbox("Show raw data"):
|
| 231 |
+
# st.dataframe(data)
|
| 232 |
+
|
| 233 |
+
# # Add a download button
|
| 234 |
+
# @st.cache_data
|
| 235 |
+
# def convert_df(df):
|
| 236 |
+
# return df.to_csv().encode('utf-8')
|
| 237 |
+
|
| 238 |
+
# csv = convert_df(data)
|
| 239 |
+
# st.download_button(
|
| 240 |
+
# label="Download data as CSV",
|
| 241 |
+
# data=csv,
|
| 242 |
+
# file_name='sample_data.csv',
|
| 243 |
+
# mime='text/csv',
|
| 244 |
+
# )
|
utils.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def upload_to_hf_dataset(file_path, dataset_name, token, repo_type="dataset"):
|
| 2 |
+
"""
|
| 3 |
+
Upload a file to a Hugging Face dataset repository.
|
| 4 |
+
|
| 5 |
+
Args:
|
| 6 |
+
file_path (str): Path to the file to upload
|
| 7 |
+
dataset_name (str): Name of the dataset in format 'username/dataset-name'
|
| 8 |
+
token (str): Hugging Face API token
|
| 9 |
+
repo_type (str): Repository type, defaults to 'dataset'
|
| 10 |
+
"""
|
| 11 |
+
from huggingface_hub import HfApi
|
| 12 |
+
import os
|
| 13 |
+
|
| 14 |
+
# Initialize the Hugging Face API client
|
| 15 |
+
api = HfApi()
|
| 16 |
+
|
| 17 |
+
try:
|
| 18 |
+
# Upload the file to the dataset repository
|
| 19 |
+
api.upload_file(
|
| 20 |
+
path_or_fileobj=file_path,
|
| 21 |
+
path_in_repo=os.path.basename(file_path), # Use filename as path in repo
|
| 22 |
+
repo_id=dataset_name,
|
| 23 |
+
repo_type=repo_type,
|
| 24 |
+
token=token,
|
| 25 |
+
commit_message=f"Upload {os.path.basename(file_path)}",
|
| 26 |
+
commit_description=f"Automated upload of {os.path.basename(file_path)} to dataset",
|
| 27 |
+
)
|
| 28 |
+
print(f"Successfully uploaded {file_path} to {dataset_name}")
|
| 29 |
+
except Exception as e:
|
| 30 |
+
print(f"Error uploading file: {str(e)}")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def download_from_hf_dataset(file_path, dataset_name, token, repo_type="dataset"):
|
| 34 |
+
"""
|
| 35 |
+
Download a file from a Hugging Face dataset repository.
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
file_path (str): Path in the repository to download from
|
| 39 |
+
dataset_name (str): Name of the dataset in format 'username/dataset-name'
|
| 40 |
+
token (str): Hugging Face API token
|
| 41 |
+
repo_type (str): Repository type, defaults to 'dataset'
|
| 42 |
+
"""
|
| 43 |
+
from huggingface_hub import HfApi
|
| 44 |
+
import os
|
| 45 |
+
|
| 46 |
+
# Initialize the Hugging Face API client
|
| 47 |
+
api = HfApi()
|
| 48 |
+
|
| 49 |
+
try:
|
| 50 |
+
# Download the file from the dataset repository
|
| 51 |
+
api.hf_hub_download(
|
| 52 |
+
repo_id=dataset_name,
|
| 53 |
+
filename=file_path,
|
| 54 |
+
repo_type=repo_type,
|
| 55 |
+
local_dir=".",
|
| 56 |
+
token=token,
|
| 57 |
+
)
|
| 58 |
+
print(f"Successfully downloaded {file_path} from {dataset_name}")
|
| 59 |
+
except Exception as e:
|
| 60 |
+
print(f"Error downloading file: {str(e)}")
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def load_hf_dataset(csv_filename, token, dataset_name_input):
|
| 64 |
+
"""
|
| 65 |
+
Load a CSV dataset from Hugging Face and return as pandas DataFrame
|
| 66 |
+
|
| 67 |
+
Args:
|
| 68 |
+
csv_filename (str): Name of the CSV file in the dataset
|
| 69 |
+
token (str): Hugging Face authentication token
|
| 70 |
+
|
| 71 |
+
Returns:
|
| 72 |
+
pandas.DataFrame: DataFrame containing the dataset
|
| 73 |
+
"""
|
| 74 |
+
from datasets import load_dataset
|
| 75 |
+
|
| 76 |
+
try:
|
| 77 |
+
dataset = load_dataset(
|
| 78 |
+
dataset_name_input, data_files=csv_filename, split="train", token=token
|
| 79 |
+
)
|
| 80 |
+
return dataset.to_pandas()
|
| 81 |
+
except Exception as e:
|
| 82 |
+
print(f"Error loading dataset: {e}")
|
| 83 |
+
return None
|
| 84 |
+
|
| 85 |
+
|