InNoobWeTrust commited on
Commit
97ae727
β€’
1 Parent(s): 4ab50ad

feat: dashboard for BTC/ETH

Browse files
.devcontainer/devcontainer.json CHANGED
@@ -27,6 +27,9 @@
27
  "onAutoForward": "openPreview"
28
  }
29
  },
 
 
 
30
  "forwardPorts": [
31
  8501
32
  ]
 
27
  "onAutoForward": "openPreview"
28
  }
29
  },
30
+ "appPort": [
31
+ 8501
32
+ ],
33
  "forwardPorts": [
34
  8501
35
  ]
requirements.txt CHANGED
@@ -1 +1,5 @@
1
- streamlit
 
 
 
 
 
1
+ streamlit
2
+ numpy
3
+ pandas
4
+ yfinance
5
+ plotly
streamlit_app.py CHANGED
@@ -1,6 +1,251 @@
1
  import streamlit as st
2
 
3
- st.title("🎈 My new app")
4
- st.write(
5
- "Let's start building! For help and inspiration, head over to [docs.streamlit.io](https://docs.streamlit.io/)."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
 
3
+ import numpy as np
4
+ import pandas as pd
5
+ import yfinance as yf
6
+
7
+ import plotly.express as px
8
+ import plotly.graph_objects as go
9
+ from plotly.subplots import make_subplots
10
+
11
+
12
+ def clean_etf_data(df):
13
+ """
14
+ Clean ETF data
15
+ """
16
+ # Set date as index
17
+ df.Date = pd.to_datetime(df.Date, dayfirst=True)
18
+ df.set_index("Date", inplace=True)
19
+ # Format index to date without time
20
+ df.index = df.index.normalize().date
21
+ # Format outflow to negative value
22
+ df.replace(to_replace=r"\(([0-9.]+)\)", value=r"-\1", regex=True, inplace=True)
23
+
24
+ # Copy original
25
+ df_original = df.copy()
26
+
27
+ # Replace '-' with 0
28
+ df.replace("-", 0, inplace=True)
29
+
30
+ # Convert from strings to numberic
31
+ df = df.apply(pd.to_numeric)
32
+
33
+ return df, df_original
34
+
35
+
36
+ ##------------------------- ETF flow --------------------------------------------
37
+
38
+ # Get Bitcoin spot ETF history
39
+ btc_etf_flow = pd.read_html(
40
+ "https://farside.co.uk/?p=1321", attrs={"class": "etf"}, skiprows=[1]
41
+ )[0]
42
+ # Drop column 'BTC'
43
+ # btc_etf_flow.drop(columns = ['BTC'], inplace = True)
44
+ # Remove summary lines
45
+ btc_etf_flow = btc_etf_flow.iloc[:-4]
46
+ # Extract symbols of ETF funds
47
+ btc_etf_funds = btc_etf_flow.drop(["Date", "Total"], axis=1).columns
48
+
49
+ # Get Ethereum spot ETF history
50
+ eth_etf_flow = pd.read_html(
51
+ "https://farside.co.uk/ethereum-etf-flow-all-data/",
52
+ attrs={"class": "etf"},
53
+ skiprows=[2, 3],
54
+ )[0]
55
+ # Drop column index level 2
56
+ eth_etf_flow.columns = eth_etf_flow.columns.droplevel(2)
57
+ # Extract symbols of ETF funds
58
+ eth_etf_funds = eth_etf_flow.drop("Total", axis=1).columns[1:].get_level_values(1)
59
+ # Merge multi-index columns
60
+ eth_etf_flow.columns = eth_etf_flow.columns.map(" | ".join)
61
+ # Name first column "Date"
62
+ eth_etf_flow.rename(
63
+ columns={
64
+ "Unnamed: 0_level_0 | Unnamed: 0_level_1": "Date",
65
+ "Total | Unnamed: 10_level_1": "Total",
66
+ },
67
+ inplace=True,
68
+ )
69
+ # Remove summary lines
70
+ eth_etf_flow = eth_etf_flow.iloc[:-1]
71
+
72
+ btc_etf_flow, btc_etf_flow_original = clean_etf_data(btc_etf_flow)
73
+ eth_etf_flow, eth_etf_flow_original = clean_etf_data(eth_etf_flow)
74
+
75
+ ##------------------------- ETF volume -----------------------------------------
76
+
77
+ # Get BTC ETF daily volume
78
+ btc_etf_volumes = pd.DataFrame()
79
+ for fund in btc_etf_funds:
80
+ btc_etf_volumes[fund] = yf.download(
81
+ f"{fund}", interval="1d", period="max", start=btc_etf_flow.index[0]
82
+ )["Volume"]
83
+
84
+ # Format index to date without time
85
+ btc_etf_volumes.index = btc_etf_volumes.index.normalize().date
86
+
87
+ # Get ETH ETF daily volume
88
+ eth_etf_volumes = pd.DataFrame()
89
+ for fund in eth_etf_funds:
90
+ eth_etf_volumes[fund] = yf.download(
91
+ f"{fund}", interval="1d", period="max", start=eth_etf_flow.index[0]
92
+ )["Volume"]
93
+
94
+ # Format index to date without time
95
+ eth_etf_volumes.index = eth_etf_volumes.index.normalize().date
96
+
97
+ ##------------------------- Asset price --------------------------------------------
98
+
99
+ # Get BTC price history
100
+ btc_price = yf.download(
101
+ "BTC-USD", interval="1d", period="max", start=btc_etf_flow.index[0]
102
  )
103
+ btc_price = btc_price.Close
104
+ # Format index to date without time
105
+ btc_price.index = btc_price.index.normalize().date
106
+
107
+ # Get ETH price history
108
+ eth_price = yf.download(
109
+ "ETH-USD", interval="1d", period="max", start=eth_etf_flow.index[0]
110
+ )
111
+ eth_price = eth_price.Close
112
+ # Format index to date without time
113
+ eth_price.index = eth_price.index.normalize().date
114
+
115
+
116
+ if __name__ == "__main__":
117
+ # Set page config
118
+ st.set_page_config(layout="wide", page_icon="πŸ“ˆ")
119
+ # Set page title
120
+ st.title("Crypto ETF Dashboard")
121
+
122
+ # Sidebar to choose ETF asset to view
123
+ st.sidebar.title("Crypto ETF Dashboard")
124
+ # Dropdown selection to choose asset (BTC, ETH)
125
+ asset = st.sidebar.selectbox("Choose asset", ("BTC", "ETH"))
126
+
127
+ # Display ETF data
128
+ if asset == "BTC":
129
+ st.header("BTC ETF")
130
+ etf_flow = btc_etf_flow
131
+ etf_volumes = btc_etf_volumes
132
+ price = btc_price
133
+ else:
134
+ st.header("ETH ETF")
135
+ etf_flow = eth_etf_flow
136
+ etf_volumes = eth_etf_volumes
137
+ price = eth_price
138
+
139
+ etf_flow_individual = etf_flow.drop("Total", axis=1)
140
+ etf_flow_total = etf_flow.Total
141
+
142
+ # Section trading volume
143
+ st.subheader(f"{asset} ETF Trading volume")
144
+ trading_vol_fig = px.bar(
145
+ etf_volumes, x=etf_volumes.index, y=etf_volumes.columns, barmode="relative"
146
+ )
147
+ st.plotly_chart(trading_vol_fig, use_container_width=True)
148
+
149
+ # Section net flow individual funds
150
+ st.subheader(f"{asset} ETF Net flow individual funds")
151
+ net_flow_individual_fig = px.bar(
152
+ etf_flow_individual,
153
+ x=etf_flow_individual.index,
154
+ y=etf_flow_individual.columns,
155
+ barmode="relative",
156
+ )
157
+ st.plotly_chart(net_flow_individual_fig, use_container_width=True)
158
+
159
+ # Section net flow total vs asset price
160
+ st.subheader(f"{asset} ETF Net flow total vs asset price")
161
+ positive_flow = etf_flow_total[etf_flow_total > 0]
162
+ negative_flow = etf_flow_total[etf_flow_total < 0]
163
+ net_flow_total_fig = make_subplots(specs=[[{"secondary_y": True}]])
164
+ # Sea green bar for positive flow
165
+ net_flow_total_fig.add_trace(
166
+ go.Bar(
167
+ x=positive_flow.index,
168
+ y=positive_flow,
169
+ name="Total (positive)",
170
+ marker_color="seagreen",
171
+ ),
172
+ secondary_y=False,
173
+ )
174
+ # Orange red bar for negative flow
175
+ net_flow_total_fig.add_trace(
176
+ go.Bar(
177
+ x=negative_flow.index,
178
+ y=negative_flow,
179
+ name="Total (negative)",
180
+ marker_color="orangered",
181
+ ),
182
+ secondary_y=False,
183
+ )
184
+ # Line chart of price
185
+ net_flow_total_fig.add_trace(
186
+ go.Scatter(
187
+ x=price.index,
188
+ y=price,
189
+ name=f"{asset} Price",
190
+ mode="lines",
191
+ line=dict(color="darkgoldenrod"),
192
+ ),
193
+ secondary_y=True,
194
+ )
195
+ net_flow_total_fig.update_layout(barmode="stack")
196
+ st.plotly_chart(net_flow_total_fig, use_container_width=True)
197
+
198
+ # Section cumulative flow individual vs asset price
199
+ st.subheader(f"{asset} ETF Cumulative flow of individual funds vs asset price")
200
+ cum_flow_individual = etf_flow_individual.cumsum()
201
+ cum_flow_individual_fig = make_subplots(specs=[[{"secondary_y": True}]])
202
+ # Stacking area chart of flow from individual funds
203
+ for col in cum_flow_individual.columns:
204
+ cum_flow_individual_fig.add_trace(
205
+ go.Scatter(
206
+ x=cum_flow_individual.index,
207
+ y=cum_flow_individual[col],
208
+ name=col,
209
+ fill="tonexty",
210
+ ),
211
+ secondary_y=False,
212
+ )
213
+ # Line chart of price
214
+ cum_flow_individual_fig.add_trace(
215
+ go.Scatter(
216
+ x=price.index,
217
+ y=price,
218
+ name=f"{asset} Price",
219
+ mode="lines",
220
+ line=dict(color="darkgoldenrod"),
221
+ ),
222
+ secondary_y=True,
223
+ )
224
+ st.plotly_chart(cum_flow_individual_fig, use_container_width=True)
225
+
226
+ # Section cumulative flow total vs asset price
227
+ st.subheader(f"{asset} ETF Cumulative flow total vs asset price")
228
+ cum_flow_total = etf_flow_total.cumsum()
229
+ cum_flow_total_fig = make_subplots(specs=[[{"secondary_y": True}]])
230
+ # Area chart for cumulative flow
231
+ cum_flow_total_fig.add_trace(
232
+ go.Scatter(
233
+ x=cum_flow_total.index,
234
+ y=cum_flow_total,
235
+ name="Cumulative flow total",
236
+ fill="tonexty",
237
+ ),
238
+ secondary_y=False,
239
+ )
240
+ # Line chart of price
241
+ cum_flow_total_fig.add_trace(
242
+ go.Scatter(
243
+ x=price.index,
244
+ y=price,
245
+ name=f"{asset} Price",
246
+ mode="lines",
247
+ line=dict(color="darkgoldenrod"),
248
+ ),
249
+ secondary_y=True,
250
+ )
251
+ st.plotly_chart(cum_flow_total_fig, use_container_width=True)