-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.py
145 lines (119 loc) · 5.64 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
from shiny import App, Inputs, Outputs, Session, render, ui, reactive
import shiny.experimental as x
from core_funcs import np, datetime, timedelta, date, min_date_dfs, stock_returns, get_params, portfolios, portfolios_plot, get_tickers,random_weights
from shinyswatch import theme
from re import sub, split
from htmltools import css
#make sure the date exists
today = date.today() - timedelta(days=3)
def card(title, values, ispercent):
cleaned_lists = sub(r'[\[\]\(\)]|[a-z]', '', str(values))
percentages = [str(np.round((np.float16(i) * 100), 2)) + "%" for i in split(' ', cleaned_lists) if i != '']
non_percent = sub(r'[\[\]]', '', str(values))
if(ispercent):
toDisplay = percentages
else:
toDisplay = non_percent
return(
x.ui.card(
x.ui.card_title(title, style=css(font_weight="bold", color="#002147")),
x.ui.card_body(
ui.div(ui.h2(sub(r'[\[\]\(\),\']', '',str(toDisplay))))
),
height = '130px'
)
)
app_ui = ui.page_fluid(
theme.cosmo(),
#ui.panel_conditional("input.calculate", x.ui.layout_column_wrap(1 / 4, *[value_box(title) for title in ["Optimal Weights", "Sharpe ratio", "three", "four"]])),
ui.output_ui("value_boxes"),
ui.layout_sidebar(
ui.panel_sidebar(
ui.row(
ui.column(
4,
#70 days, slightly bigger than 2 months, the minimum needed for there to be any variance (as were using average monthly returns)
ui.input_date(id = "start", label = "Start", max = today - timedelta(days=96), value = today - timedelta(days = 365), width = "130px"),
ui.input_date(id = "end", label = "End", max = today, value = today, min = today, width = "130px")
),
ui.column(
4,
ui.input_selectize("tickers", "Choose Tickers", choices = get_tickers(), multiple=True)
),
),
ui.input_slider("samples", "Number of Samples", min=1000, max=20000, value=5000, step = 200),
ui.input_action_button("calculate", "Calculate"),
ui.output_text("txter")
),
ui.panel_main(
ui.output_plot("plot"),
ui.output_text_verbatim("txt"),
),
),
)
def server(input: Inputs, output: Outputs, session: Session):
def weights():
return random_weights(input.samples(), len(input.tickers()))
#validation method, if all checks, we return an event, else return a modal message
@reactive.Calc
@reactive.event(input.calculate)
def validate():
if(len(input.tickers()) > 1):
return 1
else:
m = ui.modal(
"The ticker length must be greater than 1.",
easy_close=True,
footer=None,
)
return ui.modal_show(m)
#update the selectize order, to match the weights values, np.unique sorts names
@reactive.Effect
@reactive.event(validate)
def _():
a = list(input.tickers())
a.sort()
ui.update_selectize(
"tickers",
selected = a
)
@reactive.Calc
def main_df():
lower_bound, dfs = min_date_dfs(symbols = input.tickers(), start = input.start(), end = input.end())
return dfs
@reactive.Calc
def portfolio_vis_data():
df = stock_returns(main_df())
params = get_params(df)
all_portfolios = portfolios(params, df, weights=weights())
return all_portfolios
@output
@render.ui
@reactive.event(validate)
def value_boxes():
portfolio_data = portfolio_vis_data()
optimal_weights, min_variance_weights = [np.round(i, 3) for i in [portfolio_data[3], portfolio_data[4]]]
return ui.TagList(
x.ui.layout_column_wrap(1 / 4, *[card(title, val, perc) for title,val,perc in zip(["Optimal Weights", "Highest Sharpe Ratio", "Minimum Variance Portfolio", "Minimum Variance"],
[optimal_weights,
np.float16(np.round(portfolio_data[2], 3)),
min_variance_weights, portfolio_data[5]],
[True] * 3 + [False]
)])
)
@output
@render.plot
@reactive.event(validate)
def plot():
return portfolios_plot(*portfolio_vis_data()[0:2], weights = random_weights(input.samples(), len(input.tickers())), names = np.sort(input.tickers()))
@output
@render.text
@reactive.event(validate)
def txter():
lower_bound, dfs = min_date_dfs(symbols = input.tickers(), start = input.start(), end = input.end())
#conditional message if start date is lower than the lower bound
if input.start() < lower_bound - timedelta(days = 30):
return f"Lowest date all {len(input.tickers())} tickers share in common is {lower_bound}"
else:
return None
app = App(app_ui, server)