Spaces:
Runtime error
Runtime error
Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import requests
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import altair as alt
|
| 5 |
+
|
| 6 |
+
# Function to fetch data from the API
|
| 7 |
+
def fetch_data(url, headers):
|
| 8 |
+
response = requests.get(url, headers=headers)
|
| 9 |
+
if response.status_code == 200:
|
| 10 |
+
return response.json()['data']
|
| 11 |
+
else:
|
| 12 |
+
st.error("Failed to fetch data")
|
| 13 |
+
return []
|
| 14 |
+
|
| 15 |
+
# Altair chart for combined volume and average price
|
| 16 |
+
def create_chart(df, option_type, strike):
|
| 17 |
+
# Ensure datetime is correctly parsed and localized
|
| 18 |
+
df['tape_time'] = pd.to_datetime(df['tape_time']).dt.tz_localize(None)
|
| 19 |
+
|
| 20 |
+
# Volume chart with custom colors for bid, ask, and mid volumes
|
| 21 |
+
volume_chart = alt.Chart(df).transform_fold(
|
| 22 |
+
fold=['bid_volume', 'ask_volume', 'mid_volume'],
|
| 23 |
+
as_=['Volume Type', 'Volume']
|
| 24 |
+
).mark_bar().encode(
|
| 25 |
+
x='tape_time:T',
|
| 26 |
+
y=alt.Y('Volume:Q', title='Volume'),
|
| 27 |
+
color=alt.Color('Volume Type:N', scale=alt.Scale(domain=['bid_volume', 'ask_volume', 'mid_volume'], range=['#ee5365', '#1fb386', '#2448fb'])),
|
| 28 |
+
tooltip=['tape_time:T', 'bid_volume:Q', 'ask_volume:Q', 'mid_volume:Q']
|
| 29 |
+
).properties(
|
| 30 |
+
width=300,
|
| 31 |
+
height=300
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
# Average price line chart
|
| 35 |
+
price_chart = alt.Chart(df).mark_line(color='#ffa421').encode(
|
| 36 |
+
x='tape_time:T',
|
| 37 |
+
y=alt.Y('avg_price:Q', title='Average Price', scale=alt.Scale(zero=False)),
|
| 38 |
+
tooltip=['tape_time:T', 'avg_price:Q']
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
# Implied Volatility High line chart
|
| 42 |
+
iv_high_chart = alt.Chart(df).mark_line(color='purple').encode(
|
| 43 |
+
x='tape_time:T',
|
| 44 |
+
y=alt.Y('iv_high:Q', title='IV High', scale=alt.Scale(zero=False)),
|
| 45 |
+
tooltip=['tape_time:T', 'iv_high:Q']
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
# Combine the charts
|
| 49 |
+
combined_chart = alt.layer(volume_chart, price_chart, iv_high_chart).resolve_scale(
|
| 50 |
+
y='independent'
|
| 51 |
+
).properties(
|
| 52 |
+
title=f"{option_type} Option Chart for Strike {strike}",
|
| 53 |
+
width=600,
|
| 54 |
+
height=300
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
return combined_chart
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
# Streamlit UI
|
| 61 |
+
st.title('Stacked Market Data Chart for Calls and Puts')
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
with st.form(key='data_form'):
|
| 65 |
+
index = st.text_input('Index (e.g., SPXW)', value='SPXW')
|
| 66 |
+
expiry = st.date_input('Expiry Date')
|
| 67 |
+
atm_strike = st.number_input('ATM Strike (e.g., 52350)', value=52350)
|
| 68 |
+
submit_button = st.form_submit_button('Load Charts')
|
| 69 |
+
|
| 70 |
+
if submit_button:
|
| 71 |
+
headers = {
|
| 72 |
+
"Accept": "application/json",
|
| 73 |
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
| 74 |
+
"Accept-Language": "en-IN,en-DE;q=0.9,en;q=0.8,de-DE;q=0.7,de;q=0.6,en-US;q=0.5,en-GB;q=0.4,ga;q=0.3",
|
| 75 |
+
"Authorization": "Bearer Qo2zhp0Xf3qI88x81tEvgizT_fH29PlLqhrnRcMHP8gvZ_5OCjWl_r23GdbBzolL",
|
| 76 |
+
"Content-Type": "application/json",
|
| 77 |
+
"Origin": "https://unusualwhales.com",
|
| 78 |
+
"Referer": "https://unusualwhales.com/",
|
| 79 |
+
"Sec-Ch-Ua": '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
|
| 80 |
+
"Sec-Ch-Ua-Mobile": "?0",
|
| 81 |
+
"Sec-Ch-Ua-Platform": '"Windows"',
|
| 82 |
+
"Sec-Fetch-Dest": "empty",
|
| 83 |
+
"Sec-Fetch-Mode": "cors",
|
| 84 |
+
"Sec-Fetch-Site": "same-site",
|
| 85 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
| 86 |
+
"Uw-Path": "/flow/option_chains",
|
| 87 |
+
"Uw-Sh": "8lgba0upqzOp4J3C"
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
# Generate strikes in descending order
|
| 92 |
+
strikes = sorted((atm_strike + i * 5 for i in range(-5, 6)), reverse=True)
|
| 93 |
+
expiry_str = expiry.strftime('%y%m%d')
|
| 94 |
+
|
| 95 |
+
# Create columns for calls and puts side by side
|
| 96 |
+
col_call, col_put = st.columns(2)
|
| 97 |
+
|
| 98 |
+
for strike in strikes:
|
| 99 |
+
modified_strike = f"0{strike}000"
|
| 100 |
+
# Fetch and plot for call options
|
| 101 |
+
call_url = f"https://phx.unusualwhales.com/api/chain_aggregates/{index}{expiry_str}C{modified_strike}/intraday?grouping_minutes=5&market_day_timeframe=1"
|
| 102 |
+
call_data = fetch_data(call_url, headers)
|
| 103 |
+
if call_data:
|
| 104 |
+
df_call = pd.DataFrame(call_data)
|
| 105 |
+
call_chart = create_chart(df_call, 'Call', strike)
|
| 106 |
+
with col_call:
|
| 107 |
+
st.altair_chart(call_chart, use_container_width=True)
|
| 108 |
+
|
| 109 |
+
# Fetch and plot for put options
|
| 110 |
+
put_url = f"https://phx.unusualwhales.com/api/chain_aggregates/{index}{expiry_str}P{modified_strike}/intraday?grouping_minutes=5&market_day_timeframe=1"
|
| 111 |
+
put_data = fetch_data(put_url, headers)
|
| 112 |
+
if put_data:
|
| 113 |
+
df_put = pd.DataFrame(put_data)
|
| 114 |
+
put_chart = create_chart(df_put, 'Put', strike)
|
| 115 |
+
with col_put:
|
| 116 |
+
st.altair_chart(put_chart, use_container_width=True)
|
| 117 |
+
|