crypto-etf-tracker / streamlit_app.py
InNoobWeTrust
feat: Add iframes of original data from Farside Investor
cf27d1b
import pandas as pd
import streamlit as st
from streamlit.components.v1 import iframe
import altair as alt
from pygwalker.api.streamlit import StreamlitRenderer, init_streamlit_comm
from types import SimpleNamespace
from df import fetch
alt.renderers.set_embed_options(theme="dark")
@st.cache_data(ttl="30m")
def fetch_asset(asset):
return fetch(asset)
def gen_charts(asset, chart_size={"width": 560, "height": 300}):
# Gen data
data = fetch_asset(asset)
etf_volumes = data.etf_volumes
price = data.price
etf_flow_individual = data.etf_flow_individual
etf_flow_total = data.etf_flow_total
cum_flow_individual = data.cum_flow_individual
cum_flow_total = data.cum_flow_total
trading_vol_fig = (
alt.Chart(etf_volumes)
.transform_fold(
etf_volumes.drop(columns="Date").columns.to_list(), as_=["Funds", "Volume"]
)
.mark_line()
.encode(
x=alt.X("Date:T", axis=alt.Axis(tickCount="day")),
y=alt.Y("Volume:Q"),
color="Funds:N",
)
).properties(
width=chart_size["width"],
height=chart_size["height"] / 2,
)
trading_vol_total_fig = (
alt.Chart(etf_volumes)
.transform_fold(
etf_volumes.drop(columns="Date").columns.to_list(), as_=["Funds", "Volume"]
)
.mark_rule()
.encode(
x=alt.X("Date:T", axis=alt.Axis(tickCount="day", title="", labels=False)),
y=alt.Y("sum(Volume):Q", title="Total Volume"),
color=alt.value("teal"),
)
).properties(
width=chart_size["width"],
height=chart_size["height"] / 2,
)
# Combine trading volume and average trading volume
trading_vol_fig = trading_vol_total_fig & trading_vol_fig
trading_vol_fig = trading_vol_fig.properties(
title=f"{asset} ETF trading volume",
)
# Net flow individual
net_flow_individual_fig = (
alt.Chart(etf_flow_individual)
.transform_fold(
etf_flow_individual.drop(columns="Date").columns.to_list(),
as_=["Funds", "Net Flow"],
)
.mark_line()
.encode(
x=alt.X("Date:T", axis=alt.Axis(tickCount="day")),
y=alt.Y("Net Flow:Q"),
color="Funds:N",
)
).properties(
width=chart_size["width"],
height=chart_size["height"] / 2,
)
net_flow_total_fig = (
alt.Chart(etf_flow_total)
.mark_rule()
.encode(
x=alt.X("Date:T", axis=alt.Axis(tickCount="day", title="", labels=False)),
y=alt.Y("Total:Q"),
color=alt.condition(
alt.datum.Total > 0,
alt.value("seagreen"), # The positive color
alt.value("orangered"), # The negative color
),
)
).properties(
width=chart_size["width"],
height=chart_size["height"] / 2,
)
net_flow_individual_fig = net_flow_total_fig & net_flow_individual_fig
net_flow_individual_fig = net_flow_individual_fig.resolve_scale(
x="shared"
).properties(
title=f"{asset} ETF net flow of individual funds",
)
net_flow_total_fig = (
alt.Chart(etf_flow_total)
.mark_rule()
.encode(
x=alt.X("Date:T", axis=alt.Axis(tickCount="day", title="", labels=False)),
y=alt.Y("Total:Q"),
color=alt.condition(
alt.datum.Total > 0,
alt.value("seagreen"), # The positive color
alt.value("orangered"), # The negative color
),
)
).properties(
width=chart_size["width"],
height=chart_size["height"] / 2,
)
# Line chart of price
price_fig = (
alt.Chart(price)
.mark_line()
.encode(
x=alt.X("Date:T", axis=alt.Axis(tickCount="day")),
y=alt.Y("Price:Q").scale(zero=False),
color=alt.value("crimson"),
)
).properties(
width=chart_size["width"],
height=chart_size["height"] / 2,
)
net_flow_total_fig = net_flow_total_fig & price_fig
net_flow_total_fig = net_flow_total_fig.resolve_scale(x="shared").properties(
title=f"{asset} ETF net flow total vs asset price",
)
# Stacking area chart of flow from individual funds
cum_flow_individual_net_fig = (
alt.Chart(cum_flow_individual)
.transform_fold(
cum_flow_individual.drop(columns="Date").columns.to_list(),
as_=["Funds", "Net Flow"],
)
.mark_area()
.encode(
x=alt.X("Date:T", axis=alt.Axis(tickCount="day", title="", labels=False)),
y=alt.Y("Net Flow:Q"),
color=alt.Color("Funds:N", scale=alt.Scale(scheme="tableau20")),
)
).properties(
width=chart_size["width"],
height=chart_size["height"] / 2,
)
cum_flow_individual_net_fig = cum_flow_individual_net_fig & price_fig
cum_flow_individual_net_fig = cum_flow_individual_net_fig.resolve_scale(
x="shared"
).properties(
title=f"{asset} ETF cumulative flow of individual funds vs asset price",
)
# Area chart for cumulative flow
cum_flow_total_fig = (
alt.Chart(cum_flow_total)
.transform_calculate(
negative="datum.Total < 0",
)
.mark_area()
.encode(
x=alt.X("Date:T", axis=alt.Axis(tickCount="day", title="", labels=False)),
y=alt.Y("Total:Q", impute={"value": 0}),
color=alt.Color(
"negative:N", title="Negative Flow", scale=alt.Scale(scheme="set2")
),
)
).properties(
width=chart_size["width"],
height=chart_size["height"] / 2,
)
cum_flow_total_fig = cum_flow_total_fig & price_fig
cum_flow_total_fig = cum_flow_total_fig.resolve_scale(x="shared").properties(
title=f"{asset} ETF cumulative flow total vs asset price",
)
return SimpleNamespace(
trading_vol_fig=trading_vol_fig,
net_flow_individual_fig=net_flow_individual_fig,
net_flow_total_fig=net_flow_total_fig,
cum_flow_individual_net_fig=cum_flow_individual_net_fig,
cum_flow_total_fig=cum_flow_total_fig,
)
def asset_charts(asset: str, chart_size={"width": "container", "height": 300}):
charts = gen_charts(asset, chart_size)
# Vertical concat the charts in each asset into single column of that asset
all_charts = (
charts.trading_vol_fig
& charts.net_flow_individual_fig
& charts.net_flow_total_fig
& charts.cum_flow_individual_net_fig
& charts.cum_flow_total_fig
).resolve_scale(color="independent")
return all_charts
def compound_chart(chart_size={"width": 560, "height": 300}):
all_charts_btc = asset_charts("BTC", chart_size)
all_charts_eth = asset_charts("ETH", chart_size)
# Horizontal concat the charts for btc and eth
all_charts = (all_charts_btc | all_charts_eth).resolve_scale(color="independent")
return all_charts
if __name__ == "__main__":
# Set page config
st.set_page_config(layout="wide", page_icon="πŸ“ˆ")
# Initialize pygwalker communication
init_streamlit_comm()
dashboard_tab, single_view, flow_tab, volume_tab, price_tab = st.tabs(
[
"Dashboard",
"View Single ETF",
"Explore ETF Flow",
"Explore ETF Volume",
"Explore ETF Asset Price",
]
)
btc = fetch_asset("BTC")
eth = fetch_asset("ETH")
with dashboard_tab:
chart = compound_chart(chart_size={"width": 560, "height": 300})
# Display charts
st.altair_chart(chart, use_container_width=True)
btc_col, eth_col = st.columns(2)
with btc_col:
iframe(btc.url, height=1200, scrolling=True)
with eth_col:
iframe(eth.url, height=1200, scrolling=True)
with single_view:
asset = st.selectbox(
"Asset to view",
("BTC", "ETH"),
)
charts = gen_charts(asset, chart_size={"width": "container", "height": 600})
st.altair_chart(charts.trading_vol_fig, use_container_width=True)
st.altair_chart(charts.net_flow_individual_fig, use_container_width=True)
st.altair_chart(charts.net_flow_total_fig, use_container_width=True)
st.altair_chart(charts.cum_flow_individual_net_fig, use_container_width=True)
st.altair_chart(charts.cum_flow_total_fig, use_container_width=True)
iframe(fetch_asset(asset).url, height=1200, scrolling=True)
with flow_tab:
btc_flow, eth_flow = btc.etf_flow, eth.etf_flow
btc_flow["Asset"] = "BTC"
eth_flow["Asset"] = "ETH"
df = pd.concat([btc_flow, eth_flow])
df.Date = df.Date.astype(str)
StreamlitRenderer(df).explorer()
with volume_tab:
btc_volume, eth_volume = btc.etf_volumes, eth.etf_volumes
btc_volume["Asset"] = "BTC"
eth_volume["Asset"] = "ETH"
df = pd.concat([btc_volume, eth_volume])
df.Date = df.Date.astype(str)
StreamlitRenderer(df).explorer()
with price_tab:
btc_price, eth_price = btc.price, eth.price
btc_price["Asset"] = "BTC"
eth_price["Asset"] = "ETH"
df = pd.concat([btc_price, eth_price])
df.Date = df.Date.astype(str)
StreamlitRenderer(df).explorer()