InNoobWeTrust commited on
Commit
fd50cbd
Β·
1 Parent(s): 1f4b746

Simplify layout

Browse files
Files changed (2) hide show
  1. requirements.txt +2 -1
  2. streamlit_app.py +205 -89
requirements.txt CHANGED
@@ -1,4 +1,5 @@
1
  streamlit
2
  pandas
3
  yfinance
4
- altair
 
 
1
  streamlit
2
  pandas
3
  yfinance
4
+ altair
5
+ vega
streamlit_app.py CHANGED
@@ -1,47 +1,61 @@
1
- import streamlit as st
2
-
3
  import pandas as pd
4
  import yfinance as yf
5
 
 
6
  import altair as alt
7
 
 
 
 
 
8
 
9
  def clean_etf_data(df):
10
  """
11
  Clean ETF data
12
  """
 
 
13
  # Set date as index
14
- df.Date = pd.to_datetime(df.Date, dayfirst=True)
15
- df.set_index("Date", inplace=True)
16
- # Format index to date without time
17
- df.index = df.index.normalize().date
18
  # Format outflow to negative value
 
19
  df.replace(to_replace=r"\(([0-9.]+)\)", value=r"-\1", regex=True, inplace=True)
20
 
21
- # Copy original
22
- df_original = df.copy()
23
-
24
  # Replace '-' with 0
25
  df.replace("-", 0, inplace=True)
26
 
27
  # Convert from strings to numberic
28
  df = df.apply(pd.to_numeric)
 
29
 
30
  return df, df_original
31
 
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  ##------------------------- ETF flow --------------------------------------------
34
 
35
  # Get Bitcoin spot ETF history
36
  btc_etf_flow = pd.read_html(
37
  "https://farside.co.uk/?p=1321", attrs={"class": "etf"}, skiprows=[1]
38
  )[0]
39
- # Drop column 'BTC'
40
- # btc_etf_flow.drop(columns = ['BTC'], inplace = True)
41
  # Remove summary lines
42
  btc_etf_flow = btc_etf_flow.iloc[:-4]
43
  # Extract symbols of ETF funds
44
- btc_etf_funds = btc_etf_flow.drop(["Date", "Total"], axis=1).columns
45
 
46
  # Get Ethereum spot ETF history
47
  eth_etf_flow = pd.read_html(
@@ -52,7 +66,9 @@ eth_etf_flow = pd.read_html(
52
  # Drop column index level 2
53
  eth_etf_flow.columns = eth_etf_flow.columns.droplevel(2)
54
  # Extract symbols of ETF funds
55
- eth_etf_funds = eth_etf_flow.drop("Total", axis=1).columns[1:].get_level_values(1)
 
 
56
  # Merge multi-index columns
57
  eth_etf_flow.columns = eth_etf_flow.columns.map(" | ".join)
58
  # Name first column "Date"
@@ -78,8 +94,8 @@ for fund in btc_etf_funds:
78
  f"{fund}", interval="1d", period="max", start=btc_etf_flow.index[0]
79
  )["Volume"]
80
 
81
- # Format index to date without time
82
- btc_etf_volumes.index = btc_etf_volumes.index.normalize().date
83
 
84
  # Get ETH ETF daily volume
85
  eth_etf_volumes = pd.DataFrame()
@@ -88,92 +104,137 @@ for fund in eth_etf_funds:
88
  f"{fund}", interval="1d", period="max", start=eth_etf_flow.index[0]
89
  )["Volume"]
90
 
91
- # Format index to date without time
92
- eth_etf_volumes.index = eth_etf_volumes.index.normalize().date
93
 
94
  ##------------------------- Asset price --------------------------------------------
95
 
96
  # Get BTC price history
97
  btc_price = yf.download(
98
- "BTC-USD", interval="1d", period="max", start=btc_etf_flow.index[0]
99
  )
100
  btc_price = btc_price.Close
101
- # Format index to date without time
102
- btc_price.index = btc_price.index.normalize().date
103
 
104
  # Get ETH price history
105
  eth_price = yf.download(
106
- "ETH-USD", interval="1d", period="max", start=eth_etf_flow.index[0]
107
  )
108
  eth_price = eth_price.Close
109
- # Format index to date without time
110
- eth_price.index = eth_price.index.normalize().date
111
 
112
 
113
- if __name__ == "__main__":
114
- # Set page config
115
- st.set_page_config(layout="wide", page_icon="πŸ“ˆ")
116
- # Set page title
117
- st.title("Crypto ETF Dashboard")
118
-
119
- # Sidebar to choose ETF asset to view
120
- st.sidebar.title("Crypto ETF Dashboard")
121
- # Dropdown selection to choose asset (BTC, ETH)
122
- asset = st.sidebar.selectbox("Choose asset", ("BTC", "ETH"))
123
-
124
- # Display ETF data
125
  if asset == "BTC":
126
- st.header("BTC ETF")
127
- etf_flow = btc_etf_flow
128
  etf_volumes = btc_etf_volumes
129
  price = btc_price
 
 
 
130
  else:
131
- st.header("ETH ETF")
132
- etf_flow = eth_etf_flow
133
  etf_volumes = eth_etf_volumes
134
  price = eth_price
135
 
136
- etf_flow_individual = etf_flow.drop("Total", axis=1)
137
- etf_flow_total = etf_flow.Total
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
- # Section trading volume
140
- st.subheader(f"{asset} ETF Trading volume")
141
  trading_vol_fig = (
142
- alt.Chart(etf_volumes.reset_index())
143
- .transform_fold(etf_volumes.columns, as_=["Funds", "Volume"])
 
 
144
  .mark_bar()
145
  .encode(
146
- x=alt.X("index:T", title="Date"),
147
- y=alt.Y("Volume:Q", title="Volume"),
148
  color="Funds:N",
149
  )
150
  )
151
- st.altair_chart(trading_vol_fig, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
- # Section net flow individual funds
154
- st.subheader(f"{asset} ETF Net flow individual funds")
155
  net_flow_individual_fig = (
156
- alt.Chart(etf_flow_individual.reset_index())
157
  .transform_fold(
158
- etf_flow_individual.columns,
159
  as_=["Funds", "Net Flow"],
160
  )
161
  .mark_bar()
162
  .encode(
163
- x=alt.X("index:T", title="Date"),
164
- y=alt.Y("Net Flow:Q", title="Net Flow"),
165
  color="Funds:N",
166
  )
167
  )
168
- st.altair_chart(net_flow_individual_fig, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
- # Section net flow total vs asset price
171
- st.subheader(f"{asset} ETF Net flow total vs asset price")
172
  net_flow_total_fig = (
173
- alt.Chart(etf_flow_total.reset_index())
174
  .mark_bar()
175
  .encode(
176
- x=alt.X("index:T", title="Date"),
177
  y=alt.Y("Total:Q"),
178
  color=alt.condition(
179
  alt.datum.Total > 0,
@@ -184,61 +245,116 @@ if __name__ == "__main__":
184
  )
185
  # Line chart of price
186
  price_fig = (
187
- alt.Chart(price.reset_index())
188
  .mark_line()
189
  .encode(
190
- x=alt.X("index:T", title="Date"),
191
  y=alt.Y("Close:Q", title="Price"),
192
  color=alt.value("crimson"),
193
  )
194
  )
195
- st.altair_chart(
196
- (net_flow_total_fig + price_fig).resolve_scale(
197
- y="independent",
198
- ),
199
- use_container_width=True,
 
 
200
  )
201
 
202
- # Section cumulative flow individual vs asset price
203
- st.subheader(f"{asset} ETF Cumulative flow of individual funds vs asset price")
204
- cum_flow_individual = etf_flow_individual.cumsum()
205
  # Stacking area chart of flow from individual funds
206
  cum_flow_individual_net_fig = (
207
- alt.Chart(cum_flow_individual.reset_index())
208
- .transform_fold(cum_flow_individual.columns, as_=["Funds", "Net Flow"])
 
 
 
209
  .mark_area()
210
  .encode(
211
- x=alt.X("index:T", title="Date"),
212
- y=alt.Y("Net Flow:Q", title="Net Flow"),
213
- color=alt.Color("Funds:N").scale(scheme="tableau20"),
214
  )
215
  )
216
- st.altair_chart(
217
- (cum_flow_individual_net_fig + price_fig).resolve_scale(y="independent"),
218
- use_container_width=True,
 
 
 
219
  )
220
 
221
- # Section cumulative flow total vs asset price
222
- st.subheader(f"{asset} ETF Cumulative flow total vs asset price")
223
- cum_flow_total = etf_flow_total.cumsum()
224
  # Area chart for cumulative flow
225
  cum_flow_total_fig = (
226
- alt.Chart(cum_flow_total.reset_index())
227
  .transform_calculate(
228
  negative="datum.Total < 0",
229
  )
230
  .mark_area()
231
  .encode(
232
- x=alt.X("index:T"),
233
  y=alt.Y("Total:Q", impute={"value": 0}),
234
- color=alt.Color("negative:N", title="Negative Flow").scale(
235
- scheme="set2"
236
  ),
237
  )
238
  )
239
- st.altair_chart(
240
- (cum_flow_total_fig + price_fig).resolve_scale(
241
- y="independent",
242
- ),
243
- use_container_width=True,
 
 
 
 
 
 
 
 
 
244
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(
 
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"
 
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()
 
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
+ {"Date": etf_flow_total.Date, "Total": etf_flow_total.Total.cumsum()}
147
+ )
148
+
149
+ return SimpleNamespace(
150
+ etf_volumes=etf_volumes,
151
+ price=price,
152
+ etf_flow_individual=etf_flow_individual,
153
+ etf_flow_total=etf_flow_total,
154
+ cum_flow_individual=cum_flow_individual,
155
+ cum_flow_total=cum_flow_total,
156
+ )
157
+
158
+
159
+ def gen_charts(asset, chart_size={"width": 560, "height": 300}):
160
+ # Gen data
161
+ data = gen_data(asset)
162
+ etf_volumes = data.etf_volumes
163
+ price = data.price
164
+ etf_flow_individual = data.etf_flow_individual
165
+ etf_flow_total = data.etf_flow_total
166
+ cum_flow_individual = data.cum_flow_individual
167
+ cum_flow_total = data.cum_flow_total
168
 
 
 
169
  trading_vol_fig = (
170
+ alt.Chart(etf_volumes)
171
+ .transform_fold(
172
+ etf_volumes.drop(columns="Date").columns.to_list(), as_=["Funds", "Volume"]
173
+ )
174
  .mark_bar()
175
  .encode(
176
+ x=alt.X("Date:T", axis=alt.Axis(tickCount="day")),
177
+ y=alt.Y("Volume:Q"),
178
  color="Funds:N",
179
  )
180
  )
181
+ trading_vol_avg_fig = (
182
+ alt.Chart(etf_volumes)
183
+ .transform_fold(
184
+ etf_volumes.drop(columns="Date").columns.to_list(), as_=["Funds", "Volume"]
185
+ )
186
+ .mark_line()
187
+ .encode(
188
+ x=alt.X("Date:T", axis=alt.Axis(tickCount="day")),
189
+ y=alt.Y("mean(Volume):Q", title="Average Volume"),
190
+ color=alt.value("crimson"),
191
+ )
192
+ )
193
+ # Combine trading volume and average trading volume
194
+ trading_vol_fig += trading_vol_avg_fig
195
+ trading_vol_fig = trading_vol_fig.resolve_scale(
196
+ y="independent",
197
+ ).properties(
198
+ title=f"{asset} ETF trading volume",
199
+ **chart_size,
200
+ )
201
 
202
+ # Net flow individual
 
203
  net_flow_individual_fig = (
204
+ alt.Chart(etf_flow_individual)
205
  .transform_fold(
206
+ etf_flow_individual.drop(columns="Date").columns.to_list(),
207
  as_=["Funds", "Net Flow"],
208
  )
209
  .mark_bar()
210
  .encode(
211
+ x=alt.X("Date:T", axis=alt.Axis(tickCount="day")),
212
+ y=alt.Y("Net Flow:Q"),
213
  color="Funds:N",
214
  )
215
  )
216
+ net_flow_total_line_fig = (
217
+ alt.Chart(etf_flow_total)
218
+ .mark_line()
219
+ .encode(
220
+ x=alt.X("Date:T", axis=alt.Axis(tickCount="day")),
221
+ y=alt.Y("Total:Q"),
222
+ color=alt.value("crimson"),
223
+ )
224
+ )
225
+ net_flow_individual_fig += net_flow_total_line_fig
226
+ net_flow_individual_fig = net_flow_individual_fig.resolve_scale(
227
+ y="independent",
228
+ ).properties(
229
+ title=f"{asset} ETF net flow of individual funds",
230
+ **chart_size,
231
+ )
232
 
 
 
233
  net_flow_total_fig = (
234
+ alt.Chart(etf_flow_total)
235
  .mark_bar()
236
  .encode(
237
+ x=alt.X("Date:T", axis=alt.Axis(tickCount="day")),
238
  y=alt.Y("Total:Q"),
239
  color=alt.condition(
240
  alt.datum.Total > 0,
 
245
  )
246
  # Line chart of price
247
  price_fig = (
248
+ alt.Chart(price)
249
  .mark_line()
250
  .encode(
251
+ x=alt.X("Date:T", axis=alt.Axis(tickCount="day")),
252
  y=alt.Y("Close:Q", title="Price"),
253
  color=alt.value("crimson"),
254
  )
255
  )
256
+
257
+ net_flow_total_fig += price_fig
258
+ net_flow_total_fig = net_flow_total_fig.resolve_scale(
259
+ y="independent",
260
+ ).properties(
261
+ title=f"{asset} ETF net flow total vs asset price",
262
+ **chart_size,
263
  )
264
 
 
 
 
265
  # Stacking area chart of flow from individual funds
266
  cum_flow_individual_net_fig = (
267
+ alt.Chart(cum_flow_individual)
268
+ .transform_fold(
269
+ cum_flow_individual.drop(columns="Date").columns.to_list(),
270
+ as_=["Funds", "Net Flow"],
271
+ )
272
  .mark_area()
273
  .encode(
274
+ x=alt.X("Date:T", axis=alt.Axis(tickCount="day")),
275
+ y=alt.Y("Net Flow:Q"),
276
+ color=alt.Color("Funds:N", scale=alt.Scale(scheme="tableau20")),
277
  )
278
  )
279
+ cum_flow_individual_net_fig += price_fig
280
+ cum_flow_individual_net_fig = cum_flow_individual_net_fig.resolve_scale(
281
+ y="independent",
282
+ ).properties(
283
+ title=f"{asset} ETF cumulative flow of individual funds vs asset price",
284
+ **chart_size,
285
  )
286
 
 
 
 
287
  # Area chart for cumulative flow
288
  cum_flow_total_fig = (
289
+ alt.Chart(cum_flow_total)
290
  .transform_calculate(
291
  negative="datum.Total < 0",
292
  )
293
  .mark_area()
294
  .encode(
295
+ x=alt.X("Date:T", axis=alt.Axis(tickCount="day")),
296
  y=alt.Y("Total:Q", impute={"value": 0}),
297
+ color=alt.Color(
298
+ "negative:N", title="Negative Flow", scale=alt.Scale(scheme="set2")
299
  ),
300
  )
301
  )
302
+ cum_flow_total_fig += price_fig
303
+ cum_flow_total_fig = cum_flow_total_fig.resolve_scale(
304
+ y="independent",
305
+ ).properties(
306
+ title=f"{asset} ETF cumulative flow total vs asset price",
307
+ **chart_size,
308
+ )
309
+
310
+ return SimpleNamespace(
311
+ trading_vol_fig=trading_vol_fig,
312
+ net_flow_individual_fig=net_flow_individual_fig,
313
+ net_flow_total_fig=net_flow_total_fig,
314
+ cum_flow_individual_net_fig=cum_flow_individual_net_fig,
315
+ cum_flow_total_fig=cum_flow_total_fig,
316
  )
317
+
318
+
319
+ def compound_chart(chart_size={"width": 560, "height": 300}):
320
+ btc_charts = gen_charts("BTC", chart_size)
321
+ eth_charts = gen_charts("ETH", chart_size)
322
+
323
+ # Vertical concat the charts in each asset into single colume of that asset
324
+ all_charts_btc = (
325
+ btc_charts.trading_vol_fig
326
+ & btc_charts.net_flow_individual_fig
327
+ & btc_charts.net_flow_total_fig
328
+ & btc_charts.cum_flow_individual_net_fig
329
+ & btc_charts.cum_flow_total_fig
330
+ ).resolve_scale(color="independent")
331
+ all_charts_eth = (
332
+ eth_charts.trading_vol_fig
333
+ & eth_charts.net_flow_individual_fig
334
+ & eth_charts.net_flow_total_fig
335
+ & eth_charts.cum_flow_individual_net_fig
336
+ & eth_charts.cum_flow_total_fig
337
+ ).resolve_scale(color="independent")
338
+ # Horizontal concat the charts for btc and eth
339
+ all_charts = (all_charts_btc | all_charts_eth).resolve_scale(color="independent")
340
+
341
+ return all_charts
342
+
343
+
344
+ if __name__ == "__main__":
345
+ # Set page config
346
+ st.set_page_config(layout="wide", page_icon="πŸ“ˆ")
347
+
348
+ chart = compound_chart(chart_size={"width": 560, "height": 300})
349
+ all_charts = (all_charts_btc | all_charts_eth).resolve_scale(color="independent")
350
+
351
+ return all_charts
352
+
353
+
354
+ if __name__ == "__main__":
355
+ # Set page config
356
+ st.set_page_config(layout="wide", page_icon="πŸ“ˆ")
357
+
358
+ chart = compound_chart(chart_size={"width": 560, "height": 300})
359
+ # Display charts
360
+ st.altair_chart(chart, use_container_width=True)