InNoobWeTrust commited on
Commit
1609dbf
β€’
1 Parent(s): a99e2d3

feat: add single asset view and data explorer tabs

Browse files
Files changed (3) hide show
  1. df.py +143 -0
  2. requirements.txt +3 -1
  3. streamlit_app.py +65 -176
df.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import yfinance as yf
3
+ import streamlit as st
4
+
5
+ from typing import List
6
+ from types import SimpleNamespace
7
+
8
+
9
+ def clean_etf_data(df):
10
+ """
11
+ Clean ETF data
12
+ """
13
+ # Copy original
14
+ df_original = df.copy()
15
+ # Set date as index
16
+ df_original["Date"] = pd.to_datetime(df_original["Date"])
17
+
18
+ # Format outflow to negative value
19
+ df = df.drop(columns="Date")
20
+ df.replace(to_replace=r"\(([0-9.]+)\)", value=r"-\1", regex=True, inplace=True)
21
+
22
+ # Replace '-' with 0
23
+ df.replace("-", 0, inplace=True)
24
+
25
+ # Convert from strings to numeric
26
+ df = df.apply(pd.to_numeric)
27
+ df["Date"] = df_original["Date"]
28
+
29
+ return df, df_original
30
+
31
+
32
+ def extract_date_index(df):
33
+ """
34
+ Extract index from dataframe as Date
35
+ """
36
+ # Convert Series to DataFrame
37
+ if isinstance(df, pd.Series):
38
+ df = df.to_frame()
39
+ df = df.reset_index(names="Date")
40
+ # Set date as index
41
+ df.Date = pd.to_datetime(df.Date)
42
+
43
+ return df
44
+
45
+
46
+ def fetch_btc_etf():
47
+ # Get Bitcoin spot ETF history
48
+ btc_etf_flow = pd.read_html(
49
+ "https://farside.co.uk/?p=1321", attrs={"class": "etf"}, skiprows=[1]
50
+ )[0]
51
+ # Remove summary lines
52
+ btc_etf_flow = btc_etf_flow.iloc[:-4]
53
+ # Extract symbols of ETF funds
54
+ btc_etf_funds = btc_etf_flow.drop(columns=["Date", "Total"]).columns.to_list()
55
+
56
+ btc_etf_flow, btc_etf_flow_original = clean_etf_data(btc_etf_flow)
57
+
58
+ return SimpleNamespace(
59
+ flow = btc_etf_flow,
60
+ orig = btc_etf_flow_original,
61
+ funds = btc_etf_funds,
62
+ )
63
+
64
+ def fetch_eth_etf():
65
+ # Get Ethereum spot ETF history
66
+ eth_etf_flow = pd.read_html(
67
+ "https://farside.co.uk/ethereum-etf-flow-all-data/",
68
+ attrs={"class": "etf"},
69
+ skiprows=[2, 3],
70
+ )[0]
71
+ # Drop column index level 2
72
+ eth_etf_flow.columns = eth_etf_flow.columns.droplevel(2)
73
+ # Extract symbols of ETF funds
74
+ eth_etf_funds = (
75
+ eth_etf_flow.drop(columns="Total").columns[1:].get_level_values(1).to_list()
76
+ )
77
+ # Merge multi-index columns
78
+ eth_etf_flow.columns = eth_etf_flow.columns.map(" - ".join)
79
+ # Name first column "Date"
80
+ eth_etf_flow.rename(
81
+ columns={
82
+ "Unnamed: 0_level_0 - Unnamed: 0_level_1": "Date",
83
+ "Total - Unnamed: 10_level_1": "Total",
84
+ },
85
+ inplace=True,
86
+ )
87
+ # Remove summary lines
88
+ eth_etf_flow = eth_etf_flow.iloc[:-1]
89
+ eth_etf_flow, eth_etf_flow_original = clean_etf_data(eth_etf_flow)
90
+
91
+ return SimpleNamespace(
92
+ flow = eth_etf_flow,
93
+ orig = eth_etf_flow_original,
94
+ funds = eth_etf_funds,
95
+ )
96
+
97
+ def fetch_etf_volumes(funds: List[str], start_time=None):
98
+ etf_volumes = pd.DataFrame()
99
+ for fund in funds:
100
+ etf_volumes[fund] = yf.download(
101
+ str(fund), interval = '1d', period = 'max', start = start_time,
102
+ ).Volume
103
+ etf_volumes = extract_date_index(etf_volumes)
104
+
105
+ return etf_volumes
106
+
107
+ def fetch_asset_price(ticker: str, start_time=None):
108
+ price = yf.download(ticker, interval = '1d', period = 'max', start = start_time).Close
109
+ price = extract_date_index(price)
110
+
111
+ return price
112
+
113
+ @st.cache_data
114
+ def fetch(asset):
115
+ if asset == "BTC":
116
+ df = fetch_btc_etf()
117
+ else:
118
+ df = fetch_eth_etf()
119
+
120
+ etf_flow, etf_funds = df.flow, df.funds
121
+ etf_volumes = fetch_etf_volumes(etf_funds, start_time = etf_flow.Date[0])
122
+ price = fetch_asset_price(f'{asset}-USD', start_time = etf_flow.Date[0])
123
+ price.rename(columns={'Close': 'Price'}, inplace=True)
124
+
125
+ etf_flow_individual = etf_flow.drop(columns="Total")
126
+ etf_flow_total = etf_flow[["Date", "Total"]]
127
+
128
+ cum_flow_individual = etf_flow_individual.drop(columns="Date").cumsum()
129
+ cum_flow_individual["Date"] = etf_flow_individual.Date
130
+ cum_flow_total = pd.DataFrame({
131
+ "Date": etf_flow_total.Date,
132
+ "Total": etf_flow_total.Total.cumsum(),
133
+ })
134
+
135
+ return SimpleNamespace(
136
+ etf_flow=etf_flow,
137
+ etf_volumes=etf_volumes,
138
+ price=price,
139
+ etf_flow_individual=etf_flow_individual,
140
+ etf_flow_total=etf_flow_total,
141
+ cum_flow_individual=cum_flow_individual,
142
+ cum_flow_total=cum_flow_total,
143
+ )
requirements.txt CHANGED
@@ -2,4 +2,6 @@ streamlit
2
  pandas
3
  yfinance
4
  altair
5
- vega
 
 
 
2
  pandas
3
  yfinance
4
  altair
5
+ vega
6
+ workalendar
7
+ pygwalker
streamlit_app.py CHANGED
@@ -1,167 +1,18 @@
1
  import pandas as pd
2
- import yfinance as yf
3
 
4
  import streamlit as st
5
  import altair as alt
6
-
7
- from types import SimpleNamespace
8
-
9
  alt.renderers.set_embed_options(theme="dark")
 
10
 
 
11
 
12
- def clean_etf_data(df):
13
- """
14
- Clean ETF data
15
- """
16
- # Copy original
17
- df_original = df.copy()
18
- # Set date as index
19
- df_original["Date"] = pd.to_datetime(df_original["Date"])
20
-
21
- # Format outflow to negative value
22
- df = df.drop(columns="Date")
23
- df.replace(to_replace=r"\(([0-9.]+)\)", value=r"-\1", regex=True, inplace=True)
24
-
25
- # Replace '-' with 0
26
- df.replace("-", 0, inplace=True)
27
-
28
- # Convert from strings to numberic
29
- df = df.apply(pd.to_numeric)
30
- df["Date"] = df_original["Date"]
31
-
32
- return df, df_original
33
-
34
-
35
- def extract_date_index(df):
36
- """
37
- Extract index from dataframe as Date
38
- """
39
- # Convert Series to DataFrame
40
- if isinstance(df, pd.Series):
41
- df = df.to_frame()
42
- df = df.reset_index(names="Date")
43
- # Set date as index
44
- df.Date = pd.to_datetime(df.Date)
45
-
46
- return df
47
-
48
-
49
- ##------------------------- ETF flow --------------------------------------------
50
-
51
- # Get Bitcoin spot ETF history
52
- btc_etf_flow = pd.read_html(
53
- "https://farside.co.uk/?p=1321", attrs={"class": "etf"}, skiprows=[1]
54
- )[0]
55
- # Remove summary lines
56
- btc_etf_flow = btc_etf_flow.iloc[:-4]
57
- # Extract symbols of ETF funds
58
- btc_etf_funds = btc_etf_flow.drop(["Date", "Total"], axis=1).columns.to_list()
59
-
60
- # Get Ethereum spot ETF history
61
- eth_etf_flow = pd.read_html(
62
- "https://farside.co.uk/ethereum-etf-flow-all-data/",
63
- attrs={"class": "etf"},
64
- skiprows=[2, 3],
65
- )[0]
66
- # Drop column index level 2
67
- eth_etf_flow.columns = eth_etf_flow.columns.droplevel(2)
68
- # Extract symbols of ETF funds
69
- eth_etf_funds = (
70
- eth_etf_flow.drop("Total", axis=1).columns[1:].get_level_values(1).to_list()
71
- )
72
- # Merge multi-index columns
73
- eth_etf_flow.columns = eth_etf_flow.columns.map(" | ".join)
74
- # Name first column "Date"
75
- eth_etf_flow.rename(
76
- columns={
77
- "Unnamed: 0_level_0 | Unnamed: 0_level_1": "Date",
78
- "Total | Unnamed: 10_level_1": "Total",
79
- },
80
- inplace=True,
81
- )
82
- # Remove summary lines
83
- eth_etf_flow = eth_etf_flow.iloc[:-1]
84
-
85
- btc_etf_flow, btc_etf_flow_original = clean_etf_data(btc_etf_flow)
86
- eth_etf_flow, eth_etf_flow_original = clean_etf_data(eth_etf_flow)
87
-
88
- ##------------------------- ETF volume -----------------------------------------
89
-
90
- # Get BTC ETF daily volume
91
- btc_etf_volumes = pd.DataFrame()
92
- for fund in btc_etf_funds:
93
- btc_etf_volumes[fund] = yf.download(
94
- f"{fund}", interval="1d", period="max", start=btc_etf_flow.index[0]
95
- )["Volume"]
96
-
97
- # Extract Date column from index
98
- btc_etf_volumes = extract_date_index(btc_etf_volumes)
99
-
100
- # Get ETH ETF daily volume
101
- eth_etf_volumes = pd.DataFrame()
102
- for fund in eth_etf_funds:
103
- eth_etf_volumes[fund] = yf.download(
104
- f"{fund}", interval="1d", period="max", start=eth_etf_flow.index[0]
105
- )["Volume"]
106
-
107
- # Extract Date column from index
108
- eth_etf_volumes = extract_date_index(eth_etf_volumes)
109
-
110
- ##------------------------- Asset price --------------------------------------------
111
-
112
- # Get BTC price history
113
- btc_price = yf.download(
114
- "BTC-USD", interval="1d", period="max", start=btc_etf_flow["Date"][0]
115
- )
116
- btc_price = btc_price.Close
117
- # Extract Date column from index
118
- btc_price = extract_date_index(btc_price)
119
-
120
- # Get ETH price history
121
- eth_price = yf.download(
122
- "ETH-USD", interval="1d", period="max", start=eth_etf_flow["Date"][0]
123
- )
124
- eth_price = eth_price.Close
125
- # Extract Date column from index
126
- eth_price = extract_date_index(eth_price)
127
-
128
-
129
- def gen_data(asset):
130
- if asset == "BTC":
131
- etf_volumes = btc_etf_volumes
132
- price = btc_price
133
-
134
- etf_flow_individual = btc_etf_flow.drop(columns="Total")
135
- etf_flow_total = btc_etf_flow[["Date", "Total"]]
136
- else:
137
- etf_volumes = eth_etf_volumes
138
- price = eth_price
139
-
140
- etf_flow_individual = eth_etf_flow.drop(columns="Total")
141
- etf_flow_total = eth_etf_flow[["Date", "Total"]]
142
-
143
- cum_flow_individual = etf_flow_individual.drop(columns="Date").cumsum()
144
- cum_flow_individual["Date"] = etf_flow_individual.Date
145
- cum_flow_total = pd.DataFrame(
146
- {
147
- "Date": etf_flow_total.Date,
148
- "Total": etf_flow_total.Total.cumsum(),
149
- }
150
- )
151
-
152
- return SimpleNamespace(
153
- etf_volumes=etf_volumes,
154
- price=price,
155
- etf_flow_individual=etf_flow_individual,
156
- etf_flow_total=etf_flow_total,
157
- cum_flow_individual=cum_flow_individual,
158
- cum_flow_total=cum_flow_total,
159
- )
160
 
161
 
162
  def gen_charts(asset, chart_size={"width": 560, "height": 300}):
163
  # Gen data
164
- data = gen_data(asset)
165
  etf_volumes = data.etf_volumes
166
  price = data.price
167
  etf_flow_individual = data.etf_flow_individual
@@ -248,7 +99,7 @@ def gen_charts(asset, chart_size={"width": 560, "height": 300}):
248
  .mark_line()
249
  .encode(
250
  x=alt.X("Date:T", axis=alt.Axis(tickCount="day")),
251
- y=alt.Y("Close:Q", title="Price"),
252
  color=alt.value("crimson"),
253
  )
254
  )
@@ -314,26 +165,22 @@ def gen_charts(asset, chart_size={"width": 560, "height": 300}):
314
  cum_flow_total_fig=cum_flow_total_fig,
315
  )
316
 
 
 
 
 
 
 
 
 
 
 
317
 
318
- def compound_chart(chart_size={"width": 560, "height": 300}):
319
- btc_charts = gen_charts("BTC", chart_size)
320
- eth_charts = gen_charts("ETH", chart_size)
321
 
322
- # Vertical concat the charts in each asset into single colume of that asset
323
- all_charts_btc = (
324
- btc_charts.trading_vol_fig
325
- & btc_charts.net_flow_individual_fig
326
- & btc_charts.net_flow_total_fig
327
- & btc_charts.cum_flow_individual_net_fig
328
- & btc_charts.cum_flow_total_fig
329
- ).resolve_scale(color="independent")
330
- all_charts_eth = (
331
- eth_charts.trading_vol_fig
332
- & eth_charts.net_flow_individual_fig
333
- & eth_charts.net_flow_total_fig
334
- & eth_charts.cum_flow_individual_net_fig
335
- & eth_charts.cum_flow_total_fig
336
- ).resolve_scale(color="independent")
337
  # Horizontal concat the charts for btc and eth
338
  all_charts = (all_charts_btc | all_charts_eth).resolve_scale(color="independent")
339
 
@@ -343,7 +190,49 @@ def compound_chart(chart_size={"width": 560, "height": 300}):
343
  if __name__ == "__main__":
344
  # Set page config
345
  st.set_page_config(layout="wide", page_icon="πŸ“ˆ")
346
-
347
- chart = compound_chart(chart_size={"width": 560, "height": 300})
348
- # Display charts
349
- st.altair_chart(chart, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import pandas as pd
 
2
 
3
  import streamlit as st
4
  import altair as alt
 
 
 
5
  alt.renderers.set_embed_options(theme="dark")
6
+ from pygwalker.api.streamlit import StreamlitRenderer, init_streamlit_comm
7
 
8
+ from types import SimpleNamespace
9
 
10
+ from df import fetch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
 
13
  def gen_charts(asset, chart_size={"width": 560, "height": 300}):
14
  # Gen data
15
+ data = fetch(asset)
16
  etf_volumes = data.etf_volumes
17
  price = data.price
18
  etf_flow_individual = data.etf_flow_individual
 
99
  .mark_line()
100
  .encode(
101
  x=alt.X("Date:T", axis=alt.Axis(tickCount="day")),
102
+ y=alt.Y("Price:Q"),
103
  color=alt.value("crimson"),
104
  )
105
  )
 
165
  cum_flow_total_fig=cum_flow_total_fig,
166
  )
167
 
168
+ def asset_charts(asset: str, chart_size={"width": 'container', "height": 300}):
169
+ charts = gen_charts(asset, chart_size)
170
+ # Vertical concat the charts in each asset into single column of that asset
171
+ all_charts = (
172
+ charts.trading_vol_fig
173
+ & charts.net_flow_individual_fig
174
+ & charts.net_flow_total_fig
175
+ & charts.cum_flow_individual_net_fig
176
+ & charts.cum_flow_total_fig
177
+ ).resolve_scale(color="independent")
178
 
179
+ return all_charts
 
 
180
 
181
+ def compound_chart(chart_size={"width": 560, "height": 300}):
182
+ all_charts_btc = asset_charts('BTC', chart_size)
183
+ all_charts_eth = asset_charts('ETH', chart_size)
 
 
 
 
 
 
 
 
 
 
 
 
184
  # Horizontal concat the charts for btc and eth
185
  all_charts = (all_charts_btc | all_charts_eth).resolve_scale(color="independent")
186
 
 
190
  if __name__ == "__main__":
191
  # Set page config
192
  st.set_page_config(layout="wide", page_icon="πŸ“ˆ")
193
+ # Initialize pygwalker communication
194
+ init_streamlit_comm()
195
+
196
+ dashboard_tab, single_view, flow_tab, volume_tab, price_tab = st.tabs([
197
+ 'Dashboard',
198
+ 'View Single ETF',
199
+ 'Explore ETH Flow',
200
+ 'Explore ETF Volume',
201
+ 'Explore ETF Asset Price',
202
+ ])
203
+
204
+ btc = fetch('BTC')
205
+ eth = fetch('ETH')
206
+
207
+ with dashboard_tab:
208
+ chart = compound_chart(chart_size={"width": 560, "height": 300})
209
+ # Display charts
210
+ st.altair_chart(chart, use_container_width=True)
211
+ with single_view:
212
+ asset = st.selectbox(
213
+ 'Asset to view',
214
+ ('BTC', 'ETH'),
215
+ )
216
+ chart = asset_charts(asset)
217
+ st.altair_chart(chart, use_container_width = True)
218
+ with flow_tab:
219
+ btc_flow, eth_flow = btc.etf_flow, eth.etf_flow
220
+ btc_flow['Asset'] = 'BTC'
221
+ eth_flow['Asset'] = 'ETH'
222
+ df = pd.concat([btc_flow, eth_flow])
223
+ df.Date = df.Date.astype(str)
224
+ StreamlitRenderer(df).explorer()
225
+ with volume_tab:
226
+ btc_volume, eth_volume = btc.etf_volumes, eth.etf_volumes
227
+ btc_volume['Asset'] = 'BTC'
228
+ eth_volume['Asset'] = 'ETH'
229
+ df = pd.concat([btc_volume, eth_volume])
230
+ df.Date = df.Date.astype(str)
231
+ StreamlitRenderer(df).explorer()
232
+ with price_tab:
233
+ btc_price, eth_price = btc.price, eth.price
234
+ btc_price['Asset'] = 'BTC'
235
+ eth_price['Asset'] = 'ETH'
236
+ df = pd.concat([btc_price, eth_price])
237
+ df.Date = df.Date.astype(str)
238
+ StreamlitRenderer(df).explorer()