huggingface112 commited on
Commit
cd1bf03
1 Parent(s): b9d37d3

added side nav bar

Browse files
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 calculation(self):
914
- '''calculate PnL, total return and max drawdown'''
915
- pnl = self.eval_df[self.eval_df.date ==
916
- self.eval_df.date.max()].cum_pnl.values[0]
917
- total_return = self.eval_df[self.eval_df.date ==
918
- self.eval_df.date.max()].portfolio_return_p.values[0]
919
- # max draw down
920
- self.eval_df['rolling_max_return'] = self.eval_df.portfolio_return_p.rolling(
921
- window=len(self.eval_df), min_periods=1).max()
922
- self.eval_df.draw_down = abs(
923
- (1 + self.eval_df.portfolio_return_p) /
924
- (1 + self.eval_df.rolling_max_return) - 1
925
- )
926
- max_drawdown = self.eval_df.draw_down.max()
927
- return pnl, total_return, max_drawdown
 
 
 
 
 
 
 
 
 
 
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
- pnl, total_return, max_drawdown = self.calculation()
936
- self.report = self.create_report(pnl, total_return, max_drawdown)
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
- # top_header = appComponents.TopHeader(
53
- # eval_df=p_eval_df
54
- # )
55
 
56
  template = pn.template.FastListTemplate(
57
  title="Portfolio一览",
58
  # sidebar=[freq, phase],
59
  )
60
- template.main.extend([drawdown_card, stock_overview, composation_card, monthly_return_card, total_return_card])
61
- # template.main.extend(
62
- # [pn.Row(top_header),
63
- # pn.Row(
64
- # pn.Column(monthly_return_card, stock_overview,
65
- # width=500, margin=(10, 10, 10, 10)),
66
- # pn.Column(total_return_card, drawdown_card, margin=(10, 10, 10, 10)),
67
- # pn.Column(composation_card, margin=(10, 10, 10, 10)),
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:5e731caeff97539825bee38f510c68ccf49001da6f3fed73058ea4ba7891efdc
3
- size 19148800
 
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 19:34:56"
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
- # check if need to update
 
 
 
 
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'] = (1 + df[key]) / (1 + df[f'rolling_max_{key}'])
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