-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathapp.py
247 lines (205 loc) · 10.2 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# main app
# >>> streamlit run app.py
import pandas as pd
import numpy as np
import streamlit as st
import streamlit.components.v1 as components
from src.optimiser import solver, valid_input_weights
from src.processing import process_price_data, process_coin_metadata
### Functions and option dicts ####
mode_option_dict = {
"Keep it simple plz": 'data/category_groupings_simple.json'
}
mcap_dict = {
"Large market caps only": ["XL Market Cap", "Large Market Cap"],
"Medium or large caps is fine by me": ["XL Market Cap", "Large Market Cap", "Medium Market Cap"],
"Will consider small (within reason)": ["XL Market Cap", "Large Market Cap", "Medium Market Cap", "Small Market Cap"],
}
risk_level_values = {
'High': 0.05,
'Medium': 0.01,
'Low': 0.005
}
def fetch_data(mode, mcap):
category_groupings_path = mode_option_dict.get(mode, 'data/category_groupings.json')
mcap_options = mcap_dict.get(mcap, ["XL Market Cap", "Large Market Cap", "Medium Market Cap", "Small Market Cap", "XS Market Cap"])
mu_expected_return, sigma_covariance, df_prices, to_drop = process_price_data()
(df_meta, dct_category_groupings, dct_coin_category, lst_assets, lst_categories
) = process_coin_metadata(to_drop=to_drop,
mcap_option=mcap_options,
category_groupings_path=category_groupings_path)
return {
"mu_expected_return": mu_expected_return,
"sigma_covariance": sigma_covariance,
"df_prices": df_prices,
"df_meta": df_meta,
"dct_category_groupings": dct_category_groupings,
"dct_coin_category": dct_coin_category,
"lst_assets": lst_assets,
"lst_categories": lst_categories,
}
def solve_function():
allocation = solver(
lst_assets=st.session_state.fetched_data["lst_assets"],
mu_expected_return=st.session_state.fetched_data["mu_expected_return"],
sigma_covariance=st.session_state.fetched_data["sigma_covariance"],
dct_coin_category=st.session_state.fetched_data["dct_coin_category"],
dct_category_groupings=st.session_state.fetched_data["dct_category_groupings"],
weights_assets=st.session_state.weights_assets,
weights_categories=st.session_state.weights_categories,
name_dict=st.session_state.name_dict,
n_max_assets=st.session_state.max_assets,
portfolio_risk_level=risk_level_values[st.session_state.risk_level]
)
st.write("Solve function executed.")
st.write("Your portfolio:", allocation.to_frame())
return allocation
### INITIAL SETUP ###
st.set_page_config(page_title='Crypto Portfolio Optimiser', page_icon="🌙")
st.markdown(""" <style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
</style> """, unsafe_allow_html=True)
padding = 0
st.markdown(f""" <style>
.reportview-container .main .block-container{{
padding-top: {padding}rem;
padding-right: {padding}rem;
padding-left: {padding}rem;
padding-bottom: {padding}rem;
}} </style> """, unsafe_allow_html=True)
st.markdown('# Crypto Portfolio Optimiser')
# sidebar
st.sidebar.markdown('### Important config')
mode = st.sidebar.radio("Number of options:", ("Keep it simple plz", "Give me MAX options"))
mcap = st.sidebar.radio("Type of assets:", ("Large market caps only",
"Medium or large caps is fine by me",
"Will consider small (within reason)",
"I will invest in literally anything"),
index=1)
risk_level = st.sidebar.radio(
"My risk tolerance is:", ("Low", "Medium", "High"), index=1)
n_assets = st.sidebar.radio("Maximum number of assets in portfolio:",
(3, 5, 7, 10), index=1)
investor_type = st.sidebar.radio("I would describe my investment strategy as:", (
"Default",
"ETH-maxi",
"Trad-fi",
"Woof!"
))
show_clicked = st.sidebar.button("Show me which assets fall into each category")
# Check if "Solve" was clicked and call the solve_function
if show_clicked:
st.markdown('#### Assets in each category')
st.markdown('###### (double click on a cell to show all)')
st.write(st.session_state.fetched_data["dct_coin_category"])
### Session state handling ###
if ('fetched_data' not in st.session_state
or st.session_state.selected_mode != mode
or st.session_state.selected_mcap != mcap
or st.session_state.selected_investor != investor_type
or st.session_state.max_assets != n_assets
or st.session_state.risk_level != risk_level
or 'weights_assets' not in st.session_state
):
st.session_state.fetched_data = fetch_data(mode, mcap)
st.session_state.selected_mode = mode
st.session_state.selected_mcap = mcap
st.session_state.risk_level = risk_level
st.session_state.selected_investor = investor_type
st.session_state.max_assets = n_assets
st.session_state.name_dict = st.session_state.fetched_data['df_meta']['name'].to_dict()
if 'weights_assets' not in st.session_state:
st.session_state.weights_assets = {asset: [0.0, 1.0] for asset in st.session_state.fetched_data["lst_assets"]}
else:
for asset in st.session_state.fetched_data["lst_assets"]:
if asset not in st.session_state.weights_assets:
st.session_state.weights_assets[asset] = [0.0, 1.0]
if 'weights_categories' not in st.session_state:
st.session_state.weights_categories = {
grouping: {cat: [0.0, 1.0] for cat in categories}
for grouping, categories in st.session_state.fetched_data["dct_category_groupings"].items()
}
else:
for grouping, categories in st.session_state.fetched_data["dct_category_groupings"].items():
if grouping not in st.session_state.weights_categories:
st.session_state.weights_categories[grouping] = {cat: [0.0, 1.0] for cat in categories}
else:
for cat in categories:
if cat not in st.session_state.weights_categories[grouping]:
st.session_state.weights_categories[grouping][cat] = [0.0, 1.0]
if investor_type == "Trad-fi":
for key, vals in {"Stablecoins": [0.2, 1.0],
"Centralized Exchange (CEX)": [0.2, 1.0],
"Decentralized Finance (DeFi)": [0.0, 0.0],
"NFT": [0.0, 0.0],
"Meme": [0.0, 0.0]}.items():
if key in st.session_state.weights_categories['Category']:
st.session_state.weights_categories['Category'][key] = vals
if investor_type == "ETH-maxi":
for key, vals in {
"Layer 2 (L2)": [0.3, 1.0],
"Centralized Exchange (CEX)": [0.0, 0.0],
"NFT": [0.0, 1.0],
"Meme": [0.0, 1.0]}.items():
if key in st.session_state.weights_categories['Category']:
st.session_state.weights_categories['Category'][key] = vals
st.session_state.weights_categories['Ecosystem']['Ethereum Ecosystem'] = [0.1, 1.0]
if investor_type == "Woof!":
for key, vals in {"Stablecoins": [0.0, 0.0],
"Centralized Exchange (CEX)": [0.0, 0.0],
"NFT": [0.2, 1.0],
"Meme": [0.5, 1.0]}.items():
if key in st.session_state.weights_categories['Category']:
st.session_state.weights_categories['Category'][key] = vals
# add allocation forms for each category
for form_name, my_dict in st.session_state.weights_categories.items():
with st.expander(f"Weight allocation by {form_name} (click to expand)"):
with st.form(form_name):
col1, col2 = st.columns(2)
col1.markdown("#### Minimum allocation")
col2.markdown("#### Maximum allocation")
updated_values = {}
for key, values in my_dict.items():
with col1:
num1a = st.number_input(f"{key} (min)", value=values[0])
with col2:
num2a = st.number_input(f"{key} (max)", value=values[1])
updated_values[key] = [num1a, num2a]
# Form submission button
submitted = st.form_submit_button("Submit")
if submitted:
if valid_input_weights(updated_values):
st.session_state.weights_categories[form_name] = updated_values
st.write("Updated values for", form_name, ":", updated_values)
else:
st.error("Minimum weights must sum to less than or equal to 1 (and ideally less that 0.7 if you want the optimiser to converge)")
# add constraint for each asset
with st.expander(f"Constrain allocation by asset (click to expand)"):
with st.form("Cryptocurrency constraints"):
col1, col2 = st.columns(2)
col1.markdown("#### Minimum allocation")
col2.markdown("#### Maximum allocation")
updated_values = {}
for key, value in st.session_state.weights_assets.items():
if key in st.session_state.fetched_data["lst_assets"]:
with col1:
num1b = st.number_input(f"{st.session_state.name_dict[key]} (min)", value=0.0)
with col2:
num2b = st.number_input(f"{st.session_state.name_dict[key]} (max)", value=1.0)
updated_values[key] = [num1b, num2b]
# Form submission button
submitted = st.form_submit_button("Submit")
if submitted:
if valid_input_weights(updated_values):
st.session_state.weights_assets = updated_values
st.write("Updated values for assets:", updated_values)
else:
st.error("Minimum weights must sum to less than or equal to 1 (and ideally less that 0.7 if you want the optimiser to converge)")
solve_clicked = st.button("Optimise Portfolio")
# Check if "Solve" was clicked and call the solve_function
if solve_clicked:
try:
solve_function()
except Exception as exc:
st.error(f'Could not optimise portfolio, try refreshing then adjusting the constraints.')