Spaces:
Runtime error
Runtime error
huggingface112
commited on
Commit
·
cd1bf03
1
Parent(s):
b9d37d3
added side nav bar
Browse files- appComponents.py +27 -213
- index_page.py +15 -19
- instance/local.db +2 -2
- instance/log.json +1 -1
- pipeline.py +17 -9
- portfolioEditingPage.py +2 -0
- processing.py +10 -8
- sidebar.py +30 -0
appComponents.py
CHANGED
@@ -15,202 +15,6 @@ import plotly.graph_objs as go
|
|
15 |
pn.extension('mathjax')
|
16 |
pn.extension('plotly')
|
17 |
|
18 |
-
# warnings.filterwarnings("ignore", category=pd.core.common.SettingWithCopyWarning)
|
19 |
-
# overal performance default to 30 days
|
20 |
-
|
21 |
-
|
22 |
-
def create_portfolio_overview(df_list):
|
23 |
-
calculated_b_stock, calculated_p_stock, p_eval_df, sector_eval_df = df_list
|
24 |
-
|
25 |
-
range_slider = pn.widgets.DateRangeSlider(name='date range',
|
26 |
-
start=sector_eval_df.date.min(),
|
27 |
-
end=sector_eval_df.date.max(),
|
28 |
-
value=(sector_eval_df.date.max() - timedelta(days=30),
|
29 |
-
sector_eval_df.date.max()),
|
30 |
-
align='center',
|
31 |
-
sizing_mode='stretch_width',
|
32 |
-
)
|
33 |
-
size = dict(width=780)
|
34 |
-
option = dict(legend_position="left")
|
35 |
-
active_tools = dict(tools=['hover'], active_tools=[], axiswise=True)
|
36 |
-
|
37 |
-
# def create_overview_panel()
|
38 |
-
ip_eval_df = p_eval_df.interactive()
|
39 |
-
isector_eval_df = sector_eval_df.interactive()
|
40 |
-
|
41 |
-
ranged_ip_eval_df = ip_eval_df[ip_eval_df.date.between(
|
42 |
-
range_slider.param.value_start, range_slider.param.value_end)]
|
43 |
-
ranged_isector_eval_df = isector_eval_df[isector_eval_df.date.between(
|
44 |
-
range_slider.param.value_start, range_slider.param.value_end)]
|
45 |
-
# return
|
46 |
-
return_plot = ranged_ip_eval_df.hvplot.line(x='date', y=['portfolio_return_p', 'portfolio_return_b'])\
|
47 |
-
.opts(title='投资组合总回报 v.s benchmark总回报', **size, **option)
|
48 |
-
# active return
|
49 |
-
active_return_plot = ranged_ip_eval_df.hvplot.line(x='date', y=['active_return'])\
|
50 |
-
.opts(title='每日主动回报', **size, axiswise=True)
|
51 |
-
|
52 |
-
# total risk and tracking error
|
53 |
-
risk_tracking_plot = ranged_ip_eval_df.hvplot.line(x='date', y=['risk', 'tracking_error'])\
|
54 |
-
.opts(title='风险和追踪误差', **size, **option)
|
55 |
-
|
56 |
-
# sector return
|
57 |
-
sector_return_plot = ranged_isector_eval_df.hvplot.line(x='date', y=['portfolio_return_p'], by='aggregate_sector')\
|
58 |
-
.opts(title='投资组合各行业总回报', **size, **option)
|
59 |
-
|
60 |
-
# bsector_return_plot = ranged_isector_eval_df.hvplot.line(x='date', y=['portfolio_return_b'], by='aggregate_sector')\
|
61 |
-
# .opts(title='benchmark sector return', **size, **option)
|
62 |
-
|
63 |
-
# sector active return
|
64 |
-
s_active_return_plot = ranged_isector_eval_df.hvplot.line(x='date', y=['active_return'], by='aggregate_sector')\
|
65 |
-
.opts(title='投资组合各行业每日主动回报', **size, **option)
|
66 |
-
|
67 |
-
# sector risk and tracking error
|
68 |
-
s_risk_plot = ranged_isector_eval_df.hvplot.line(x='date', y=['tracking_error'], by='aggregate_sector')\
|
69 |
-
.opts(title='投资组合各行业追踪误差', **size, **option)
|
70 |
-
s_tracking_plot = ranged_isector_eval_df.hvplot.line(x='date', y=['risk'], by='aggregate_sector')\
|
71 |
-
.opts(title='投资组合各行业风险', **size, **option)
|
72 |
-
|
73 |
-
# attribute
|
74 |
-
def create_attribute_plot(start, end, calculated_b_stock, calculated_p_stock):
|
75 |
-
result = processing.calculate_attributes_between_dates(
|
76 |
-
start, end, calculated_b_stock, calculated_p_stock)
|
77 |
-
portfolio_attribute = result.aggregate({
|
78 |
-
'interaction': 'sum',
|
79 |
-
'allocation': 'sum',
|
80 |
-
'selection': 'sum',
|
81 |
-
})
|
82 |
-
layout = pn.Column(
|
83 |
-
pn.pane.DataFrame(portfolio_attribute.transpose()),
|
84 |
-
result.hvplot.bar(x='display_name_p',
|
85 |
-
y=['interaction', 'allocation', 'selection'],
|
86 |
-
shared_axes=False,
|
87 |
-
stacked=True,
|
88 |
-
rot=90).opts(**size, **option, title='投资组合总主动回报归因')
|
89 |
-
)
|
90 |
-
return layout
|
91 |
-
|
92 |
-
attribute_plot = pn.bind(create_attribute_plot,
|
93 |
-
start=range_slider.param.value_start,
|
94 |
-
end=range_slider.param.value_end,
|
95 |
-
calculated_b_stock=calculated_b_stock,
|
96 |
-
calculated_p_stock=calculated_p_stock)
|
97 |
-
|
98 |
-
# stock performance
|
99 |
-
# selected_p_stock = calculated_p_stock[calculated_p_stock.date ==
|
100 |
-
# calculated_p_stock.date.max()]
|
101 |
-
# stock_radar_plot = go.Figure()
|
102 |
-
# category = ['return', 'risk', 'portfolio_return', 'prev_w_in_p']
|
103 |
-
# for display_name, group in selected_p_stock.groupby('display_name'):
|
104 |
-
# stock_radar_plot.add_trace(go.Scatterpolar(
|
105 |
-
# r=group[category].values[0],
|
106 |
-
# theta=category,
|
107 |
-
# fill='toself',
|
108 |
-
# name=display_name
|
109 |
-
# ))
|
110 |
-
total_view_plots = pn.Column(return_plot.opts(**active_tools).output(),
|
111 |
-
risk_tracking_plot.opts(
|
112 |
-
**active_tools).output(),
|
113 |
-
active_return_plot.opts(
|
114 |
-
**active_tools).output(),
|
115 |
-
attribute_plot,
|
116 |
-
height=1000,
|
117 |
-
scroll=True)
|
118 |
-
sector_view_plots = pn.Column(sector_return_plot.opts(**active_tools).output(),
|
119 |
-
s_risk_plot.opts(**active_tools).output(),
|
120 |
-
s_tracking_plot.opts(
|
121 |
-
**active_tools).output(),
|
122 |
-
s_active_return_plot.opts(
|
123 |
-
**active_tools).output(),
|
124 |
-
height=1000,
|
125 |
-
scroll=True)
|
126 |
-
|
127 |
-
return pn.Column(
|
128 |
-
# pn.Row(align='center'),
|
129 |
-
range_slider,
|
130 |
-
pn.Row(total_view_plots, sector_view_plots, align='center'))
|
131 |
-
|
132 |
-
|
133 |
-
def attribution_view(daily_bnb_result, daily_sector_bnb_result, p_eval_df):
|
134 |
-
p_eval_df.date = pd.to_datetime(p_eval_df.date)
|
135 |
-
daily_bnb_result.date = pd.to_datetime(daily_bnb_result.date)
|
136 |
-
daily_sector_bnb_result.date = pd.to_datetime(daily_sector_bnb_result.date)
|
137 |
-
|
138 |
-
# interactive widget
|
139 |
-
dt_range = pn.widgets.DateRangeSlider(start=p_eval_df.date.min(
|
140 |
-
), end=p_eval_df.date.max(), value=(p_eval_df.date.min(), p_eval_df.date.max()))
|
141 |
-
# total attribution and return
|
142 |
-
p_eval_df_i = p_eval_df.interactive()
|
143 |
-
daily_bnb_result_i = daily_bnb_result.interactive()
|
144 |
-
daily_return_plot = p_eval_df_i[(p_eval_df_i.date >= dt_range.param.value_start) & (
|
145 |
-
p_eval_df_i.date <= dt_range.param.value_end)].hvplot(x='date', y=['portfolio_return_p', 'portfolio_return_b'], title='投资组合总回报 v.s benchmark总回报').output()
|
146 |
-
daily_bnb_plot = daily_bnb_result_i[daily_bnb_result_i.date.between(dt_range.param.value_start, dt_range.param.value_end)]\
|
147 |
-
.hvplot.bar(x='date', y=['allocation', 'selection', 'interaction', "active_return"], stacked=True, title='每日主动收益归因', yformatter='%.2f', xlabel='日期', shared_axes=False).output()
|
148 |
-
|
149 |
-
# return
|
150 |
-
daily_sector_bnb_df_i = daily_sector_bnb_result.interactive()
|
151 |
-
selected_range_df = daily_sector_bnb_df_i[daily_sector_bnb_df_i.date.between(
|
152 |
-
dt_range.param.value_start, dt_range.param.value_end)]
|
153 |
-
sector_active_return_plot = selected_range_df.hvplot.line(
|
154 |
-
x='date', y='active_return', by='aggregate_sector', width=1000, height=400, title='投资组合行业每日主动回报').output()
|
155 |
-
|
156 |
-
# attribution
|
157 |
-
def plot_attribute_by_sector(sector):
|
158 |
-
selected_sector_df = selected_range_df[selected_range_df.aggregate_sector == sector]
|
159 |
-
return selected_sector_df.hvplot.bar(x='date', y=['active_return', 'allocation', 'selection', 'interaction'], title='投资组合行业每日主动收入归因', stacked=True, shared_axes=False).output()
|
160 |
-
sector_attr_plot_tabs = pn.Tabs(*[(sector, plot_attribute_by_sector(sector))
|
161 |
-
for sector in daily_sector_bnb_result.aggregate_sector.unique()], dymacic=True)
|
162 |
-
|
163 |
-
# layout
|
164 |
-
sector_view = pn.Column(sector_attr_plot_tabs, sector_active_return_plot)
|
165 |
-
total_view = pn.Column(daily_return_plot, daily_bnb_plot)
|
166 |
-
return pn.Column(
|
167 |
-
pn.Row(dt_range),
|
168 |
-
pn.Row(total_view, sector_view)
|
169 |
-
)
|
170 |
-
|
171 |
-
# plot explore
|
172 |
-
|
173 |
-
|
174 |
-
def create_hvplot_explore(calculated_b_stock, calculated_p_stock, p_eval_df, sector_eval_df, attribution_result_df, s_attribution_result_df):
|
175 |
-
|
176 |
-
options = ['calculated_b_stock', 'calculated_p_stock', 'p_eval_df',
|
177 |
-
'sector_eval_df', 'attribution_result_df', 's_attribution_result_df']
|
178 |
-
name_to_df = {
|
179 |
-
'calculated_b_stock': calculated_b_stock,
|
180 |
-
'calculated_p_stock': calculated_p_stock,
|
181 |
-
'p_eval_df': p_eval_df,
|
182 |
-
'sector_eval_df': sector_eval_df,
|
183 |
-
'attribution_result_df': attribution_result_df,
|
184 |
-
's_attribution_result_df': s_attribution_result_df
|
185 |
-
|
186 |
-
}
|
187 |
-
|
188 |
-
selector = pn.widgets.Select(
|
189 |
-
name='Select', options=options, value=options[0])
|
190 |
-
|
191 |
-
def create_exploer(name):
|
192 |
-
df = name_to_df[name]
|
193 |
-
explorer = hvplot.explorer(df)
|
194 |
-
|
195 |
-
def plot_code(**kwargs):
|
196 |
-
code = f'```python\n{explorer.plot_code()}\n```'
|
197 |
-
return pn.pane.Markdown(code, sizing_mode='stretch_width')
|
198 |
-
pn.Column(
|
199 |
-
explorer,
|
200 |
-
'**Code**:',
|
201 |
-
pn.bind(plot_code, **explorer.param.objects())
|
202 |
-
)
|
203 |
-
return explorer
|
204 |
-
|
205 |
-
def create_perspective(name):
|
206 |
-
df = name_to_df[name]
|
207 |
-
return pn.pane.Perspective(df, columns=list(df.columns), width=1500, height=800)
|
208 |
-
perspective = pn.bind(create_perspective, name=selector)
|
209 |
-
exploer = pn.bind(create_exploer, name=selector)
|
210 |
-
exploer_component = pn.Column(selector, exploer, perspective)
|
211 |
-
return exploer_component
|
212 |
-
|
213 |
-
|
214 |
class TotalReturnCard(Viewer):
|
215 |
|
216 |
start_date = param.Parameter()
|
@@ -910,21 +714,31 @@ class TopHeader(Viewer):
|
|
910 |
'''
|
911 |
return
|
912 |
|
913 |
-
def
|
914 |
-
'''calculate
|
915 |
-
|
916 |
-
|
917 |
-
|
918 |
-
|
919 |
-
#
|
920 |
-
|
921 |
-
|
922 |
-
|
923 |
-
|
924 |
-
|
925 |
-
)
|
926 |
-
|
927 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
928 |
|
929 |
def create_report(self, pnl, total_return, max_drawdown):
|
930 |
return pn.FlexBox(
|
@@ -932,8 +746,8 @@ class TopHeader(Viewer):
|
|
932 |
|
933 |
def __init__(self, eval_df, **params):
|
934 |
self.eval_df = eval_df
|
935 |
-
|
936 |
-
self.report = self.create_report(
|
937 |
super().__init__(**params)
|
938 |
|
939 |
def __panel__(self):
|
|
|
15 |
pn.extension('mathjax')
|
16 |
pn.extension('plotly')
|
17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
class TotalReturnCard(Viewer):
|
19 |
|
20 |
start_date = param.Parameter()
|
|
|
714 |
'''
|
715 |
return
|
716 |
|
717 |
+
def _process(self):
|
718 |
+
'''calculate accumulative pnl, total return and Max Drawdown on return'''
|
719 |
+
|
720 |
+
# return
|
721 |
+
result_df = processing.calculate_weighted_return(self.eval_df)
|
722 |
+
|
723 |
+
# merge by date
|
724 |
+
agg_df = result_df.groupby('time').aggregate({
|
725 |
+
'weighted_return': 'sum',
|
726 |
+
'cash': 'sum',
|
727 |
+
'pnl': 'sum',
|
728 |
+
})
|
729 |
+
agg_df.reset_index(inplace=True)
|
730 |
+
|
731 |
+
# accumulative pnl
|
732 |
+
agg_df['cum_pnl'] = agg_df['pnl'].cumsum()
|
733 |
+
|
734 |
+
# calcualte drawdown
|
735 |
+
result = processing.calculate_draw_down_on(agg_df)
|
736 |
+
max_draw_down = result.drawn_down.min()
|
737 |
+
|
738 |
+
# last row
|
739 |
+
last_row = agg_df.loc[agg_df.time.idxmax()]
|
740 |
+
|
741 |
+
return last_row.cum_pnl, last_row.weighted_return, max_draw_down
|
742 |
|
743 |
def create_report(self, pnl, total_return, max_drawdown):
|
744 |
return pn.FlexBox(
|
|
|
746 |
|
747 |
def __init__(self, eval_df, **params):
|
748 |
self.eval_df = eval_df
|
749 |
+
cum_pnl, total_return, max_drawdown = self._process()
|
750 |
+
self.report = self.create_report(cum_pnl, total_return, max_drawdown)
|
751 |
super().__init__(**params)
|
752 |
|
753 |
def __panel__(self):
|
index_page.py
CHANGED
@@ -4,6 +4,7 @@ from datetime import datetime, timedelta
|
|
4 |
import plotly.express as px
|
5 |
import holoviews as hv
|
6 |
import numpy as np
|
|
|
7 |
import random
|
8 |
import scipy.stats as stats
|
9 |
import hvplot.pandas # noqa
|
@@ -49,28 +50,23 @@ total_return_card = appComponents.TotalReturnCard(name='Range',
|
|
49 |
drawdown_card = appComponents.DrawDownCard(
|
50 |
calculated_p_stock=analytic_p)
|
51 |
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
|
56 |
template = pn.template.FastListTemplate(
|
57 |
title="Portfolio一览",
|
58 |
# sidebar=[freq, phase],
|
59 |
)
|
60 |
-
template.
|
61 |
-
# template.main.extend(
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
|
|
70 |
template.servable()
|
71 |
-
# pn.Row(
|
72 |
-
|
73 |
-
# pn.Column(monthly_return_card, stock_overview, width=500),
|
74 |
-
# pn.Column(total_return_card),
|
75 |
-
# pn.Column(composation_card)
|
76 |
-
# ).servable()
|
|
|
4 |
import plotly.express as px
|
5 |
import holoviews as hv
|
6 |
import numpy as np
|
7 |
+
from sidebar import SideNavBar
|
8 |
import random
|
9 |
import scipy.stats as stats
|
10 |
import hvplot.pandas # noqa
|
|
|
50 |
drawdown_card = appComponents.DrawDownCard(
|
51 |
calculated_p_stock=analytic_p)
|
52 |
|
53 |
+
top_header = appComponents.TopHeader(
|
54 |
+
eval_df = analytic_p,
|
55 |
+
)
|
56 |
|
57 |
template = pn.template.FastListTemplate(
|
58 |
title="Portfolio一览",
|
59 |
# sidebar=[freq, phase],
|
60 |
)
|
61 |
+
template.sidebar.append(SideNavBar())
|
62 |
+
# template.main.extend([drawdown_card, stock_overview, composation_card, monthly_return_card, total_return_card])
|
63 |
+
template.main.extend(
|
64 |
+
[pn.Row(top_header),
|
65 |
+
pn.Row(
|
66 |
+
pn.Column(monthly_return_card, stock_overview,
|
67 |
+
width=500, margin=(10, 10, 10, 10)),
|
68 |
+
pn.Column(total_return_card, drawdown_card, margin=(10, 10, 10, 10)),
|
69 |
+
pn.Column(composation_card, margin=(10, 10, 10, 10)),
|
70 |
+
)]
|
71 |
+
)
|
72 |
template.servable()
|
|
|
|
|
|
|
|
|
|
|
|
instance/local.db
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
-
size
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:cbb513c601ed039cdf11c093c2192afc7383eaa8f9db8f7dead5fba68a94ff0d
|
3 |
+
size 164798464
|
instance/log.json
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
{
|
2 |
-
"daily_update": "2023-08-31
|
3 |
}
|
|
|
1 |
{
|
2 |
+
"daily_update": "2023-08-31 23:45:18"
|
3 |
}
|
pipeline.py
CHANGED
@@ -139,8 +139,6 @@ def need_to_update_stocks_price(delta_time):
|
|
139 |
return False
|
140 |
|
141 |
|
142 |
-
|
143 |
-
|
144 |
def add_details_to_stock_df(stock_df):
|
145 |
with create_engine(db_url).connect() as conn:
|
146 |
detail_df = pd.read_sql(ts.STOCKS_DETAILS_TABLE, con=conn)
|
@@ -407,26 +405,36 @@ def batch_processing():
|
|
407 |
# pnl
|
408 |
processing.calculate_pnl(analytic_p)
|
409 |
# log return
|
410 |
-
# need to crop on left side of benchmark
|
411 |
-
analytic_b = analytic_b[analytic_b['time'] >= analytic_p.time.min()].copy()
|
412 |
processing.calculate_log_return(analytic_p)
|
413 |
processing.calculate_log_return(analytic_b)
|
414 |
db.save_portfolio_analytic_df(analytic_p)
|
415 |
db.save_benchmark_analytic_df(analytic_b)
|
416 |
|
|
|
417 |
async def daily_update():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
418 |
last_update = log.get_time('daily_update')
|
|
|
419 |
if last_update is None or utils.time_in_beijing() - last_update >= dt.timedelta(days=1):
|
420 |
print("running daily update")
|
421 |
-
|
|
|
|
|
|
|
|
|
422 |
# update stock price
|
423 |
left_fill_stocks_price()
|
424 |
right_fill_stock_price()
|
425 |
print("updated stocks price")
|
426 |
-
# update benchmark index
|
427 |
-
left_fill_benchmark_profile()
|
428 |
-
right_fill_bechmark_profile()
|
429 |
-
print("updated benchmark profile")
|
430 |
# update all stock detail
|
431 |
update_stocks_details_to_db()
|
432 |
print("updated stocks details")
|
|
|
139 |
return False
|
140 |
|
141 |
|
|
|
|
|
142 |
def add_details_to_stock_df(stock_df):
|
143 |
with create_engine(db_url).connect() as conn:
|
144 |
detail_df = pd.read_sql(ts.STOCKS_DETAILS_TABLE, con=conn)
|
|
|
405 |
# pnl
|
406 |
processing.calculate_pnl(analytic_p)
|
407 |
# log return
|
408 |
+
# need to crop on left side of benchmark
|
409 |
+
analytic_b = analytic_b[analytic_b['time'] >= analytic_p.time.min()].copy()
|
410 |
processing.calculate_log_return(analytic_p)
|
411 |
processing.calculate_log_return(analytic_b)
|
412 |
db.save_portfolio_analytic_df(analytic_p)
|
413 |
db.save_benchmark_analytic_df(analytic_b)
|
414 |
|
415 |
+
|
416 |
async def daily_update():
|
417 |
+
'''
|
418 |
+
left and right fill stock price and benchmark weight based on portfolio
|
419 |
+
|
420 |
+
the sequence of the update matter,
|
421 |
+
specifically the benchmark profile need to be updated first before stock,
|
422 |
+
cause update method of stock price depend on the benchmark profile
|
423 |
+
|
424 |
+
'''
|
425 |
last_update = log.get_time('daily_update')
|
426 |
+
# check if need to update
|
427 |
if last_update is None or utils.time_in_beijing() - last_update >= dt.timedelta(days=1):
|
428 |
print("running daily update")
|
429 |
+
|
430 |
+
# update benchmark index, this need to be done before update stock price
|
431 |
+
left_fill_benchmark_profile()
|
432 |
+
right_fill_bechmark_profile()
|
433 |
+
print("updated benchmark profile")
|
434 |
# update stock price
|
435 |
left_fill_stocks_price()
|
436 |
right_fill_stock_price()
|
437 |
print("updated stocks price")
|
|
|
|
|
|
|
|
|
438 |
# update all stock detail
|
439 |
update_stocks_details_to_db()
|
440 |
print("updated stocks details")
|
portfolioEditingPage.py
CHANGED
@@ -12,6 +12,7 @@ from pipeline import update_portfolio_profile_to_db
|
|
12 |
import table_schema
|
13 |
import pipeline
|
14 |
import db_operation as db
|
|
|
15 |
db_url = 'sqlite:///instance/local.db'
|
16 |
pn.extension()
|
17 |
pn.extension('tabulator')
|
@@ -349,5 +350,6 @@ def app():
|
|
349 |
|
350 |
# app
|
351 |
template = pn.template.FastListTemplate(title='portfolio编辑')
|
|
|
352 |
template.main.append(app())
|
353 |
template.servable()
|
|
|
12 |
import table_schema
|
13 |
import pipeline
|
14 |
import db_operation as db
|
15 |
+
from sidebar import SideNavBar
|
16 |
db_url = 'sqlite:///instance/local.db'
|
17 |
pn.extension()
|
18 |
pn.extension('tabulator')
|
|
|
350 |
|
351 |
# app
|
352 |
template = pn.template.FastListTemplate(title='portfolio编辑')
|
353 |
+
template.sidebar.append(SideNavBar())
|
354 |
template.main.append(app())
|
355 |
template.servable()
|
processing.py
CHANGED
@@ -690,7 +690,7 @@ def post_process_merged_analytic_df(merged_df):
|
|
690 |
patch aggregate_sector, display_name, in_portfolio, in_benchmark,
|
691 |
|
692 |
'''
|
693 |
-
# merge both
|
694 |
merged_df['in_portfolio'].fillna(False, inplace=True)
|
695 |
merged_df['in_benchmark'].fillna(False, inplace=True)
|
696 |
# complement fill aggregate_sector and display_name
|
@@ -703,7 +703,7 @@ def post_process_merged_analytic_df(merged_df):
|
|
703 |
merged_df.drop(columns=['aggregate_sector_p',
|
704 |
'display_name_p'], inplace=True)
|
705 |
|
706 |
-
|
707 |
def calculate_weighted_pct(df):
|
708 |
'''
|
709 |
patch df with weighted pct, if pct is not calculated patch that as well
|
@@ -712,6 +712,7 @@ def calculate_weighted_pct(df):
|
|
712 |
calculate_pct(df)
|
713 |
df['weighted_pct'] = df['pct'] * df['weight']
|
714 |
|
|
|
715 |
def aggregate_analytic_df_by_period(df, freq):
|
716 |
'''
|
717 |
return an aggregated analytic_df with weekly, monthly, yearly or daily frequency
|
@@ -753,7 +754,7 @@ def aggregate_analytic_df_by_period(df, freq):
|
|
753 |
'log_return': 'sum',
|
754 |
'weight': 'last'
|
755 |
}
|
756 |
-
|
757 |
# handle aggregate on benchamrk
|
758 |
if 'cash' in df.columns and 'shares' in df.columns:
|
759 |
agg_rules['cash'] = 'last'
|
@@ -779,6 +780,7 @@ def aggregate_bhb_df(df, by="total"):
|
|
779 |
'notional_active_return']].sum()
|
780 |
return agg_df
|
781 |
|
|
|
782 |
def calculate_draw_down_on(df, key='weighted_return'):
|
783 |
'''
|
784 |
calculate draw down on anlaytic df based on either return or accumulative pnl
|
@@ -798,12 +800,12 @@ def calculate_draw_down_on(df, key='weighted_return'):
|
|
798 |
window=len(df), min_periods=1).max()
|
799 |
if key == 'pnl':
|
800 |
df['drawn_down'] = df[key] / df[f'rolling_max_{key}']
|
801 |
-
|
802 |
else:
|
803 |
-
df['drawn_down'] =
|
804 |
-
|
805 |
return df
|
806 |
-
|
807 |
# def calculate_accumulative_pnl(df):
|
808 |
# '''
|
809 |
# calculate accumulative pnl on analytic df
|
@@ -812,4 +814,4 @@ def calculate_draw_down_on(df, key='weighted_return'):
|
|
812 |
# df['accumulative_pnl'] = df.groupby('ticker')['pnl'].rolling(
|
813 |
|
814 |
# )
|
815 |
-
# return df
|
|
|
690 |
patch aggregate_sector, display_name, in_portfolio, in_benchmark,
|
691 |
|
692 |
'''
|
693 |
+
# merge both
|
694 |
merged_df['in_portfolio'].fillna(False, inplace=True)
|
695 |
merged_df['in_benchmark'].fillna(False, inplace=True)
|
696 |
# complement fill aggregate_sector and display_name
|
|
|
703 |
merged_df.drop(columns=['aggregate_sector_p',
|
704 |
'display_name_p'], inplace=True)
|
705 |
|
706 |
+
|
707 |
def calculate_weighted_pct(df):
|
708 |
'''
|
709 |
patch df with weighted pct, if pct is not calculated patch that as well
|
|
|
712 |
calculate_pct(df)
|
713 |
df['weighted_pct'] = df['pct'] * df['weight']
|
714 |
|
715 |
+
|
716 |
def aggregate_analytic_df_by_period(df, freq):
|
717 |
'''
|
718 |
return an aggregated analytic_df with weekly, monthly, yearly or daily frequency
|
|
|
754 |
'log_return': 'sum',
|
755 |
'weight': 'last'
|
756 |
}
|
757 |
+
|
758 |
# handle aggregate on benchamrk
|
759 |
if 'cash' in df.columns and 'shares' in df.columns:
|
760 |
agg_rules['cash'] = 'last'
|
|
|
780 |
'notional_active_return']].sum()
|
781 |
return agg_df
|
782 |
|
783 |
+
|
784 |
def calculate_draw_down_on(df, key='weighted_return'):
|
785 |
'''
|
786 |
calculate draw down on anlaytic df based on either return or accumulative pnl
|
|
|
800 |
window=len(df), min_periods=1).max()
|
801 |
if key == 'pnl':
|
802 |
df['drawn_down'] = df[key] / df[f'rolling_max_{key}']
|
803 |
+
|
804 |
else:
|
805 |
+
df['drawn_down'] = (1 + df[key]) / (1 + df[f'rolling_max_{key}'])
|
806 |
+
|
807 |
return df
|
808 |
+
|
809 |
# def calculate_accumulative_pnl(df):
|
810 |
# '''
|
811 |
# calculate accumulative pnl on analytic df
|
|
|
814 |
# df['accumulative_pnl'] = df.groupby('ticker')['pnl'].rolling(
|
815 |
|
816 |
# )
|
817 |
+
# return df
|
sidebar.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from panel.viewable import Viewer
|
2 |
+
import panel as pn
|
3 |
+
|
4 |
+
|
5 |
+
class SideNavBar(Viewer):
|
6 |
+
|
7 |
+
def __init__(self, **params):
|
8 |
+
self.pages = {
|
9 |
+
'编辑Portfolio': "/portfolioEditingPage",
|
10 |
+
'主页': "/index_page",
|
11 |
+
}
|
12 |
+
self.styles = {
|
13 |
+
'text-decoration': 'none',
|
14 |
+
'color': '#1E90FF',
|
15 |
+
'font-size': '18px',
|
16 |
+
'font-weight': 'bold'
|
17 |
+
}
|
18 |
+
|
19 |
+
super().__init__(**params)
|
20 |
+
|
21 |
+
def _create_link(self, name, url):
|
22 |
+
return pn.pane.HTML(f"""<a href="{url}">{name}</a>""", styles=self.styles)
|
23 |
+
|
24 |
+
def __panel__(self):
|
25 |
+
|
26 |
+
self._layout = pn.Column()
|
27 |
+
self._layout.extend(
|
28 |
+
[self._create_link(name, url) for name, url in self.pages.items()]
|
29 |
+
)
|
30 |
+
return self._layout
|