huggingface112 commited on
Commit
976166f
1 Parent(s): fec3fd9

move files to normal tracking except .db

Browse files
.gitattributes CHANGED
@@ -33,10 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
- *.md filter=lfs diff=lfs merge=lfs -text
37
- *.gitignore filter=lfs diff=lfs merge=lfs -text
38
- /Dockerfile filter=lfs diff=lfs merge=lfs -text
39
- *.py filter=lfs diff=lfs merge=lfs -text
40
- *.ipynb filter=lfs diff=lfs merge=lfs -text
41
- *.txt filter=lfs diff=lfs merge=lfs -text
42
  *.db filter=lfs diff=lfs merge=lfs -text
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
36
  *.db filter=lfs diff=lfs merge=lfs -text
Dockerfile CHANGED
@@ -1,3 +1,16 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:a9c00611604e6f4573bccfadf332fabb75d29da697418d25490513ab1a3efbfe
3
- size 478
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11
2
+
3
+ WORKDIR /code
4
+
5
+ COPY ./requirements.txt /code/requirements.txt
6
+ RUN python3 -m pip install --no-cache-dir --upgrade pip
7
+ RUN python3 -m pip install --no-cache-dir --upgrade -r /code/requirements.txt
8
+
9
+ COPY . /code
10
+
11
+ CMD ["panel", "serve", "/code/portfolioEditingPage.py", "--address", "0.0.0.0", "--port", "7860", "--allow-websocket-origin", "lamonkey-portfolio-management.hf.space"]
12
+
13
+ RUN mkdir /.cache
14
+ RUN chmod 777 /.cache
15
+ RUN mkdir .chroma
16
+ RUN chmod 777 .chroma
README.md CHANGED
@@ -1,3 +1,9 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:81ce2114bb00e93ca40ab89241d28276a2a84fde1ca34e9d9667f8e78105ad37
3
- size 206
 
 
 
 
 
 
 
1
+ ---
2
+ title: Portfolio Management App
3
+ emoji: 📈
4
+ colorFrom: gray
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
api.py CHANGED
@@ -1,3 +1,346 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:36359baa0769e9a965b43b0302a6784bd27cc8bc486a1a2f40099bc89fdd9875
3
- size 11874
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ '''
3
+ contain method for api call to jqdatasdk
4
+ '''
5
+ from dotenv import load_dotenv
6
+ from datetime import datetime, timedelta
7
+ import jqdatasdk as jq
8
+ import pandas as pd
9
+ from typing import List, Optional
10
+ from sqlalchemy import create_engine
11
+ import table_schema as ts
12
+ import os
13
+ db_url = 'sqlite:///local.db'
14
+ load_dotenv()
15
+ user_name = os.environ.get('JQDATA_USER')
16
+ password = os.environ.get('JQDATA_PASSWORD')
17
+
18
+
19
+ def auth_api(func):
20
+ """
21
+ decorator for function require jqdatasdk api
22
+ """
23
+ def wrapper(*args, **kwargs):
24
+
25
+ if (not jq.is_auth()):
26
+ jq.auth(user_name, password)
27
+
28
+ result = func(*args, **kwargs)
29
+ return result
30
+
31
+ return wrapper
32
+
33
+
34
+ def aggregate_sector(input: str) -> Optional[str]:
35
+ '''
36
+ mapping from sector to aggregated sector retur None if not found
37
+ this handling is for spotting undefined sector in current mapping
38
+ later
39
+
40
+ Return: str -- aggregated sector
41
+ None if no mapping
42
+ '''
43
+ mapping = {
44
+ '电气设备I': '工业',
45
+ '建筑装饰I': '工业',
46
+ '交通运输I': '工业',
47
+ '机械设备I': '工业',
48
+ '国防军工I': '工业',
49
+ '综合I': '工业',
50
+ '电子I': '信息与通信',
51
+ '计算机I': '信息与通信',
52
+ '通信I': '信息与通信',
53
+ '传媒I': '信息与通信',
54
+ '纺织服装I': '消费',
55
+ '家用电器I': '消费',
56
+ '汽车I': '消费',
57
+ '休闲服务I': '消费',
58
+ '商业贸易I': '消费',
59
+ '食品饮料I': '消费',
60
+ '美容护理I': '消费',
61
+ '农林牧渔I': '消费',
62
+ '钢铁I': '原料与能源',
63
+ '建筑材料I': '原料与能源',
64
+ '有色金属I': '原料与能源',
65
+ '化工I': '原料与能源',
66
+ '轻工制造I': '原料与能源',
67
+ '煤炭I': '原料与能源',
68
+ '石油石化I': '原料与能源',
69
+ '采掘I': '原料与能源',
70
+ '医药生物I': '医药卫生',
71
+ '公用事业I': '公用事业',
72
+ '环保I': '公用事业',
73
+ '房地产I': '金融与地产',
74
+ '银行I': '金融与地产',
75
+ '非银金融I': '金融与地产'
76
+ }
77
+ # return the first mapping found
78
+ sectors = input.split(" ")
79
+ maped_name = "其他"
80
+ for sector in sectors:
81
+ maped_name = mapping.get(sector, None)
82
+ if maped_name is not None:
83
+ return maped_name
84
+
85
+ return maped_name
86
+
87
+
88
+ @auth_api
89
+ def get_all_stock_info() -> tuple[pd.DataFrame, List[str]]:
90
+ '''
91
+ return all stock information
92
+
93
+ Return
94
+ ------
95
+ tuple: tuple(pd.DataFrame, List[str])
96
+ DataFrame -- display_name | name | start_date | end_date | type
97
+ '''
98
+ error = []
99
+ try:
100
+ df = jq.get_all_securities()
101
+ df['ticker'] = df.index
102
+ df.reset_index(drop=True, inplace=True)
103
+ # df.reset_index(inplace=True)
104
+ return df, error
105
+ except Exception as e:
106
+ error.append(f'get_all_stock_info\n{e}')
107
+ return None, error
108
+
109
+
110
+ @auth_api
111
+ def add_detail_to_stocks(df: pd.DataFrame) -> List[str]:
112
+ """
113
+ add display_name, name, sector, and aggregate sector to each stock if not exist already
114
+ return a list of error message
115
+
116
+ Args: pd.DataFrame
117
+ ticker | date | weight | sector | aggregate_sector | display_name | name
118
+
119
+ Returns: List[str], error messages
120
+ """
121
+ error = []
122
+ df[['sector', 'aggregate_sector']] = df.groupby(
123
+ 'ticker')[['sector', 'aggregate_sector']].ffill()
124
+ df[['display_name', 'name']] = df.groupby(
125
+ 'ticker')[['display_name', 'name']].ffill()
126
+ not_have_sector = list(
127
+ df[df['aggregate_sector'].isnull()]['ticker'].unique())
128
+ not_have_name = list(df[df['name'].isnull()]['ticker'].unique())
129
+ # sector and aggregate sector
130
+ if len(not_have_sector) != 0:
131
+ try:
132
+ sectors = jq.get_industry(security=not_have_sector)
133
+ df['sector'] = df.apply(lambda x: x.sector if not pd.isna(x.sector)
134
+ else " ".join(value['industry_name']
135
+ for value in sectors[x.ticker].values()), axis=1)
136
+ df['aggregate_sector'] = df.apply(
137
+ lambda x: x.aggregate_sector if not pd.isna(x.aggregate_sector)
138
+ else aggregate_sector(x.sector), axis=1
139
+ )
140
+ except Exception as e:
141
+ error.append(f'Error on creaet_sector_information\n{ticker}\n{e}')
142
+
143
+ # display_name and name
144
+ if len(not_have_name) != 0:
145
+ try:
146
+ for ticker in not_have_name:
147
+ detail = jq.get_security_info(ticker)
148
+ df.loc[df.ticker.isin(not_have_name)
149
+ ]['display_name'] = detail.display_name
150
+ df.loc[df.ticker.isin(not_have_name)]['name'] = detail.name
151
+ except Exception as e:
152
+ error.append(f'Error on get display_name and name\n{ticker}\n{e}')
153
+
154
+ return error
155
+
156
+
157
+ @auth_api
158
+ def update_portfolio_profile(stocks: List[dict], current_p: pd.DataFrame = None) -> tuple[pd.DataFrame, List[str]]:
159
+ """create or update a portfolio profile,
160
+ return a time series of profile
161
+
162
+ Parameters
163
+ ----------
164
+ stocks : List[{ticker: Str, shares: float, date:datetime}]
165
+
166
+ update profile with a list of stock information
167
+
168
+ current_p : pd.DataFrame, optional
169
+ current portfolio profile, default is None
170
+
171
+ Returns
172
+ -------
173
+ updated_profile : pd.DataFrame
174
+ ticker | date | weight | sector | aggregate_sector | display_name | name
175
+
176
+ error : List[str]
177
+ a list of error message
178
+ """
179
+
180
+ error = []
181
+ profile_df = pd.DataFrame(stocks)
182
+ profile_df['sector'] = None
183
+ profile_df['aggregate_sector'] = None
184
+
185
+ # add display_name
186
+ try:
187
+ with create_engine(db_url).connect() as conn:
188
+ info_df = pd.read_sql_table(ts.STOCKS_DETAILS_TABLE, conn)
189
+ profile_df = pd.merge(
190
+ profile_df, info_df[['display_name', 'ticker', 'name', 'aggregate_sector', ]], on='ticker', how='left')
191
+ except Exception as e:
192
+ error.append(f'create_portfolio \n{e}')
193
+
194
+ # get sector information
195
+ incoming_error = add_detail_to_stocks(profile_df)
196
+ error.extend(incoming_error)
197
+
198
+ # concate to existing profile if exist
199
+ if current_p is not None:
200
+ profile_df = pd.concat([profile_df, current_p], ignore_index=True)
201
+ profile_df.drop_duplicates(
202
+ subset=['ticker', 'date'], keep='last', inplace=True)
203
+ profile_df.reset_index(drop=True, inplace=True)
204
+
205
+ return profile_df, error
206
+
207
+
208
+ @auth_api
209
+ def get_all_stocks_detail():
210
+ '''get df contain all stock display_name, name, sector, aggregate_sector'''
211
+ detail_df = jq.get_all_securities()
212
+ detail_df['ticker'] = detail_df.index
213
+ detail_df.reset_index(drop=True, inplace=True)
214
+ industry_info = jq.get_industry(detail_df.ticker.to_list())
215
+ detail_df['sector'] = detail_df.apply(lambda x: " ".join(
216
+ value['industry_name']for value in industry_info[x.ticker].values()), axis=1)
217
+ detail_df['aggregate_sector'] = detail_df.apply(
218
+ lambda x: aggregate_sector(x.sector), axis=1)
219
+ return detail_df
220
+
221
+
222
+ @auth_api
223
+ def get_api_usage():
224
+ return jq.get_query_count()
225
+
226
+
227
+ @auth_api
228
+ def get_stocks_price(profile: pd.DataFrame, start_date: datetime, end_date: datetime, frequency='daily') -> tuple[pd.DataFrame, List[str]]:
229
+ """
230
+ Return a dataframe contain stock price between period of time for price in a portfolio profile
231
+
232
+ Arguments:
233
+ profile {pd.DataFrame} -- ticker | date | weight | sector | aggregate_sector | display_name | name
234
+ start_date {datetime} -- start date of the period include start date
235
+ end_date {datetime} -- end date of the period include end date
236
+ frequency {str} -- resolution of the price, default is daily
237
+
238
+ Returns: Tuple(pd.DataFrame, List[str])
239
+ pd.DataFrame -- ticker date open close high low volumn money
240
+ error_message {list} -- a list of error message
241
+ """
242
+ error_message = []
243
+ start_str = start_date.strftime('%Y-%m-%d')
244
+ end_str = end_date.strftime('%Y-%m-%d')
245
+ if profile.date.min() < start_date:
246
+ # hanlde benchmark doesn't have weight on the exact date
247
+ start_str = profile.date.min().strftime('%Y-%m-%d')
248
+
249
+ ticker = profile['ticker'].to_list()
250
+ try:
251
+
252
+ data = jq.get_price(ticker, start_date=start_str,
253
+ end_date=end_str, frequency=frequency)
254
+ data.rename(columns={'time': 'date', 'code': "ticker"}, inplace=True)
255
+ return data, error_message
256
+ except Exception as e:
257
+ error_message.append(f'Error when fetching {ticker} \n {e}')
258
+ return None, error_message
259
+
260
+
261
+ @auth_api
262
+ def fetch_stocks_price(**params):
263
+ '''request list of stock price from start_date to end_date with frequency or count'''
264
+ stocks_df = jq.get_price(**params)
265
+ stocks_df.rename(columns={'code': 'ticker'}, inplace=True)
266
+ return stocks_df
267
+
268
+
269
+ @auth_api
270
+ def update_benchmark_profile(start_date: datetime,
271
+ end_date: datetime,
272
+ benchmark="000905.XSHG",
273
+ delta_time=timedelta(days=7),
274
+ profile: pd.DataFrame = None
275
+ ) -> tuple[pd.DataFrame, List[str]]:
276
+ """
277
+ update benchmark profile with available new update between start_date and end_date,
278
+ if no profile is given, create a new profile for that duration
279
+ the minimum period is 1 day
280
+
281
+ return an updated dataframe if new update exist
282
+
283
+ Returns:
284
+ pd.DateFrame -- ticker | date | weight | sector | aggregate_sector | display_name | name
285
+ date| weight | display_name | actual_data(the date in the api database) | ticker
286
+ """
287
+ error_message = []
288
+ results = []
289
+ while start_date < end_date:
290
+ try:
291
+ date_str = start_date.strftime('%Y-%m-%d')
292
+ result = jq.get_index_weights(benchmark, date=date_str)
293
+ results.append(result)
294
+
295
+ except Exception as e:
296
+ error_message.append(f'Error when fetching {benchmark}\n\
297
+ update on {date_str} is missing\n\
298
+ {e}')
299
+
300
+ start_date += delta_time
301
+ # inlcude end date
302
+ try:
303
+ date_str = end_date.strftime('%Y-%m-%d')
304
+ result = jq.get_index_weights(benchmark, date=date_str)
305
+ results.append(result)
306
+ except Exception as e:
307
+ error_message.append(f'Error when fetching {benchmark}\n\
308
+ update on {date_str} is missing\n\
309
+ {e}')
310
+ # no update
311
+ if len(results) == 0:
312
+ return profile, error_message
313
+
314
+ # concate all result
315
+ update_df = pd.concat(results)
316
+ update_df['ticker'] = update_df.index
317
+ update_df['sector'] = None
318
+ update_df['aggregate_sector'] = None
319
+ update_df.reset_index(drop=True, inplace=True)
320
+ update_df['date'] = pd.to_datetime(update_df['date'])
321
+ # add display_name
322
+ try:
323
+ with create_engine(db_url).connect() as conn:
324
+ info_df = pd.read_sql('all_stock_info', conn)
325
+ update_df = pd.merge(
326
+ update_df, info_df[['ticker', 'name']], on='ticker', how='left')
327
+ except Exception as e:
328
+ # if all_stock_info not exist then create a name column manually
329
+ update_df['name'] = None
330
+ error_message.append(f'create_portfolio \n{e}')
331
+
332
+ # combine with existing profile if given
333
+ if profile is not None:
334
+ update_df = pd.concat([profile, update_df])
335
+
336
+ # remove duplicate result
337
+ update_df.drop_duplicates(
338
+ subset=['ticker', 'date'], keep='last', inplace=True)
339
+
340
+ # update deail
341
+ incoming_error = add_detail_to_stocks(update_df)
342
+ error_message.extend(incoming_error)
343
+
344
+ return update_df, error_message
345
+
346
+ # get_all_stocks_detail()
apiMonitorPage.py CHANGED
@@ -1,3 +1,40 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:f131cfcd1af6e8c49821cd5e777890ada6e774ec6c8a98213700d75c85d819fc
3
- size 1173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import panel as pn
2
+ import pandas as pd
3
+ import numpy as np
4
+ from streamz import Stream
5
+ from pipeline import stock_price_stream
6
+ stream = Stream()
7
+ stock_price_stream
8
+ pn.extension('tabulator')
9
+ pn.extension('vega')
10
+
11
+ stream_df = pd.DataFrame(columns=['time', 'ticker', 'open', 'close', 'high', 'low',
12
+ 'volume', 'money', 'in_portfolio', 'in_benchmark', 'aggregate_sector', 'display_name'])
13
+
14
+ stream_table = pn.widgets.Tabulator(
15
+ stream_df, layout='fit_columns', width=1200, height=1200)
16
+ # stream_table
17
+
18
+
19
+ def stream_data(stream_df):
20
+ print('updating stream!!!')
21
+ # stream_df = pd.DataFrame(np.random.randn(5, 5), columns=list('ABCDE'))
22
+ stream_table.stream(stream_df, follow=True)
23
+
24
+
25
+ def create_new_stream():
26
+ stream_df = pd.DataFrame(np.random.randn(5, 5), columns=list('ABCDE'))
27
+ stock_price_stream.emit(stream_df)
28
+ # pn.state.add_periodic_callback(create_new_stream, period=1000, count=100)
29
+
30
+
31
+ stock_price_stream.sink(stream_data)
32
+ template = pn.template.FastListTemplate(
33
+ title='api monitor')
34
+ # stock_price_stream.sink(print)
35
+ template.main.extend(
36
+ [stream_table]
37
+ )
38
+ # )
39
+ # stock_price_stream.sink(print)
40
+ template.servable()
api_data_samples.ipynb CHANGED
@@ -1,3 +1,887 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:ad238008ccf71420f361d8cf659820229f2ed95fe71e2d22cd59e27c931a93b9
3
- size 31348
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "from sqlalchemy import create_engine\n",
10
+ "import pandas as pd\n",
11
+ "from datetime import timedelta\n",
12
+ "import jqdatasdk as jq\n"
13
+ ]
14
+ },
15
+ {
16
+ "cell_type": "code",
17
+ "execution_count": null,
18
+ "metadata": {},
19
+ "outputs": [],
20
+ "source": [
21
+ "# load existing portfolio"
22
+ ]
23
+ },
24
+ {
25
+ "cell_type": "code",
26
+ "execution_count": null,
27
+ "metadata": {},
28
+ "outputs": [],
29
+ "source": [
30
+ "# check if database need update\n",
31
+ "def fetch_data_for_table():\n",
32
+ " '''\n",
33
+ " return None if no update needed\n",
34
+ " else return (starttime, endtime, frequency)\n",
35
+ " '''\n",
36
+ " pass"
37
+ ]
38
+ },
39
+ {
40
+ "cell_type": "markdown",
41
+ "metadata": {},
42
+ "source": [
43
+ "Need to fetch stock price periodically from jqdatasdk, also need to check if portfolio updated, but this case can be handled seperately \n"
44
+ ]
45
+ },
46
+ {
47
+ "cell_type": "code",
48
+ "execution_count": null,
49
+ "metadata": {},
50
+ "outputs": [],
51
+ "source": [
52
+ "# fetch stock price from api\n",
53
+ "def fetch_stock_price():\n",
54
+ " pass"
55
+ ]
56
+ },
57
+ {
58
+ "cell_type": "code",
59
+ "execution_count": 2,
60
+ "metadata": {},
61
+ "outputs": [
62
+ {
63
+ "name": "stdout",
64
+ "output_type": "stream",
65
+ "text": [
66
+ "auth success \n"
67
+ ]
68
+ }
69
+ ],
70
+ "source": [
71
+ "# TODO auth, remove later\n",
72
+ "user_name = \"13126862272\"\n",
73
+ "password = \"862272\"\n",
74
+ "jq.auth(user_name, password)"
75
+ ]
76
+ },
77
+ {
78
+ "cell_type": "code",
79
+ "execution_count": 9,
80
+ "metadata": {},
81
+ "outputs": [
82
+ {
83
+ "data": {
84
+ "text/html": [
85
+ "<div>\n",
86
+ "<style scoped>\n",
87
+ " .dataframe tbody tr th:only-of-type {\n",
88
+ " vertical-align: middle;\n",
89
+ " }\n",
90
+ "\n",
91
+ " .dataframe tbody tr th {\n",
92
+ " vertical-align: top;\n",
93
+ " }\n",
94
+ "\n",
95
+ " .dataframe thead th {\n",
96
+ " text-align: right;\n",
97
+ " }\n",
98
+ "</style>\n",
99
+ "<table border=\"1\" class=\"dataframe\">\n",
100
+ " <thead>\n",
101
+ " <tr style=\"text-align: right;\">\n",
102
+ " <th></th>\n",
103
+ " <th>open</th>\n",
104
+ " <th>close</th>\n",
105
+ " <th>high</th>\n",
106
+ " <th>low</th>\n",
107
+ " <th>volume</th>\n",
108
+ " <th>money</th>\n",
109
+ " </tr>\n",
110
+ " </thead>\n",
111
+ " <tbody>\n",
112
+ " <tr>\n",
113
+ " <th>2021-01-05 09:31:00</th>\n",
114
+ " <td>32.54</td>\n",
115
+ " <td>31.73</td>\n",
116
+ " <td>32.54</td>\n",
117
+ " <td>31.72</td>\n",
118
+ " <td>1205715.0</td>\n",
119
+ " <td>38788451.0</td>\n",
120
+ " </tr>\n",
121
+ " <tr>\n",
122
+ " <th>2021-01-05 09:32:00</th>\n",
123
+ " <td>31.68</td>\n",
124
+ " <td>32.00</td>\n",
125
+ " <td>32.00</td>\n",
126
+ " <td>31.39</td>\n",
127
+ " <td>1084879.0</td>\n",
128
+ " <td>34358448.0</td>\n",
129
+ " </tr>\n",
130
+ " <tr>\n",
131
+ " <th>2021-01-05 09:33:00</th>\n",
132
+ " <td>31.91</td>\n",
133
+ " <td>31.62</td>\n",
134
+ " <td>31.91</td>\n",
135
+ " <td>31.61</td>\n",
136
+ " <td>811350.0</td>\n",
137
+ " <td>25785154.0</td>\n",
138
+ " </tr>\n",
139
+ " <tr>\n",
140
+ " <th>2021-01-05 09:34:00</th>\n",
141
+ " <td>31.62</td>\n",
142
+ " <td>32.07</td>\n",
143
+ " <td>32.07</td>\n",
144
+ " <td>31.62</td>\n",
145
+ " <td>760611.0</td>\n",
146
+ " <td>24135183.0</td>\n",
147
+ " </tr>\n",
148
+ " <tr>\n",
149
+ " <th>2021-01-05 09:35:00</th>\n",
150
+ " <td>32.11</td>\n",
151
+ " <td>31.98</td>\n",
152
+ " <td>32.16</td>\n",
153
+ " <td>31.98</td>\n",
154
+ " <td>655989.0</td>\n",
155
+ " <td>21046238.0</td>\n",
156
+ " </tr>\n",
157
+ " <tr>\n",
158
+ " <th>...</th>\n",
159
+ " <td>...</td>\n",
160
+ " <td>...</td>\n",
161
+ " <td>...</td>\n",
162
+ " <td>...</td>\n",
163
+ " <td>...</td>\n",
164
+ " <td>...</td>\n",
165
+ " </tr>\n",
166
+ " <tr>\n",
167
+ " <th>2021-01-05 11:26:00</th>\n",
168
+ " <td>32.25</td>\n",
169
+ " <td>32.29</td>\n",
170
+ " <td>32.29</td>\n",
171
+ " <td>32.20</td>\n",
172
+ " <td>118071.0</td>\n",
173
+ " <td>3807211.0</td>\n",
174
+ " </tr>\n",
175
+ " <tr>\n",
176
+ " <th>2021-01-05 11:27:00</th>\n",
177
+ " <td>32.29</td>\n",
178
+ " <td>32.30</td>\n",
179
+ " <td>32.30</td>\n",
180
+ " <td>32.21</td>\n",
181
+ " <td>114251.0</td>\n",
182
+ " <td>3686602.0</td>\n",
183
+ " </tr>\n",
184
+ " <tr>\n",
185
+ " <th>2021-01-05 11:28:00</th>\n",
186
+ " <td>32.26</td>\n",
187
+ " <td>32.24</td>\n",
188
+ " <td>32.30</td>\n",
189
+ " <td>32.21</td>\n",
190
+ " <td>60077.0</td>\n",
191
+ " <td>1938244.0</td>\n",
192
+ " </tr>\n",
193
+ " <tr>\n",
194
+ " <th>2021-01-05 11:29:00</th>\n",
195
+ " <td>32.25</td>\n",
196
+ " <td>32.28</td>\n",
197
+ " <td>32.30</td>\n",
198
+ " <td>32.22</td>\n",
199
+ " <td>65634.0</td>\n",
200
+ " <td>2117204.0</td>\n",
201
+ " </tr>\n",
202
+ " <tr>\n",
203
+ " <th>2021-01-05 11:30:00</th>\n",
204
+ " <td>32.23</td>\n",
205
+ " <td>32.30</td>\n",
206
+ " <td>32.30</td>\n",
207
+ " <td>32.23</td>\n",
208
+ " <td>49052.0</td>\n",
209
+ " <td>1582947.0</td>\n",
210
+ " </tr>\n",
211
+ " </tbody>\n",
212
+ "</table>\n",
213
+ "<p>120 rows × 6 columns</p>\n",
214
+ "</div>"
215
+ ],
216
+ "text/plain": [
217
+ " open close high low volume money\n",
218
+ "2021-01-05 09:31:00 32.54 31.73 32.54 31.72 1205715.0 38788451.0\n",
219
+ "2021-01-05 09:32:00 31.68 32.00 32.00 31.39 1084879.0 34358448.0\n",
220
+ "2021-01-05 09:33:00 31.91 31.62 31.91 31.61 811350.0 25785154.0\n",
221
+ "2021-01-05 09:34:00 31.62 32.07 32.07 31.62 760611.0 24135183.0\n",
222
+ "2021-01-05 09:35:00 32.11 31.98 32.16 31.98 655989.0 21046238.0\n",
223
+ "... ... ... ... ... ... ...\n",
224
+ "2021-01-05 11:26:00 32.25 32.29 32.29 32.20 118071.0 3807211.0\n",
225
+ "2021-01-05 11:27:00 32.29 32.30 32.30 32.21 114251.0 3686602.0\n",
226
+ "2021-01-05 11:28:00 32.26 32.24 32.30 32.21 60077.0 1938244.0\n",
227
+ "2021-01-05 11:29:00 32.25 32.28 32.30 32.22 65634.0 2117204.0\n",
228
+ "2021-01-05 11:30:00 32.23 32.30 32.30 32.23 49052.0 1582947.0\n",
229
+ "\n",
230
+ "[120 rows x 6 columns]"
231
+ ]
232
+ },
233
+ "execution_count": 9,
234
+ "metadata": {},
235
+ "output_type": "execute_result"
236
+ }
237
+ ],
238
+ "source": [
239
+ "## exam return type of each api \n",
240
+ "\n",
241
+ "# range of stock price in different resolution\n",
242
+ "single_stock_df = jq.get_price('002709.XSHE',start_date='2021-01-05 9:00:00', end_date='2021-01-05 12:00:00', frequency='1m')\n",
243
+ "single_stock_df\n",
244
+ "\n"
245
+ ]
246
+ },
247
+ {
248
+ "cell_type": "code",
249
+ "execution_count": 10,
250
+ "metadata": {},
251
+ "outputs": [
252
+ {
253
+ "data": {
254
+ "text/html": [
255
+ "<div>\n",
256
+ "<style scoped>\n",
257
+ " .dataframe tbody tr th:only-of-type {\n",
258
+ " vertical-align: middle;\n",
259
+ " }\n",
260
+ "\n",
261
+ " .dataframe tbody tr th {\n",
262
+ " vertical-align: top;\n",
263
+ " }\n",
264
+ "\n",
265
+ " .dataframe thead th {\n",
266
+ " text-align: right;\n",
267
+ " }\n",
268
+ "</style>\n",
269
+ "<table border=\"1\" class=\"dataframe\">\n",
270
+ " <thead>\n",
271
+ " <tr style=\"text-align: right;\">\n",
272
+ " <th></th>\n",
273
+ " <th>time</th>\n",
274
+ " <th>code</th>\n",
275
+ " <th>open</th>\n",
276
+ " <th>close</th>\n",
277
+ " <th>high</th>\n",
278
+ " <th>low</th>\n",
279
+ " <th>volume</th>\n",
280
+ " <th>money</th>\n",
281
+ " </tr>\n",
282
+ " </thead>\n",
283
+ " <tbody>\n",
284
+ " <tr>\n",
285
+ " <th>0</th>\n",
286
+ " <td>2021-01-05 09:31:00</td>\n",
287
+ " <td>600409.XSHG</td>\n",
288
+ " <td>9.23</td>\n",
289
+ " <td>9.16</td>\n",
290
+ " <td>9.23</td>\n",
291
+ " <td>9.08</td>\n",
292
+ " <td>1417593.0</td>\n",
293
+ " <td>13000397.0</td>\n",
294
+ " </tr>\n",
295
+ " <tr>\n",
296
+ " <th>1</th>\n",
297
+ " <td>2021-01-05 09:32:00</td>\n",
298
+ " <td>600409.XSHG</td>\n",
299
+ " <td>9.17</td>\n",
300
+ " <td>9.17</td>\n",
301
+ " <td>9.17</td>\n",
302
+ " <td>9.15</td>\n",
303
+ " <td>394595.0</td>\n",
304
+ " <td>3613283.0</td>\n",
305
+ " </tr>\n",
306
+ " <tr>\n",
307
+ " <th>2</th>\n",
308
+ " <td>2021-01-05 09:33:00</td>\n",
309
+ " <td>600409.XSHG</td>\n",
310
+ " <td>9.16</td>\n",
311
+ " <td>9.15</td>\n",
312
+ " <td>9.16</td>\n",
313
+ " <td>9.13</td>\n",
314
+ " <td>492224.0</td>\n",
315
+ " <td>4501667.0</td>\n",
316
+ " </tr>\n",
317
+ " <tr>\n",
318
+ " <th>3</th>\n",
319
+ " <td>2021-01-05 09:34:00</td>\n",
320
+ " <td>600409.XSHG</td>\n",
321
+ " <td>9.14</td>\n",
322
+ " <td>9.21</td>\n",
323
+ " <td>9.22</td>\n",
324
+ " <td>9.14</td>\n",
325
+ " <td>451578.0</td>\n",
326
+ " <td>4152355.0</td>\n",
327
+ " </tr>\n",
328
+ " <tr>\n",
329
+ " <th>4</th>\n",
330
+ " <td>2021-01-05 09:35:00</td>\n",
331
+ " <td>600409.XSHG</td>\n",
332
+ " <td>9.20</td>\n",
333
+ " <td>9.20</td>\n",
334
+ " <td>9.22</td>\n",
335
+ " <td>9.20</td>\n",
336
+ " <td>503040.0</td>\n",
337
+ " <td>4631781.0</td>\n",
338
+ " </tr>\n",
339
+ " <tr>\n",
340
+ " <th>...</th>\n",
341
+ " <td>...</td>\n",
342
+ " <td>...</td>\n",
343
+ " <td>...</td>\n",
344
+ " <td>...</td>\n",
345
+ " <td>...</td>\n",
346
+ " <td>...</td>\n",
347
+ " <td>...</td>\n",
348
+ " <td>...</td>\n",
349
+ " </tr>\n",
350
+ " <tr>\n",
351
+ " <th>715</th>\n",
352
+ " <td>2021-01-05 11:26:00</td>\n",
353
+ " <td>600415.XSHG</td>\n",
354
+ " <td>5.87</td>\n",
355
+ " <td>5.87</td>\n",
356
+ " <td>5.87</td>\n",
357
+ " <td>5.87</td>\n",
358
+ " <td>279118.0</td>\n",
359
+ " <td>1639550.0</td>\n",
360
+ " </tr>\n",
361
+ " <tr>\n",
362
+ " <th>716</th>\n",
363
+ " <td>2021-01-05 11:27:00</td>\n",
364
+ " <td>600415.XSHG</td>\n",
365
+ " <td>5.87</td>\n",
366
+ " <td>5.87</td>\n",
367
+ " <td>5.87</td>\n",
368
+ " <td>5.87</td>\n",
369
+ " <td>253667.0</td>\n",
370
+ " <td>1490048.0</td>\n",
371
+ " </tr>\n",
372
+ " <tr>\n",
373
+ " <th>717</th>\n",
374
+ " <td>2021-01-05 11:28:00</td>\n",
375
+ " <td>600415.XSHG</td>\n",
376
+ " <td>5.87</td>\n",
377
+ " <td>5.87</td>\n",
378
+ " <td>5.87</td>\n",
379
+ " <td>5.87</td>\n",
380
+ " <td>137293.0</td>\n",
381
+ " <td>806465.0</td>\n",
382
+ " </tr>\n",
383
+ " <tr>\n",
384
+ " <th>718</th>\n",
385
+ " <td>2021-01-05 11:29:00</td>\n",
386
+ " <td>600415.XSHG</td>\n",
387
+ " <td>5.87</td>\n",
388
+ " <td>5.87</td>\n",
389
+ " <td>5.87</td>\n",
390
+ " <td>5.87</td>\n",
391
+ " <td>218351.0</td>\n",
392
+ " <td>1282600.0</td>\n",
393
+ " </tr>\n",
394
+ " <tr>\n",
395
+ " <th>719</th>\n",
396
+ " <td>2021-01-05 11:30:00</td>\n",
397
+ " <td>600415.XSHG</td>\n",
398
+ " <td>5.87</td>\n",
399
+ " <td>5.87</td>\n",
400
+ " <td>5.87</td>\n",
401
+ " <td>5.87</td>\n",
402
+ " <td>248941.0</td>\n",
403
+ " <td>1462285.0</td>\n",
404
+ " </tr>\n",
405
+ " </tbody>\n",
406
+ "</table>\n",
407
+ "<p>720 rows × 8 columns</p>\n",
408
+ "</div>"
409
+ ],
410
+ "text/plain": [
411
+ " time code open close high low volume \\\n",
412
+ "0 2021-01-05 09:31:00 600409.XSHG 9.23 9.16 9.23 9.08 1417593.0 \n",
413
+ "1 2021-01-05 09:32:00 600409.XSHG 9.17 9.17 9.17 9.15 394595.0 \n",
414
+ "2 2021-01-05 09:33:00 600409.XSHG 9.16 9.15 9.16 9.13 492224.0 \n",
415
+ "3 2021-01-05 09:34:00 600409.XSHG 9.14 9.21 9.22 9.14 451578.0 \n",
416
+ "4 2021-01-05 09:35:00 600409.XSHG 9.20 9.20 9.22 9.20 503040.0 \n",
417
+ ".. ... ... ... ... ... ... ... \n",
418
+ "715 2021-01-05 11:26:00 600415.XSHG 5.87 5.87 5.87 5.87 279118.0 \n",
419
+ "716 2021-01-05 11:27:00 600415.XSHG 5.87 5.87 5.87 5.87 253667.0 \n",
420
+ "717 2021-01-05 11:28:00 600415.XSHG 5.87 5.87 5.87 5.87 137293.0 \n",
421
+ "718 2021-01-05 11:29:00 600415.XSHG 5.87 5.87 5.87 5.87 218351.0 \n",
422
+ "719 2021-01-05 11:30:00 600415.XSHG 5.87 5.87 5.87 5.87 248941.0 \n",
423
+ "\n",
424
+ " money \n",
425
+ "0 13000397.0 \n",
426
+ "1 3613283.0 \n",
427
+ "2 4501667.0 \n",
428
+ "3 4152355.0 \n",
429
+ "4 4631781.0 \n",
430
+ ".. ... \n",
431
+ "715 1639550.0 \n",
432
+ "716 1490048.0 \n",
433
+ "717 806465.0 \n",
434
+ "718 1282600.0 \n",
435
+ "719 1462285.0 \n",
436
+ "\n",
437
+ "[720 rows x 8 columns]"
438
+ ]
439
+ },
440
+ "execution_count": 10,
441
+ "metadata": {},
442
+ "output_type": "execute_result"
443
+ }
444
+ ],
445
+ "source": [
446
+ "# range of multiple stocks price in different resolution\n",
447
+ "mul_stocks_df = jq.get_..0n63\n",
448
+ "\n",
449
+ "\n",
450
+ "price(['002709.XSHE',\n",
451
+ " '002920.XSHE',\n",
452
+ " '300274.XSHE',\n",
453
+ " '600409.XSHG',\n",
454
+ " '600415.XSHG',\n",
455
+ " '603882.XSHG'],start_date='2021-01-05 9:00:00', end_date='2021-01-05 12:00:00', frequency='1m')\n",
456
+ "mul_stocks_df\n",
457
+ "\n"
458
+ ]
459
+ },
460
+ {
461
+ "cell_type": "code",
462
+ "execution_count": 17,
463
+ "metadata": {},
464
+ "outputs": [
465
+ {
466
+ "data": {
467
+ "text/html": [
468
+ "<div>\n",
469
+ "<style scoped>\n",
470
+ " .dataframe tbody tr th:only-of-type {\n",
471
+ " vertical-align: middle;\n",
472
+ " }\n",
473
+ "\n",
474
+ " .dataframe tbody tr th {\n",
475
+ " vertical-align: top;\n",
476
+ " }\n",
477
+ "\n",
478
+ " .dataframe thead th {\n",
479
+ " text-align: right;\n",
480
+ " }\n",
481
+ "</style>\n",
482
+ "<table border=\"1\" class=\"dataframe\">\n",
483
+ " <thead>\n",
484
+ " <tr style=\"text-align: right;\">\n",
485
+ " <th></th>\n",
486
+ " <th>date</th>\n",
487
+ " <th>weight</th>\n",
488
+ " <th>display_name</th>\n",
489
+ " </tr>\n",
490
+ " </thead>\n",
491
+ " <tbody>\n",
492
+ " <tr>\n",
493
+ " <th>000008.XSHE</th>\n",
494
+ " <td>2020-12-31</td>\n",
495
+ " <td>0.088</td>\n",
496
+ " <td>神州高铁</td>\n",
497
+ " </tr>\n",
498
+ " <tr>\n",
499
+ " <th>000009.XSHE</th>\n",
500
+ " <td>2020-12-31</td>\n",
501
+ " <td>0.344</td>\n",
502
+ " <td>中国宝安</td>\n",
503
+ " </tr>\n",
504
+ " <tr>\n",
505
+ " <th>000012.XSHE</th>\n",
506
+ " <td>2020-12-31</td>\n",
507
+ " <td>0.180</td>\n",
508
+ " <td>南玻A</td>\n",
509
+ " </tr>\n",
510
+ " <tr>\n",
511
+ " <th>000021.XSHE</th>\n",
512
+ " <td>2020-12-31</td>\n",
513
+ " <td>0.297</td>\n",
514
+ " <td>深科技</td>\n",
515
+ " </tr>\n",
516
+ " <tr>\n",
517
+ " <th>001872.XSHE</th>\n",
518
+ " <td>2020-12-31</td>\n",
519
+ " <td>0.030</td>\n",
520
+ " <td>招商港口</td>\n",
521
+ " </tr>\n",
522
+ " <tr>\n",
523
+ " <th>...</th>\n",
524
+ " <td>...</td>\n",
525
+ " <td>...</td>\n",
526
+ " <td>...</td>\n",
527
+ " </tr>\n",
528
+ " <tr>\n",
529
+ " <th>688002.XSHG</th>\n",
530
+ " <td>2020-12-31</td>\n",
531
+ " <td>0.438</td>\n",
532
+ " <td>睿创微纳</td>\n",
533
+ " </tr>\n",
534
+ " <tr>\n",
535
+ " <th>688099.XSHG</th>\n",
536
+ " <td>2020-12-31</td>\n",
537
+ " <td>0.287</td>\n",
538
+ " <td>晶晨股份</td>\n",
539
+ " </tr>\n",
540
+ " <tr>\n",
541
+ " <th>688088.XSHG</th>\n",
542
+ " <td>2020-12-31</td>\n",
543
+ " <td>0.252</td>\n",
544
+ " <td>虹软科技</td>\n",
545
+ " </tr>\n",
546
+ " <tr>\n",
547
+ " <th>688321.XSHG</th>\n",
548
+ " <td>2020-12-31</td>\n",
549
+ " <td>0.134</td>\n",
550
+ " <td>微芯生物</td>\n",
551
+ " </tr>\n",
552
+ " <tr>\n",
553
+ " <th>688029.XSHG</th>\n",
554
+ " <td>2020-12-31</td>\n",
555
+ " <td>0.130</td>\n",
556
+ " <td>南微医学</td>\n",
557
+ " </tr>\n",
558
+ " </tbody>\n",
559
+ "</table>\n",
560
+ "<p>500 rows × 3 columns</p>\n",
561
+ "</div>"
562
+ ],
563
+ "text/plain": [
564
+ " date weight display_name\n",
565
+ "000008.XSHE 2020-12-31 0.088 神州高铁\n",
566
+ "000009.XSHE 2020-12-31 0.344 中国宝安\n",
567
+ "000012.XSHE 2020-12-31 0.180 南玻A\n",
568
+ "000021.XSHE 2020-12-31 0.297 深科技\n",
569
+ "001872.XSHE 2020-12-31 0.030 招商港口\n",
570
+ "... ... ... ...\n",
571
+ "688002.XSHG 2020-12-31 0.438 睿创微纳\n",
572
+ "688099.XSHG 2020-12-31 0.287 晶晨股份\n",
573
+ "688088.XSHG 2020-12-31 0.252 虹软科技\n",
574
+ "688321.XSHG 2020-12-31 0.134 微芯生物\n",
575
+ "688029.XSHG 2020-12-31 0.130 南微医学\n",
576
+ "\n",
577
+ "[500 rows x 3 columns]"
578
+ ]
579
+ },
580
+ "execution_count": 17,
581
+ "metadata": {},
582
+ "output_type": "execute_result"
583
+ }
584
+ ],
585
+ "source": [
586
+ "# composition of benchmark\n",
587
+ "jq.get_index_weights('000905.XSHG',date='2021-01-20 9:00:00')\n"
588
+ ]
589
+ },
590
+ {
591
+ "cell_type": "code",
592
+ "execution_count": 18,
593
+ "metadata": {},
594
+ "outputs": [
595
+ {
596
+ "data": {
597
+ "text/plain": [
598
+ "{'600409.XSHG': {'sw_l1': {'industry_code': '801030', 'industry_name': '化工I'},\n",
599
+ " 'sw_l2': {'industry_code': '801033', 'industry_name': '化学原料II'},\n",
600
+ " 'sw_l3': {'industry_code': '850321', 'industry_name': '纯碱III'},\n",
601
+ " 'zjw': {'industry_code': 'C26', 'industry_name': '化学原料和化学制品制造业'},\n",
602
+ " 'jq_l2': {'industry_code': 'HY02107', 'industry_name': '粘胶'},\n",
603
+ " 'jq_l1': {'industry_code': 'HY002', 'industry_name': '原材料'}},\n",
604
+ " '603882.XSHG': {'sw_l1': {'industry_code': '801150',\n",
605
+ " 'industry_name': '医药生物I'},\n",
606
+ " 'sw_l2': {'industry_code': '801156', 'industry_name': '医疗服务II'},\n",
607
+ " 'sw_l3': {'industry_code': '851562', 'industry_name': '诊断服务III'},\n",
608
+ " 'zjw': {'industry_code': 'Q83', 'industry_name': '卫生'},\n",
609
+ " 'jq_l2': {'industry_code': 'HY06103', 'industry_name': '体外诊断'},\n",
610
+ " 'jq_l1': {'industry_code': 'HY006', 'industry_name': '医药卫生'}},\n",
611
+ " '300274.XSHE': {'sw_l1': {'industry_code': '801730',\n",
612
+ " 'industry_name': '电气设备I'},\n",
613
+ " 'sw_l2': {'industry_code': '801735', 'industry_name': '光伏设备II'},\n",
614
+ " 'sw_l3': {'industry_code': '857353', 'industry_name': '逆变器III'},\n",
615
+ " 'zjw': {'industry_code': 'C38', 'industry_name': '电气机械和器材制造业'},\n",
616
+ " 'jq_l2': {'industry_code': 'HY03111', 'industry_name': '光伏设备'},\n",
617
+ " 'jq_l1': {'industry_code': 'HY003', 'industry_name': '工业'}},\n",
618
+ " '002709.XSHE': {'sw_l1': {'industry_code': '801730',\n",
619
+ " 'industry_name': '电气设备I'},\n",
620
+ " 'sw_l2': {'industry_code': '801737', 'industry_name': '电池II'},\n",
621
+ " 'sw_l3': {'industry_code': '857372', 'industry_name': '电池化学品III'},\n",
622
+ " 'zjw': {'industry_code': 'C26', 'industry_name': '化学原料和化学制品制造业'},\n",
623
+ " 'jq_l2': {'industry_code': 'HY03119', 'industry_name': '电池部件及材料'},\n",
624
+ " 'jq_l1': {'industry_code': 'HY003', 'industry_name': '工业'}},\n",
625
+ " '002920.XSHE': {'sw_l1': {'industry_code': '801750', 'industry_name': '计算机I'},\n",
626
+ " 'sw_l2': {'industry_code': '801104', 'industry_name': '软件开发II'},\n",
627
+ " 'sw_l3': {'industry_code': '851041', 'industry_name': '垂直应用软件III'},\n",
628
+ " 'zjw': {'industry_code': 'C36', 'industry_name': '汽车制造业'},\n",
629
+ " 'jq_l2': {'industry_code': 'HY04103', 'industry_name': '汽车电子'},\n",
630
+ " 'jq_l1': {'industry_code': 'HY004', 'industry_name': '可选消费'}},\n",
631
+ " '600415.XSHG': {'sw_l1': {'industry_code': '801200',\n",
632
+ " 'industry_name': '商业贸易I'},\n",
633
+ " 'sw_l2': {'industry_code': '801203', 'industry_name': '一般零售II'},\n",
634
+ " 'sw_l3': {'industry_code': '852034', 'industry_name': '商业物业经营III'},\n",
635
+ " 'zjw': {'industry_code': 'L72', 'industry_name': '商务服务业'},\n",
636
+ " 'jq_l2': {'industry_code': 'HY03147', 'industry_name': '市场服务'},\n",
637
+ " 'jq_l1': {'industry_code': 'HY003', 'industry_name': '工业'}}}"
638
+ ]
639
+ },
640
+ "execution_count": 18,
641
+ "metadata": {},
642
+ "output_type": "execute_result"
643
+ }
644
+ ],
645
+ "source": [
646
+ "# sector information of each stock\n",
647
+ "jq.get_industry(['002709.XSHE',\n",
648
+ " '002920.XSHE',\n",
649
+ " '300274.XSHE',\n",
650
+ " '600409.XSHG',\n",
651
+ " '600415.XSHG',\n",
652
+ " '603882.XSHG'])\n",
653
+ "# display name of each stock"
654
+ ]
655
+ },
656
+ {
657
+ "cell_type": "code",
658
+ "execution_count": 20,
659
+ "metadata": {},
660
+ "outputs": [
661
+ {
662
+ "data": {
663
+ "text/plain": [
664
+ "array([datetime.date(2005, 1, 4), datetime.date(2005, 1, 5),\n",
665
+ " datetime.date(2005, 1, 6), ..., datetime.date(2025, 8, 11),\n",
666
+ " datetime.date(2025, 8, 12), datetime.date(2025, 8, 13)],\n",
667
+ " dtype=object)"
668
+ ]
669
+ },
670
+ "execution_count": 20,
671
+ "metadata": {},
672
+ "output_type": "execute_result"
673
+ }
674
+ ],
675
+ "source": [
676
+ "# get all trading days\n",
677
+ "jq.get_all_trade_days()"
678
+ ]
679
+ },
680
+ {
681
+ "cell_type": "code",
682
+ "execution_count": 3,
683
+ "metadata": {},
684
+ "outputs": [
685
+ {
686
+ "data": {
687
+ "text/html": [
688
+ "<div>\n",
689
+ "<style scoped>\n",
690
+ " .dataframe tbody tr th:only-of-type {\n",
691
+ " vertical-align: middle;\n",
692
+ " }\n",
693
+ "\n",
694
+ " .dataframe tbody tr th {\n",
695
+ " vertical-align: top;\n",
696
+ " }\n",
697
+ "\n",
698
+ " .dataframe thead th {\n",
699
+ " text-align: right;\n",
700
+ " }\n",
701
+ "</style>\n",
702
+ "<table border=\"1\" class=\"dataframe\">\n",
703
+ " <thead>\n",
704
+ " <tr style=\"text-align: right;\">\n",
705
+ " <th></th>\n",
706
+ " <th>display_name</th>\n",
707
+ " <th>name</th>\n",
708
+ " <th>start_date</th>\n",
709
+ " <th>end_date</th>\n",
710
+ " <th>type</th>\n",
711
+ " </tr>\n",
712
+ " </thead>\n",
713
+ " <tbody>\n",
714
+ " <tr>\n",
715
+ " <th>000001.XSHE</th>\n",
716
+ " <td>平安银行</td>\n",
717
+ " <td>PAYH</td>\n",
718
+ " <td>1991-04-03</td>\n",
719
+ " <td>2200-01-01</td>\n",
720
+ " <td>stock</td>\n",
721
+ " </tr>\n",
722
+ " <tr>\n",
723
+ " <th>000002.XSHE</th>\n",
724
+ " <td>万科A</td>\n",
725
+ " <td>WKA</td>\n",
726
+ " <td>1991-01-29</td>\n",
727
+ " <td>2200-01-01</td>\n",
728
+ " <td>stock</td>\n",
729
+ " </tr>\n",
730
+ " <tr>\n",
731
+ " <th>000004.XSHE</th>\n",
732
+ " <td>国华网安</td>\n",
733
+ " <td>GHWA</td>\n",
734
+ " <td>1990-12-01</td>\n",
735
+ " <td>2200-01-01</td>\n",
736
+ " <td>stock</td>\n",
737
+ " </tr>\n",
738
+ " <tr>\n",
739
+ " <th>000005.XSHE</th>\n",
740
+ " <td>ST星源</td>\n",
741
+ " <td>STXY</td>\n",
742
+ " <td>1990-12-10</td>\n",
743
+ " <td>2200-01-01</td>\n",
744
+ " <td>stock</td>\n",
745
+ " </tr>\n",
746
+ " <tr>\n",
747
+ " <th>000006.XSHE</th>\n",
748
+ " <td>深振业A</td>\n",
749
+ " <td>SZYA</td>\n",
750
+ " <td>1992-04-27</td>\n",
751
+ " <td>2200-01-01</td>\n",
752
+ " <td>stock</td>\n",
753
+ " </tr>\n",
754
+ " <tr>\n",
755
+ " <th>...</th>\n",
756
+ " <td>...</td>\n",
757
+ " <td>...</td>\n",
758
+ " <td>...</td>\n",
759
+ " <td>...</td>\n",
760
+ " <td>...</td>\n",
761
+ " </tr>\n",
762
+ " <tr>\n",
763
+ " <th>688799.XSHG</th>\n",
764
+ " <td>华纳药厂</td>\n",
765
+ " <td>HNYC</td>\n",
766
+ " <td>2021-07-13</td>\n",
767
+ " <td>2200-01-01</td>\n",
768
+ " <td>stock</td>\n",
769
+ " </tr>\n",
770
+ " <tr>\n",
771
+ " <th>688800.XSHG</th>\n",
772
+ " <td>瑞可达</td>\n",
773
+ " <td>RKD</td>\n",
774
+ " <td>2021-07-22</td>\n",
775
+ " <td>2200-01-01</td>\n",
776
+ " <td>stock</td>\n",
777
+ " </tr>\n",
778
+ " <tr>\n",
779
+ " <th>688819.XSHG</th>\n",
780
+ " <td>天能股份</td>\n",
781
+ " <td>TNGF</td>\n",
782
+ " <td>2021-01-18</td>\n",
783
+ " <td>2200-01-01</td>\n",
784
+ " <td>stock</td>\n",
785
+ " </tr>\n",
786
+ " <tr>\n",
787
+ " <th>688981.XSHG</th>\n",
788
+ " <td>中芯国际</td>\n",
789
+ " <td>ZXGJ</td>\n",
790
+ " <td>2020-07-16</td>\n",
791
+ " <td>2200-01-01</td>\n",
792
+ " <td>stock</td>\n",
793
+ " </tr>\n",
794
+ " <tr>\n",
795
+ " <th>689009.XSHG</th>\n",
796
+ " <td>九号公司</td>\n",
797
+ " <td>JHGS</td>\n",
798
+ " <td>2020-10-29</td>\n",
799
+ " <td>2200-01-01</td>\n",
800
+ " <td>stock</td>\n",
801
+ " </tr>\n",
802
+ " </tbody>\n",
803
+ "</table>\n",
804
+ "<p>5248 rows × 5 columns</p>\n",
805
+ "</div>"
806
+ ],
807
+ "text/plain": [
808
+ " display_name name start_date end_date type\n",
809
+ "000001.XSHE 平安银行 PAYH 1991-04-03 2200-01-01 stock\n",
810
+ "000002.XSHE 万科A WKA 1991-01-29 2200-01-01 stock\n",
811
+ "000004.XSHE 国华网安 GHWA 1990-12-01 2200-01-01 stock\n",
812
+ "000005.XSHE ST星源 STXY 1990-12-10 2200-01-01 stock\n",
813
+ "000006.XSHE 深振业A SZYA 1992-04-27 2200-01-01 stock\n",
814
+ "... ... ... ... ... ...\n",
815
+ "688799.XSHG 华纳药厂 HNYC 2021-07-13 2200-01-01 stock\n",
816
+ "688800.XSHG 瑞可达 RKD 2021-07-22 2200-01-01 stock\n",
817
+ "688819.XSHG 天能股份 TNGF 2021-01-18 2200-01-01 stock\n",
818
+ "688981.XSHG 中芯国际 ZXGJ 2020-07-16 2200-01-01 stock\n",
819
+ "689009.XSHG 九号公司 JHGS 2020-10-29 2200-01-01 stock\n",
820
+ "\n",
821
+ "[5248 rows x 5 columns]"
822
+ ]
823
+ },
824
+ "execution_count": 3,
825
+ "metadata": {},
826
+ "output_type": "execute_result"
827
+ }
828
+ ],
829
+ "source": [
830
+ "jq.get_all_securities()"
831
+ ]
832
+ },
833
+ {
834
+ "cell_type": "code",
835
+ "execution_count": 4,
836
+ "metadata": {},
837
+ "outputs": [
838
+ {
839
+ "ename": "NameError",
840
+ "evalue": "name 'engine' is not defined",
841
+ "output_type": "error",
842
+ "traceback": [
843
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
844
+ "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
845
+ "Cell \u001b[0;32mIn[4], line 9\u001b[0m\n\u001b[1;32m 7\u001b[0m df[\u001b[39m'\u001b[39m\u001b[39mdate\u001b[39m\u001b[39m'\u001b[39m] \u001b[39m=\u001b[39m pd\u001b[39m.\u001b[39mto_datetime(df[\u001b[39m'\u001b[39m\u001b[39mdate\u001b[39m\u001b[39m'\u001b[39m])\n\u001b[1;32m 8\u001b[0m \u001b[39mreturn\u001b[39;00m df\n\u001b[0;32m----> 9\u001b[0m get_most_recent_profile(\u001b[39m'\u001b[39;49m\u001b[39mportfolio\u001b[39;49m\u001b[39m'\u001b[39;49m)\n",
846
+ "Cell \u001b[0;32mIn[4], line 5\u001b[0m, in \u001b[0;36mget_most_recent_profile\u001b[0;34m(type)\u001b[0m\n\u001b[1;32m 3\u001b[0m table_name \u001b[39m=\u001b[39m \u001b[39m'\u001b[39m\u001b[39mbenchmark_profile\u001b[39m\u001b[39m'\u001b[39m \u001b[39mif\u001b[39;00m \u001b[39mtype\u001b[39m \u001b[39m==\u001b[39m \u001b[39m'\u001b[39m\u001b[39mbenchmark\u001b[39m\u001b[39m'\u001b[39m \u001b[39melse\u001b[39;00m \u001b[39m'\u001b[39m\u001b[39mportfolio_profile\u001b[39m\u001b[39m'\u001b[39m\n\u001b[1;32m 4\u001b[0m query \u001b[39m=\u001b[39m \u001b[39mf\u001b[39m\u001b[39m\"\u001b[39m\u001b[39mSELECT * FROM \u001b[39m\u001b[39m{\u001b[39;00mtable_name\u001b[39m}\u001b[39;00m\u001b[39m WHERE date = (SELECT MAX(date) FROM \u001b[39m\u001b[39m{\u001b[39;00mtable_name\u001b[39m}\u001b[39;00m\u001b[39m)\u001b[39m\u001b[39m\"\u001b[39m\n\u001b[0;32m----> 5\u001b[0m df \u001b[39m=\u001b[39m pd\u001b[39m.\u001b[39mread_sql(query, con\u001b[39m=\u001b[39mengine)\n\u001b[1;32m 6\u001b[0m \u001b[39m# convert date to datetime object\u001b[39;00m\n\u001b[1;32m 7\u001b[0m df[\u001b[39m'\u001b[39m\u001b[39mdate\u001b[39m\u001b[39m'\u001b[39m] \u001b[39m=\u001b[39m pd\u001b[39m.\u001b[39mto_datetime(df[\u001b[39m'\u001b[39m\u001b[39mdate\u001b[39m\u001b[39m'\u001b[39m])\n",
847
+ "\u001b[0;31mNameError\u001b[0m: name 'engine' is not defined"
848
+ ]
849
+ }
850
+ ],
851
+ "source": [
852
+ "# when update stock price just need the latest portfolio frame\n",
853
+ "def get_most_recent_profile(type):\n",
854
+ " table_name = 'benchmark_profile' if type == 'benchmark' else 'portfolio_profile'\n",
855
+ " query = f\"SELECT * FROM {table_name} WHERE date = (SELECT MAX(date) FROM {table_name})\"\n",
856
+ " with create_engine(db_url).connect() as conn:\n",
857
+ " df = pd.read_sql(query, con=engine)\n",
858
+ " # convert date to datetime object\n",
859
+ " df['date'] = pd.to_datetime(df['date'])\n",
860
+ " return df\n",
861
+ "get_most_recent_profile('portfolio')"
862
+ ]
863
+ }
864
+ ],
865
+ "metadata": {
866
+ "kernelspec": {
867
+ "display_name": "portfolio_risk_assesment",
868
+ "language": "python",
869
+ "name": "python3"
870
+ },
871
+ "language_info": {
872
+ "codemirror_mode": {
873
+ "name": "ipython",
874
+ "version": 3
875
+ },
876
+ "file_extension": ".py",
877
+ "mimetype": "text/x-python",
878
+ "name": "python",
879
+ "nbconvert_exporter": "python",
880
+ "pygments_lexer": "ipython3",
881
+ "version": "3.11.4"
882
+ },
883
+ "orig_nbformat": 4
884
+ },
885
+ "nbformat": 4,
886
+ "nbformat_minor": 2
887
+ }
app.ipynb CHANGED
The diff for this file is too large to render. See raw diff
 
appComponents.py CHANGED
@@ -1,3 +1,817 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:60c6ae82e349660ba410e78e371fb97ced027c60bd53401bb8524dca04818d3e
3
- size 35435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from script import processing
2
+ from datetime import datetime, timedelta
3
+ import panel as pn
4
+ import pandas as pd
5
+ import hvplot.pandas # noqa
6
+ import plotly.express as px
7
+ import numpy as np
8
+ import hvplot.pandas # noqa
9
+ from panel.viewable import Viewer
10
+ import param
11
+ from script import styling
12
+ from script import description
13
+ import plotly.graph_objs as go
14
+ # import warnings
15
+ pn.extension('mathjax')
16
+ pn.extension('plotly')
17
+ pn.extension('plotly')
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
+ # TODO convert below to a class
42
+ ranged_ip_eval_df = ip_eval_df[ip_eval_df.date.between(
43
+ range_slider.param.value_start, range_slider.param.value_end)]
44
+ ranged_isector_eval_df = isector_eval_df[isector_eval_df.date.between(
45
+ range_slider.param.value_start, range_slider.param.value_end)]
46
+ # return
47
+ return_plot = ranged_ip_eval_df.hvplot.line(x='date', y=['portfolio_return_p', 'portfolio_return_b'])\
48
+ .opts(title='投资组合总回报 v.s benchmark总回报', **size, **option)
49
+ # active return
50
+ active_return_plot = ranged_ip_eval_df.hvplot.line(x='date', y=['active_return'])\
51
+ .opts(title='每日主动回报', **size, axiswise=True)
52
+
53
+ # total risk and tracking error
54
+ risk_tracking_plot = ranged_ip_eval_df.hvplot.line(x='date', y=['risk', 'tracking_error'])\
55
+ .opts(title='风险和追踪误差', **size, **option)
56
+
57
+ # sector return
58
+ sector_return_plot = ranged_isector_eval_df.hvplot.line(x='date', y=['portfolio_return_p'], by='aggregate_sector')\
59
+ .opts(title='投资组合各行业总回报', **size, **option)
60
+
61
+ # bsector_return_plot = ranged_isector_eval_df.hvplot.line(x='date', y=['portfolio_return_b'], by='aggregate_sector')\
62
+ # .opts(title='benchmark sector return', **size, **option)
63
+
64
+ # sector active return
65
+ s_active_return_plot = ranged_isector_eval_df.hvplot.line(x='date', y=['active_return'], by='aggregate_sector')\
66
+ .opts(title='投资组合各行业每日主动回报', **size, **option)
67
+
68
+ # sector risk and tracking error
69
+ s_risk_plot = ranged_isector_eval_df.hvplot.line(x='date', y=['tracking_error'], by='aggregate_sector')\
70
+ .opts(title='投资组合各行业追踪误差', **size, **option)
71
+ s_tracking_plot = ranged_isector_eval_df.hvplot.line(x='date', y=['risk'], by='aggregate_sector')\
72
+ .opts(title='投资组合各行业风险', **size, **option)
73
+
74
+ # attribute
75
+ def create_attribute_plot(start, end, calculated_b_stock, calculated_p_stock):
76
+ result = processing.calculate_attributes_between_dates(
77
+ start, end, calculated_b_stock, calculated_p_stock)
78
+ portfolio_attribute = result.aggregate({
79
+ 'interaction': 'sum',
80
+ 'allocation': 'sum',
81
+ 'selection': 'sum',
82
+ })
83
+ layout = pn.Column(
84
+ pn.pane.DataFrame(portfolio_attribute.transpose()),
85
+ result.hvplot.bar(x='display_name_p',
86
+ y=['interaction', 'allocation', 'selection'],
87
+ shared_axes=False,
88
+ stacked=True,
89
+ rot=90).opts(**size, **option, title='投资组合总主动回报归因')
90
+ )
91
+ return layout
92
+
93
+ attribute_plot = pn.bind(create_attribute_plot,
94
+ start=range_slider.param.value_start,
95
+ end=range_slider.param.value_end,
96
+ calculated_b_stock=calculated_b_stock,
97
+ calculated_p_stock=calculated_p_stock)
98
+
99
+ # stock performance
100
+ # selected_p_stock = calculated_p_stock[calculated_p_stock.date ==
101
+ # calculated_p_stock.date.max()]
102
+ # stock_radar_plot = go.Figure()
103
+ # category = ['return', 'risk', 'portfolio_return', 'prev_w_in_p']
104
+ # for display_name, group in selected_p_stock.groupby('display_name'):
105
+ # stock_radar_plot.add_trace(go.Scatterpolar(
106
+ # r=group[category].values[0],
107
+ # theta=category,
108
+ # fill='toself',
109
+ # name=display_name
110
+ # ))
111
+ total_view_plots = pn.Column(return_plot.opts(**active_tools).output(),
112
+ risk_tracking_plot.opts(
113
+ **active_tools).output(),
114
+ active_return_plot.opts(
115
+ **active_tools).output(),
116
+ attribute_plot,
117
+ height=1000,
118
+ scroll=True)
119
+ sector_view_plots = pn.Column(sector_return_plot.opts(**active_tools).output(),
120
+ s_risk_plot.opts(**active_tools).output(),
121
+ s_tracking_plot.opts(
122
+ **active_tools).output(),
123
+ s_active_return_plot.opts(
124
+ **active_tools).output(),
125
+ height=1000,
126
+ scroll=True)
127
+
128
+ return pn.Column(
129
+ # pn.Row(align='center'),
130
+ range_slider,
131
+ pn.Row(total_view_plots, sector_view_plots, align='center'))
132
+
133
+
134
+ def attribution_view(daily_bnb_result, daily_sector_bnb_result, p_eval_df):
135
+ p_eval_df.date = pd.to_datetime(p_eval_df.date)
136
+ daily_bnb_result.date = pd.to_datetime(daily_bnb_result.date)
137
+ daily_sector_bnb_result.date = pd.to_datetime(daily_sector_bnb_result.date)
138
+
139
+ # interactive widget
140
+ dt_range = pn.widgets.DateRangeSlider(start=p_eval_df.date.min(
141
+ ), end=p_eval_df.date.max(), value=(p_eval_df.date.min(), p_eval_df.date.max()))
142
+ # total attribution and return
143
+ p_eval_df_i = p_eval_df.interactive()
144
+ daily_bnb_result_i = daily_bnb_result.interactive()
145
+ daily_return_plot = p_eval_df_i[(p_eval_df_i.date >= dt_range.param.value_start) & (
146
+ 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()
147
+ daily_bnb_plot = daily_bnb_result_i[daily_bnb_result_i.date.between(dt_range.param.value_start, dt_range.param.value_end)]\
148
+ .hvplot.bar(x='date', y=['allocation', 'selection', 'interaction', "active_return"], stacked=True, title='每日主动收益归因', yformatter='%.2f', xlabel='日期', shared_axes=False).output()
149
+
150
+ # return
151
+ daily_sector_bnb_df_i = daily_sector_bnb_result.interactive()
152
+ selected_range_df = daily_sector_bnb_df_i[daily_sector_bnb_df_i.date.between(
153
+ dt_range.param.value_start, dt_range.param.value_end)]
154
+ sector_active_return_plot = selected_range_df.hvplot.line(
155
+ x='date', y='active_return', by='aggregate_sector', width=1000, height=400, title='投资组合行业每日主动回报').output()
156
+
157
+ # attribution
158
+ def plot_attribute_by_sector(sector):
159
+ selected_sector_df = selected_range_df[selected_range_df.aggregate_sector == sector]
160
+ return selected_sector_df.hvplot.bar(x='date', y=['active_return', 'allocation', 'selection', 'interaction'], title='投资组合行业每日主动收入归因', stacked=True, shared_axes=False).output()
161
+ sector_attr_plot_tabs = pn.Tabs(*[(sector, plot_attribute_by_sector(sector))
162
+ for sector in daily_sector_bnb_result.aggregate_sector.unique()], dymacic=True)
163
+
164
+ # layout
165
+ sector_view = pn.Column(sector_attr_plot_tabs, sector_active_return_plot)
166
+ total_view = pn.Column(daily_return_plot, daily_bnb_plot)
167
+ return pn.Column(
168
+ pn.Row(dt_range),
169
+ pn.Row(total_view, sector_view)
170
+ )
171
+
172
+ # plot explore
173
+
174
+
175
+ def create_hvplot_explore(calculated_b_stock, calculated_p_stock, p_eval_df, sector_eval_df, attribution_result_df, s_attribution_result_df):
176
+
177
+ options = ['calculated_b_stock', 'calculated_p_stock', 'p_eval_df',
178
+ 'sector_eval_df', 'attribution_result_df', 's_attribution_result_df']
179
+ name_to_df = {
180
+ 'calculated_b_stock': calculated_b_stock,
181
+ 'calculated_p_stock': calculated_p_stock,
182
+ 'p_eval_df': p_eval_df,
183
+ 'sector_eval_df': sector_eval_df,
184
+ 'attribution_result_df': attribution_result_df,
185
+ 's_attribution_result_df': s_attribution_result_df
186
+
187
+ }
188
+
189
+ selector = pn.widgets.Select(
190
+ name='Select', options=options, value=options[0])
191
+
192
+ def create_exploer(name):
193
+ df = name_to_df[name]
194
+ explorer = hvplot.explorer(df)
195
+
196
+ def plot_code(**kwargs):
197
+ code = f'```python\n{explorer.plot_code()}\n```'
198
+ return pn.pane.Markdown(code, sizing_mode='stretch_width')
199
+ pn.Column(
200
+ explorer,
201
+ '**Code**:',
202
+ pn.bind(plot_code, **explorer.param.objects())
203
+ )
204
+ return explorer
205
+
206
+ def create_perspective(name):
207
+ df = name_to_df[name]
208
+ return pn.pane.Perspective(df, columns=list(df.columns), width=1500, height=800)
209
+ perspective = pn.bind(create_perspective, name=selector)
210
+ exploer = pn.bind(create_exploer, name=selector)
211
+ exploer_component = pn.Column(selector, exploer, perspective)
212
+ return exploer_component
213
+
214
+
215
+ class TotalReturnCard(Viewer):
216
+
217
+ value = param.Range(doc="A numeric range.")
218
+ width = param.Integer(default=300)
219
+ start_date = param.Parameter()
220
+ end_date = param.Parameter()
221
+ eval_df = param.Parameter()
222
+ b_stock_df = param.Parameter()
223
+ p_stock_df = param.Parameter()
224
+ selected_df = param.Parameter()
225
+ plot_pane = param.Parameter()
226
+ report = param.Parameter()
227
+
228
+ def format_number(self, num):
229
+ return f'{round(num * 100, 2)}%'
230
+
231
+ def get_color(self, num):
232
+ return 'green' if num >= 0 else 'red'
233
+
234
+ def create_report(self):
235
+ # Calculate the total return and risk
236
+ result = processing.calculate_return(
237
+ self.eval_df, self.start_date, self.end_date)
238
+ most_recent_row = result.tail(1)
239
+ active_return = most_recent_row.active_return.values[0]
240
+ tracking_error = result.active_return.std() * np.sqrt(252)
241
+ total_return = most_recent_row.return_p.values[0]
242
+ mkt_cap = most_recent_row.mkt_cap.values[0]
243
+ risk = result['return_b'].std() * np.sqrt(252)
244
+
245
+ # Calculate the total attribution
246
+ attributes = processing.calculate_attributes_between_dates(
247
+ self.start_date, self.end_date, self.p_stock_df, self.b_stock_df)
248
+ total_attributes = attributes.aggregate({
249
+ 'interaction': 'sum',
250
+ 'allocation': 'sum',
251
+ 'selection': 'sum',
252
+ 'active_return': 'sum',
253
+ 'notional_return': 'sum'
254
+ })
255
+ active_return_from_stock = total_attributes.active_return
256
+ notional_return = total_attributes.notional_return
257
+ interaction = total_attributes.interaction
258
+ allocation = total_attributes.allocation
259
+ selection = total_attributes.selection
260
+
261
+ # Create a function for text report
262
+ report = f"""
263
+ <style>
264
+ .compact-container {{
265
+ display: flex;
266
+ flex-direction: column;
267
+ gap: 5px;
268
+ }}
269
+
270
+ .compact-container > div {{
271
+ display: flex;
272
+ justify-content: space-between;
273
+ margin-bottom: 2px;
274
+ }}
275
+
276
+ .compact-container > div > h2,
277
+ .compact-container > div > h3,
278
+ .compact-container > div > p,
279
+ .compact-container > div > ul > li {{
280
+ margin: 0;
281
+ }}
282
+
283
+ .compact-container > ul {{
284
+ padding: 0;
285
+ margin: 0;
286
+ list-style-type: none;
287
+ }}
288
+
289
+ .compact-container > ul > li {{
290
+ display: flex;
291
+ margin-bottom: 2px;
292
+ }}
293
+ </style>
294
+
295
+ <div class="compact-container">
296
+ <u><b>总市值</b></u>
297
+ <div>
298
+ <h2 style="margin: 0;">¥{round(mkt_cap,2)}</h2>
299
+ <h2 style='color: {self.get_color(total_return)}; margin: 0;'>{self.format_number(total_return)}</h2>
300
+ </div>
301
+ <div>
302
+ <p style="margin: 0;">追踪误差</p>
303
+ <p style='color: {self.get_color(tracking_error)}; margin: 0;'>{self.format_number(tracking_error)}</p>
304
+ </div>
305
+ <div>
306
+ <p style="margin: 0;">风险</p>
307
+ <p style='color: {self.get_color(risk)}; margin: 0;'>{self.format_number(risk)}</p>
308
+ </div>
309
+ <div>
310
+ <p style="margin: 0;">归因</p>
311
+ <ul style="padding: 0; margin: 0; list-style-type: none;">
312
+ <li style="margin-bottom: 2px;">
313
+ <div style="display: flex;">
314
+ <p style="margin: 0;">主动回报:</p>
315
+ <p style="color: {self.get_color(active_return)}; margin: 0;">{self.format_number(active_return)}</p>
316
+ </div>
317
+ </li>
318
+ <li style="margin-bottom: 2px;">
319
+ <div style="display: flex;">
320
+ <p style="margin: 0;">交互:</p>
321
+ <p style="color: {self.get_color(interaction)}; margin: 0;">{self.format_number(interaction)}</p>
322
+ </div>
323
+ </li>
324
+ <li style="margin-bottom: 2px;">
325
+ <div style="display: flex;">
326
+ <p style="margin: 0;">名义主动回报:</p>
327
+ <p style="color: {self.get_color(notional_return)}; margin: 0;">{self.format_number(notional_return)}</p>
328
+ </div>
329
+ </li>
330
+ <li style="margin-bottom: 2px;">
331
+ <div style="display: flex;">
332
+ <p style="margin: 0;">选择:</p>
333
+ <p style="color: {self.get_color(selection)}; margin: 0;">{self.format_number(selection)}</p>
334
+ </div>
335
+ </li>
336
+ <li style="margin-bottom: 2px;">
337
+ <div style="display: flex;">
338
+ <p style="margin: 0;">分配:</p>
339
+ <p style="color: {self.get_color(allocation)}; margin: 0;">{self.format_number(allocation)}</p>
340
+ </div>
341
+ </li>
342
+ </ul>
343
+ </div>
344
+ </div>
345
+ """
346
+
347
+ return report
348
+
349
+ def create_plot(self):
350
+ result = processing.calculate_return(
351
+ self.eval_df, self.start_date, self.end_date)
352
+ fig = px.line(result, x="date", y=['return_p', 'return_b'])
353
+ fig.update_traces(mode="lines+markers",
354
+ marker=dict(size=5), line=dict(width=2))
355
+ fig.update_layout(styling.plot_layout)
356
+ colname_to_name = {
357
+ 'return_p': 'Portfolio回报',
358
+ 'return_b': 'benchmark回报'
359
+ }
360
+ fig.for_each_trace(lambda t: t.update(name=colname_to_name.get(t.name, t.name),
361
+ legendgroup=colname_to_name.get(
362
+ t.name, t.name),
363
+ hovertemplate=t.hovertemplate.replace(
364
+ t.name, colname_to_name.get(t.name, t.name))
365
+ ))
366
+ # fig.layout.autosize = True
367
+ return fig.to_dict()
368
+
369
+ @param.depends('start_date', 'end_date', 'eval_df', watch=True)
370
+ def update(self):
371
+ fig = self.create_plot()
372
+ report = self.create_report()
373
+ self.report.object = report
374
+ self.plot_pane.object = fig
375
+
376
+ def __init__(self, eval_df, b_stock_df, p_stock_df, **params):
377
+ self.eval_df = eval_df
378
+ self.b_stock_df = b_stock_df
379
+ self.p_stock_df = p_stock_df
380
+ self._date_range = pn.widgets.DateRangeSlider(
381
+ start=eval_df.date.min(),
382
+ end=eval_df.date.max(),
383
+ value=(eval_df.date.max() - timedelta(days=7), eval_df.date.max())
384
+ )
385
+ self.start_date = self._date_range.value_start
386
+ self.end_date = self._date_range.value_end
387
+ self.plot_pane = pn.pane.Plotly(
388
+ self.create_plot(), sizing_mode='stretch_width')
389
+
390
+ self.report = pn.pane.HTML(
391
+ self.create_report(), sizing_mode='stretch_width')
392
+ super().__init__(**params)
393
+ self._sync_widgets()
394
+
395
+ def __panel__(self):
396
+ self._layout = pn.Card(self._date_range, self.report, self.plot_pane,
397
+ width=500, header=pn.Row(pn.pane.Str('投资组合总结'),
398
+ pn.widgets.TooltipIcon(value=description.summary_card)))
399
+ return self._layout
400
+
401
+ @param.depends('start_date', 'end_date', 'eval_df', watch=True)
402
+ def update_selected_df(self):
403
+ self.selected_df = self.eval_df[self.eval_df.date.between(
404
+ self.start_date, self.end_date
405
+ )]
406
+
407
+ @param.depends('value', 'width', watch=True)
408
+ def _sync_widgets(self):
409
+ pass
410
+
411
+ @param.depends('_date_range.value', watch=True)
412
+ def _sync_params(self):
413
+ self.start_date = self._date_range.value[0]
414
+ self.end_date = self._date_range.value[1]
415
+
416
+
417
+ class DrawDownCard(Viewer):
418
+ def __init__(self, eval_df, calculated_p_stock, calculated_b_stock, **params):
419
+ self.eval_df = eval_df
420
+ self.calculated_p_stock = calculated_p_stock
421
+ self.calculated_b_stock = calculated_b_stock
422
+ self.drawdown_plot = pn.pane.Plotly(self.plot_drawdown())
423
+ super().__init__(**params)
424
+
425
+ def calculate_drawdown(self):
426
+ df = self.eval_df.copy()
427
+ # rolling max return
428
+ # TODO: consider adding this to the processing code
429
+ df['rolling_max_return_p'] = df['portfolio_return_p'].rolling(
430
+ window=len(df), min_periods=1).max()
431
+ # calculate drawdown
432
+ df['drawn_down'] = abs(
433
+ (1 + df.portfolio_return_p) / (1 + df.rolling_max_return_p) - 1)
434
+ return df
435
+
436
+ def plot_drawdown(self):
437
+ df = self.calculate_drawdown()
438
+ fig = px.line(df, x="date", y=['drawn_down'])
439
+ # add scatter to represetn new high
440
+ new_height_pnl = df[df.portfolio_return_p == df.rolling_max_return_p]
441
+ fig.add_trace(go.Scatter(
442
+ x=new_height_pnl['date'], y=new_height_pnl['drawn_down'], mode='markers', name='新的最高总回报'))
443
+ colname_to_name = {
444
+ 'drawn_down': '回撤'
445
+ }
446
+ fig.update_layout(styling.plot_layout)
447
+ fig.for_each_trace(lambda t: t.update(name=colname_to_name.get(t.name, t.name),
448
+ legendgroup=colname_to_name.get(
449
+ t.name, t.name),
450
+ # hovertemplate=t.hovertemplate.replace(
451
+ # t.name, colname_to_name.get(t.name, t.name))
452
+ ))
453
+ return fig
454
+
455
+ def update(self):
456
+ pass
457
+
458
+ def __panel__(self):
459
+ self._layout = pn.Card(self.drawdown_plot,
460
+ header=pn.Row(pn.pane.Str('回撤分析')),
461
+ width=500
462
+ )
463
+ return self._layout
464
+
465
+
466
+ class HistReturnCard(Viewer):
467
+ eval_df = param.Parameter()
468
+ return_barplot = param.Parameterized()
469
+ select_resolution = param.ObjectSelector(
470
+ default='每月回报', objects=['每日回报', '每周回报', '每月回报', '每年回报'])
471
+
472
+ def update_aggregate_df(self):
473
+ freq = None
474
+ if self.select_resolution == "每日回报":
475
+ return self.eval_df
476
+ elif self.select_resolution == "每月回报":
477
+ freq = 'M'
478
+ elif self.select_resolution == "每年回报":
479
+ freq = 'Y'
480
+ elif self.select_resolution == "每周回报":
481
+ freq = 'W'
482
+ # I don't think this formula is correct, check this later
483
+ agg_df = self.eval_df.groupby([pd.Grouper(key='date', freq=freq)])\
484
+ .aggregate({'portfolio_pct_p': 'sum', 'portfolio_pct_b': 'sum'})
485
+ agg_df['portfolio_return_p'] = np.exp(agg_df.portfolio_pct_p) - 1
486
+ agg_df['portfolio_return_b'] = np.exp(agg_df.portfolio_pct_b) - 1
487
+ return agg_df.reset_index()
488
+
489
+ def create_attributes_barplot(self):
490
+ self.attribute_df = self.update_attributes_df()
491
+ fig = px.bar(self.attribute_df, x='date', y=[
492
+ 'allocation', 'selection', 'interaction', 'notional_return', 'active_return'])
493
+ colname_to_name = {
494
+ 'allocation': '分配',
495
+ 'selection': '选择',
496
+ 'interaction': '交互',
497
+ 'notional_return': '名义主动回报',
498
+ 'active_return': '实际主动回报'
499
+ }
500
+ fig.for_each_trace(lambda t: t.update(name=colname_to_name.get(t.name, t.name),
501
+ legendgroup=colname_to_name.get(
502
+ t.name, t.name),
503
+ hovertemplate=t.hovertemplate.replace(
504
+ t.name, colname_to_name.get(t.name, t.name))
505
+ ))
506
+ fig.update_layout(barmode='group', title='主动回报归因',
507
+ bargap=0.0, bargroupgap=0.0)
508
+ fig.update_layout(**styling.plot_layout)
509
+ fig.update_traces(**styling.barplot_trace)
510
+ return fig.to_dict()
511
+
512
+ def create_return_barplot(self):
513
+ self.agg_df = self.update_aggregate_df()
514
+ fig = px.bar(self.agg_df, x='date', y=[
515
+ 'portfolio_return_p', 'portfolio_return_b'],
516
+ barmode='overlay',
517
+ title='周期回报',
518
+ )
519
+ # update legend
520
+ colname_to_name = {
521
+ 'portfolio_return_p': 'portfolio回报率',
522
+ 'portfolio_return_b': 'benchmark回报率'
523
+ }
524
+ fig.for_each_trace(lambda t: t.update(name=colname_to_name.get(t.name, t.name),
525
+ legendgroup=colname_to_name.get(
526
+ t.name, t.name),
527
+ hovertemplate=t.hovertemplate.replace(
528
+ t.name, colname_to_name.get(t.name, t.name))
529
+ ))
530
+
531
+ fig.update_layout(**styling.plot_layout)
532
+
533
+ fig.update_traces(**styling.barplot_trace)
534
+
535
+ return fig.to_dict()
536
+
537
+ @param.depends('eval_df', 'select_resolution', watch=True)
538
+ def update(self):
539
+ return_barplot = self.create_return_barplot()
540
+ self.return_barplot.object = return_barplot
541
+ attributes_barplot = self.create_attributes_barplot()
542
+ self.attribute_barplot.object = attributes_barplot
543
+
544
+ def update_attributes_df(self):
545
+ freq = None
546
+ if self.select_resolution == "每日回报":
547
+ freq = 'D'
548
+ elif self.select_resolution == "每月回报":
549
+ freq = 'M'
550
+ elif self.select_resolution == "每年回报":
551
+ freq = 'Y'
552
+ elif self.select_resolution == "每周回报":
553
+ freq = 'W'
554
+ p_stock = processing.change_resolution(self.calculated_p_stock, freq)
555
+ b_stock = processing.change_resolution(self.calculated_b_stock, freq)
556
+ return processing.calculate_total_attribution(p_stock, b_stock)
557
+
558
+ def __init__(self, eval_df, calculated_p_stock, calculated_b_stock, **params):
559
+ self.eval_df = eval_df
560
+ self.calculated_p_stock = calculated_p_stock
561
+ self.calculated_b_stock = calculated_b_stock
562
+ self._range_slider = pn.widgets.DateRangeSlider(
563
+ name='Date Range Slider',
564
+ start=self.eval_df.date.min(), end=self.eval_df.date.max(),
565
+ value=(self.eval_df.date.min(), self.eval_df.date.max()),
566
+
567
+ )
568
+ self.return_barplot = pn.pane.Plotly(self.create_return_barplot())
569
+ self.attribute_barplot = pn.pane.Plotly(
570
+ self.create_attributes_barplot())
571
+ super().__init__(**params)
572
+
573
+ def __panel__(self):
574
+ self._layout = pn.Card(pn.Param(self.param.select_resolution, name='选择周期'),
575
+ self.return_barplot, self.attribute_barplot, width=500, header=pn.Row(pn.pane.Str('周期回报'),
576
+ pn.widgets.TooltipIcon(value=description.periodic_return_report)))
577
+ return self._layout
578
+
579
+
580
+ class PortfolioComposationCard(Viewer):
581
+ p_stock_df = param.Parameterized()
582
+
583
+ def create_cash_position_df(self):
584
+ aggregate_df = self.p_stock_df.groupby('date', as_index=False).agg({
585
+ 'current_weight': 'sum'
586
+ })
587
+ aggregate_df['type'] = 'portfolio'
588
+ not_in_portfolio_df = aggregate_df.copy()
589
+ not_in_portfolio_df['type'] = 'not_in_portfolio'
590
+ not_in_portfolio_df['current_weight'] = 1000
591
+ # append df
592
+ aggregate_df = pd.concat([aggregate_df, not_in_portfolio_df])
593
+ # sort
594
+ aggregate_df.sort_values(by=['date'], inplace=True)
595
+ return aggregate_df[aggregate_df.date.between(self.date_range.value[0], self.date_range.value[1])]
596
+
597
+ @param.depends('p_stock_df', 'date_range.value', watch=True)
598
+ def update_trend_plot(self):
599
+ self.trend_plot.object = self.create_trend_plot()
600
+
601
+ def create_trend_plot(self):
602
+ aggregate_df = self.create_cash_position_df()
603
+ fig = px.bar(aggregate_df, x='date', y='current_weight', color='type')
604
+ fig.update_layout(legend=dict(
605
+ orientation="h",
606
+ yanchor="bottom",
607
+ y=1.02,
608
+ xanchor="right",
609
+ x=1
610
+ ))
611
+ fig.update_traces(
612
+ marker_line_width=0,
613
+ selector=dict(type="bar"))
614
+ fig.update_layout(bargap=0,
615
+ bargroupgap=0,
616
+ )
617
+ fig.update_layout(uniformtext_minsize=8, uniformtext_mode='hide',
618
+ yaxis_title=None, xaxis_title=None,
619
+ margin=dict(l=0, r=0, t=0, b=0))
620
+ return fig.to_dict()
621
+
622
+ def create_treemap(self):
623
+ self.selected_df['position'] = 'portfolio'
624
+ not_in_portfolio_row = pd.DataFrame({
625
+ 'display_name': ['不在portfolio中'],
626
+ 'position': ['not_in_portfolio'],
627
+ 'aggregate_sector': ['不在portfolio中'],
628
+ 'current_weight': [1000],
629
+ 'portfolio_return': [0],
630
+ 'portfolio_pct': [0]
631
+ })
632
+ df = pd.concat([self.selected_df, not_in_portfolio_row],
633
+ ignore_index=True)
634
+ fig = px.treemap(df, path=[px.Constant('cash_position'), 'position', 'aggregate_sector', 'display_name'], values='current_weight',
635
+ color='portfolio_return', hover_data=['portfolio_return', 'portfolio_pct'],
636
+ color_continuous_scale='RdBu',
637
+ color_continuous_midpoint=np.average(
638
+ df['portfolio_return'])
639
+ )
640
+ fig.update_layout(styling.plot_layout)
641
+ fig.update_layout(coloraxis_colorbar=dict(
642
+ title="weighted return"))
643
+ colname_to_name = {
644
+ 'cash_position': '现金分布',
645
+ 'portfolio_return': '加权回报',
646
+ 'not_in_portfolio': '不在portfolio中',
647
+ 'current_weight': '现金',
648
+
649
+ }
650
+ fig.for_each_trace(lambda t: t.update(name=colname_to_name.get(t.name, t.name),
651
+ hovertemplate=t.hovertemplate.replace(
652
+ t.name, colname_to_name.get(t.name, t.name))
653
+ ))
654
+ return fig.to_dict()
655
+
656
+ def __init__(self, p_stock_df, **params):
657
+ self.p_stock_df = p_stock_df
658
+ self.date_picker = pn.widgets.DatetimePicker(name='选择某日资金分布',
659
+ start=self.p_stock_df.date.min(),
660
+ end=self.p_stock_df.date.max(),
661
+ value=self.p_stock_df.date.max(),
662
+ enabled_dates=[datetime_object.date(
663
+ ) for datetime_object in self.p_stock_df.date.unique()],
664
+ enable_time=False,
665
+ )
666
+ self.date_range = pn.widgets.DateRangeSlider(name='选择资金分布走势区间',
667
+ start=self.p_stock_df.date.min(),
668
+ end=self.p_stock_df.date.max(),
669
+ value=(self.p_stock_df.date.min(
670
+ ), self.p_stock_df.date.max()),
671
+ )
672
+ self.selected_df = self.p_stock_df[self.p_stock_df.date ==
673
+ self.date_picker.value]
674
+ self.tree_plot = pn.pane.Plotly(self.create_treemap())
675
+ self.trend_plot = pn.pane.Plotly(self.create_trend_plot())
676
+
677
+ # calculate money position
678
+ super().__init__(**params)
679
+
680
+ def __panel__(self):
681
+ self._layout = pn.Card(self.date_picker, self.tree_plot, self.date_range, self.trend_plot,
682
+ width=500, header=pn.pane.Str('资金分布'))
683
+ return self._layout
684
+
685
+ @param.depends('date_picker.value', 'p_stock_df', watch=True)
686
+ def update(self):
687
+ self.selected_df = self.p_stock_df[self.p_stock_df.date ==
688
+ self.date_picker.value]
689
+ tree_plot = self.create_treemap()
690
+ self.tree_plot.object = tree_plot
691
+
692
+
693
+ class BestAndWorstStocks(Viewer):
694
+ p_stock_df = param.Parameter()
695
+ b_stock_df = param.Parameter()
696
+ start_date = param.Parameter()
697
+ end_date = param.Parameter()
698
+
699
+ def calculate_attributes(self):
700
+ result_df = processing.calculate_attributes_between_dates(self.start_date,
701
+ self.end_date,
702
+ self.p_stock_df,
703
+ self.b_stock_df)
704
+
705
+ return result_df
706
+
707
+ def create_tabulator(self, df):
708
+ col_title_map = {
709
+ 'display_name_p': '股票名称',
710
+ 'ticker': '股票代码',
711
+ 'pct_p': '加权回报率',
712
+ 'prev_w_in_p_b': '在benchmark中的权重',
713
+ 'prev_w_in_p_p': '在portfolio中的权重',
714
+ 'allocation': '分配分数',
715
+ 'selection': '选择分数',
716
+ 'interaction': '交互分数',
717
+ 'return': '未加权回报率',
718
+ 'active_return': '加权主动回报率',
719
+ }
720
+ return pn.widgets.Tabulator(df, sizing_mode='stretch_width',
721
+ hidden_columns=['index', 'display_name_b',
722
+ 'pct_b', 'in_portfolio',
723
+ ],
724
+ frozen_columns=['display_name_p'],
725
+ titles=col_title_map)
726
+
727
+ @param.depends('start_date', 'end_date', watch=True)
728
+ def update(self):
729
+ result_df = self.get_processed_df()
730
+ self.best_5_tabulator.value = result_df.tail(5)
731
+ self.worst_5_tabulator.value = result_df.head(5)
732
+
733
+ def get_processed_df(self):
734
+ '''
735
+ calculate attributes and return a sorted dataframe on weighted return
736
+ '''
737
+
738
+ result_df = self.calculate_attributes()
739
+ result_df = result_df[result_df.in_portfolio]
740
+ result_df.sort_values(by='return', inplace=True)
741
+ return result_df
742
+
743
+ def __init__(self, p_stock_df, b_stock_df, **params):
744
+ self.p_stock_df = p_stock_df
745
+ self.b_stock_df = b_stock_df
746
+ self._date_range = pn.widgets.DateRangeSlider(
747
+ name='选择计算回报的时间区间',
748
+ start=p_stock_df.date.min(),
749
+ end=p_stock_df.date.max(),
750
+ value=(p_stock_df.date.max() -
751
+ timedelta(days=7), p_stock_df.date.max())
752
+ )
753
+ self.start_date = self._date_range.value_start
754
+ self.end_date = self._date_range.value_end
755
+ result_df = self.get_processed_df()
756
+ self.best_5_tabulator = self.create_tabulator(result_df.tail(5))
757
+ self.worst_5_tabulator = self.create_tabulator(result_df.head(5))
758
+ super().__init__(**params)
759
+
760
+ @param.depends('_date_range.value', watch=True)
761
+ def _sync_params(self):
762
+ self.start_date = self._date_range.value[0]
763
+ self.end_date = self._date_range.value[1]
764
+ # print('update range...')
765
+
766
+ def __panel__(self):
767
+ self._layout = pn.Card(self._date_range,
768
+ pn.pane.Str('加权回报率最高回报5只股票'),
769
+ self.best_5_tabulator,
770
+ pn.pane.Str('加权回报率最低回报5只股票'),
771
+ self.worst_5_tabulator,
772
+ max_width=500, header=pn.pane.Str('Portfolio中最高回报和最低加权回报率股票'))
773
+ return self._layout
774
+
775
+
776
+ class TopHeader(Viewer):
777
+ '''
778
+ display up to todays' PnL, total return and max drawdown
779
+ '''
780
+ eval_df = param.Parameter()
781
+
782
+ @param.depends('eval_df', watch=True)
783
+ def update(self):
784
+ '''
785
+ update Pnl, total return and max drawdown when df is updated
786
+ '''
787
+ return
788
+
789
+ def calculation(self):
790
+ '''calculate PnL, total return and max drawdown'''
791
+ pnl = self.eval_df[self.eval_df.date ==
792
+ self.eval_df.date.max()].cum_pnl.values[0]
793
+ total_return = self.eval_df[self.eval_df.date ==
794
+ self.eval_df.date.max()].portfolio_return_p.values[0]
795
+ # max draw down
796
+ self.eval_df['rolling_max_return'] = self.eval_df.portfolio_return_p.rolling(
797
+ window=len(self.eval_df), min_periods=1).max()
798
+ self.eval_df.draw_down = abs(
799
+ (1 + self.eval_df.portfolio_return_p) /
800
+ (1 + self.eval_df.rolling_max_return) - 1
801
+ )
802
+ max_drawdown = self.eval_df.draw_down.max()
803
+ return pnl, total_return, max_drawdown
804
+
805
+ def create_report(self, pnl, total_return, max_drawdown):
806
+ return pn.FlexBox(
807
+ f"PnL:{round(pnl,2)}¥", f"回报:{round(total_return * 100,2)}%", f'最大回撤:{round(max_drawdown * 100,2)}%', justify_content='space-evenly')
808
+
809
+ def __init__(self, eval_df, **params):
810
+ self.eval_df = eval_df
811
+ pnl, total_return, max_drawdown = self.calculation()
812
+ self.report = self.create_report(pnl, total_return, max_drawdown)
813
+ super().__init__(**params)
814
+
815
+ def __panel__(self):
816
+ self._layout = pn.Card(self.report, sizing_mode='stretch_width')
817
+ return self._layout
app_ini.ipynb CHANGED
@@ -1,3 +1,723 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:1178e33b9c1d97aac01ebe954a92585270ea63da5fca3f3ae388a3ae70b317ad
3
- size 56961
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "data": {
10
+ "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n var py_version = '3.1.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n var reloading = false;\n var Bokeh = root.Bokeh;\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n run_callbacks();\n return null;\n }\n if (!reloading) {\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n var skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n require([\"jspanel\"], function(jsPanel) {\n\twindow.jsPanel = jsPanel\n\ton_load()\n })\n require([\"jspanel-modal\"], function() {\n\ton_load()\n })\n require([\"jspanel-tooltip\"], function() {\n\ton_load()\n })\n require([\"jspanel-hint\"], function() {\n\ton_load()\n })\n require([\"jspanel-layout\"], function() {\n\ton_load()\n })\n require([\"jspanel-contextmenu\"], function() {\n\ton_load()\n })\n require([\"jspanel-dock\"], function() {\n\ton_load()\n })\n require([\"gridstack\"], function(GridStack) {\n\twindow.GridStack = GridStack\n\ton_load()\n })\n require([\"notyf\"], function() {\n\ton_load()\n })\n root._bokeh_is_loading = css_urls.length + 9;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n var existing_stylesheets = []\n var links = document.getElementsByTagName('link')\n for (var i = 0; i < links.length; i++) {\n var link = links[i]\n if (link.href != null) {\n\texisting_stylesheets.push(link.href)\n }\n }\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n if (existing_stylesheets.indexOf(url) !== -1) {\n\ton_load()\n\tcontinue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } var existing_scripts = []\n var scripts = document.getElementsByTagName('script')\n for (var i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n\texisting_scripts.push(script.src)\n }\n }\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (var i = 0; i < js_modules.length; i++) {\n var url = js_modules[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n var url = js_exports[name];\n if (skip.indexOf(url) >= 0 || root[name] != null) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js\", \"https://cdn.holoviz.org/panel/1.1.1/dist/panel.min.js\"];\n var js_modules = [];\n var js_exports = {};\n var css_urls = [];\n var inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n\tvar NewBokeh = root.Bokeh;\n\tif (Bokeh.versions === undefined) {\n\t Bokeh.versions = new Map();\n\t}\n\tif (NewBokeh.version !== Bokeh.version) {\n\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n\t}\n\troot.Bokeh = Bokeh;\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n Bokeh = root.Bokeh;\n bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n if (!reloading && (!bokeh_loaded || is_dev)) {\n\troot.Bokeh = undefined;\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n\trun_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));",
11
+ "application/vnd.holoviews_load.v0+json": ""
12
+ },
13
+ "metadata": {},
14
+ "output_type": "display_data"
15
+ },
16
+ {
17
+ "data": {
18
+ "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n",
19
+ "application/vnd.holoviews_load.v0+json": ""
20
+ },
21
+ "metadata": {},
22
+ "output_type": "display_data"
23
+ },
24
+ {
25
+ "data": {
26
+ "text/html": [
27
+ "<style>*[data-root-id],\n",
28
+ "*[data-root-id] > * {\n",
29
+ " box-sizing: border-box;\n",
30
+ " font-family: var(--jp-ui-font-family);\n",
31
+ " font-size: var(--jp-ui-font-size1);\n",
32
+ " color: var(--vscode-editor-foreground, var(--jp-ui-font-color1));\n",
33
+ "}\n",
34
+ "\n",
35
+ "/* Override VSCode background color */\n",
36
+ ".cell-output-ipywidget-background:has(> .cell-output-ipywidget-background\n",
37
+ " > .lm-Widget\n",
38
+ " > *[data-root-id]),\n",
39
+ ".cell-output-ipywidget-background:has(> .lm-Widget > *[data-root-id]) {\n",
40
+ " background-color: transparent !important;\n",
41
+ "}\n",
42
+ "</style>"
43
+ ]
44
+ },
45
+ "metadata": {},
46
+ "output_type": "display_data"
47
+ }
48
+ ],
49
+ "source": [
50
+ "import pandas as pd\n",
51
+ "from datetime import datetime, timedelta\n",
52
+ "from script import processing\n",
53
+ "from script import api\n",
54
+ "from sqlalchemy import create_engine\n",
55
+ "import pytz\n",
56
+ "import numpy as np\n",
57
+ "import hvplot.pandas\n",
58
+ "db_url = 'sqlite:///local.db'\n",
59
+ "engine = create_engine(db_url)\n"
60
+ ]
61
+ },
62
+ {
63
+ "cell_type": "code",
64
+ "execution_count": 14,
65
+ "metadata": {},
66
+ "outputs": [
67
+ {
68
+ "name": "stdout",
69
+ "output_type": "stream",
70
+ "text": [
71
+ "The autoreload extension is already loaded. To reload it, use:\n",
72
+ " %reload_ext autoreload\n"
73
+ ]
74
+ }
75
+ ],
76
+ "source": [
77
+ "%load_ext autoreload\n",
78
+ "%autoreload 2"
79
+ ]
80
+ },
81
+ {
82
+ "cell_type": "code",
83
+ "execution_count": 2,
84
+ "metadata": {},
85
+ "outputs": [
86
+ {
87
+ "ename": "FileNotFoundError",
88
+ "evalue": "[Errno 2] No such file or directory: './data/p_profile.pkl'",
89
+ "output_type": "error",
90
+ "traceback": [
91
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
92
+ "\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)",
93
+ "Cell \u001b[0;32mIn[2], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[39m## initialize by batchprocess to have initial result \u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m p_profile \u001b[39m=\u001b[39m pd\u001b[39m.\u001b[39;49mread_pickle(\u001b[39m'\u001b[39;49m\u001b[39m./data/p_profile.pkl\u001b[39;49m\u001b[39m'\u001b[39;49m)\n\u001b[1;32m 3\u001b[0m start_date \u001b[39m=\u001b[39m p_profile\u001b[39m.\u001b[39mdate\u001b[39m.\u001b[39mmin()\n\u001b[1;32m 4\u001b[0m end_date \u001b[39m=\u001b[39m pd\u001b[39m.\u001b[39mto_datetime(datetime\u001b[39m.\u001b[39mnow()\u001b[39m-\u001b[39mtimedelta(days\u001b[39m=\u001b[39m\u001b[39m7\u001b[39m))\n",
94
+ "File \u001b[0;32m/opt/homebrew/Caskroom/miniforge/base/envs/portfolio_risk_assesment/lib/python3.11/site-packages/pandas/io/pickle.py:179\u001b[0m, in \u001b[0;36mread_pickle\u001b[0;34m(filepath_or_buffer, compression, storage_options)\u001b[0m\n\u001b[1;32m 115\u001b[0m \u001b[39m\u001b[39m\u001b[39m\"\"\"\u001b[39;00m\n\u001b[1;32m 116\u001b[0m \u001b[39mLoad pickled pandas object (or any object) from file.\u001b[39;00m\n\u001b[1;32m 117\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 176\u001b[0m \u001b[39m4 4 9\u001b[39;00m\n\u001b[1;32m 177\u001b[0m \u001b[39m\"\"\"\u001b[39;00m\n\u001b[1;32m 178\u001b[0m excs_to_catch \u001b[39m=\u001b[39m (\u001b[39mAttributeError\u001b[39;00m, \u001b[39mImportError\u001b[39;00m, \u001b[39mModuleNotFoundError\u001b[39;00m, \u001b[39mTypeError\u001b[39;00m)\n\u001b[0;32m--> 179\u001b[0m \u001b[39mwith\u001b[39;00m get_handle(\n\u001b[1;32m 180\u001b[0m filepath_or_buffer,\n\u001b[1;32m 181\u001b[0m \u001b[39m\"\u001b[39;49m\u001b[39mrb\u001b[39;49m\u001b[39m\"\u001b[39;49m,\n\u001b[1;32m 182\u001b[0m compression\u001b[39m=\u001b[39;49mcompression,\n\u001b[1;32m 183\u001b[0m is_text\u001b[39m=\u001b[39;49m\u001b[39mFalse\u001b[39;49;00m,\n\u001b[1;32m 184\u001b[0m storage_options\u001b[39m=\u001b[39;49mstorage_options,\n\u001b[1;32m 185\u001b[0m ) \u001b[39mas\u001b[39;00m handles:\n\u001b[1;32m 186\u001b[0m \u001b[39m# 1) try standard library Pickle\u001b[39;00m\n\u001b[1;32m 187\u001b[0m \u001b[39m# 2) try pickle_compat (older pandas version) to handle subclass changes\u001b[39;00m\n\u001b[1;32m 188\u001b[0m \u001b[39m# 3) try pickle_compat with latin-1 encoding upon a UnicodeDecodeError\u001b[39;00m\n\u001b[1;32m 190\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[1;32m 191\u001b[0m \u001b[39m# TypeError for Cython complaints about object.__new__ vs Tick.__new__\u001b[39;00m\n\u001b[1;32m 192\u001b[0m \u001b[39mtry\u001b[39;00m:\n",
95
+ "File \u001b[0;32m/opt/homebrew/Caskroom/miniforge/base/envs/portfolio_risk_assesment/lib/python3.11/site-packages/pandas/io/common.py:868\u001b[0m, in \u001b[0;36mget_handle\u001b[0;34m(path_or_buf, mode, encoding, compression, memory_map, is_text, errors, storage_options)\u001b[0m\n\u001b[1;32m 859\u001b[0m handle \u001b[39m=\u001b[39m \u001b[39mopen\u001b[39m(\n\u001b[1;32m 860\u001b[0m handle,\n\u001b[1;32m 861\u001b[0m ioargs\u001b[39m.\u001b[39mmode,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 864\u001b[0m newline\u001b[39m=\u001b[39m\u001b[39m\"\u001b[39m\u001b[39m\"\u001b[39m,\n\u001b[1;32m 865\u001b[0m )\n\u001b[1;32m 866\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[1;32m 867\u001b[0m \u001b[39m# Binary mode\u001b[39;00m\n\u001b[0;32m--> 868\u001b[0m handle \u001b[39m=\u001b[39m \u001b[39mopen\u001b[39;49m(handle, ioargs\u001b[39m.\u001b[39;49mmode)\n\u001b[1;32m 869\u001b[0m handles\u001b[39m.\u001b[39mappend(handle)\n\u001b[1;32m 871\u001b[0m \u001b[39m# Convert BytesIO or file objects passed with an encoding\u001b[39;00m\n",
96
+ "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: './data/p_profile.pkl'"
97
+ ]
98
+ }
99
+ ],
100
+ "source": [
101
+ "## initialize by batchprocess to have initial result \n",
102
+ "p_profile = pd.read_pickle('./data/p_profile.pkl')\n",
103
+ "start_date = p_profile.date.min()\n",
104
+ "end_date = pd.to_datetime(datetime.now()-timedelta(days=7))\n",
105
+ "# collect data upto 7 days ago \n",
106
+ "b_profile, error = api.update_benchmark_profile(start_date, end_date)\n",
107
+ "p_stocks, error = api.get_stocks_price(p_profile, start_date, end_date)\n",
108
+ "b_stocks, error = api.get_stocks_price(b_profile, start_date, end_date)"
109
+ ]
110
+ },
111
+ {
112
+ "cell_type": "code",
113
+ "execution_count": 13,
114
+ "metadata": {},
115
+ "outputs": [],
116
+ "source": [
117
+ "# save result \n",
118
+ "# p_profile.to_pickle('./data/p_profile.pkl')\n",
119
+ "# b_profile.to_pickle('./data/b_profile.pkl')\n",
120
+ "p_stocks.to_pickle('./data/p_stocks.pkl')\n",
121
+ "b_stocks.to_pickle('./data/b_stocks.pkl')\n"
122
+ ]
123
+ },
124
+ {
125
+ "cell_type": "code",
126
+ "execution_count": 12,
127
+ "metadata": {},
128
+ "outputs": [
129
+ {
130
+ "name": "stderr",
131
+ "output_type": "stream",
132
+ "text": [
133
+ "/Users/lamonkey/Desktop/risk monitor/script/processing.py:262: SettingWithCopyWarning: \n",
134
+ "A value is trying to be set on a copy of a slice from a DataFrame\n",
135
+ "\n",
136
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
137
+ " df.fillna(0, inplace=True)\n",
138
+ "/Users/lamonkey/Desktop/risk monitor/script/processing.py:263: SettingWithCopyWarning: \n",
139
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
140
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
141
+ "\n",
142
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
143
+ " df['active_return'] = df.pct_p * \\\n",
144
+ "/Users/lamonkey/Desktop/risk monitor/script/processing.py:266: SettingWithCopyWarning: \n",
145
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
146
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
147
+ "\n",
148
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
149
+ " df['allocation'] = (df.prev_w_in_p_p - df.prev_w_in_p_b) * df.pct_b\n",
150
+ "/Users/lamonkey/Desktop/risk monitor/script/processing.py:267: SettingWithCopyWarning: \n",
151
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
152
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
153
+ "\n",
154
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
155
+ " df['selection'] = (df.pct_p - df.pct_b) * df.prev_w_in_p_b\n",
156
+ "/Users/lamonkey/Desktop/risk monitor/script/processing.py:268: SettingWithCopyWarning: \n",
157
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
158
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
159
+ "\n",
160
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
161
+ " df['interaction'] = (df.pct_p - df.pct_b) * \\\n",
162
+ "/Users/lamonkey/Desktop/risk monitor/script/processing.py:270: SettingWithCopyWarning: \n",
163
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
164
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
165
+ "\n",
166
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
167
+ " df['notinal_return'] = df.allocation + df.selection + df.interaction\n"
168
+ ]
169
+ }
170
+ ],
171
+ "source": [
172
+ "## batch processing \n",
173
+ "calculated_p_stock = processing.get_processing_result_of_stocks_df(p_stocks, p_profile)\n",
174
+ "calculated_b_stock = processing.get_processing_result_of_stocks_df(b_stocks, b_profile)\n",
175
+ "p_eval_df = processing.get_portfolio_evaluation(calculated_p_stock, calculated_b_stock, p_profile)\n",
176
+ "sector_eval_df = processing.get_portfolio_sector_evaluation(calculated_p_stock, calculated_b_stock)\n",
177
+ "attribution_result_df = processing.calculate_total_attribution(calculated_p_stock, calculated_b_stock)\n",
178
+ "s_attribution_result_df = processing.calculate_total_attribution_by_sector(calculated_p_stock, calculated_b_stock)"
179
+ ]
180
+ },
181
+ {
182
+ "cell_type": "code",
183
+ "execution_count": 15,
184
+ "metadata": {},
185
+ "outputs": [],
186
+ "source": [
187
+ "## save result to db\n",
188
+ "with engine.connect() as connection:\n",
189
+ " # all_stock_info.to_sql('all_stock_info', con=connection, if_exists='replace', index=False)\n",
190
+ " calculated_b_stock.to_sql('calculated_b_stock', con=connection, if_exists='replace', index=False)\n",
191
+ " calculated_p_stock.to_sql('calculated_p_stock', con=connection, if_exists='replace', index=False)\n",
192
+ " p_eval_df.to_sql('p_eval_result', con=connection, if_exists='replace', index=False)\n",
193
+ " sector_eval_df.to_sql('sector_eval_result', con=connection, if_exists='replace', index=False)\n",
194
+ " attribution_result_df.to_sql('attribution_result', con=connection, if_exists='replace', index=False)\n",
195
+ " s_attribution_result_df.to_sql('s_attribution_result', con=connection, if_exists='replace', index=False)"
196
+ ]
197
+ },
198
+ {
199
+ "cell_type": "code",
200
+ "execution_count": 63,
201
+ "metadata": {},
202
+ "outputs": [],
203
+ "source": [
204
+ "# load from sql\n",
205
+ "name_df_map = dict()\n",
206
+ "with engine.connect() as connection:\n",
207
+ " for table in ['calculated_b_stock','calculated_p_stock','p_eval_result','sector_eval_result']:\n",
208
+ " try:\n",
209
+ " df = pd.read_sql_table(table, con=connection)\n",
210
+ " name_df_map[table] = df\n",
211
+ " except:\n",
212
+ " pass\n",
213
+ " # TODO load data from api and calculate result \n",
214
+ " "
215
+ ]
216
+ },
217
+ {
218
+ "cell_type": "code",
219
+ "execution_count": 91,
220
+ "metadata": {},
221
+ "outputs": [],
222
+ "source": [
223
+ "# load data upto now\n",
224
+ "# Get the current time in UTC\n",
225
+ "current_time = datetime.datetime.utcnow()\n",
226
+ "# Set the timezone to Beijing\n",
227
+ "beijing_timezone = pytz.timezone('Asia/Shanghai')\n",
228
+ "# Convert the current time to Beijing time\n",
229
+ "end_time = pd.to_datetime(current_time.astimezone(beijing_timezone).date())\n",
230
+ "start_time = name_df_map['p_eval_result'].date.max() + timedelta(days=1)\n",
231
+ "\n",
232
+ "# get data up to today\n",
233
+ "b_profile, error = api.update_benchmark_profile(start_time, end_time)\n",
234
+ "p_stocks, error = api.get_stocks_price(p_profile, start_time, end_time)\n",
235
+ "b_stocks, error = api.get_stocks_price(b_profile, start_time, end_time)"
236
+ ]
237
+ },
238
+ {
239
+ "cell_type": "code",
240
+ "execution_count": 185,
241
+ "metadata": {},
242
+ "outputs": [
243
+ {
244
+ "ename": "ValueError",
245
+ "evalue": "The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().",
246
+ "output_type": "error",
247
+ "traceback": [
248
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
249
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
250
+ "\u001b[0;32m/var/folders/v5/2108rh5964q9j741wg_s8r1w0000gn/T/ipykernel_35506/2587190678.py\u001b[0m in \u001b[0;36m?\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mmost_recent_df\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mname_df_map\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'calculated_p_stock'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgroupby\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'ticker'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlast\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreset_index\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mconcat_df\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconcat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mmost_recent_df\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mp_stocks\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maxis\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mjoin\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'outer'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mprocessing\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_processing_result_of_stocks_df\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconcat_df\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mp_profile\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
251
+ "\u001b[0;32m~/Desktop/risk monitor/script/processing.py\u001b[0m in \u001b[0;36m?\u001b[0;34m(stock_df, profile_df)\u001b[0m\n\u001b[1;32m 38\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mindex\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrow\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mgroup\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miterrows\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 39\u001b[0m \u001b[0mcur_w\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfloat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'nan'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 40\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 41\u001b[0m \u001b[0;31m# if has initial weight, the following row all use this initial weight\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 42\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mpd\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misna\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrow\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'initial_weight'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 43\u001b[0m \u001b[0mini_w\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mrow\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'initial_weight'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 44\u001b[0m \u001b[0mcur_w\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mini_w\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 45\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
252
+ "\u001b[0;32m/opt/homebrew/Caskroom/miniforge/base/envs/portfolio_risk_assesment/lib/python3.11/site-packages/pandas/core/generic.py\u001b[0m in \u001b[0;36m?\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1464\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mfinal\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1465\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__nonzero__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0mNoReturn\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1466\u001b[0;31m raise ValueError(\n\u001b[0m\u001b[1;32m 1467\u001b[0m \u001b[0;34mf\"The truth value of a {type(self).__name__} is ambiguous. \"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1468\u001b[0m \u001b[0;34m\"Use a.empty, a.bool(), a.item(), a.any() or a.all().\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1469\u001b[0m )\n",
253
+ "\u001b[0;31mValueError\u001b[0m: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all()."
254
+ ]
255
+ }
256
+ ],
257
+ "source": [
258
+ "most_recent_df = name_df_map['calculated_p_stock'].groupby('ticker').last().reset_index()\n",
259
+ "concat_df = pd.concat([most_recent_df, p_stocks], axis=0, join='outer')\n",
260
+ "processing.get_processing_result_of_stocks_df(concat_df, p_profile)"
261
+ ]
262
+ },
263
+ {
264
+ "cell_type": "code",
265
+ "execution_count": 182,
266
+ "metadata": {},
267
+ "outputs": [
268
+ {
269
+ "data": {
270
+ "text/html": [
271
+ "<div>\n",
272
+ "<style scoped>\n",
273
+ " .dataframe tbody tr th:only-of-type {\n",
274
+ " vertical-align: middle;\n",
275
+ " }\n",
276
+ "\n",
277
+ " .dataframe tbody tr th {\n",
278
+ " vertical-align: top;\n",
279
+ " }\n",
280
+ "\n",
281
+ " .dataframe thead th {\n",
282
+ " text-align: right;\n",
283
+ " }\n",
284
+ "</style>\n",
285
+ "<table border=\"1\" class=\"dataframe\">\n",
286
+ " <thead>\n",
287
+ " <tr style=\"text-align: right;\">\n",
288
+ " <th></th>\n",
289
+ " <th>date</th>\n",
290
+ " <th>ticker</th>\n",
291
+ " <th>open</th>\n",
292
+ " <th>close</th>\n",
293
+ " <th>high</th>\n",
294
+ " <th>low</th>\n",
295
+ " <th>volume</th>\n",
296
+ " <th>money</th>\n",
297
+ " <th>display_name</th>\n",
298
+ " <th>name</th>\n",
299
+ " <th>...</th>\n",
300
+ " <th>portfolio_pct</th>\n",
301
+ " <th>prev_w_in_sectore</th>\n",
302
+ " <th>ini_w_in_sector</th>\n",
303
+ " <th>sector_pct</th>\n",
304
+ " <th>portfolio_return</th>\n",
305
+ " <th>cum_pct</th>\n",
306
+ " <th>return</th>\n",
307
+ " <th>sector_return</th>\n",
308
+ " <th>cur_w_in_p</th>\n",
309
+ " <th>pre_w_in_sector</th>\n",
310
+ " </tr>\n",
311
+ " </thead>\n",
312
+ " <tbody>\n",
313
+ " <tr>\n",
314
+ " <th>0</th>\n",
315
+ " <td>2021-01-05</td>\n",
316
+ " <td>600409.XSHG</td>\n",
317
+ " <td>9.23</td>\n",
318
+ " <td>9.57</td>\n",
319
+ " <td>9.66</td>\n",
320
+ " <td>9.08</td>\n",
321
+ " <td>82669289.0</td>\n",
322
+ " <td>7.803391e+08</td>\n",
323
+ " <td>三友化工</td>\n",
324
+ " <td>SYHG</td>\n",
325
+ " <td>...</td>\n",
326
+ " <td>NaN</td>\n",
327
+ " <td>NaN</td>\n",
328
+ " <td>1.0</td>\n",
329
+ " <td>NaN</td>\n",
330
+ " <td>NaN</td>\n",
331
+ " <td>NaN</td>\n",
332
+ " <td>NaN</td>\n",
333
+ " <td>NaN</td>\n",
334
+ " <td>NaN</td>\n",
335
+ " <td>NaN</td>\n",
336
+ " </tr>\n",
337
+ " <tr>\n",
338
+ " <th>1</th>\n",
339
+ " <td>2021-01-05</td>\n",
340
+ " <td>300274.XSHE</td>\n",
341
+ " <td>76.03</td>\n",
342
+ " <td>76.45</td>\n",
343
+ " <td>80.20</td>\n",
344
+ " <td>75.27</td>\n",
345
+ " <td>51384827.0</td>\n",
346
+ " <td>3.961995e+09</td>\n",
347
+ " <td>阳光电源</td>\n",
348
+ " <td>YGDY</td>\n",
349
+ " <td>...</td>\n",
350
+ " <td>NaN</td>\n",
351
+ " <td>NaN</td>\n",
352
+ " <td>0.5</td>\n",
353
+ " <td>NaN</td>\n",
354
+ " <td>NaN</td>\n",
355
+ " <td>NaN</td>\n",
356
+ " <td>NaN</td>\n",
357
+ " <td>NaN</td>\n",
358
+ " <td>NaN</td>\n",
359
+ " <td>NaN</td>\n",
360
+ " </tr>\n",
361
+ " <tr>\n",
362
+ " <th>2</th>\n",
363
+ " <td>2021-01-05</td>\n",
364
+ " <td>002920.XSHE</td>\n",
365
+ " <td>85.44</td>\n",
366
+ " <td>87.25</td>\n",
367
+ " <td>87.95</td>\n",
368
+ " <td>84.07</td>\n",
369
+ " <td>3852674.0</td>\n",
370
+ " <td>3.322598e+08</td>\n",
371
+ " <td>德赛西威</td>\n",
372
+ " <td>DSXW</td>\n",
373
+ " <td>...</td>\n",
374
+ " <td>NaN</td>\n",
375
+ " <td>NaN</td>\n",
376
+ " <td>1.0</td>\n",
377
+ " <td>NaN</td>\n",
378
+ " <td>NaN</td>\n",
379
+ " <td>NaN</td>\n",
380
+ " <td>NaN</td>\n",
381
+ " <td>NaN</td>\n",
382
+ " <td>NaN</td>\n",
383
+ " <td>NaN</td>\n",
384
+ " </tr>\n",
385
+ " <tr>\n",
386
+ " <th>3</th>\n",
387
+ " <td>2021-01-05</td>\n",
388
+ " <td>002709.XSHE</td>\n",
389
+ " <td>32.54</td>\n",
390
+ " <td>33.89</td>\n",
391
+ " <td>34.22</td>\n",
392
+ " <td>31.39</td>\n",
393
+ " <td>59152352.0</td>\n",
394
+ " <td>1.942406e+09</td>\n",
395
+ " <td>天赐材料</td>\n",
396
+ " <td>TCCL</td>\n",
397
+ " <td>...</td>\n",
398
+ " <td>NaN</td>\n",
399
+ " <td>NaN</td>\n",
400
+ " <td>0.5</td>\n",
401
+ " <td>NaN</td>\n",
402
+ " <td>NaN</td>\n",
403
+ " <td>NaN</td>\n",
404
+ " <td>NaN</td>\n",
405
+ " <td>NaN</td>\n",
406
+ " <td>NaN</td>\n",
407
+ " <td>NaN</td>\n",
408
+ " </tr>\n",
409
+ " <tr>\n",
410
+ " <th>4</th>\n",
411
+ " <td>2021-01-05</td>\n",
412
+ " <td>603882.XSHG</td>\n",
413
+ " <td>125.25</td>\n",
414
+ " <td>124.64</td>\n",
415
+ " <td>128.31</td>\n",
416
+ " <td>121.68</td>\n",
417
+ " <td>6803710.0</td>\n",
418
+ " <td>8.458543e+08</td>\n",
419
+ " <td>金域医学</td>\n",
420
+ " <td>JYYX</td>\n",
421
+ " <td>...</td>\n",
422
+ " <td>NaN</td>\n",
423
+ " <td>NaN</td>\n",
424
+ " <td>1.0</td>\n",
425
+ " <td>NaN</td>\n",
426
+ " <td>NaN</td>\n",
427
+ " <td>NaN</td>\n",
428
+ " <td>NaN</td>\n",
429
+ " <td>NaN</td>\n",
430
+ " <td>NaN</td>\n",
431
+ " <td>NaN</td>\n",
432
+ " </tr>\n",
433
+ " <tr>\n",
434
+ " <th>...</th>\n",
435
+ " <td>...</td>\n",
436
+ " <td>...</td>\n",
437
+ " <td>...</td>\n",
438
+ " <td>...</td>\n",
439
+ " <td>...</td>\n",
440
+ " <td>...</td>\n",
441
+ " <td>...</td>\n",
442
+ " <td>...</td>\n",
443
+ " <td>...</td>\n",
444
+ " <td>...</td>\n",
445
+ " <td>...</td>\n",
446
+ " <td>...</td>\n",
447
+ " <td>...</td>\n",
448
+ " <td>...</td>\n",
449
+ " <td>...</td>\n",
450
+ " <td>...</td>\n",
451
+ " <td>...</td>\n",
452
+ " <td>...</td>\n",
453
+ " <td>...</td>\n",
454
+ " <td>...</td>\n",
455
+ " <td>...</td>\n",
456
+ " </tr>\n",
457
+ " <tr>\n",
458
+ " <th>3609</th>\n",
459
+ " <td>2023-06-27</td>\n",
460
+ " <td>600415.XSHG</td>\n",
461
+ " <td>8.52</td>\n",
462
+ " <td>8.69</td>\n",
463
+ " <td>8.78</td>\n",
464
+ " <td>8.40</td>\n",
465
+ " <td>151396630.0</td>\n",
466
+ " <td>1.305075e+09</td>\n",
467
+ " <td>小商品城</td>\n",
468
+ " <td>XSPC</td>\n",
469
+ " <td>...</td>\n",
470
+ " <td>NaN</td>\n",
471
+ " <td>NaN</td>\n",
472
+ " <td>NaN</td>\n",
473
+ " <td>0.027187</td>\n",
474
+ " <td>NaN</td>\n",
475
+ " <td>NaN</td>\n",
476
+ " <td>NaN</td>\n",
477
+ " <td>NaN</td>\n",
478
+ " <td>0.301143</td>\n",
479
+ " <td>1.0</td>\n",
480
+ " </tr>\n",
481
+ " <tr>\n",
482
+ " <th>3610</th>\n",
483
+ " <td>2023-06-28</td>\n",
484
+ " <td>600415.XSHG</td>\n",
485
+ " <td>8.60</td>\n",
486
+ " <td>8.63</td>\n",
487
+ " <td>8.68</td>\n",
488
+ " <td>8.37</td>\n",
489
+ " <td>103167271.0</td>\n",
490
+ " <td>8.798186e+08</td>\n",
491
+ " <td>小商品城</td>\n",
492
+ " <td>XSPC</td>\n",
493
+ " <td>...</td>\n",
494
+ " <td>NaN</td>\n",
495
+ " <td>NaN</td>\n",
496
+ " <td>NaN</td>\n",
497
+ " <td>-0.006904</td>\n",
498
+ " <td>NaN</td>\n",
499
+ " <td>NaN</td>\n",
500
+ " <td>NaN</td>\n",
501
+ " <td>NaN</td>\n",
502
+ " <td>0.299958</td>\n",
503
+ " <td>1.0</td>\n",
504
+ " </tr>\n",
505
+ " <tr>\n",
506
+ " <th>3611</th>\n",
507
+ " <td>2023-06-29</td>\n",
508
+ " <td>600415.XSHG</td>\n",
509
+ " <td>8.60</td>\n",
510
+ " <td>8.74</td>\n",
511
+ " <td>8.88</td>\n",
512
+ " <td>8.58</td>\n",
513
+ " <td>128969467.0</td>\n",
514
+ " <td>1.125704e+09</td>\n",
515
+ " <td>小商品城</td>\n",
516
+ " <td>XSPC</td>\n",
517
+ " <td>...</td>\n",
518
+ " <td>NaN</td>\n",
519
+ " <td>NaN</td>\n",
520
+ " <td>NaN</td>\n",
521
+ " <td>0.012746</td>\n",
522
+ " <td>NaN</td>\n",
523
+ " <td>NaN</td>\n",
524
+ " <td>NaN</td>\n",
525
+ " <td>NaN</td>\n",
526
+ " <td>0.301804</td>\n",
527
+ " <td>1.0</td>\n",
528
+ " </tr>\n",
529
+ " <tr>\n",
530
+ " <th>3612</th>\n",
531
+ " <td>2023-06-30</td>\n",
532
+ " <td>600415.XSHG</td>\n",
533
+ " <td>8.74</td>\n",
534
+ " <td>8.53</td>\n",
535
+ " <td>8.77</td>\n",
536
+ " <td>8.48</td>\n",
537
+ " <td>103029932.0</td>\n",
538
+ " <td>8.844883e+08</td>\n",
539
+ " <td>小商品城</td>\n",
540
+ " <td>XSPC</td>\n",
541
+ " <td>...</td>\n",
542
+ " <td>NaN</td>\n",
543
+ " <td>NaN</td>\n",
544
+ " <td>NaN</td>\n",
545
+ " <td>-0.024027</td>\n",
546
+ " <td>NaN</td>\n",
547
+ " <td>NaN</td>\n",
548
+ " <td>NaN</td>\n",
549
+ " <td>NaN</td>\n",
550
+ " <td>0.293612</td>\n",
551
+ " <td>1.0</td>\n",
552
+ " </tr>\n",
553
+ " <tr>\n",
554
+ " <th>3613</th>\n",
555
+ " <td>2023-07-03</td>\n",
556
+ " <td>600415.XSHG</td>\n",
557
+ " <td>8.45</td>\n",
558
+ " <td>8.37</td>\n",
559
+ " <td>8.46</td>\n",
560
+ " <td>8.05</td>\n",
561
+ " <td>133732493.0</td>\n",
562
+ " <td>1.108033e+09</td>\n",
563
+ " <td>小商品城</td>\n",
564
+ " <td>XSPC</td>\n",
565
+ " <td>...</td>\n",
566
+ " <td>NaN</td>\n",
567
+ " <td>NaN</td>\n",
568
+ " <td>NaN</td>\n",
569
+ " <td>-0.018757</td>\n",
570
+ " <td>NaN</td>\n",
571
+ " <td>NaN</td>\n",
572
+ " <td>NaN</td>\n",
573
+ " <td>NaN</td>\n",
574
+ " <td>0.286010</td>\n",
575
+ " <td>1.0</td>\n",
576
+ " </tr>\n",
577
+ " </tbody>\n",
578
+ "</table>\n",
579
+ "<p>3614 rows × 27 columns</p>\n",
580
+ "</div>"
581
+ ],
582
+ "text/plain": [
583
+ " date ticker open close high low volume \\\n",
584
+ "0 2021-01-05 600409.XSHG 9.23 9.57 9.66 9.08 82669289.0 \n",
585
+ "1 2021-01-05 300274.XSHE 76.03 76.45 80.20 75.27 51384827.0 \n",
586
+ "2 2021-01-05 002920.XSHE 85.44 87.25 87.95 84.07 3852674.0 \n",
587
+ "3 2021-01-05 002709.XSHE 32.54 33.89 34.22 31.39 59152352.0 \n",
588
+ "4 2021-01-05 603882.XSHG 125.25 124.64 128.31 121.68 6803710.0 \n",
589
+ "... ... ... ... ... ... ... ... \n",
590
+ "3609 2023-06-27 600415.XSHG 8.52 8.69 8.78 8.40 151396630.0 \n",
591
+ "3610 2023-06-28 600415.XSHG 8.60 8.63 8.68 8.37 103167271.0 \n",
592
+ "3611 2023-06-29 600415.XSHG 8.60 8.74 8.88 8.58 128969467.0 \n",
593
+ "3612 2023-06-30 600415.XSHG 8.74 8.53 8.77 8.48 103029932.0 \n",
594
+ "3613 2023-07-03 600415.XSHG 8.45 8.37 8.46 8.05 133732493.0 \n",
595
+ "\n",
596
+ " money display_name name ... portfolio_pct prev_w_in_sectore \\\n",
597
+ "0 7.803391e+08 三友化工 SYHG ... NaN NaN \n",
598
+ "1 3.961995e+09 阳光电源 YGDY ... NaN NaN \n",
599
+ "2 3.322598e+08 德赛西威 DSXW ... NaN NaN \n",
600
+ "3 1.942406e+09 天赐材料 TCCL ... NaN NaN \n",
601
+ "4 8.458543e+08 金域医学 JYYX ... NaN NaN \n",
602
+ "... ... ... ... ... ... ... \n",
603
+ "3609 1.305075e+09 小商品城 XSPC ... NaN NaN \n",
604
+ "3610 8.798186e+08 小商品城 XSPC ... NaN NaN \n",
605
+ "3611 1.125704e+09 小商品城 XSPC ... NaN NaN \n",
606
+ "3612 8.844883e+08 小商品城 XSPC ... NaN NaN \n",
607
+ "3613 1.108033e+09 小商品城 XSPC ... NaN NaN \n",
608
+ "\n",
609
+ " ini_w_in_sector sector_pct portfolio_return cum_pct return \\\n",
610
+ "0 1.0 NaN NaN NaN NaN \n",
611
+ "1 0.5 NaN NaN NaN NaN \n",
612
+ "2 1.0 NaN NaN NaN NaN \n",
613
+ "3 0.5 NaN NaN NaN NaN \n",
614
+ "4 1.0 NaN NaN NaN NaN \n",
615
+ "... ... ... ... ... ... \n",
616
+ "3609 NaN 0.027187 NaN NaN NaN \n",
617
+ "3610 NaN -0.006904 NaN NaN NaN \n",
618
+ "3611 NaN 0.012746 NaN NaN NaN \n",
619
+ "3612 NaN -0.024027 NaN NaN NaN \n",
620
+ "3613 NaN -0.018757 NaN NaN NaN \n",
621
+ "\n",
622
+ " sector_return cur_w_in_p pre_w_in_sector \n",
623
+ "0 NaN NaN NaN \n",
624
+ "1 NaN NaN NaN \n",
625
+ "2 NaN NaN NaN \n",
626
+ "3 NaN NaN NaN \n",
627
+ "4 NaN NaN NaN \n",
628
+ "... ... ... ... \n",
629
+ "3609 NaN 0.301143 1.0 \n",
630
+ "3610 NaN 0.299958 1.0 \n",
631
+ "3611 NaN 0.301804 1.0 \n",
632
+ "3612 NaN 0.293612 1.0 \n",
633
+ "3613 NaN 0.286010 1.0 \n",
634
+ "\n",
635
+ "[3614 rows x 27 columns]"
636
+ ]
637
+ },
638
+ "execution_count": 182,
639
+ "metadata": {},
640
+ "output_type": "execute_result"
641
+ }
642
+ ],
643
+ "source": [
644
+ "most_recent_df = name_df_map['calculated_p_stock'].groupby('ticker').last().reset_index()\n",
645
+ "\n",
646
+ "def get_last_values(row):\n",
647
+ " ticker = row['ticker']\n",
648
+ " if ticker in p_profile['ticker'].values:\n",
649
+ " return p_profile.loc[p_profile['ticker'] == ticker, ['display_name', 'name', 'aggregate_sector']].iloc[-1]\n",
650
+ " else:\n",
651
+ " return pd.Series([np.nan, np.nan, np.nan], index=['display_name', 'name', 'aggregate_sector'])\n",
652
+ "# dispaly_name, name and aggregate_sector\n",
653
+ "p_stocks[['display_name', 'name', 'aggregate_sector']] = p_stocks.apply(get_last_values, axis=1)\n",
654
+ "\n",
655
+ "# use the most recent result to resume calculation\n",
656
+ "concat_df = pd.concat([most_recent_df, p_stocks], axis=0, join='outer')\n",
657
+ "\n",
658
+ "# pct\n",
659
+ "concat_df['pct'] = concat_df.groupby('ticker')['close'].pct_change()\n",
660
+ "\n",
661
+ "# calculate not normalized previous weight and current weight\n",
662
+ "groups = concat_df.groupby('ticker')\n",
663
+ "for _, group in groups:\n",
664
+ " cur_weight = np.nan\n",
665
+ " for index, row in group.iterrows():\n",
666
+ " if pd.notna(row['current_weight']):\n",
667
+ " cur_weight = row['current_weight']\n",
668
+ " else:\n",
669
+ " concat_df.loc[index, 'previous_weight'] = cur_weight\n",
670
+ " cur_weight = cur_weight * (1 + row['pct'])\n",
671
+ " concat_df.loc[index, 'current_weight'] = cur_weight\n",
672
+ "\n",
673
+ "# calculate normalized previous and current weight\n",
674
+ "concat_df['prev_w_in_p'] = concat_df['previous_weight'] / \\\n",
675
+ " concat_df.groupby('date')['previous_weight'].transform('sum')\n",
676
+ "concat_df['cur_w_in_p'] = concat_df['current_weight'] / \\\n",
677
+ " concat_df.groupby('date')['current_weight'].transform('sum')\n",
678
+ "\n",
679
+ "# calculate previous weight in sector\n",
680
+ "concat_df['pre_w_in_sector'] = concat_df['prev_w_in_p'] / \\\n",
681
+ " concat_df.groupby(['date', 'aggregate_sector'])['prev_w_in_p'].transform('sum')\n",
682
+ "\n",
683
+ "# calculate pct in sector\n",
684
+ "concat_df['sector_pct'] = concat_df['pct'] * concat_df['pre_w_in_sector']\n",
685
+ "\n",
686
+ "\n",
687
+ "\n",
688
+ "\n",
689
+ "\n",
690
+ "# remove group with first date\n",
691
+ "min_date_group = concat_df.groupby('date')['date'].idxmin()\n",
692
+ "concat_df = concat_df.drop(min_date_group)\n",
693
+ "\n",
694
+ "# merge back to calculated_stock\n",
695
+ "pd.concat([name_df_map['calculated_p_stock'],concat_df]).reset_index(drop=True)\n",
696
+ "\n",
697
+ "# concat_df[concat_df.ticker == '002709.XSHE'][['date','pct','current_weight','previous_weight','prev_w_in_p','cur_w_in_p']]"
698
+ ]
699
+ }
700
+ ],
701
+ "metadata": {
702
+ "kernelspec": {
703
+ "display_name": "portfolio_risk_assesment",
704
+ "language": "python",
705
+ "name": "python3"
706
+ },
707
+ "language_info": {
708
+ "codemirror_mode": {
709
+ "name": "ipython",
710
+ "version": 3
711
+ },
712
+ "file_extension": ".py",
713
+ "mimetype": "text/x-python",
714
+ "name": "python",
715
+ "nbconvert_exporter": "python",
716
+ "pygments_lexer": "ipython3",
717
+ "version": "3.11.4"
718
+ },
719
+ "orig_nbformat": 4
720
+ },
721
+ "nbformat": 4,
722
+ "nbformat_minor": 2
723
+ }
find_outlier.ipynb CHANGED
@@ -1,3 +1,120 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:d032b15baf8608f845eb33d643f02a1deb7543fc883732c7bae925bb06a5b281
3
- size 2947
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "import panel as pn\n",
10
+ "import pandas as pd\n",
11
+ "import scipy.stats as stats\n",
12
+ "from sqlalchemy import create_engine\n",
13
+ "import hvplot.pandas\n",
14
+ "pn.extension()\n",
15
+ "db_url = 'sqlite:///local.db'\n",
16
+ "engine = create_engine(db_url)"
17
+ ]
18
+ },
19
+ {
20
+ "cell_type": "markdown",
21
+ "metadata": {},
22
+ "source": [
23
+ "## Expirement to use z-score to find outlier of daily pct, not sure if this is helpful but here is it"
24
+ ]
25
+ },
26
+ {
27
+ "cell_type": "code",
28
+ "execution_count": null,
29
+ "metadata": {},
30
+ "outputs": [],
31
+ "source": [
32
+ "b_stocks_df = None\n",
33
+ "# load benchmark stock\n",
34
+ "with engine.connect() as connection:\n",
35
+ " b_stocks_df = pd.read_sql('calculated_b_stock', con=connection)"
36
+ ]
37
+ },
38
+ {
39
+ "cell_type": "markdown",
40
+ "metadata": {},
41
+ "source": [
42
+ "### z = (x - μ) / σ"
43
+ ]
44
+ },
45
+ {
46
+ "cell_type": "code",
47
+ "execution_count": null,
48
+ "metadata": {},
49
+ "outputs": [],
50
+ "source": [
51
+ "b_stocks_df['pct_mean'] = b_stocks_df.groupby('ticker')['pct']\\\n",
52
+ " .transform(lambda x: x.rolling(7, min_periods=1).mean())\n",
53
+ "b_stocks_df['pct_std'] = b_stocks_df.groupby('ticker')['pct']\\\n",
54
+ " .transform(lambda x: x.rolling(7, min_periods=1).std())\n",
55
+ "b_stocks_df['pct_z_score'] = (b_stocks_df['pct'] - b_stocks_df['pct_mean']) / b_stocks_df['pct_std']"
56
+ ]
57
+ },
58
+ {
59
+ "cell_type": "code",
60
+ "execution_count": null,
61
+ "metadata": {},
62
+ "outputs": [],
63
+ "source": [
64
+ "select = pn.widgets.Select(name='Select', options=b_stocks_df.display_name.unique().tolist())"
65
+ ]
66
+ },
67
+ {
68
+ "cell_type": "code",
69
+ "execution_count": null,
70
+ "metadata": {},
71
+ "outputs": [],
72
+ "source": [
73
+ "ib_stocks_df = b_stocks_df.interactive()\n"
74
+ ]
75
+ },
76
+ {
77
+ "cell_type": "code",
78
+ "execution_count": null,
79
+ "metadata": {},
80
+ "outputs": [],
81
+ "source": [
82
+ "selected_stock_df = ib_stocks_df[ib_stocks_df.display_name == select]\n",
83
+ "outliers = selected_stock_df[((ib_stocks_df['pct_z_score'] > 2) | (ib_stocks_df['pct_z_score'] < -2))]"
84
+ ]
85
+ },
86
+ {
87
+ "cell_type": "code",
88
+ "execution_count": null,
89
+ "metadata": {},
90
+ "outputs": [],
91
+ "source": [
92
+ "outlier_plot = outliers.hvplot.scatter(x='date',y='pct',color='red')\n",
93
+ "pct_plot = selected_stock_df.hvplot(x='date',y='pct')\n",
94
+ "outlier_plot * pct_plot\n"
95
+ ]
96
+ }
97
+ ],
98
+ "metadata": {
99
+ "kernelspec": {
100
+ "display_name": "portfolio_risk_assesment",
101
+ "language": "python",
102
+ "name": "python3"
103
+ },
104
+ "language_info": {
105
+ "codemirror_mode": {
106
+ "name": "ipython",
107
+ "version": 3
108
+ },
109
+ "file_extension": ".py",
110
+ "mimetype": "text/x-python",
111
+ "name": "python",
112
+ "nbconvert_exporter": "python",
113
+ "pygments_lexer": "ipython3",
114
+ "version": "3.11.4"
115
+ },
116
+ "orig_nbformat": 4
117
+ },
118
+ "nbformat": 4,
119
+ "nbformat_minor": 2
120
+ }
index_page.py CHANGED
@@ -1,3 +1,73 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:ff4adeb8072294a101900b8d10e3d544db694872c9cdb31353d5bf59ac3351d5
3
- size 2544
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import panel as pn
2
+ import pandas as pd
3
+ 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
10
+ from sqlalchemy import create_engine
11
+ from . import api
12
+ # from backgroundTask import stocks_stream
13
+ from functools import partial
14
+ import plotly.graph_objects as go
15
+ from panel.viewable import Viewer
16
+ from script import processing
17
+ import appComponents
18
+ import param
19
+ # import warnings
20
+ pn.extension('mathjax')
21
+ # warnings.filterwarnings("ignore", category=pd.core.common.SettingWithCopyWarning)
22
+ pn.extension('plotly')
23
+ pn.extension('tabulator')
24
+ db_url = 'sqlite:///local.db'
25
+ engine = create_engine(db_url)
26
+
27
+
28
+ p_eval_df = None
29
+ calculated_b_stock = None
30
+ calculated_p_stock = None
31
+ # load benchmark stock
32
+ with engine.connect() as connection:
33
+ calculated_b_stock = pd.read_sql('calculated_b_stock', con=connection)
34
+ calculated_p_stock = pd.read_sql('calculated_p_stock', con=connection)
35
+ p_eval_df = pd.read_sql('p_eval_result', con=connection)
36
+
37
+ stock_overview = appComponents.BestAndWorstStocks(
38
+ p_stock_df=calculated_p_stock, b_stock_df=calculated_b_stock)
39
+ composation_card = appComponents.PortfolioComposationCard(
40
+ p_stock_df=calculated_p_stock)
41
+ monthly_return_card = appComponents.HistReturnCard(
42
+ eval_df=p_eval_df, calculated_p_stock=calculated_p_stock, calculated_b_stock=calculated_b_stock)
43
+ total_return_card = appComponents.TotalReturnCard(name='Range', eval_df=p_eval_df,
44
+ b_stock_df=calculated_b_stock,
45
+ p_stock_df=calculated_p_stock,
46
+ value=(0, 20))
47
+ drawdown_card = appComponents.DrawDownCard(
48
+ eval_df=p_eval_df, calculated_p_stock=calculated_p_stock, calculated_b_stock=calculated_b_stock)
49
+
50
+ top_header = appComponents.TopHeader(
51
+ eval_df=p_eval_df
52
+ )
53
+
54
+ template = pn.template.FastListTemplate(
55
+ title="Portfolio一览",
56
+ # sidebar=[freq, phase],
57
+ )
58
+ template.main.extend(
59
+ [pn.Row(top_header),
60
+ pn.Row(
61
+ pn.Column(monthly_return_card, stock_overview,
62
+ width=500, margin=(10, 10, 10, 10)),
63
+ pn.Column(total_return_card, drawdown_card, margin=(10, 10, 10, 10)),
64
+ pn.Column(composation_card, margin=(10, 10, 10, 10)),
65
+ )]
66
+ )
67
+ template.servable()
68
+ # pn.Row(
69
+
70
+ # pn.Column(monthly_return_card, stock_overview, width=500),
71
+ # pn.Column(total_return_card),
72
+ # pn.Column(composation_card)
73
+ # ).servable()
initialize_db.py CHANGED
@@ -1,3 +1,28 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:8f0779a757c8a2a2ab42df0d79a4a932642fca6184fcaf95884af0b3f98bd66c
3
- size 998
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import create_engine
2
+ import pandas as pd
3
+ import table_schema as ts
4
+ db_url = "sqlite:///local.db"
5
+
6
+
7
+ def _create_table_with_schema(table_name: str, table_schema: dict):
8
+ with create_engine(db_url).connect() as conn:
9
+ df = pd.DataFrame(
10
+ columns=table_schema.keys()).astype(table_schema)
11
+ df.to_sql(
12
+ table_name, conn, if_exists='replace', index=False)
13
+ return True
14
+
15
+
16
+ def initialize_db():
17
+ # initialize portfolio profile table
18
+ if not _create_table_with_schema(ts.PORTFOLIO_TABLE, ts.PORTFOLIO_TABLE_SCHEMA):
19
+ raise Exception(
20
+ f'INITIALIZATION ERROR: cannot create table {ts.PORTFOLIO_TABLE} ')
21
+ # initialize stocks details table
22
+ if not _create_table_with_schema(ts.STOCKS_DETAILS_TABLE, ts.STOCKS_DETAILS_TABLE_SCHEMA):
23
+ raise Exception(
24
+ f'INITIALIZATION ERROR: cannot create table {ts.STOCKS_DETAILS_TABLE} ')
25
+
26
+ # allow to be run as script
27
+ if __name__ == '__main__':
28
+ initialize_db()
newBackgroundTask.py CHANGED
@@ -1,3 +1,12 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:87f3213d968c3f343949a31758a09973c8e189b2c13d4ec710130f0e5c96bde2
3
- size 285
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ sys.path.append('/Users/lamonkey/Desktop/risk-monitor-dashboard')
3
+ from pipeline import update
4
+ import panel as pn
5
+ from datetime import timedelta
6
+
7
+ # pn.state.schedule_task(
8
+ # 'task', run, period=timedelta(seconds=3)
9
+ # )
10
+
11
+ # update stock price and benchmark profile
12
+ update()
pipeline.py CHANGED
@@ -1,3 +1,337 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:ddf43d57700db70f9b7e3b5a4de30913ee461a7d8d0360a7b654155ed2db2105
3
- size 10888
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ # sys.path.append('/Users/lamonkey/Desktop/risk-monitor-dashboard')
3
+ import panel as pn
4
+ import datetime as dt
5
+ import asyncio
6
+ import random
7
+ from sqlalchemy import create_engine, text
8
+ import pandas as pd
9
+ from streamz import Stream
10
+ from datetime import timedelta
11
+ import settings
12
+ import os
13
+ import utils
14
+ import api
15
+ import numpy as np
16
+ import pytz
17
+ import table_schema as table_s
18
+ # fetch new stock price
19
+ stock_price_stream = Stream()
20
+
21
+ # save stock price to db
22
+ # stock_price_stream.sink(save_stock_price)
23
+ # from dask.distributed import Client
24
+ # client = Client()
25
+ # import nest_asyncio
26
+ # nest_asyncio.apply()
27
+ # import settings
28
+
29
+ # run using --setup
30
+ db_url = "sqlite:///local.db"
31
+
32
+
33
+ def create_portfolio_profile_df(stocks: list[dict]):
34
+ profile_df = pd.DataFrame(stocks)
35
+ profile_df = add_details_to_stock_df(profile_df)
36
+
37
+ # check if there is duplicate ticker
38
+ if profile_df.ticker.duplicated().any():
39
+ raise Exception(
40
+ 'VALIDATION ERROR: cannot have duplicate ticker with the same date')
41
+
42
+ return profile_df
43
+
44
+
45
+ def need_to_update(table_name: str, freq: dt.datetime):
46
+ '''check table with table_name need to update
47
+ Return
48
+ ------
49
+ None if no need to update
50
+ (start_date, end_date, freq) if need to update
51
+ '''
52
+ with create_engine(db_url).connect() as conn:
53
+ max_date = conn.execute(
54
+ text(f"SELECT MAX(date) FROM {table_name}")).fetchone()[0]
55
+ max_date = utils.convert_string_to_datetime(max_date)
56
+ current_time = utils.time_in_beijing()
57
+ if current_time - max_date > freq:
58
+ return (max_date + freq, current_time, freq)
59
+ else:
60
+ return None
61
+
62
+
63
+ def need_to_fetch_new_stock_price():
64
+ '''
65
+ check if need to pull new stock price from jq
66
+
67
+ RETURN
68
+ ------
69
+ (min_date, max_date) : if update is needed
70
+ the start and end date need to fetch new stock price
71
+ None if no need to fetch new stock price
72
+
73
+ '''
74
+ # get min date from portfolio_profile
75
+ with create_engine(db_url).connect() as conn:
76
+ table_name = 'portfolio_profile'
77
+ query = f"SELECT DISTINCT date FROM {table_name} ORDER BY date ASC LIMIT 1"
78
+ df = pd.read_sql(query, con=conn)
79
+ df.date = pd.to_datetime(df.date)
80
+ min_date = df.date[0]
81
+
82
+ # compare to min date from stocks_price
83
+ with create_engine(db_url).connect() as conn:
84
+ table_name = 'stocks_price'
85
+ query = f"SELECT DISTINCT time FROM {table_name} ORDER BY time ASC LIMIT 1"
86
+ df = pd.read_sql(query, con=conn)
87
+ df.time = pd.to_datetime(df.time)
88
+
89
+ # return
90
+ if min_date <= df.time[0]:
91
+ return (min_date, df.time[0] - dt.timedelta(days=1))
92
+ else:
93
+ return None
94
+
95
+
96
+ def get_most_recent_profile(type):
97
+ table_name = 'benchmark_profile' if type == 'benchmark' else 'portfolio_profile'
98
+ query = f"SELECT * FROM {table_name} WHERE date = (SELECT MAX(date) FROM {table_name})"
99
+ with create_engine(db_url).connect() as conn:
100
+ df = pd.read_sql(query, con=conn)
101
+ # convert date to datetime object
102
+ df['date'] = pd.to_datetime(df['date'])
103
+ return df
104
+
105
+
106
+ def update_stocks_details_to_db():
107
+ '''create table contain all stocks detail in db
108
+ will override existing table if exists
109
+ Table Schema
110
+ ------------
111
+ 'display_name', 'name', 'start_date', 'end_date', 'type', 'ticker',
112
+ 'sector', 'aggregate_sector'
113
+ '''
114
+ df = api.get_all_stocks_detail()
115
+ # validation
116
+ if not _validate_schema(df, table_s.STOCKS_DETAILS_TABLE_SCHEMA):
117
+ raise ValueError(
118
+ 'df has different schema than STOCKS_DETAILS_TABLE_SCHEMA')
119
+ with create_engine(db_url).connect() as conn:
120
+ df.to_sql(table_s.STOCKS_DETAILS_TABLE, con=conn,
121
+ if_exists='replace', index=False)
122
+
123
+
124
+ def fetch_new_stocks_price():
125
+ '''
126
+ get a df contain updated stock prices for both benchmark and portfolio,
127
+ also indicate if the stock is in portfolio and benchmark
128
+ '''
129
+ # most recent profiles
130
+ p_portfolio = get_most_recent_profile('portfolio')
131
+ p_benchmark = get_most_recent_profile('benchmark')
132
+ # combine ticker
133
+ unique_tickers = pd.concat([p_portfolio, p_benchmark])[
134
+ 'ticker'].unique().tolist()
135
+ # fetch list of stock
136
+ # TODO: hard code delta time to 1 day
137
+ start_date = p_portfolio.date[0] + dt.timedelta(days=1)
138
+ end_date = utils.time_in_beijing()
139
+ freq = 'daily'
140
+ stock_df = api.fetch_stocks_price(
141
+ unique_tickers, start_date, end_date, freq)
142
+ stock_df['in_portfolio'] = stock_df['ticker'].isin(
143
+ p_portfolio['ticker'].unique().tolist())
144
+ stock_df['in_benchmark'] = stock_df['ticker'].isin(
145
+ p_benchmark['ticker'].unique().tolist())
146
+ return stock_df
147
+
148
+
149
+ def need_to_update_stocks_price(delta_time):
150
+ # convert p_portfolio.date[0] to timezone-aware datetime object
151
+ tz = pytz.timezone('Asia/Shanghai')
152
+ # get stock price df
153
+ with create_engine(db_url).connect() as conn:
154
+ # check if a table exist
155
+ if not conn.dialect.has_table(conn, 'stocks_price'):
156
+ return True
157
+ else:
158
+ query = "SELECT * FROM stocks_price WHERE time = (SELECT MAX(time) FROM stocks_price)"
159
+ most_recent_price = pd.read_sql(query, con=conn)
160
+ most_recent_price.time = pd.to_datetime(most_recent_price.time)
161
+ date_time = tz.localize(most_recent_price.time[0].to_pydatetime())
162
+ if utils.time_in_beijing() - date_time > delta_time:
163
+ return True
164
+ else:
165
+ return False
166
+
167
+
168
+ def processing():
169
+ '''
170
+ run the whole processing pipeline here
171
+ '''
172
+ pass
173
+
174
+
175
+ def add_details_to_stock_df(stock_df):
176
+ with create_engine(db_url).connect() as conn:
177
+ detail_df = pd.read_sql('stocks_details', con=conn)
178
+ merged_df = pd.merge(stock_df, detail_df[
179
+ ['sector', 'name',
180
+ 'aggregate_sector',
181
+ 'display_name',
182
+ 'ticker']
183
+ ], on='ticker', how='left')
184
+ merged_df['aggregate_sector'].fillna('其他', inplace=True)
185
+ return merged_df
186
+
187
+
188
+ def _validate_schema(df, schema):
189
+ '''
190
+ validate df has the same columns and data types as schema
191
+
192
+ Parameters
193
+ ----------
194
+ df: pd.DataFrame
195
+ schema: dict
196
+ {column_name: data_type}
197
+
198
+ Returns
199
+ -------
200
+ bool
201
+ True if df has the same columns and data types as schema
202
+ False otherwise
203
+ '''
204
+
205
+ # check if the DataFrame has the same columns as the schema
206
+ if set(df.columns) != set(schema.keys()):
207
+ return False
208
+ # check if the data types of the columns match the schema
209
+ # TODO: ignoring type check for now
210
+ # for col, dtype in schema.items():
211
+ # if df[col].dtype != dtype:
212
+ # return False
213
+ return True
214
+
215
+
216
+ def save_stock_price_to_db(df: pd.DataFrame):
217
+ print('saving to stock to db')
218
+ with create_engine(db_url).connect() as conn:
219
+ df.to_sql('stocks_price', con=conn, if_exists='append', index=False)
220
+
221
+
222
+ def update_portfolio_profile_to_db(portfolio_df):
223
+ '''overwrite the portfolio profile table in db'''
224
+
225
+ if (_validate_schema(portfolio_df, table_s.PORTFOLIO_TABLE_SCHEMA)):
226
+ raise ValueError(
227
+ 'portfoliijuo_df has different schema than PORTFOLIO_DB_SCHEMA')
228
+
229
+ with create_engine(db_url).connect() as conn:
230
+ print("updating profile to db")
231
+ try:
232
+ portfolio_df[table_s.PORTFOLIO_TABLE_SCHEMA.keys()].to_sql(
233
+ table_s.PORTFOLIO_TABLE, con=conn, if_exists='append', index=False)
234
+ return True
235
+ except:
236
+ return False
237
+ # TODO trigger recomputation of analysis
238
+
239
+
240
+ def update_stock_price():
241
+ '''get daily stocks price until today'''
242
+ # most recent profiles
243
+ p_portfolio = get_most_recent_profile('portfolio')
244
+ p_benchmark = get_most_recent_profile('benchmark')
245
+ # combine ticker
246
+ unique_tickers = pd.concat([p_portfolio, p_benchmark])[
247
+ 'ticker'].unique().tolist()
248
+ # fetch list of stock
249
+ # TODO: hard code delta time to 1 day
250
+ start_date = p_portfolio.date[0] + dt.timedelta(days=1)
251
+ end_date = utils.time_in_beijing()
252
+ freq = 'daily'
253
+ stock_df = api.fetch_stocks_price(
254
+ unique_tickers, start_date, end_date, freq)
255
+ stock_df['in_portfolio'] = stock_df['ticker'].isin(
256
+ p_portfolio['ticker'].unique().tolist())
257
+ stock_df['in_benchmark'] = stock_df['ticker'].isin(
258
+ p_benchmark['ticker'].unique().tolist())
259
+ return stock_df
260
+
261
+
262
+ def patch_stock_prices_db(window):
263
+ '''
264
+ patch stock price db with all daily stock price within window
265
+
266
+ Parameters
267
+ ----------
268
+ window: tuple
269
+ (start, end) date of the window
270
+
271
+ Returns
272
+ -------
273
+ None
274
+ '''
275
+ start, end = window
276
+ # all trading stock between start day and end date
277
+ with create_engine(db_url).connect() as conn:
278
+ all_stocks = pd.read_sql('stocks_details', con=conn)
279
+
280
+ selected_stocks = all_stocks[(all_stocks.start_date <= end) & (
281
+ all_stocks.end_date >= start)]
282
+ tickers = selected_stocks.ticker.to_list()
283
+
284
+ # fetch stock price and append to db
285
+ stock_price = api.fetch_stocks_price(tickers, start, end, 'daily')
286
+ detailed_stock_df = add_details_to_stock_df(stock_price)
287
+ # drop where closing price is null
288
+ detailed_stock_df.dropna(subset=['close'], inplace=True)
289
+ with create_engine(db_url).connect() as conn:
290
+ detailed_stock_df.to_sql(
291
+ 'stocks_price', con=conn, if_exists='append', index=False)
292
+ return detailed_stock_df
293
+
294
+
295
+ def update():
296
+ '''
297
+ run only once, update stock price and benchmark profile
298
+ '''
299
+ print("Checking stock_price table")
300
+ # collect daily stock price until today in beijing time
301
+ if need_to_update_stocks_price(dt.timedelta(days=1)):
302
+ print("Updating stock_price table")
303
+ stock_df = update_stock_price()
304
+ stock_df = add_details_to_stock_df(stock_df)
305
+ save_stock_price_to_db(stock_df)
306
+ stock_price_stream.emit(stock_df)
307
+
308
+
309
+ async def run():
310
+ '''
311
+ start the pipeline here to check update and fetch new data
312
+ '''
313
+ print("background_task running!")
314
+ # TODO: update benchmark_profile
315
+ # if (need_to_update_stocks_price()):
316
+ if True:
317
+ print("running update")
318
+ # TODO testing code get stock price df
319
+ with create_engine(db_url).connect() as conn:
320
+ stock_df = pd.read_sql('stocks_price', con=conn)
321
+ print('sending data!')
322
+ # print(stock_df)
323
+ stock_price_stream.emit(stock_df)
324
+
325
+ # # latest stock price
326
+ # stock_df = update_stocks_price()
327
+ # # add display name and sector to stock_df
328
+ # stock_df = add_details_to_stock_df(stock_df)
329
+ # save_stock_price_to_db(stock_df)
330
+ # stock_price_stream.emit(stock_df)
331
+ # update sotck_price
332
+
333
+ # send fetched data
334
+
335
+ # run processing
336
+
337
+ # send fetched data
pipeline/bhb.ipynb CHANGED
The diff for this file is too large to render. See raw diff
 
pipeline/create_dumpy_data.ipynb CHANGED
@@ -1,3 +1,36 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:b4e751763ecdbde68a22e49c407308cafb8b6d48bffa145c2ae8d2027f8ab9f8
3
- size 683
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "from test_db_peration import db_operator\n",
10
+ "from datetime import datetime\n"
11
+ ]
12
+ }
13
+ ],
14
+ "metadata": {
15
+ "kernelspec": {
16
+ "display_name": "portfolio_risk_assesment",
17
+ "language": "python",
18
+ "name": "python3"
19
+ },
20
+ "language_info": {
21
+ "codemirror_mode": {
22
+ "name": "ipython",
23
+ "version": 3
24
+ },
25
+ "file_extension": ".py",
26
+ "mimetype": "text/x-python",
27
+ "name": "python",
28
+ "nbconvert_exporter": "python",
29
+ "pygments_lexer": "ipython3",
30
+ "version": "3.11.4"
31
+ },
32
+ "orig_nbformat": 4
33
+ },
34
+ "nbformat": 4,
35
+ "nbformat_minor": 2
36
+ }
pipeline/db_operation.py CHANGED
@@ -1,3 +1,35 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:38192fe7d6dba1eb0c98f2f17c3acf4d662777defe5031fd6157601b35726466
3
- size 1003
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from model import Stock
2
+ from sqlalchemy import create_engine
3
+ from sqlalchemy.orm import Session
4
+
5
+
6
+ class db_operator():
7
+ def __init__(self, db_url):
8
+ self.engine = create_engine(db_url)
9
+
10
+ def get_stocks_between(self, d1, d2):
11
+ with Session(self.engine) as session:
12
+ return session.query(Stock).filter(
13
+ Stock.date.between(d1, d2)
14
+ ).all()
15
+
16
+ def add_stock(self, stock_data: dict):
17
+ with Session(self.engine) as session:
18
+ new_stock = Stock(**stock_data)
19
+ session.add(new_stock)
20
+ session.commit()
21
+
22
+ def delete_stocks_between(self, d1, d2):
23
+ with Session(self.engine) as session:
24
+ session.query(Stock).filter(
25
+ Stock.date.between(d1, d2)
26
+ ).delete()
27
+ session.commit()
28
+
29
+ def delete_all_stocks(self):
30
+ with Session(self.engine) as session:
31
+ session.query(Stock).delete()
32
+ session.commit()
33
+
34
+
35
+
pipeline/model.py CHANGED
@@ -1,3 +1,41 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:138123323449aabb36ea46e03512564a88c13ebebd1a82c2a7730ba5f9196daa
3
- size 1335
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Column, Integer, String, Float, create_engine, DateTime, PickleType
2
+ from sqlalchemy.ext.declarative import declarative_base
3
+ from sqlalchemy.orm import sessionmaker
4
+
5
+ Base = declarative_base()
6
+
7
+ class Stock(Base):
8
+ __tablename__ = 'stock'
9
+
10
+ id = Column(Integer, primary_key=True)
11
+ ticker = Column(String(50))
12
+ weight = Column(Float, nullable=True)
13
+ display_name = Column(String(50), nullable=False)
14
+ date = Column(DateTime, nullable=False)
15
+ order = Column(Integer, autoincrement=True, nullable=True)
16
+
17
+ def __repr__(self):
18
+ return f"<{self.ticker}\
19
+ {self.date}\
20
+ {self.display_name}\
21
+ {round(self.weight * 100)}%>"
22
+
23
+
24
+ class Portfolio(Base):
25
+ __tablename__ = 'portfolio'
26
+
27
+ id = Column(Integer, primary_key=True)
28
+ stocks = Column(PickleType, nullable=False)
29
+ cached_result = Column(PickleType, nullable=True)
30
+ # data = Column(PickleType, nullable=False)
31
+ date = Column(DateTime, nullable=False)
32
+ order = Column(Integer, autoincrement=True, nullable=True)
33
+
34
+
35
+ # db_url = 'sqlite:///local_db.db' # Replace 'stocks.db' with the desired database name and location
36
+
37
+ # engine = create_engine(db_url)
38
+ # Base.metadata.create_all(engine)
39
+
40
+ # Session = sessionmaker(bind=engine)
41
+ # session = Session()
pipeline/test_db_peration.py CHANGED
@@ -1,3 +1,107 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:0de9a67f5db93566eb0eb9003dc54a51c253d1c2b33cbeb3a8122b7929701b3f
3
- size 4275
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from unittest import TestCase, main
2
+ from db_operation import db_operator
3
+ from datetime import datetime
4
+ class test_db_operation(TestCase):
5
+ def setUp(self) -> None:
6
+ self.db_operator = db_operator('sqlite:///local_db.db')
7
+ self.db_operator.delete_all_stocks()
8
+ self.stock1 = {
9
+ "ticker": 'AAPL',
10
+ "weight": 1.0,
11
+ "display_name": 'Apple Inc.',
12
+ "date": datetime(2021, 1, 1)
13
+ }
14
+ self.stock2 = {
15
+ "ticker": 'MSFT',
16
+ "weight": 1.0,
17
+ "display_name": 'Microsoft Corporation',
18
+ "date": datetime(2021, 1, 1)
19
+ }
20
+
21
+ def test_insert(self):
22
+ stock1 = {
23
+ "ticker": 'AAPL',
24
+ "weight": 1.0,
25
+ "display_name": 'Apple Inc.',
26
+ "date": datetime(2021, 1, 1)
27
+ }
28
+ self.db_operator.add_stock(stock1)
29
+ retrieved_stock = self.db_operator.get_stocks_between(datetime(2021, 1, 1), datetime(2021, 1, 1))[0]
30
+ self.assertEqual(retrieved_stock.ticker, 'AAPL')
31
+ self.assertEqual(retrieved_stock.weight, 1.0)
32
+ self.assertEqual(retrieved_stock.display_name, 'Apple Inc.')
33
+ self.assertEqual(retrieved_stock.date, datetime(2021, 1, 1))
34
+
35
+ def test_delete(self):
36
+ self.db_operator.add_stock(self.stock1)
37
+ self.db_operator.add_stock(self.stock2)
38
+ self.db_operator.delete_stocks_between(
39
+ datetime(2021, 1, 1),
40
+ datetime(2021, 1, 1))
41
+ retrieved_stocks = self.db_operator.get_stocks_between(
42
+ datetime(2021, 1, 1),
43
+ datetime(2021, 1, 1))
44
+ self.assertEqual(len(retrieved_stocks), 0)
45
+
46
+ def test_query_window_1d(self):
47
+ # insert 2 stocks between 2021-01-01 and 2021-01-01 every hour
48
+ for i in range(24):
49
+ self.stock1['date'] = datetime(2021, 1, 1, i)
50
+ self.stock2['date'] = datetime(2021, 1, 1, i)
51
+ self.db_operator.add_stock(self.stock1)
52
+ self.db_operator.add_stock(self.stock2)
53
+ # insert two on 2021-01-02
54
+ self.stock1['date'] = datetime(2021, 1, 2)
55
+ self.stock2['date'] = datetime(2021, 1, 2)
56
+ self.db_operator.add_stock(self.stock1)
57
+ self.db_operator.add_stock(self.stock2)
58
+ # query 1d
59
+ retrieved_stocks = self.db_operator.get_stocks_between(
60
+ datetime(2021, 1, 1),
61
+ datetime(2021, 1, 2))
62
+ self.assertEqual(len(retrieved_stocks), 50)
63
+
64
+ def test_query_window_12h(self):
65
+ # insert 2 stocks every hour between 2021-01-01 and 2021-01-01
66
+ for i in range(24):
67
+ self.stock1['date'] = datetime(2021, 1, 1, i)
68
+ self.stock2['date'] = datetime(2021, 1, 1, i)
69
+ self.db_operator.add_stock(self.stock1)
70
+ self.db_operator.add_stock(self.stock2)
71
+ # query 12h
72
+ retrieved_stocks = self.db_operator.get_stocks_between(
73
+ datetime(2021, 1, 1, 0),
74
+ datetime(2021, 1, 1, 12))
75
+ self.assertEqual(len(retrieved_stocks), 26)
76
+ # self.assertTrue(False)
77
+
78
+ def test_query_window_1h(self):
79
+ # insert 2 stocks every mins between 2021-01-01 and 2021-01-01
80
+ for i in range(60):
81
+ self.stock1['date'] = datetime(2021, 1, 1, 0, i)
82
+ self.stock2['date'] = datetime(2021, 1, 1, 0, i)
83
+ self.db_operator.add_stock(self.stock1)
84
+ self.db_operator.add_stock(self.stock2)
85
+ # query 1h
86
+ retrieved_stocks = self.db_operator.get_stocks_between(
87
+ datetime(2021, 1, 1, 0),
88
+ datetime(2021, 1, 1, 1))
89
+ self.assertEqual(len(retrieved_stocks), 120)
90
+
91
+ def test_query_window_30m(self):
92
+ # insert 2 stocks every 1 between 2021-01-01-00:00 and 2021-01-01-00:20
93
+ for i in range(20):
94
+ self.stock1['date'] = datetime(2021, 1, 1, 0, i)
95
+ self.stock2['date'] = datetime(2021, 1, 1, 0, i)
96
+ self.db_operator.add_stock(self.stock1)
97
+ self.db_operator.add_stock(self.stock2)
98
+ # query 30m
99
+ retrieved_stocks = self.db_operator.get_stocks_between(
100
+ datetime(2021, 1, 1, 0),
101
+ datetime(2021, 1, 1, 0, 30))
102
+ self.assertEqual(len(retrieved_stocks), 40)
103
+
104
+
105
+
106
+ if __name__ == '__main__':
107
+ main()
portfolioEditingPage.ipynb CHANGED
The diff for this file is too large to render. See raw diff
 
portfolioEditingPage.py CHANGED
@@ -1,3 +1,377 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:d46685058cab392b20622cd6f2e346aa3c7fde90e17d82a8b585d787f9c8c03c
3
- size 15763
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # %%
2
+ # load portfolio
3
+ import panel as pn
4
+ from utils import create_stocks_entry_from_excel, style_number, create_share_changes_report
5
+ import datetime as dt
6
+ import pytz
7
+ import io
8
+ from bokeh.models.widgets.tables import CheckboxEditor, NumberEditor, SelectEditor
9
+ from utils import time_in_beijing
10
+ import api
11
+ import pandas as pd
12
+ from sqlalchemy import create_engine
13
+ from pipeline import update_portfolio_profile_to_db
14
+ import table_schema
15
+ import pipeline
16
+ db_url = 'sqlite:///local.db'
17
+ pn.extension()
18
+ pn.extension('tabulator')
19
+ pn.extension('plotly')
20
+ pn.extension('floatpanel')
21
+
22
+
23
+ # %%
24
+ # the width of iphone se
25
+ MIN_COMPONENT_WIDTH = 375
26
+ MAX_COMPONENT_WIDTH = 600
27
+
28
+ # %%
29
+
30
+
31
+ def app():
32
+
33
+ # load portfolio df
34
+ with create_engine(db_url).connect() as conn:
35
+ p_profile = pd.read_sql_table(table_schema.PORTFOLIO_TABLE, con=conn)
36
+ p_profile.date = pd.to_datetime(p_profile.date)
37
+ p_profile.sort_values(by=['date'], inplace=True)
38
+ # change in shares for same ticker
39
+ p_profile['share_changes'] = p_profile.groupby(['ticker'])[
40
+ 'shares'].diff()
41
+ p_profile['share_changes'] = p_profile['share_changes'].fillna(
42
+ p_profile['shares'])
43
+ # indicate if change is saved
44
+ p_profile['change_saved'] = True
45
+ p_profile['sync_to_db'] = True
46
+
47
+ # get all stocks ticker for auto fill
48
+ stock_details = pd.read_sql_table(table_schema.STOCKS_DETAILS_TABLE, con=conn)
49
+ all_tickers = stock_details.ticker.to_list()
50
+
51
+ # get most recent portfolio for auto generate entry
52
+ most_recent_portfolio = None
53
+ if len(p_profile) == 0:
54
+ most_recent_portfolio = p_profile
55
+ else:
56
+ most_recent_portfolio = p_profile[p_profile.date == max(
57
+ p_profile.date)]
58
+
59
+ # create portfolio table tabulator
60
+ hidden_column = ['index', 'sector', 'name']
61
+ col_to_titles = {'ticker': '证劵代码', 'weight': '权重',
62
+ 'date': '时间', 'aggregate_sector': '分类',
63
+ 'display_name': '名称',
64
+ 'shares': '持仓', 'change_saved': '已同步',
65
+ 'sync_to_db': '存入', 'share_changes': '持仓变化',
66
+ 'cash': '现金', 'ave_price': '平均成本',
67
+ }
68
+ # styling
69
+ tabulator_formatters = {
70
+ # 'float': {'type': 'progress', 'max': 10},
71
+ 'sync_to_db': {'type': 'tickCross'},
72
+ 'change_saved': {'type': 'tickCross'},
73
+ }
74
+ bokeh_editors = {
75
+ 'ticker': SelectEditor(options=all_tickers),
76
+ 'shares': NumberEditor(),
77
+ }
78
+ # frozen_columns = ['date','ticker','display_name','shares','sync_to_db','change_saved']
79
+
80
+ portfolio_tabulator = pn.widgets.Tabulator(p_profile,
81
+ layout='fit_columns',
82
+ height_policy='max',
83
+ width=1000,
84
+ groupby=['date'],
85
+ hidden_columns=hidden_column, titles=col_to_titles,
86
+ formatters=tabulator_formatters,
87
+ editors=bokeh_editors,
88
+ pagination='local',
89
+ # page_size=25,
90
+ # frozen_columns=frozen_columns
91
+ )
92
+
93
+ portfolio_tabulator.style.apply(style_number, subset=['share_changes'])
94
+
95
+ # history tabulator
96
+ history_dt = p_profile[['date', 'sync_to_db', 'change_saved']].copy()
97
+ history_dt = history_dt.groupby('date').agg({
98
+ "sync_to_db": lambda x: all(x),
99
+ 'change_saved': lambda x: all(x),
100
+ })
101
+ history_dt['date'] = history_dt.index
102
+ history_dt.reset_index(drop=True, inplace=True)
103
+ history_tabulator = pn.widgets.Tabulator(history_dt,
104
+ formatters=tabulator_formatters,
105
+ buttons={'detail': "<i>📋</i>"},
106
+ hidden_columns=hidden_column,
107
+ height_policy='max',
108
+ titles=col_to_titles)
109
+
110
+ # create component
111
+ new_stock_btn = pn.widgets.Button(
112
+ name='增加新股票', button_type='primary', sizing_mode='stretch_width')
113
+ preview_btn = pn.widgets.Button(
114
+ name='预览', button_type='primary', sizing_mode='stretch_width')
115
+ file_input = pn.widgets.FileInput(
116
+ accept='.xlsx', sizing_mode='stretch_width')
117
+ # strip timezone info
118
+ datetime_picker = pn.widgets.DatetimePicker(name='Datetime Picker',
119
+ value=time_in_beijing().replace(tzinfo=None),
120
+ sizing_mode='stretch_width')
121
+ upload_to_db_btn = pn.widgets.Button(
122
+ name='保存到数据库', button_type='warning', sizing_mode='stretch_width')
123
+ # emtpy stock_column to display new entires
124
+ stock_column = pn.Column(
125
+ width_policy='max', height_policy='max', scroll=True)
126
+ # floating window row
127
+ floating_windows = pn.Row()
128
+
129
+ def _update_history_tabulator(action, df=None):
130
+ '''handle update history tabulator'''
131
+ # handle add new entires to view
132
+ if action == 'append' and df is not None:
133
+ index = history_tabulator.value[history_tabulator.value.date ==
134
+ df.date[0]].index.to_list()
135
+ if len(index) == 0:
136
+ # drop duplicate date in df
137
+ df = df.drop_duplicates(subset='date', keep='first')
138
+ # if not in history tabulator add new entry
139
+ selected_df = df[['date', 'sync_to_db', 'change_saved']]
140
+ # if stream to empty tabulator, index will be mismatched
141
+ if (len(history_tabulator.value) == 0):
142
+ history_tabulator.value = selected_df
143
+ else:
144
+ history_tabulator.stream(
145
+ df[['date', 'sync_to_db', 'change_saved']], follow=True)
146
+ else:
147
+ # if in history tabulator patch change_saved to false
148
+ history_tabulator.patch({
149
+ 'change_saved': [(index[0], False)]
150
+ }, as_index=True)
151
+ # hanlde editing portoflio tabulator
152
+ elif action == 'edit':
153
+ # mark synced_to_db to false when entry is edited
154
+ date = df
155
+ index = history_tabulator.value[history_tabulator.value.date == date].index.to_list(
156
+ )
157
+ history_tabulator.patch({
158
+ 'change_saved': [(index[0], False)]
159
+ }, as_index=True)
160
+ # handle sync to db
161
+ elif action == 'sync':
162
+ # patch all synced_to_db to true
163
+ indices = history_tabulator.value[
164
+ ~history_tabulator.value['change_saved']].index.to_list()
165
+
166
+ # add an offset to address the issue when df is empty index start from 1
167
+
168
+ history_tabulator.patch({
169
+ 'change_saved': [(index, True) for index in indices]
170
+ }, as_index=True)
171
+ # mark synced_to_db to false when editing or select not synced_to_db
172
+ # if dt is not None and df.date[0] in history_tabulator.value.date.values:
173
+ # history_tabulator.stream(df[['date','sync_to_db','change_saved']], follow=True)
174
+
175
+ # update mark all synced_to_db to true when update
176
+
177
+ def delete_stock(row):
178
+ '''delete a stock entry'''
179
+ stock_column.remove(row)
180
+
181
+ def create_new_stock_entry(ticker=None, shares=0, ave_price=0.0, disable_ticker=True):
182
+ '''create a new new stock entry'''
183
+ delete_btn = pn.widgets.Button(
184
+ name='❌', width=50, height=60, sizing_mode='fixed')
185
+ ticker_selector = pn.widgets.AutocompleteInput(
186
+ value=ticker,
187
+ name='证劵代码',
188
+ sizing_mode='stretch_width',
189
+ options=all_tickers,
190
+ placeholder='input ticker',
191
+
192
+ )
193
+ share_input = pn.widgets.IntInput(
194
+ name='持仓',
195
+ value=shares,
196
+ step=1,
197
+ start=0,
198
+ sizing_mode='stretch_width')
199
+
200
+ mean_price_input = pn.widgets.FloatInput(
201
+ name='平均成本',
202
+ value=ave_price, step=0.01, start=0, sizing_mode='stretch_width')
203
+
204
+ row = pn.Row(
205
+ delete_btn,
206
+ ticker_selector,
207
+ share_input,
208
+ mean_price_input,
209
+ width_policy='max',
210
+ )
211
+ delete_btn.on_click(lambda _, row=row: delete_stock(row))
212
+ return row
213
+
214
+ def update_stock_column(xlsx_file=None):
215
+ stock_entries = []
216
+ if xlsx_file is None:
217
+ for ticker, shares in most_recent_portfolio[['ticker', 'shares']].values:
218
+ stock_entries.append(create_new_stock_entry(
219
+ ticker=ticker, shares=shares))
220
+ # create from xlsx_file
221
+ else:
222
+ stocks_list = create_stocks_entry_from_excel(xlsx_file)
223
+ for entry in stocks_list:
224
+ stock_entries.append(create_new_stock_entry(
225
+ ave_price=entry['mean_price'],
226
+ ticker=entry['ticker'],
227
+ shares=entry['shares']))
228
+ # modify time
229
+ datetime_picker.value = stocks_list[0]['date']
230
+ file_input.value = None
231
+
232
+ # update
233
+ stock_column.clear()
234
+ stock_column.extend(stock_entries)
235
+
236
+ def _get_stocks_price(df):
237
+ '''return a df with latest stock price added the new portfolio entry'''
238
+ stock_price = api.fetch_stocks_price(
239
+ security=df.ticker.to_list(),
240
+ end_date=df.date[0],
241
+ count=1,
242
+ frequency='minute',
243
+ )
244
+ stock_price.rename(columns={'time': 'stock_price_ts'}, inplace=True)
245
+ merged_df = df.merge(
246
+ stock_price[['ticker', 'stock_price_ts', 'close']], on='ticker', how='left')
247
+ return merged_df
248
+
249
+ def _calculate_weigth(df):
250
+ '''
251
+ calculate weight on new portfolio entry
252
+ '''
253
+ df['total_value'] = df.shares * df.close
254
+ df['weight'] = df.total_value / df.total_value.sum()
255
+
256
+ def update_profile_tabulator(e):
257
+ '''add all stocks entry to ui'''
258
+ new_entry = [dict(ticker=row[1].value,
259
+ shares=row[2].value,
260
+ ave_price=row[3].value,
261
+ date=datetime_picker.value) for row in stock_column]
262
+
263
+ if len(new_entry) == 0:
264
+ print("no entry added")
265
+ return
266
+
267
+ new_profile = pipeline.create_portfolio_profile_df(new_entry)
268
+ # calculate share changes
269
+ tmp_profile = pd.concat([p_profile, new_profile], ignore_index=True)
270
+ tmp_profile.sort_values(by='date', inplace=True)
271
+ tmp_profile['share_changes'] = tmp_profile.groupby('ticker')[
272
+ 'shares'].diff()
273
+ tmp_profile['share_changes'] = tmp_profile['share_changes'].fillna(
274
+ tmp_profile['shares'])
275
+ new_profile = new_profile.merge(tmp_profile[[
276
+ 'ticker', 'date', 'share_changes', 'change_saved']], on=['ticker', 'date'], how='left')
277
+ # fill emtpy change_saved to False
278
+ new_profile['change_saved'] = new_profile['change_saved'].fillna(False)
279
+ new_profile['sync_to_db'] = True
280
+ # calculate cash and weight
281
+ new_profile['cash'] = new_profile.shares * new_profile.ave_price
282
+ new_profile['weight'] = new_profile.cash / new_profile.cash.sum()
283
+
284
+ # update history tabulator
285
+ _update_history_tabulator('append', new_profile)
286
+ _stream_to_portfolio_tabulator(new_profile)
287
+
288
+ def add_new_stock(e):
289
+ row = create_new_stock_entry()
290
+ stock_column.append(row)
291
+
292
+ def _stream_to_portfolio_tabulator(entry):
293
+ if len(portfolio_tabulator.value) == 0:
294
+ portfolio_tabulator.value = entry
295
+ else:
296
+ portfolio_tabulator.stream(entry, follow=True)
297
+
298
+ def handle_click_on_history_tabulator(e):
299
+ '''handle click click on history tabulator'''
300
+ if e.column == 'detail':
301
+ row_index = e.row
302
+ date = history_tabulator.value.iloc[row_index]['date']
303
+ date_str = date.strftime("%Y-%m-%d : %H:%M:%S")
304
+ record_df = portfolio_tabulator.value[portfolio_tabulator.value.date == date]
305
+ floatpanel = pn.layout.FloatPanel(create_share_changes_report(
306
+ record_df), name=date_str, margin=20, position='right-top')
307
+ floating_windows.append(floatpanel)
308
+
309
+ def handle_sync_to_db(e):
310
+ # TODO: change to use profile df instead, because tabulator might not contain all entry
311
+ '''sync selected entry to db'''
312
+ new_portfolio = portfolio_tabulator.value
313
+ # TODO when initially df is empty, there is a 0 row in df as place holder
314
+ # only update selected row to db
315
+ selected_portfolio = new_portfolio[new_portfolio['sync_to_db']]
316
+ successed = update_portfolio_profile_to_db(selected_portfolio)
317
+ # update history tabulator and portfolio tabulator
318
+ if successed:
319
+ # mark changes as saved
320
+ indices = selected_portfolio[~selected_portfolio['change_saved']].index.to_list()
321
+ portfolio_tabulator.patch({
322
+ 'change_saved': [(index, True) for index in indices]
323
+ }, as_index=True)
324
+
325
+
326
+ _update_history_tabulator('sync')
327
+
328
+ def handle_edit_portfolio_tabulator(e):
329
+ date = portfolio_tabulator.value.iloc[e.row]['date']
330
+ _update_history_tabulator(df=date, action='edit')
331
+ print(date)
332
+
333
+ # %%
334
+ # register event handler
335
+ upload_to_db_btn.on_click(handle_sync_to_db)
336
+ preview_btn.on_click(update_profile_tabulator)
337
+ new_stock_btn.on_click(add_new_stock)
338
+ history_tabulator.on_click(
339
+ handle_click_on_history_tabulator
340
+ )
341
+ portfolio_tabulator.on_edit(handle_edit_portfolio_tabulator)
342
+
343
+ # %%
344
+ # create handler component to add to panel so can be listened to
345
+ upload_xlsx_handler = pn.bind(update_stock_column, file_input)
346
+
347
+ # %%
348
+ # layout
349
+
350
+ editor_widget = pn.Column(floating_windows, datetime_picker, upload_to_db_btn, new_stock_btn,
351
+ preview_btn, file_input, pn.widgets.TooltipIcon(
352
+ value="用于更新修改持仓信息,默认股票为最近持仓,默认时间为目前北京时间,点击增加新股票按钮,输入股票代码和持仓选择日期(北京时间),点击预览,确认无误后点击保存到数据库。或者直接���拽excel文件到下方上传按钮"),
353
+ stock_column, width=MIN_COMPONENT_WIDTH, height_policy='max')
354
+ # tooltip
355
+ toolTip2 = pn.widgets.TooltipIcon(
356
+ value="持仓总结,每一行的已同步到数据库代表所做更改是否已同步到数据库,点击保存到数据库将上传所有更改。点击右侧📋按钮查看详细持仓变化报告")
357
+
358
+ return pn.Row(
359
+ pn.layout.HSpacer(),
360
+ editor_widget,
361
+ pn.Spacer(width=10),
362
+ history_tabulator,
363
+ pn.Spacer(width=10),
364
+ portfolio_tabulator,
365
+ pn.Spacer(width=10),
366
+ upload_xlsx_handler,
367
+ pn.layout.HSpacer(),
368
+ height=1500,
369
+ # width_policy='max', height_policy='max')
370
+ # sizing_mode='stretch_both',
371
+ )
372
+
373
+
374
+ # app
375
+ template = pn.template.FastListTemplate(title='portfolio编辑')
376
+ template.main.append(app())
377
+ template.servable()
portfolio_page.py CHANGED
@@ -1,3 +1,4 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:0b8e6557dc04a241847cf244f74af06c7830b18eef037126cdc88db2c252d6d6
3
- size 66
 
 
1
+ '''
2
+ A page to view history of portfolio and update portfolio
3
+ '''
4
+
requirements.txt CHANGED
@@ -1,3 +1,136 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:2a41e4b76e79fa147f1a49339e56ec5ff8e22f21fc60f5e51437dd159bd99600
3
- size 2430
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ anyio==3.7.0
2
+ appnope==0.1.3
3
+ argon2-cffi==21.3.0
4
+ argon2-cffi-bindings==21.2.0
5
+ arrow==1.2.3
6
+ asttokens==2.2.1
7
+ async-lru==2.0.2
8
+ attrs==23.1.0
9
+ Babel==2.12.1
10
+ backcall==0.2.0
11
+ backports.functools-lru-cache==1.6.4
12
+ beautifulsoup4==4.12.2
13
+ bleach==6.0.0
14
+ bokeh==3.1.1
15
+ certifi==2023.5.7
16
+ cffi==1.15.1
17
+ charset-normalizer==3.1.0
18
+ colorcet==3.0.1
19
+ comm==0.1.3
20
+ contourpy==1.1.0
21
+ cycler==0.11.0
22
+ debugpy==1.6.7
23
+ decorator==5.1.1
24
+ defusedxml==0.7.1
25
+ et-xmlfile==1.1.0
26
+ executing==1.2.0
27
+ fastjsonschema==2.17.1
28
+ fonttools==4.40.0
29
+ fqdn==1.5.1
30
+ holoviews==1.16.2
31
+ hvplot==0.8.4
32
+ idna==3.4
33
+ importlib-metadata==6.7.0
34
+ ipykernel==6.23.2
35
+ ipython==8.14.0
36
+ ipywidgets==8.0.6
37
+ isoduration==20.11.0
38
+ jedi==0.18.2
39
+ Jinja2==3.1.2
40
+ jqdatasdk==1.8.11
41
+ json5==0.9.14
42
+ jsonpointer==2.4
43
+ jsonschema==4.17.3
44
+ jupyter-bokeh==3.0.7
45
+ jupyter_client==8.2.0
46
+ jupyter_core==5.3.1
47
+ jupyter-events==0.6.3
48
+ jupyter-lsp==2.2.0
49
+ jupyter_server==2.6.0
50
+ jupyter_server_terminals==0.4.4
51
+ jupyterlab==4.0.2
52
+ jupyterlab-pygments==0.2.2
53
+ jupyterlab_server==2.23.0
54
+ jupyterlab-widgets==3.0.7
55
+ kiwisolver==1.4.4
56
+ linkify-it-py==2.0.2
57
+ Markdown==3.4.3
58
+ markdown-it-py==2.2.0
59
+ MarkupSafe==2.1.3
60
+ matplotlib==3.7.1
61
+ matplotlib-inline==0.1.6
62
+ mdit-py-plugins==0.4.0
63
+ mdurl==0.1.2
64
+ mistune==3.0.1
65
+ msgpack==1.0.5
66
+ nbclient==0.8.0
67
+ nbconvert==7.6.0
68
+ nbformat==5.9.0
69
+ nest-asyncio==1.5.6
70
+ notebook_shim==0.2.3
71
+ numpy==1.25.0
72
+ openpyxl==3.1.2
73
+ overrides==7.3.1
74
+ packaging==23.1
75
+ pandas==2.0.2
76
+ pandocfilters==1.5.0
77
+ panel==1.1.1
78
+ param==1.13.0
79
+ parso==0.8.3
80
+ pexpect==4.8.0
81
+ pickleshare==0.7.5
82
+ Pillow==9.5.0
83
+ pip==23.1.2
84
+ platformdirs==3.6.0
85
+ plotly==5.15.0
86
+ ply==3.11
87
+ prometheus-client==0.17.0
88
+ prompt-toolkit==3.0.38
89
+ psutil==5.9.5
90
+ ptyprocess==0.7.0
91
+ pure-eval==0.2.2
92
+ pycparser==2.21
93
+ pyct==0.5.0
94
+ Pygments==2.15.1
95
+ PyMySQL==1.0.3
96
+ pyparsing==3.1.0
97
+ pyrsistent==0.19.3
98
+ python-dateutil==2.8.2
99
+ python-json-logger==2.0.7
100
+ pytz==2023.3
101
+ pyviz-comms==2.3.2
102
+ PyYAML==6.0
103
+ pyzmq==25.1.0
104
+ requests==2.31.0
105
+ rfc3339-validator==0.1.4
106
+ rfc3986-validator==0.1.1
107
+ scipy==1.10.1
108
+ Send2Trash==1.8.2
109
+ setuptools==67.7.2
110
+ six==1.16.0
111
+ sniffio==1.3.0
112
+ soupsieve==2.4.1
113
+ SQLAlchemy==2.0.16
114
+ stack-data==0.6.2
115
+ tenacity==8.2.2
116
+ terminado==0.17.1
117
+ thriftpy2==0.4.16
118
+ tinycss2==1.2.1
119
+ tornado==6.3.2
120
+ tqdm==4.65.0
121
+ traitlets==5.9.0
122
+ typing_extensions==4.6.3
123
+ tzdata==2023.3
124
+ uc-micro-py==1.0.2
125
+ uri-template==1.3.0
126
+ urllib3==2.0.3
127
+ wcwidth==0.2.6
128
+ webcolors==1.13
129
+ webencodings==0.5.1
130
+ websocket-client==1.6.0
131
+ wheel==0.40.0
132
+ widgetsnbextension==4.0.7
133
+ xyzservices==2023.5.0
134
+ zipp==3.15.0
135
+ streamz==0.6.4
136
+ python-dotenv==1.0.0
script/api_test.ipynb CHANGED
The diff for this file is too large to render. See raw diff
 
script/description.py CHANGED
@@ -1,3 +1,14 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:a9ebc0011567cb67b90e946044ba3e92107255fb7c963044358438586fd1193f
3
- size 411
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ This file store the tooltip to show in the GUI
3
+
4
+ '''
5
+ #周期回报
6
+ periodic_return_report = '''
7
+ 选择周期查看每个周期的回报率,以及每个周期主动回报的归因。
8
+ 周期回报的数据点代表周期结束。
9
+ 主动回报归因的数据点代表周期开始。
10
+ '''
11
+
12
+ summary_card = '''
13
+ 选择周期查看每个周期的总市值,总回报率,主动回报率,主动回报归因。
14
+ '''
script/downloadData.ipynb CHANGED
@@ -1,3 +1,751 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:e9498800f6ef5fa8a6901907b8902f0c5a1f783ae8e2d453d6fd1194d9a92f59
3
- size 27769
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "import pandas as pd\n",
10
+ "from api import create_portfolio, get_portfile_data, get_benchmark_portfolio\n",
11
+ "from datetime import datetime"
12
+ ]
13
+ },
14
+ {
15
+ "cell_type": "code",
16
+ "execution_count": 2,
17
+ "metadata": {},
18
+ "outputs": [],
19
+ "source": [
20
+ "# df = pd.read_pickle('../dummy_portfolio.pkl')\n",
21
+ "# df.head(20)"
22
+ ]
23
+ },
24
+ {
25
+ "cell_type": "code",
26
+ "execution_count": 3,
27
+ "metadata": {},
28
+ "outputs": [],
29
+ "source": [
30
+ "# ## create a portfolio\n",
31
+ "\n",
32
+ "# # weight can use mony\n",
33
+ "# portfolio_profile_data = [\n",
34
+ "# {'ticker': '002709.XSHE', 'date': datetime(2021, 1, 5), 'weight': 100},\n",
35
+ "# {'ticker': '002920.XSHE', 'date': datetime(2021, 1, 5), 'weight': 100},\n",
36
+ "# {'ticker': '300274.XSHE', 'date': datetime(2021, 1, 5), 'weight': 100},\n",
37
+ "# {'ticker': '600409.XSHG', 'date': datetime(2021, 1, 5), 'weight': 100},\n",
38
+ "# {'ticker': '600415.XSHG', 'date': datetime(2021, 1, 5), 'weight': 100},\n",
39
+ "# {'ticker': '603882.XSHG', 'date': datetime(2021, 1, 5), 'weight': 100},\n",
40
+ "# ]\n",
41
+ "# # ten thousand\n",
42
+ "# mkt_cap = 100000\n",
43
+ "# portfolio_profile, error = create_portfolio(portfolio_profile_data, mkt_cap)"
44
+ ]
45
+ },
46
+ {
47
+ "cell_type": "code",
48
+ "execution_count": 4,
49
+ "metadata": {},
50
+ "outputs": [],
51
+ "source": [
52
+ "# print(error)\n",
53
+ "# # save \n",
54
+ "# portfolio_profile.to_pickle('../data/portfolio_portfile.pkl')\n",
55
+ "# portfolio_profile.head(10)"
56
+ ]
57
+ },
58
+ {
59
+ "cell_type": "code",
60
+ "execution_count": 5,
61
+ "metadata": {},
62
+ "outputs": [
63
+ {
64
+ "data": {
65
+ "text/html": [
66
+ "<div>\n",
67
+ "<style scoped>\n",
68
+ " .dataframe tbody tr th:only-of-type {\n",
69
+ " vertical-align: middle;\n",
70
+ " }\n",
71
+ "\n",
72
+ " .dataframe tbody tr th {\n",
73
+ " vertical-align: top;\n",
74
+ " }\n",
75
+ "\n",
76
+ " .dataframe thead th {\n",
77
+ " text-align: right;\n",
78
+ " }\n",
79
+ "</style>\n",
80
+ "<table border=\"1\" class=\"dataframe\">\n",
81
+ " <thead>\n",
82
+ " <tr style=\"text-align: right;\">\n",
83
+ " <th></th>\n",
84
+ " <th>ticker</th>\n",
85
+ " <th>date</th>\n",
86
+ " <th>weight</th>\n",
87
+ " <th>display_name</th>\n",
88
+ " <th>name</th>\n",
89
+ " <th>sector</th>\n",
90
+ " <th>aggregate_sector</th>\n",
91
+ " </tr>\n",
92
+ " </thead>\n",
93
+ " <tbody>\n",
94
+ " <tr>\n",
95
+ " <th>0</th>\n",
96
+ " <td>002709.XSHE</td>\n",
97
+ " <td>2021-01-05</td>\n",
98
+ " <td>100</td>\n",
99
+ " <td>天赐材料</td>\n",
100
+ " <td>TCCL</td>\n",
101
+ " <td>电气设备I 电池II 电池化学品III 化学原料和化学制品制造业 电池部件及材料 工业</td>\n",
102
+ " <td>工业</td>\n",
103
+ " </tr>\n",
104
+ " <tr>\n",
105
+ " <th>1</th>\n",
106
+ " <td>002920.XSHE</td>\n",
107
+ " <td>2021-01-05</td>\n",
108
+ " <td>100</td>\n",
109
+ " <td>德赛西威</td>\n",
110
+ " <td>DSXW</td>\n",
111
+ " <td>计算机I 软件开发II 垂直应用软件III 汽车制造业 汽车电子 可选消费</td>\n",
112
+ " <td>信息与通信</td>\n",
113
+ " </tr>\n",
114
+ " <tr>\n",
115
+ " <th>2</th>\n",
116
+ " <td>300274.XSHE</td>\n",
117
+ " <td>2021-01-05</td>\n",
118
+ " <td>100</td>\n",
119
+ " <td>阳光电源</td>\n",
120
+ " <td>YGDY</td>\n",
121
+ " <td>电气设备I 光伏设备II 逆变器III 电气机械和器材制造业 光伏设备 工业</td>\n",
122
+ " <td>工业</td>\n",
123
+ " </tr>\n",
124
+ " <tr>\n",
125
+ " <th>3</th>\n",
126
+ " <td>600409.XSHG</td>\n",
127
+ " <td>2021-01-05</td>\n",
128
+ " <td>100</td>\n",
129
+ " <td>三友化工</td>\n",
130
+ " <td>SYHG</td>\n",
131
+ " <td>化工I 化学原料II 纯碱III 化学原料和化学制品制造业 粘胶 原材料</td>\n",
132
+ " <td>原料与能源</td>\n",
133
+ " </tr>\n",
134
+ " <tr>\n",
135
+ " <th>4</th>\n",
136
+ " <td>600415.XSHG</td>\n",
137
+ " <td>2021-01-05</td>\n",
138
+ " <td>100</td>\n",
139
+ " <td>小商品城</td>\n",
140
+ " <td>XSPC</td>\n",
141
+ " <td>商业贸易I 一般零售II 商业物业经营III 商务服务业 市场服务 工业</td>\n",
142
+ " <td>消费</td>\n",
143
+ " </tr>\n",
144
+ " <tr>\n",
145
+ " <th>5</th>\n",
146
+ " <td>603882.XSHG</td>\n",
147
+ " <td>2021-01-05</td>\n",
148
+ " <td>100</td>\n",
149
+ " <td>金域医学</td>\n",
150
+ " <td>JYYX</td>\n",
151
+ " <td>医药生物I 医疗服务II 诊断服务III 卫生 体外诊断 医药卫生</td>\n",
152
+ " <td>医药卫生</td>\n",
153
+ " </tr>\n",
154
+ " </tbody>\n",
155
+ "</table>\n",
156
+ "</div>"
157
+ ],
158
+ "text/plain": [
159
+ " ticker date weight display_name name \\\n",
160
+ "0 002709.XSHE 2021-01-05 100 天赐材料 TCCL \n",
161
+ "1 002920.XSHE 2021-01-05 100 德赛西威 DSXW \n",
162
+ "2 300274.XSHE 2021-01-05 100 阳光电源 YGDY \n",
163
+ "3 600409.XSHG 2021-01-05 100 三友化工 SYHG \n",
164
+ "4 600415.XSHG 2021-01-05 100 小商品城 XSPC \n",
165
+ "5 603882.XSHG 2021-01-05 100 金域医学 JYYX \n",
166
+ "\n",
167
+ " sector aggregate_sector \n",
168
+ "0 电气设备I 电池II 电池化学品III 化学原料和化学制品制造业 电池部件及材料 工业 工业 \n",
169
+ "1 计算机I 软件开发II 垂直应用软件III 汽车制造业 汽车电子 可选消费 信息与通信 \n",
170
+ "2 电气设备I 光伏设备II 逆变器III 电气机械和器材制造业 光伏设备 工业 工业 \n",
171
+ "3 化工I 化学原料II 纯碱III 化学原料和化学制品制造业 粘胶 原材料 原料与能源 \n",
172
+ "4 商业贸易I 一般零售II 商业物业经营III 商务服务业 市场服务 工业 消费 \n",
173
+ "5 医药生物I 医疗服务II 诊断服务III 卫生 体外诊断 医药卫生 医药卫生 "
174
+ ]
175
+ },
176
+ "execution_count": 5,
177
+ "metadata": {},
178
+ "output_type": "execute_result"
179
+ }
180
+ ],
181
+ "source": [
182
+ "## load portfolio_profile\n",
183
+ "portfolio_profile = pd.read_pickle('../data/portfolio_portfile.pkl')\n",
184
+ "portfolio_profile.head(10)"
185
+ ]
186
+ },
187
+ {
188
+ "cell_type": "code",
189
+ "execution_count": 6,
190
+ "metadata": {},
191
+ "outputs": [],
192
+ "source": [
193
+ "start_date = datetime(2021, 1, 5)\n",
194
+ "end_date = datetime(2022, 1, 10)"
195
+ ]
196
+ },
197
+ {
198
+ "cell_type": "code",
199
+ "execution_count": 7,
200
+ "metadata": {},
201
+ "outputs": [
202
+ {
203
+ "name": "stdout",
204
+ "output_type": "stream",
205
+ "text": [
206
+ "auth success \n"
207
+ ]
208
+ }
209
+ ],
210
+ "source": [
211
+ "# get portfolio data\n",
212
+ "portfile_data, error = get_portfile_data(portfolio_profile, start_date, end_date)"
213
+ ]
214
+ },
215
+ {
216
+ "cell_type": "code",
217
+ "execution_count": 8,
218
+ "metadata": {},
219
+ "outputs": [
220
+ {
221
+ "name": "stdout",
222
+ "output_type": "stream",
223
+ "text": [
224
+ "[]\n",
225
+ "(1482, 8)\n"
226
+ ]
227
+ },
228
+ {
229
+ "data": {
230
+ "text/html": [
231
+ "<div>\n",
232
+ "<style scoped>\n",
233
+ " .dataframe tbody tr th:only-of-type {\n",
234
+ " vertical-align: middle;\n",
235
+ " }\n",
236
+ "\n",
237
+ " .dataframe tbody tr th {\n",
238
+ " vertical-align: top;\n",
239
+ " }\n",
240
+ "\n",
241
+ " .dataframe thead th {\n",
242
+ " text-align: right;\n",
243
+ " }\n",
244
+ "</style>\n",
245
+ "<table border=\"1\" class=\"dataframe\">\n",
246
+ " <thead>\n",
247
+ " <tr style=\"text-align: right;\">\n",
248
+ " <th></th>\n",
249
+ " <th>ticker</th>\n",
250
+ " <th>date</th>\n",
251
+ " <th>open</th>\n",
252
+ " <th>close</th>\n",
253
+ " <th>high</th>\n",
254
+ " <th>low</th>\n",
255
+ " <th>volume</th>\n",
256
+ " <th>money</th>\n",
257
+ " </tr>\n",
258
+ " </thead>\n",
259
+ " <tbody>\n",
260
+ " <tr>\n",
261
+ " <th>2022-01-07</th>\n",
262
+ " <td>603882.XSHG</td>\n",
263
+ " <td>2022-01-07</td>\n",
264
+ " <td>91.13</td>\n",
265
+ " <td>87.99</td>\n",
266
+ " <td>91.17</td>\n",
267
+ " <td>87.72</td>\n",
268
+ " <td>6971998.0</td>\n",
269
+ " <td>6.176535e+08</td>\n",
270
+ " </tr>\n",
271
+ " <tr>\n",
272
+ " <th>2022-01-07</th>\n",
273
+ " <td>002709.XSHE</td>\n",
274
+ " <td>2022-01-07</td>\n",
275
+ " <td>51.28</td>\n",
276
+ " <td>51.72</td>\n",
277
+ " <td>52.62</td>\n",
278
+ " <td>50.47</td>\n",
279
+ " <td>32210458.0</td>\n",
280
+ " <td>1.661823e+09</td>\n",
281
+ " </tr>\n",
282
+ " <tr>\n",
283
+ " <th>2022-01-07</th>\n",
284
+ " <td>600409.XSHG</td>\n",
285
+ " <td>2022-01-07</td>\n",
286
+ " <td>8.23</td>\n",
287
+ " <td>8.22</td>\n",
288
+ " <td>8.29</td>\n",
289
+ " <td>8.19</td>\n",
290
+ " <td>35003739.0</td>\n",
291
+ " <td>2.884990e+08</td>\n",
292
+ " </tr>\n",
293
+ " <tr>\n",
294
+ " <th>2022-01-07</th>\n",
295
+ " <td>600415.XSHG</td>\n",
296
+ " <td>2022-01-07</td>\n",
297
+ " <td>4.74</td>\n",
298
+ " <td>4.70</td>\n",
299
+ " <td>4.79</td>\n",
300
+ " <td>4.68</td>\n",
301
+ " <td>24902567.0</td>\n",
302
+ " <td>1.178837e+08</td>\n",
303
+ " </tr>\n",
304
+ " <tr>\n",
305
+ " <th>2022-01-10</th>\n",
306
+ " <td>300274.XSHE</td>\n",
307
+ " <td>2022-01-10</td>\n",
308
+ " <td>127.49</td>\n",
309
+ " <td>124.09</td>\n",
310
+ " <td>127.49</td>\n",
311
+ " <td>123.29</td>\n",
312
+ " <td>17238708.0</td>\n",
313
+ " <td>2.148032e+09</td>\n",
314
+ " </tr>\n",
315
+ " <tr>\n",
316
+ " <th>2022-01-10</th>\n",
317
+ " <td>600409.XSHG</td>\n",
318
+ " <td>2022-01-10</td>\n",
319
+ " <td>8.24</td>\n",
320
+ " <td>8.35</td>\n",
321
+ " <td>8.39</td>\n",
322
+ " <td>8.21</td>\n",
323
+ " <td>32516017.0</td>\n",
324
+ " <td>2.699300e+08</td>\n",
325
+ " </tr>\n",
326
+ " <tr>\n",
327
+ " <th>2022-01-10</th>\n",
328
+ " <td>002920.XSHE</td>\n",
329
+ " <td>2022-01-10</td>\n",
330
+ " <td>130.36</td>\n",
331
+ " <td>138.43</td>\n",
332
+ " <td>141.96</td>\n",
333
+ " <td>130.11</td>\n",
334
+ " <td>5005400.0</td>\n",
335
+ " <td>6.901614e+08</td>\n",
336
+ " </tr>\n",
337
+ " <tr>\n",
338
+ " <th>2022-01-10</th>\n",
339
+ " <td>002709.XSHE</td>\n",
340
+ " <td>2022-01-10</td>\n",
341
+ " <td>51.63</td>\n",
342
+ " <td>50.73</td>\n",
343
+ " <td>51.93</td>\n",
344
+ " <td>50.03</td>\n",
345
+ " <td>29821246.0</td>\n",
346
+ " <td>1.518902e+09</td>\n",
347
+ " </tr>\n",
348
+ " <tr>\n",
349
+ " <th>2022-01-10</th>\n",
350
+ " <td>600415.XSHG</td>\n",
351
+ " <td>2022-01-10</td>\n",
352
+ " <td>4.70</td>\n",
353
+ " <td>4.75</td>\n",
354
+ " <td>4.85</td>\n",
355
+ " <td>4.67</td>\n",
356
+ " <td>39278041.0</td>\n",
357
+ " <td>1.859827e+08</td>\n",
358
+ " </tr>\n",
359
+ " <tr>\n",
360
+ " <th>2022-01-10</th>\n",
361
+ " <td>603882.XSHG</td>\n",
362
+ " <td>2022-01-10</td>\n",
363
+ " <td>88.45</td>\n",
364
+ " <td>95.53</td>\n",
365
+ " <td>95.59</td>\n",
366
+ " <td>88.39</td>\n",
367
+ " <td>6991445.0</td>\n",
368
+ " <td>6.468392e+08</td>\n",
369
+ " </tr>\n",
370
+ " </tbody>\n",
371
+ "</table>\n",
372
+ "</div>"
373
+ ],
374
+ "text/plain": [
375
+ " ticker date open close high low \\\n",
376
+ "2022-01-07 603882.XSHG 2022-01-07 91.13 87.99 91.17 87.72 \n",
377
+ "2022-01-07 002709.XSHE 2022-01-07 51.28 51.72 52.62 50.47 \n",
378
+ "2022-01-07 600409.XSHG 2022-01-07 8.23 8.22 8.29 8.19 \n",
379
+ "2022-01-07 600415.XSHG 2022-01-07 4.74 4.70 4.79 4.68 \n",
380
+ "2022-01-10 300274.XSHE 2022-01-10 127.49 124.09 127.49 123.29 \n",
381
+ "2022-01-10 600409.XSHG 2022-01-10 8.24 8.35 8.39 8.21 \n",
382
+ "2022-01-10 002920.XSHE 2022-01-10 130.36 138.43 141.96 130.11 \n",
383
+ "2022-01-10 002709.XSHE 2022-01-10 51.63 50.73 51.93 50.03 \n",
384
+ "2022-01-10 600415.XSHG 2022-01-10 4.70 4.75 4.85 4.67 \n",
385
+ "2022-01-10 603882.XSHG 2022-01-10 88.45 95.53 95.59 88.39 \n",
386
+ "\n",
387
+ " volume money \n",
388
+ "2022-01-07 6971998.0 6.176535e+08 \n",
389
+ "2022-01-07 32210458.0 1.661823e+09 \n",
390
+ "2022-01-07 35003739.0 2.884990e+08 \n",
391
+ "2022-01-07 24902567.0 1.178837e+08 \n",
392
+ "2022-01-10 17238708.0 2.148032e+09 \n",
393
+ "2022-01-10 32516017.0 2.699300e+08 \n",
394
+ "2022-01-10 5005400.0 6.901614e+08 \n",
395
+ "2022-01-10 29821246.0 1.518902e+09 \n",
396
+ "2022-01-10 39278041.0 1.859827e+08 \n",
397
+ "2022-01-10 6991445.0 6.468392e+08 "
398
+ ]
399
+ },
400
+ "execution_count": 8,
401
+ "metadata": {},
402
+ "output_type": "execute_result"
403
+ }
404
+ ],
405
+ "source": [
406
+ "print(error)\n",
407
+ "print(portfile_data.shape)\n",
408
+ "portfile_data.sort_values(by=['date'], inplace=True)\n",
409
+ "portfile_data.tail(10)"
410
+ ]
411
+ },
412
+ {
413
+ "cell_type": "code",
414
+ "execution_count": 26,
415
+ "metadata": {},
416
+ "outputs": [],
417
+ "source": [
418
+ "# save\n",
419
+ "portfile_data.to_pickle('../data/portfolio_data.pkl')"
420
+ ]
421
+ },
422
+ {
423
+ "cell_type": "code",
424
+ "execution_count": 27,
425
+ "metadata": {},
426
+ "outputs": [],
427
+ "source": [
428
+ "# load benchmark portfolio\n",
429
+ "benchmark_portfolio, error = get_benchmark_portfolio(start_date, end_date)"
430
+ ]
431
+ },
432
+ {
433
+ "cell_type": "code",
434
+ "execution_count": 28,
435
+ "metadata": {},
436
+ "outputs": [
437
+ {
438
+ "name": "stdout",
439
+ "output_type": "stream",
440
+ "text": [
441
+ "[]\n",
442
+ "(185500, 15)\n"
443
+ ]
444
+ },
445
+ {
446
+ "data": {
447
+ "text/html": [
448
+ "<div>\n",
449
+ "<style scoped>\n",
450
+ " .dataframe tbody tr th:only-of-type {\n",
451
+ " vertical-align: middle;\n",
452
+ " }\n",
453
+ "\n",
454
+ " .dataframe tbody tr th {\n",
455
+ " vertical-align: top;\n",
456
+ " }\n",
457
+ "\n",
458
+ " .dataframe thead th {\n",
459
+ " text-align: right;\n",
460
+ " }\n",
461
+ "</style>\n",
462
+ "<table border=\"1\" class=\"dataframe\">\n",
463
+ " <thead>\n",
464
+ " <tr style=\"text-align: right;\">\n",
465
+ " <th></th>\n",
466
+ " <th>date</th>\n",
467
+ " <th>weight</th>\n",
468
+ " <th>display_name_x</th>\n",
469
+ " <th>actual_data</th>\n",
470
+ " <th>ticker</th>\n",
471
+ " <th>open</th>\n",
472
+ " <th>close</th>\n",
473
+ " <th>high</th>\n",
474
+ " <th>low</th>\n",
475
+ " <th>volume</th>\n",
476
+ " <th>money</th>\n",
477
+ " <th>display_name_y</th>\n",
478
+ " <th>name</th>\n",
479
+ " <th>sector</th>\n",
480
+ " <th>aggregate_sector</th>\n",
481
+ " </tr>\n",
482
+ " </thead>\n",
483
+ " <tbody>\n",
484
+ " <tr>\n",
485
+ " <th>185163</th>\n",
486
+ " <td>2022-01-10</td>\n",
487
+ " <td>0.274</td>\n",
488
+ " <td>厦门钨业</td>\n",
489
+ " <td>2021-12-31</td>\n",
490
+ " <td>600549.XSHG</td>\n",
491
+ " <td>21.50</td>\n",
492
+ " <td>21.59</td>\n",
493
+ " <td>21.75</td>\n",
494
+ " <td>21.43</td>\n",
495
+ " <td>10451128.0</td>\n",
496
+ " <td>2.254199e+08</td>\n",
497
+ " <td>厦门钨业</td>\n",
498
+ " <td>XMWY</td>\n",
499
+ " <td>有色金属I 稀有金属II 钨III 有色金属冶炼和压延加工业 钨钼 原材料</td>\n",
500
+ " <td>原料与能源</td>\n",
501
+ " </tr>\n",
502
+ " <tr>\n",
503
+ " <th>185162</th>\n",
504
+ " <td>2022-01-10</td>\n",
505
+ " <td>0.116</td>\n",
506
+ " <td>山煤国际</td>\n",
507
+ " <td>2021-12-31</td>\n",
508
+ " <td>600546.XSHG</td>\n",
509
+ " <td>6.74</td>\n",
510
+ " <td>6.86</td>\n",
511
+ " <td>6.86</td>\n",
512
+ " <td>6.71</td>\n",
513
+ " <td>66929559.0</td>\n",
514
+ " <td>4.539690e+08</td>\n",
515
+ " <td>山煤国际</td>\n",
516
+ " <td>SMGJ</td>\n",
517
+ " <td>煤炭I 煤炭开采II 动力煤III 批发业 煤炭 能源</td>\n",
518
+ " <td>原料与能源</td>\n",
519
+ " </tr>\n",
520
+ " <tr>\n",
521
+ " <th>185161</th>\n",
522
+ " <td>2022-01-10</td>\n",
523
+ " <td>0.211</td>\n",
524
+ " <td>中国软件</td>\n",
525
+ " <td>2021-12-31</td>\n",
526
+ " <td>600536.XSHG</td>\n",
527
+ " <td>29.01</td>\n",
528
+ " <td>30.90</td>\n",
529
+ " <td>31.45</td>\n",
530
+ " <td>28.98</td>\n",
531
+ " <td>35489167.0</td>\n",
532
+ " <td>1.087404e+09</td>\n",
533
+ " <td>中国软件</td>\n",
534
+ " <td>ZGRJ</td>\n",
535
+ " <td>计算机I IT服务II IT服务III 软件和信息技术服务业 行业应用软件 信息技术</td>\n",
536
+ " <td>信息与通信</td>\n",
537
+ " </tr>\n",
538
+ " <tr>\n",
539
+ " <th>185160</th>\n",
540
+ " <td>2022-01-10</td>\n",
541
+ " <td>0.205</td>\n",
542
+ " <td>天士力</td>\n",
543
+ " <td>2021-12-31</td>\n",
544
+ " <td>600535.XSHG</td>\n",
545
+ " <td>15.04</td>\n",
546
+ " <td>15.52</td>\n",
547
+ " <td>15.75</td>\n",
548
+ " <td>14.95</td>\n",
549
+ " <td>46823950.0</td>\n",
550
+ " <td>7.247465e+08</td>\n",
551
+ " <td>天士力</td>\n",
552
+ " <td>TSL</td>\n",
553
+ " <td>医药生物I 中药II 中药III 医药制造业 中成药 医药卫生</td>\n",
554
+ " <td>医药卫生</td>\n",
555
+ " </tr>\n",
556
+ " <tr>\n",
557
+ " <th>185159</th>\n",
558
+ " <td>2022-01-10</td>\n",
559
+ " <td>0.297</td>\n",
560
+ " <td>山东药玻</td>\n",
561
+ " <td>2021-12-31</td>\n",
562
+ " <td>600529.XSHG</td>\n",
563
+ " <td>42.39</td>\n",
564
+ " <td>40.14</td>\n",
565
+ " <td>42.39</td>\n",
566
+ " <td>39.47</td>\n",
567
+ " <td>12543641.0</td>\n",
568
+ " <td>5.049473e+08</td>\n",
569
+ " <td>山东药玻</td>\n",
570
+ " <td>SDYB</td>\n",
571
+ " <td>医药生物I 医疗器械II 医疗耗材III 非金属矿物制品业 医疗耗材 医药卫生</td>\n",
572
+ " <td>医药卫生</td>\n",
573
+ " </tr>\n",
574
+ " <tr>\n",
575
+ " <th>185158</th>\n",
576
+ " <td>2022-01-10</td>\n",
577
+ " <td>0.156</td>\n",
578
+ " <td>中铁工业</td>\n",
579
+ " <td>2021-12-31</td>\n",
580
+ " <td>600528.XSHG</td>\n",
581
+ " <td>8.69</td>\n",
582
+ " <td>9.08</td>\n",
583
+ " <td>9.26</td>\n",
584
+ " <td>8.68</td>\n",
585
+ " <td>98213530.0</td>\n",
586
+ " <td>8.961637e+08</td>\n",
587
+ " <td>中铁工业</td>\n",
588
+ " <td>ZTGY</td>\n",
589
+ " <td>机械设备I 运输设备II 铁路设备III 专用设备制造业 城轨铁路 工业</td>\n",
590
+ " <td>工业</td>\n",
591
+ " </tr>\n",
592
+ " <tr>\n",
593
+ " <th>185157</th>\n",
594
+ " <td>2022-01-10</td>\n",
595
+ " <td>0.642</td>\n",
596
+ " <td>中天科技</td>\n",
597
+ " <td>2021-12-31</td>\n",
598
+ " <td>600522.XSHG</td>\n",
599
+ " <td>15.21</td>\n",
600
+ " <td>14.76</td>\n",
601
+ " <td>15.26</td>\n",
602
+ " <td>14.53</td>\n",
603
+ " <td>112744173.0</td>\n",
604
+ " <td>1.670952e+09</td>\n",
605
+ " <td>中天科技</td>\n",
606
+ " <td>ZTKJ</td>\n",
607
+ " <td>通信I 通信设备II 通信线缆及配套III 电气机械和器材制造业 通信系统设备及组件 通信服务</td>\n",
608
+ " <td>信息与通信</td>\n",
609
+ " </tr>\n",
610
+ " <tr>\n",
611
+ " <th>185156</th>\n",
612
+ " <td>2022-01-10</td>\n",
613
+ " <td>0.276</td>\n",
614
+ " <td>华海药业</td>\n",
615
+ " <td>2021-12-31</td>\n",
616
+ " <td>600521.XSHG</td>\n",
617
+ " <td>22.32</td>\n",
618
+ " <td>23.32</td>\n",
619
+ " <td>23.43</td>\n",
620
+ " <td>21.90</td>\n",
621
+ " <td>29810065.0</td>\n",
622
+ " <td>6.827298e+08</td>\n",
623
+ " <td>华海药业</td>\n",
624
+ " <td>HHYY</td>\n",
625
+ " <td>医药生物I 化学制药II 化学制剂III 医药制造业 药品制剂 医药卫生</td>\n",
626
+ " <td>医药卫生</td>\n",
627
+ " </tr>\n",
628
+ " <tr>\n",
629
+ " <th>185169</th>\n",
630
+ " <td>2022-01-10</td>\n",
631
+ " <td>0.240</td>\n",
632
+ " <td>卧龙电驱</td>\n",
633
+ " <td>2021-12-31</td>\n",
634
+ " <td>600580.XSHG</td>\n",
635
+ " <td>16.66</td>\n",
636
+ " <td>16.51</td>\n",
637
+ " <td>16.68</td>\n",
638
+ " <td>16.23</td>\n",
639
+ " <td>19294606.0</td>\n",
640
+ " <td>3.175398e+08</td>\n",
641
+ " <td>卧龙电驱</td>\n",
642
+ " <td>WLDQ</td>\n",
643
+ " <td>电气设备I 电机II 电机III 电气机械和器材制造业 电动机与工控自动化 工业</td>\n",
644
+ " <td>工业</td>\n",
645
+ " </tr>\n",
646
+ " <tr>\n",
647
+ " <th>185499</th>\n",
648
+ " <td>2022-01-10</td>\n",
649
+ " <td>0.350</td>\n",
650
+ " <td>思瑞浦</td>\n",
651
+ " <td>2021-12-31</td>\n",
652
+ " <td>688536.XSHG</td>\n",
653
+ " <td>476.70</td>\n",
654
+ " <td>463.10</td>\n",
655
+ " <td>476.70</td>\n",
656
+ " <td>446.03</td>\n",
657
+ " <td>924992.0</td>\n",
658
+ " <td>4.254053e+08</td>\n",
659
+ " <td>思瑞浦</td>\n",
660
+ " <td>SRP</td>\n",
661
+ " <td>电子I 半导体II 模拟芯片设计III 软件和信息技术服务业 集成电路设计 信息技术</td>\n",
662
+ " <td>信息与通信</td>\n",
663
+ " </tr>\n",
664
+ " </tbody>\n",
665
+ "</table>\n",
666
+ "</div>"
667
+ ],
668
+ "text/plain": [
669
+ " date weight display_name_x actual_data ticker open \\\n",
670
+ "185163 2022-01-10 0.274 厦门钨业 2021-12-31 600549.XSHG 21.50 \n",
671
+ "185162 2022-01-10 0.116 山煤国际 2021-12-31 600546.XSHG 6.74 \n",
672
+ "185161 2022-01-10 0.211 中国软件 2021-12-31 600536.XSHG 29.01 \n",
673
+ "185160 2022-01-10 0.205 天士力 2021-12-31 600535.XSHG 15.04 \n",
674
+ "185159 2022-01-10 0.297 山东药玻 2021-12-31 600529.XSHG 42.39 \n",
675
+ "185158 2022-01-10 0.156 中铁工业 2021-12-31 600528.XSHG 8.69 \n",
676
+ "185157 2022-01-10 0.642 中天科技 2021-12-31 600522.XSHG 15.21 \n",
677
+ "185156 2022-01-10 0.276 华海药业 2021-12-31 600521.XSHG 22.32 \n",
678
+ "185169 2022-01-10 0.240 卧龙电驱 2021-12-31 600580.XSHG 16.66 \n",
679
+ "185499 2022-01-10 0.350 思瑞浦 2021-12-31 688536.XSHG 476.70 \n",
680
+ "\n",
681
+ " close high low volume money display_name_y \\\n",
682
+ "185163 21.59 21.75 21.43 10451128.0 2.254199e+08 厦门钨业 \n",
683
+ "185162 6.86 6.86 6.71 66929559.0 4.539690e+08 山煤国际 \n",
684
+ "185161 30.90 31.45 28.98 35489167.0 1.087404e+09 中国软件 \n",
685
+ "185160 15.52 15.75 14.95 46823950.0 7.247465e+08 天士力 \n",
686
+ "185159 40.14 42.39 39.47 12543641.0 5.049473e+08 山东药玻 \n",
687
+ "185158 9.08 9.26 8.68 98213530.0 8.961637e+08 中铁工业 \n",
688
+ "185157 14.76 15.26 14.53 112744173.0 1.670952e+09 中天科技 \n",
689
+ "185156 23.32 23.43 21.90 29810065.0 6.827298e+08 华海药业 \n",
690
+ "185169 16.51 16.68 16.23 19294606.0 3.175398e+08 卧龙电驱 \n",
691
+ "185499 463.10 476.70 446.03 924992.0 4.254053e+08 思瑞浦 \n",
692
+ "\n",
693
+ " name sector aggregate_sector \n",
694
+ "185163 XMWY 有色金属I 稀有金属II 钨III 有色金属冶炼和压延加工业 钨钼 原材料 原料与能源 \n",
695
+ "185162 SMGJ 煤炭I 煤炭开采II 动力煤III 批发业 煤炭 能源 原料与能源 \n",
696
+ "185161 ZGRJ 计算机I IT服务II IT服务III 软件和信息技术服务业 行业应用软件 信息技术 信息与通信 \n",
697
+ "185160 TSL 医药生物I 中药II 中药III 医药制造业 中成药 医药卫生 医药卫生 \n",
698
+ "185159 SDYB 医药生物I 医疗器械II 医疗耗材III 非金属矿物制品业 医疗耗材 医药卫生 医药卫生 \n",
699
+ "185158 ZTGY 机械设备I 运输设备II 铁路设备III 专用设备制造业 城轨铁路 工业 工业 \n",
700
+ "185157 ZTKJ 通信I 通信设备II 通信线缆及配套III 电气机械和器材制造业 通信系统设备及组件 通信服务 信息与通信 \n",
701
+ "185156 HHYY 医药生物I 化学制药II 化学制剂III 医药制造业 药品制剂 医药卫生 医药卫生 \n",
702
+ "185169 WLDQ 电气设备I 电机II 电机III 电气机械和器材制造业 电动机与工控自动化 工业 工业 \n",
703
+ "185499 SRP 电子I 半导体II 模拟芯片设计III 软件和信息技术服务业 集成电路设计 信息技术 信息与通信 "
704
+ ]
705
+ },
706
+ "execution_count": 28,
707
+ "metadata": {},
708
+ "output_type": "execute_result"
709
+ }
710
+ ],
711
+ "source": [
712
+ "print(error)\n",
713
+ "print(benchmark_portfolio.shape)\n",
714
+ "benchmark_portfolio.sort_values(by=['date'], inplace=True)\n",
715
+ "benchmark_portfolio.tail(10)\n"
716
+ ]
717
+ },
718
+ {
719
+ "cell_type": "code",
720
+ "execution_count": 29,
721
+ "metadata": {},
722
+ "outputs": [],
723
+ "source": [
724
+ "# save\n",
725
+ "benchmark_portfolio.to_pickle('../data/benchmark_portfolio.pkl')"
726
+ ]
727
+ }
728
+ ],
729
+ "metadata": {
730
+ "kernelspec": {
731
+ "display_name": "portfolio_risk_assesment",
732
+ "language": "python",
733
+ "name": "python3"
734
+ },
735
+ "language_info": {
736
+ "codemirror_mode": {
737
+ "name": "ipython",
738
+ "version": 3
739
+ },
740
+ "file_extension": ".py",
741
+ "mimetype": "text/x-python",
742
+ "name": "python",
743
+ "nbconvert_exporter": "python",
744
+ "pygments_lexer": "ipython3",
745
+ "version": "3.11.4"
746
+ },
747
+ "orig_nbformat": 4
748
+ },
749
+ "nbformat": 4,
750
+ "nbformat_minor": 2
751
+ }
script/downloadData.py CHANGED
@@ -1,3 +1,10 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:c09badb2fa652cafccfc5ac41fe1955279bcf476e066e5d7f6eeaf29e2421f8f
3
- size 177
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from api import update_portfolio_profile, get_stocks_price, get_benchmark_portfolio
3
+
4
+ portfolio_profile_data = [
5
+
6
+ ]
7
+
8
+
9
+ def main():
10
+ # create a portfolio
script/pipeline.ipynb CHANGED
@@ -1,3 +1,521 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:20e6c5c9a495ce9720e90e775ced8bf83bfe9ce072faa30cb5984dfa7758c8cb
3
- size 18969
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 36,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "import pandas as pd\n",
10
+ "import math\n",
11
+ "from datetime import datetime\n",
12
+ "import hvplot.pandas\n",
13
+ "import math\n",
14
+ "import numpy as np\n",
15
+ "# load data\n",
16
+ "profile_df = pd.read_pickle('../data/portfolio_portfile.pkl')\n",
17
+ "benchmark_df = pd.read_pickle('../data/benchmark_portfolio.pkl')\n",
18
+ "portfolio_df = pd.read_pickle('../data/portfolio_data.pkl')"
19
+ ]
20
+ },
21
+ {
22
+ "attachments": {},
23
+ "cell_type": "markdown",
24
+ "metadata": {},
25
+ "source": [
26
+ "## This section transfrom benchmark_df and creat an benchmark profile to accomadate current pipeline"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "execution_count": 37,
32
+ "metadata": {},
33
+ "outputs": [],
34
+ "source": [
35
+ "# drop weight\n",
36
+ "# benchmark_df = benchmark_df.drop(columns=['weight'])\n",
37
+ "\n",
38
+ "## simulate update potfolio weigth at 2021-01-10\n",
39
+ "update_profile_df = profile_df.copy()\n",
40
+ "update_profile_df['date'] = datetime(2021,1,10)\n",
41
+ "update_profile_df['weight'] = [50,100,200,300,400,500]\n",
42
+ "profile_df = pd.concat([profile_df, update_profile_df])\n"
43
+ ]
44
+ },
45
+ {
46
+ "cell_type": "code",
47
+ "execution_count": 38,
48
+ "metadata": {},
49
+ "outputs": [],
50
+ "source": [
51
+ "## create a profile for benchmark\n",
52
+ "b_profile = benchmark_df.drop_duplicates(subset=['ticker', 'actual_data'])\n",
53
+ "# df_unique[df_unique.ticker == \"000008.XSHE\"]\n",
54
+ "# only keep ticker\tdate\tweight\tdisplay_name\tname\tsector\taggregate_sector column\n",
55
+ "b_profile = b_profile[['ticker','date','weight','name','sector','aggregate_sector','display_name_x']]\n",
56
+ "b_profile.rename(columns={'display_name_x': 'display_name'}, inplace=True)"
57
+ ]
58
+ },
59
+ {
60
+ "cell_type": "code",
61
+ "execution_count": 39,
62
+ "metadata": {},
63
+ "outputs": [
64
+ {
65
+ "data": {
66
+ "text/html": [
67
+ "<div>\n",
68
+ "<style scoped>\n",
69
+ " .dataframe tbody tr th:only-of-type {\n",
70
+ " vertical-align: middle;\n",
71
+ " }\n",
72
+ "\n",
73
+ " .dataframe tbody tr th {\n",
74
+ " vertical-align: top;\n",
75
+ " }\n",
76
+ "\n",
77
+ " .dataframe thead th {\n",
78
+ " text-align: right;\n",
79
+ " }\n",
80
+ "</style>\n",
81
+ "<table border=\"1\" class=\"dataframe\">\n",
82
+ " <thead>\n",
83
+ " <tr style=\"text-align: right;\">\n",
84
+ " <th></th>\n",
85
+ " <th>date</th>\n",
86
+ " <th>ticker</th>\n",
87
+ " <th>open</th>\n",
88
+ " <th>close</th>\n",
89
+ " <th>high</th>\n",
90
+ " <th>low</th>\n",
91
+ " <th>volume</th>\n",
92
+ " <th>money</th>\n",
93
+ " </tr>\n",
94
+ " </thead>\n",
95
+ " <tbody>\n",
96
+ " <tr>\n",
97
+ " <th>0</th>\n",
98
+ " <td>2021-01-05</td>\n",
99
+ " <td>000008.XSHE</td>\n",
100
+ " <td>2.52</td>\n",
101
+ " <td>2.57</td>\n",
102
+ " <td>2.67</td>\n",
103
+ " <td>2.49</td>\n",
104
+ " <td>33215803.0</td>\n",
105
+ " <td>85358605.99</td>\n",
106
+ " </tr>\n",
107
+ " </tbody>\n",
108
+ "</table>\n",
109
+ "</div>"
110
+ ],
111
+ "text/plain": [
112
+ " date ticker open close high low volume money\n",
113
+ "0 2021-01-05 000008.XSHE 2.52 2.57 2.67 2.49 33215803.0 85358605.99"
114
+ ]
115
+ },
116
+ "execution_count": 39,
117
+ "metadata": {},
118
+ "output_type": "execute_result"
119
+ }
120
+ ],
121
+ "source": [
122
+ "# drop weight in benchmark\n",
123
+ "benchmark_df = benchmark_df.drop(columns=['weight'])\n",
124
+ "benchmark_df = benchmark_df.drop(columns=['display_name_x'])\n",
125
+ "# drop display_name_y\n",
126
+ "benchmark_df = benchmark_df.drop(columns=['display_name_y'])\n",
127
+ "# drop actual_data\n",
128
+ "benchmark_df = benchmark_df.drop(columns=['actual_data'])\n",
129
+ "# drop name\n",
130
+ "benchmark_df = benchmark_df.drop(columns=['name'])\n",
131
+ "# drop aggregate_sector\n",
132
+ "benchmark_df = benchmark_df.drop(columns=['aggregate_sector'])\n",
133
+ "# drop sector\n",
134
+ "benchmark_df = benchmark_df.drop(columns=['sector'])\n",
135
+ "benchmark_df.head(1)"
136
+ ]
137
+ },
138
+ {
139
+ "cell_type": "code",
140
+ "execution_count": 40,
141
+ "metadata": {},
142
+ "outputs": [],
143
+ "source": [
144
+ "for col_name in benchmark_df.columns:\n",
145
+ " if (col_name not in portfolio_df.columns):\n",
146
+ " print(f'portfolio does not have {col_name}')\n",
147
+ " \n",
148
+ "\n",
149
+ "for col_name in portfolio_df.columns:\n",
150
+ " if (col_name not in benchmark_df.columns):\n",
151
+ " print(f'benchmark does not have {col_name}')"
152
+ ]
153
+ },
154
+ {
155
+ "cell_type": "code",
156
+ "execution_count": 41,
157
+ "metadata": {},
158
+ "outputs": [
159
+ {
160
+ "name": "stdout",
161
+ "output_type": "stream",
162
+ "text": [
163
+ "True\n",
164
+ "True\n",
165
+ "True\n",
166
+ "True\n",
167
+ "True\n",
168
+ "True\n",
169
+ "True\n",
170
+ "True\n",
171
+ "True\n",
172
+ "True\n",
173
+ "True\n",
174
+ "True\n",
175
+ "True\n",
176
+ "True\n"
177
+ ]
178
+ }
179
+ ],
180
+ "source": [
181
+ "for col_name in b_profile.columns:\n",
182
+ " print(col_name in profile_df.columns)\n",
183
+ "\n",
184
+ "for col_name in profile_df.columns:\n",
185
+ " print(col_name in b_profile.columns)"
186
+ ]
187
+ },
188
+ {
189
+ "attachments": {},
190
+ "cell_type": "markdown",
191
+ "metadata": {},
192
+ "source": [
193
+ "## calculate result for each individual stock this part should return a table"
194
+ ]
195
+ },
196
+ {
197
+ "cell_type": "code",
198
+ "execution_count": 42,
199
+ "metadata": {},
200
+ "outputs": [],
201
+ "source": [
202
+ "def get_processing_result_of_stocks_df(stock_df, profile_df):\n",
203
+ " ## add sector_name display_name name\n",
204
+ " ticker_sector_map = dict(zip(profile_df['ticker'], profile_df['aggregate_sector']))\n",
205
+ " ticker_display_name_map = dict(zip(profile_df['ticker'], profile_df['display_name']))\n",
206
+ " ticker_name_map = dict(zip(profile_df['ticker'], profile_df['name']))\n",
207
+ "\n",
208
+ " stock_df['display_name'] = stock_df['ticker'].map(ticker_display_name_map)\n",
209
+ " stock_df['name'] = stock_df['ticker'].map(ticker_name_map)\n",
210
+ " stock_df['aggregate_sector'] = stock_df['ticker'].map(ticker_sector_map)\n",
211
+ "\n",
212
+ " ## calculate pct using closing price\n",
213
+ " stock_df.sort_values(by=['date'], inplace=True)\n",
214
+ " stock_df['pct'] = stock_df.groupby('ticker')['close'].pct_change()\n",
215
+ "\n",
216
+ " ## calculate weight TODO: think about how to optimize this\n",
217
+ " stock_df = stock_df.merge(profile_df[['weight', 'date', 'ticker']], on=['ticker', 'date'], how='outer')\n",
218
+ " stock_df.rename(columns={'weight': 'initial_weight'}, inplace=True)\n",
219
+ " stock_df['current_weight'] = float('nan')\n",
220
+ " stock_df['previous_weight'] = float('nan')\n",
221
+ " df_grouped = stock_df.groupby('ticker')\n",
222
+ " for _, group in df_grouped:\n",
223
+ " pre_w = float('nan')\n",
224
+ " ini_w = float('nan')\n",
225
+ " for index, row in group.iterrows():\n",
226
+ " cur_w = float('nan')\n",
227
+ "\n",
228
+ " # if has initial weight, the following row all use this initial weight\n",
229
+ " if not pd.isna(row['initial_weight']):\n",
230
+ " ini_w = row['initial_weight']\n",
231
+ " cur_w = ini_w\n",
232
+ "\n",
233
+ " # just calculate current weight based on previous weight\n",
234
+ " else:\n",
235
+ " cur_w = pre_w * (1 + row['pct'])\n",
236
+ "\n",
237
+ " stock_df.loc[index, 'current_weight'] = cur_w \n",
238
+ " stock_df.loc[index, 'previous_weight'] = pre_w\n",
239
+ " stock_df.loc[index, 'initial_weight'] = ini_w\n",
240
+ " pre_w = cur_w\n",
241
+ "\n",
242
+ " stock_df.rename(columns={'weight': 'initial_weight'}, inplace=True)\n",
243
+ " stock_df.dropna(subset=['close'], inplace=True)\n",
244
+ "\n",
245
+ " ## normalize weight\n",
246
+ " stock_df['prev_w_in_p'] = stock_df['previous_weight'] / \\\n",
247
+ " stock_df.groupby('date')['previous_weight'].transform('sum')\n",
248
+ "\n",
249
+ " stock_df['ini_w_in_p'] = stock_df['initial_weight'] / \\\n",
250
+ " stock_df.groupby('date')['initial_weight'].transform('sum')\n",
251
+ "\n",
252
+ " ## calculate weighted pct in portfolio\n",
253
+ " stock_df['portfolio_pct'] = stock_df['pct'] * stock_df['prev_w_in_p']\n",
254
+ "\n",
255
+ " ## calculate weight in sector TODO: remove\n",
256
+ " stock_df['prev_w_in_sectore'] = stock_df['previous_weight'] / \\\n",
257
+ " stock_df.groupby(['date', 'aggregate_sector'])['previous_weight'].transform('sum')\n",
258
+ " stock_df['ini_w_in_sector'] = stock_df['initial_weight'] / \\\n",
259
+ " stock_df.groupby(['date', 'aggregate_sector'])['initial_weight'].transform('sum')\n",
260
+ " ## weighted pct in sector TODO: remove\n",
261
+ " stock_df['sector_pct'] = stock_df['pct'] * stock_df['prev_w_in_sectore']\n",
262
+ "\n",
263
+ " ## portfolio return\n",
264
+ " stock_df['cum_p_pct'] = stock_df.groupby('ticker')['portfolio_pct'].cumsum()\n",
265
+ " stock_df['portfolio_return'] = np.exp(stock_df['cum_p_pct']) -1 \n",
266
+ " # drop intermediate columns\n",
267
+ " stock_df = stock_df.drop(columns=['cum_p_pct'])\n",
268
+ "\n",
269
+ "\n",
270
+ " ## sector return TODO:remove \n",
271
+ " # stock_df['sector_return'] = stock_df['ini_w_in_sector'] * stock_df['return']\n",
272
+ "\n",
273
+ " return stock_df\n"
274
+ ]
275
+ },
276
+ {
277
+ "cell_type": "code",
278
+ "execution_count": 43,
279
+ "metadata": {},
280
+ "outputs": [],
281
+ "source": [
282
+ "# portfolio_stock = get_processing_result_of_stocks_df(portfolio_df, profile_df)\n",
283
+ "# portfolio_stock[portfolio_stock.ticker == '002709.XSHE'][['date','portfolio_pct','prev_w_in_p','portfolio_return']]\n",
284
+ "# # benchmark_stock = get_processing_result_of_stocks_df(benchmark_df, b_profile)"
285
+ ]
286
+ },
287
+ {
288
+ "cell_type": "code",
289
+ "execution_count": 44,
290
+ "metadata": {},
291
+ "outputs": [],
292
+ "source": [
293
+ "# profile_df.groupby('date')['weight'].sum()"
294
+ ]
295
+ },
296
+ {
297
+ "cell_type": "code",
298
+ "execution_count": 45,
299
+ "metadata": {},
300
+ "outputs": [],
301
+ "source": [
302
+ "## total return by date\n",
303
+ "def get_portfolio_evaluation(portfolio_stock, benchmark_stock, profile_df):\n",
304
+ " # add pct of benchmark \n",
305
+ " merged_df = portfolio_stock.merge(benchmark_stock[['ticker','date','portfolio_pct','portfolio_return']], \n",
306
+ " on=['ticker','date'],how='left',suffixes=('_p','_b'))\n",
307
+ "\n",
308
+ " # sum up pct and return from portfolio and benchmark\n",
309
+ " merged_df = merged_df.groupby('date',as_index=False).agg({'portfolio_return_p':'sum',\n",
310
+ " 'portfolio_return_b':'sum',\n",
311
+ " 'portfolio_pct_p':'sum',\n",
312
+ " 'portfolio_pct_b':'sum'})\n",
313
+ "\n",
314
+ " # portfolio mkt cap\n",
315
+ " mkt_adjustment = pd.DataFrame(profile_df.groupby('date')['weight'].sum())\n",
316
+ " mkt_adjustment.rename(columns={'weight':'mkt_cap'}, inplace=True)\n",
317
+ " merged_df = merged_df.merge(mkt_adjustment, on=['date'], how='outer')\n",
318
+ "\n",
319
+ " for i in range(len(merged_df)):\n",
320
+ " if pd.isna(merged_df.loc[i, 'mkt_cap']):\n",
321
+ " merged_df.loc[i, 'mkt_cap'] = merged_df.loc[i-1, 'mkt_cap'] * (1 + merged_df.loc[i, 'portfolio_pct_p'])\n",
322
+ " # drop where portfolio_return_p is nan\n",
323
+ " merged_df.dropna(subset=['portfolio_return_p'], inplace=True)\n",
324
+ " # portfolio pnl TODO seem I can just use current wegith to do this\n",
325
+ " merged_df['prev_mkt_cap'] = merged_df['mkt_cap'].shift(1)\n",
326
+ " merged_df['pnl'] = merged_df['prev_mkt_cap'] * merged_df['portfolio_pct_p']\n",
327
+ "\n",
328
+ " # risk std(pct)\n",
329
+ " merged_df['risk'] = merged_df['portfolio_pct_p'].rolling(len(merged_df), min_periods=1).std() * math.sqrt(252)\n",
330
+ "\n",
331
+ " # active return\n",
332
+ " merged_df['active_return'] = merged_df['portfolio_pct_p'] - merged_df['portfolio_pct_b']\n",
333
+ "\n",
334
+ " # tracking errro std(active return)\n",
335
+ " merged_df['tracking_error'] = merged_df['active_return'].rolling(len(merged_df), min_periods=1).std() * math.sqrt(252)\n",
336
+ "\n",
337
+ " # cum pnl\n",
338
+ " merged_df['cum_pnl'] = merged_df['pnl'].cumsum()\n",
339
+ "\n",
340
+ " return merged_df\n"
341
+ ]
342
+ },
343
+ {
344
+ "cell_type": "code",
345
+ "execution_count": 46,
346
+ "metadata": {},
347
+ "outputs": [],
348
+ "source": [
349
+ "# portfolio_eval_df = get_portfolio_evaluation(portfolio_stock, benchmark_stock, profile_df)"
350
+ ]
351
+ },
352
+ {
353
+ "cell_type": "code",
354
+ "execution_count": 47,
355
+ "metadata": {},
356
+ "outputs": [],
357
+ "source": [
358
+ "# portfolio_stock.columns"
359
+ ]
360
+ },
361
+ {
362
+ "cell_type": "code",
363
+ "execution_count": 48,
364
+ "metadata": {},
365
+ "outputs": [],
366
+ "source": [
367
+ "## TODO convert below to funciton\n",
368
+ "\n",
369
+ "def get_portfolio_sector_evaluation(portfolio_stock,benchmark_df):\n",
370
+ "# aggregate on sector and day\n",
371
+ " p_sector_df = portfolio_stock.groupby(['date','aggregate_sector'], as_index=False)\\\n",
372
+ " .agg({'prev_w_in_p': 'sum','ini_w_in_p':\"sum\",\"current_weight\":'sum',\\\n",
373
+ " \"portfolio_pct\":\"sum\", \"portfolio_return\":\"sum\"})\n",
374
+ " # TODO shrink it down before aggregate\n",
375
+ " b_sector_df = benchmark_df.groupby(['date','aggregate_sector'], as_index=False)\\\n",
376
+ " .agg({'prev_w_in_p': 'sum','ini_w_in_p':\"sum\",\"current_weight\":'sum',\\\n",
377
+ " \"portfolio_pct\":\"sum\", \"portfolio_return\":\"sum\"})\n",
378
+ " \n",
379
+ " # merge portfolio and benchmark\n",
380
+ " merge_df = p_sector_df.merge(b_sector_df, on=['date','aggregate_sector'], how='left', suffixes=('_p','_b'))\n",
381
+ "\n",
382
+ " return merge_df"
383
+ ]
384
+ },
385
+ {
386
+ "cell_type": "code",
387
+ "execution_count": 49,
388
+ "metadata": {},
389
+ "outputs": [],
390
+ "source": [
391
+ "# sector_eval_df = get_portfolio_sector_evaluation(portfolio_stock, benchmark_stock)\n",
392
+ "# sector_eval_df[sector_eval_df.date == datetime(2021, 10,13)].hvplot.bar(x='aggregate_sector', y=['portfolio_pct_p','portfolio_pct_b'], stacked=True, rot=90, title='sector pct')"
393
+ ]
394
+ },
395
+ {
396
+ "cell_type": "code",
397
+ "execution_count": 50,
398
+ "metadata": {},
399
+ "outputs": [],
400
+ "source": [
401
+ "def merge_on_date(portfolio_stock, benchmark_df):\n",
402
+ " p_selected = portfolio_stock.reset_index()[['ini_w_in_p', 'portfolio_return', 'date', 'ticker', 'display_name']]\n",
403
+ " b_selected = benchmark_df.reset_index()[['ini_w_in_p', 'portfolio_return', 'date', 'ticker']]\n",
404
+ " merged_stock_df = pd.merge(p_selected, b_selected, on=['date', 'ticker'], how='outer', suffixes=('_p', '_b'))\n",
405
+ " return merged_stock_df"
406
+ ]
407
+ },
408
+ {
409
+ "cell_type": "code",
410
+ "execution_count": 51,
411
+ "metadata": {},
412
+ "outputs": [],
413
+ "source": [
414
+ "# merged_df = merge_on_date(portfolio_stock, benchmark_stock)"
415
+ ]
416
+ },
417
+ {
418
+ "cell_type": "code",
419
+ "execution_count": 52,
420
+ "metadata": {},
421
+ "outputs": [],
422
+ "source": [
423
+ "def get_bhb_result(merged_stock_df):\n",
424
+ " # merged_stock_df['ini_w_in_p_p'].fillna(0, inplace=True)\n",
425
+ " # merged_stock_df['ini_w_in_p_b'].fillna(0, inplace=True)\n",
426
+ " # merged_stock_df['portfolio_return_b'].fillna(0, inplace=True)\n",
427
+ " # merged_stock_df['portfolio_return_p'].fillna(0, inplace=True)\n",
428
+ " # allocation\n",
429
+ " merged_stock_df['allocation'] = (merged_stock_df['ini_w_in_p_p'] - merged_stock_df['ini_w_in_p_b']) \\\n",
430
+ " * merged_stock_df['portfolio_return_b']\n",
431
+ "\n",
432
+ " # selection\n",
433
+ " merged_stock_df['selection'] = merged_stock_df['ini_w_in_p_b'] * \\\n",
434
+ " (merged_stock_df['portfolio_return_p'] - merged_stock_df['portfolio_return_b'])\n",
435
+ "\n",
436
+ " # interaction\n",
437
+ " merged_stock_df['interaction'] = (merged_stock_df['ini_w_in_p_p'] - merged_stock_df['ini_w_in_p_b']) * \\\n",
438
+ " (merged_stock_df['portfolio_return_p'] - merged_stock_df['portfolio_return_b'])\n",
439
+ "\n",
440
+ " # excess\n",
441
+ " merged_stock_df['excess'] = merged_stock_df['portfolio_return_p'] - merged_stock_df['portfolio_return_b']\n",
442
+ "\n",
443
+ " # replace inf with nan\n",
444
+ " merged_stock_df.replace([np.inf, -np.inf], np.nan, inplace=True)\n",
445
+ " return merged_stock_df"
446
+ ]
447
+ },
448
+ {
449
+ "cell_type": "code",
450
+ "execution_count": 53,
451
+ "metadata": {},
452
+ "outputs": [],
453
+ "source": [
454
+ "# test ing pipeline here "
455
+ ]
456
+ },
457
+ {
458
+ "cell_type": "code",
459
+ "execution_count": 54,
460
+ "metadata": {},
461
+ "outputs": [],
462
+ "source": [
463
+ "portfolio_stock = get_processing_result_of_stocks_df(portfolio_df, profile_df)\n",
464
+ "benchmark_stock = get_processing_result_of_stocks_df(benchmark_df, b_profile)\n",
465
+ "\n",
466
+ "portfolio_eval_df = get_portfolio_evaluation(portfolio_stock, benchmark_stock, profile_df)\n",
467
+ "sector_eval_df = get_portfolio_sector_evaluation(portfolio_stock, benchmark_stock)\n",
468
+ "merged_df = merge_on_date(portfolio_stock, benchmark_stock)\n",
469
+ "bnb_sector_result = get_bhb_result(sector_eval_df)\n",
470
+ "bnb_stock_result = get_bhb_result(merged_df)\n"
471
+ ]
472
+ },
473
+ {
474
+ "cell_type": "code",
475
+ "execution_count": null,
476
+ "metadata": {},
477
+ "outputs": [],
478
+ "source": [
479
+ "# save result \n",
480
+ "portfolio_eval_df.to_pickle('../data/portfolio_eval_df.pkl')\n",
481
+ "sector_eval_df.to_pickle('../data/sector_eval_df.pkl')\n",
482
+ "# merged_df.to_csv('merged_df.csv')\n",
483
+ "bnb_sector_result.to_pickle('../data/bnb_sector_result.pkl')\n",
484
+ "bnb_stock_result.to_pickle('../data/bnb_stock_result.pkl')\n",
485
+ "profile_df.to_pickle('../data/protfolio_profile.pkl')\n",
486
+ "b_profile.to_pickle('../data/benchmark_profile.pkl')"
487
+ ]
488
+ },
489
+ {
490
+ "cell_type": "code",
491
+ "execution_count": null,
492
+ "metadata": {},
493
+ "outputs": [],
494
+ "source": [
495
+ "# bnb_sector_result[bnb_sector_result.date == datetime(2021, 10,13)].hvplot.bar(x='aggregate_sector', y=['allocation','selection','interaction'], stacked=True, rot=90, title='sector allocation')"
496
+ ]
497
+ }
498
+ ],
499
+ "metadata": {
500
+ "kernelspec": {
501
+ "display_name": "portfolio_risk_assesment",
502
+ "language": "python",
503
+ "name": "python3"
504
+ },
505
+ "language_info": {
506
+ "codemirror_mode": {
507
+ "name": "ipython",
508
+ "version": 3
509
+ },
510
+ "file_extension": ".py",
511
+ "mimetype": "text/x-python",
512
+ "name": "python",
513
+ "nbconvert_exporter": "python",
514
+ "pygments_lexer": "ipython3",
515
+ "version": "3.11.4"
516
+ },
517
+ "orig_nbformat": 4
518
+ },
519
+ "nbformat": 4,
520
+ "nbformat_minor": 2
521
+ }
script/processing.ipynb CHANGED
@@ -1,3 +1,501 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:8a5fbb45900fe3b82e3ebf11e97ec3808f865f816a46782063de9d0c08eaf038
3
- size 38406
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "data": {
10
+ "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n var py_version = '3.1.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n var reloading = false;\n var Bokeh = root.Bokeh;\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n run_callbacks();\n return null;\n }\n if (!reloading) {\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n var skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n require([\"jspanel\"], function(jsPanel) {\n\twindow.jsPanel = jsPanel\n\ton_load()\n })\n require([\"jspanel-modal\"], function() {\n\ton_load()\n })\n require([\"jspanel-tooltip\"], function() {\n\ton_load()\n })\n require([\"jspanel-hint\"], function() {\n\ton_load()\n })\n require([\"jspanel-layout\"], function() {\n\ton_load()\n })\n require([\"jspanel-contextmenu\"], function() {\n\ton_load()\n })\n require([\"jspanel-dock\"], function() {\n\ton_load()\n })\n require([\"gridstack\"], function(GridStack) {\n\twindow.GridStack = GridStack\n\ton_load()\n })\n require([\"notyf\"], function() {\n\ton_load()\n })\n root._bokeh_is_loading = css_urls.length + 9;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n var existing_stylesheets = []\n var links = document.getElementsByTagName('link')\n for (var i = 0; i < links.length; i++) {\n var link = links[i]\n if (link.href != null) {\n\texisting_stylesheets.push(link.href)\n }\n }\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n if (existing_stylesheets.indexOf(url) !== -1) {\n\ton_load()\n\tcontinue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } var existing_scripts = []\n var scripts = document.getElementsByTagName('script')\n for (var i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n\texisting_scripts.push(script.src)\n }\n }\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (var i = 0; i < js_modules.length; i++) {\n var url = js_modules[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n var url = js_exports[name];\n if (skip.indexOf(url) >= 0 || root[name] != null) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js\", \"https://cdn.holoviz.org/panel/1.1.1/dist/panel.min.js\"];\n var js_modules = [];\n var js_exports = {};\n var css_urls = [];\n var inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n\tvar NewBokeh = root.Bokeh;\n\tif (Bokeh.versions === undefined) {\n\t Bokeh.versions = new Map();\n\t}\n\tif (NewBokeh.version !== Bokeh.version) {\n\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n\t}\n\troot.Bokeh = Bokeh;\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n Bokeh = root.Bokeh;\n bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n if (!reloading && (!bokeh_loaded || is_dev)) {\n\troot.Bokeh = undefined;\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n\trun_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));",
11
+ "application/vnd.holoviews_load.v0+json": ""
12
+ },
13
+ "metadata": {},
14
+ "output_type": "display_data"
15
+ },
16
+ {
17
+ "data": {
18
+ "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n",
19
+ "application/vnd.holoviews_load.v0+json": ""
20
+ },
21
+ "metadata": {},
22
+ "output_type": "display_data"
23
+ },
24
+ {
25
+ "data": {
26
+ "text/html": [
27
+ "<style>*[data-root-id],\n",
28
+ "*[data-root-id] > * {\n",
29
+ " box-sizing: border-box;\n",
30
+ " font-family: var(--jp-ui-font-family);\n",
31
+ " font-size: var(--jp-ui-font-size1);\n",
32
+ " color: var(--vscode-editor-foreground, var(--jp-ui-font-color1));\n",
33
+ "}\n",
34
+ "\n",
35
+ "/* Override VSCode background color */\n",
36
+ ".cell-output-ipywidget-background:has(> .cell-output-ipywidget-background\n",
37
+ " > .lm-Widget\n",
38
+ " > *[data-root-id]),\n",
39
+ ".cell-output-ipywidget-background:has(> .lm-Widget > *[data-root-id]) {\n",
40
+ " background-color: transparent !important;\n",
41
+ "}\n",
42
+ "</style>"
43
+ ]
44
+ },
45
+ "metadata": {},
46
+ "output_type": "display_data"
47
+ }
48
+ ],
49
+ "source": [
50
+ "import pandas as pd\n",
51
+ "import math\n",
52
+ "from datetime import datetime\n",
53
+ "import hvplot.pandas\n",
54
+ "# load data\n",
55
+ "profile_df = pd.read_pickle('../data/portfolio_portfile.pkl')\n",
56
+ "benchmark_df = pd.read_pickle('../data/benchmark_portfolio.pkl')\n",
57
+ "portfolio_df = pd.read_pickle('../data/portfolio_data.pkl')"
58
+ ]
59
+ },
60
+ {
61
+ "cell_type": "code",
62
+ "execution_count": 2,
63
+ "metadata": {},
64
+ "outputs": [],
65
+ "source": [
66
+ "## calculate pct using closing price for each date\n",
67
+ "portfolio_df.sort_values(by=['date'], inplace=True)\n",
68
+ "portfolio_df['pct'] = portfolio_df.groupby(['ticker'])['close'].pct_change()\n",
69
+ "\n",
70
+ "benchmark_df.sort_values(by=['date'], inplace=True)\n",
71
+ "benchmark_df['pct'] = benchmark_df.groupby(['ticker'])['close'].pct_change()\n"
72
+ ]
73
+ },
74
+ {
75
+ "cell_type": "code",
76
+ "execution_count": 3,
77
+ "metadata": {},
78
+ "outputs": [],
79
+ "source": [
80
+ "## return and weight\n",
81
+ "merge_df = portfolio_df.merge(profile_df[['ticker','weight','date']], on=['ticker','date'], how='left')\n",
82
+ "merge_df.sort_values(by=['date'], inplace=True)\n",
83
+ "benchmark_df.sort_values(by=['date'], inplace=True)\n",
84
+ "\n",
85
+ "# return for each stock\n",
86
+ "# portfolio_df['return'] = portfolio_df['close'] / portfolio_df.groupby(['ticker'])['close'].transform('first') - 1\n",
87
+ "# benchmark_df['return'] = benchmark_df['close'] / benchmark_df.groupby(['ticker'])['close'].transform('first') - 1\n",
88
+ "\n",
89
+ "# stock return in portfolio\n",
90
+ "first_close_per_ticker = merge_df.groupby('ticker')['close'].transform('first')\n",
91
+ "merge_df['return'] = merge_df['close'] / first_close_per_ticker - 1\n",
92
+ "\n",
93
+ "# stock return in benchmark\n",
94
+ "first_close_per_ticker = benchmark_df.groupby('ticker')['close'].transform('first')\n",
95
+ "benchmark_df['return'] = benchmark_df['close'] / first_close_per_ticker - 1\n",
96
+ "\n",
97
+ "# weight\n",
98
+ "initial_weight = merge_df.groupby('ticker')['weight'].transform('first') \n",
99
+ "merge_df['weight'] = initial_weight + (merge_df['return'] * initial_weight)\n",
100
+ "\n",
101
+ "# initial_weight\n",
102
+ "# merge_df[merge_df.ticker==\"002709.XSHE\"]\n",
103
+ "# merge_df\n",
104
+ "# benchmark_df"
105
+ ]
106
+ },
107
+ {
108
+ "cell_type": "code",
109
+ "execution_count": 4,
110
+ "metadata": {},
111
+ "outputs": [],
112
+ "source": [
113
+ "portfolio_df = merge_df"
114
+ ]
115
+ },
116
+ {
117
+ "cell_type": "code",
118
+ "execution_count": 5,
119
+ "metadata": {},
120
+ "outputs": [],
121
+ "source": [
122
+ "## normalize weight\n",
123
+ "benchmark_df['norm_weight'] = benchmark_df['weight'] / benchmark_df.groupby(['date'])['weight'].transform('sum')\n",
124
+ "portfolio_df['norm_weight'] = portfolio_df['weight'] / portfolio_df.groupby(['date'])['weight'].transform('sum')"
125
+ ]
126
+ },
127
+ {
128
+ "cell_type": "code",
129
+ "execution_count": 6,
130
+ "metadata": {},
131
+ "outputs": [],
132
+ "source": [
133
+ "## calculate weighted return\n",
134
+ "portfolio_df['weighted_pct'] = portfolio_df['pct'] * portfolio_df['norm_weight']\n",
135
+ "benchmark_df['weighted_pct'] = benchmark_df['pct'] * benchmark_df['norm_weight']\n"
136
+ ]
137
+ },
138
+ {
139
+ "cell_type": "code",
140
+ "execution_count": 7,
141
+ "metadata": {},
142
+ "outputs": [],
143
+ "source": [
144
+ "## Calculate portfolio pct of return by summing the 'weighted_return' for each date\n",
145
+ "\n",
146
+ "# reset index becaue its a new table\n",
147
+ "portfolio_pct = portfolio_df.groupby('date')['weighted_pct'].sum().reset_index()\n",
148
+ "portfolio_pct.columns = ['date', 'pct']\n",
149
+ "\n",
150
+ "# benchmark pct summing the 'weighted_return' for each date\n",
151
+ "#TODO chagne to use new weight in order to perform simulation\n",
152
+ "benchmark_pct = benchmark_df.groupby('date')['weighted_pct'].sum().reset_index()\n",
153
+ "benchmark_pct.columns = ['date', 'pct']\n",
154
+ "\n",
155
+ "# 'date', 'portfolio_return', and 'benchmark_return' columns\n",
156
+ "return_result = pd.merge(portfolio_pct, benchmark_pct, on='date', suffixes=('_portfolio', '_benchmark'))"
157
+ ]
158
+ },
159
+ {
160
+ "cell_type": "code",
161
+ "execution_count": 8,
162
+ "metadata": {},
163
+ "outputs": [],
164
+ "source": [
165
+ "## add total return\n",
166
+ "portfolio_df['weighted_return'] = portfolio_df['return'] * portfolio_df['norm_weight']\n",
167
+ "portfolio_return = portfolio_df.groupby('date')['weighted_return'].sum().reset_index()\n",
168
+ "portfolio_return.columns = ['date', 'return']\n",
169
+ "return_result = return_result.merge(portfolio_return, on='date')"
170
+ ]
171
+ },
172
+ {
173
+ "cell_type": "code",
174
+ "execution_count": 9,
175
+ "metadata": {},
176
+ "outputs": [],
177
+ "source": [
178
+ "# daily_active_return\n",
179
+ "return_result['daily_active_return'] = return_result['pct_portfolio'] - return_result['pct_benchmark']"
180
+ ]
181
+ },
182
+ {
183
+ "cell_type": "code",
184
+ "execution_count": 10,
185
+ "metadata": {},
186
+ "outputs": [],
187
+ "source": [
188
+ "# daily pnl\n",
189
+ "return_result['mkt_cap'] = None\n",
190
+ "\n",
191
+ "# assign initial market cap\n",
192
+ "# sum weight because it is the money invested in the stock\n",
193
+ "# TODO chagne here to add simulation functionality\n",
194
+ "return_result.loc[return_result['date'] == profile_df['date'].values[0],'mkt_cap'] = profile_df['weight'].sum()\n",
195
+ "return_result.sort_values(by=['date'], inplace=True)\n",
196
+ "\n",
197
+ "# calculate daily mkt_cap\n",
198
+ "for i in range(1, len(return_result)):\n",
199
+ " return_result.loc[i, 'mkt_cap'] = return_result.loc[i-1, 'mkt_cap'] * (1 + return_result.loc[i, 'pct_portfolio'])\n",
200
+ "\n",
201
+ "# calculate daily pnl\n",
202
+ "return_result['daily_pnl'] = return_result['mkt_cap'].diff()\n",
203
+ "\n",
204
+ "\n"
205
+ ]
206
+ },
207
+ {
208
+ "cell_type": "code",
209
+ "execution_count": 11,
210
+ "metadata": {},
211
+ "outputs": [],
212
+ "source": [
213
+ "## return volatilty mean return tracking erro cumulative pnl\n",
214
+ "# volatitly\n",
215
+ "return_result['pct_volitity_p'] = return_result['pct_portfolio'].expanding().std() * math.sqrt(252)\n",
216
+ "return_result['pct_volitity_b'] = return_result['pct_benchmark'].expanding().std() * math.sqrt(252)\n",
217
+ "# mean return\n",
218
+ "return_result['mean_return'] = return_result['return'].mean()\n",
219
+ "# tracking error \n",
220
+ "return_result['tracking_error'] = return_result['daily_active_return'].expanding().std() * math.sqrt(252)\n",
221
+ "# cumulative pnl\n",
222
+ "return_result['cum_pnl'] = return_result['daily_pnl'].cumsum()\n",
223
+ "# drawdown \n",
224
+ "return_result['drawdown'] = (return_result['cum_pnl'] - return_result['cum_pnl'].cummax()) / return_result['cum_pnl'].cummax()"
225
+ ]
226
+ },
227
+ {
228
+ "cell_type": "code",
229
+ "execution_count": 12,
230
+ "metadata": {},
231
+ "outputs": [],
232
+ "source": [
233
+ "## save\n",
234
+ "return_result.to_pickle('../data/risk_return_result.pkl')"
235
+ ]
236
+ },
237
+ {
238
+ "cell_type": "code",
239
+ "execution_count": 13,
240
+ "metadata": {},
241
+ "outputs": [],
242
+ "source": [
243
+ "# add sector info to portfolio_df\n",
244
+ "portfolio_df = pd.merge(portfolio_df, profile_df[['ticker','aggregate_sector']], on='ticker', how='left')"
245
+ ]
246
+ },
247
+ {
248
+ "cell_type": "code",
249
+ "execution_count": 14,
250
+ "metadata": {},
251
+ "outputs": [
252
+ {
253
+ "name": "stderr",
254
+ "output_type": "stream",
255
+ "text": [
256
+ "/var/folders/v5/2108rh5964q9j741wg_s8r1w0000gn/T/ipykernel_1263/1040563745.py:2: SettingWithCopyWarning: \n",
257
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
258
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
259
+ "\n",
260
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
261
+ " benchmark_df[benchmark_df.aggregate_sector.isna()].aggregate_sector = '其他'\n"
262
+ ]
263
+ }
264
+ ],
265
+ "source": [
266
+ "# hanlde sector is nan\n",
267
+ "benchmark_df[benchmark_df.aggregate_sector.isna()].aggregate_sector = '其他'\n",
268
+ "portfolio_df[portfolio_df.aggregate_sector.isna()].aggregate_sector = '其他'"
269
+ ]
270
+ },
271
+ {
272
+ "cell_type": "code",
273
+ "execution_count": 15,
274
+ "metadata": {},
275
+ "outputs": [
276
+ {
277
+ "data": {
278
+ "text/html": [
279
+ "<div>\n",
280
+ "<style scoped>\n",
281
+ " .dataframe tbody tr th:only-of-type {\n",
282
+ " vertical-align: middle;\n",
283
+ " }\n",
284
+ "\n",
285
+ " .dataframe tbody tr th {\n",
286
+ " vertical-align: top;\n",
287
+ " }\n",
288
+ "\n",
289
+ " .dataframe thead th {\n",
290
+ " text-align: right;\n",
291
+ " }\n",
292
+ "</style>\n",
293
+ "<table border=\"1\" class=\"dataframe\">\n",
294
+ " <thead>\n",
295
+ " <tr style=\"text-align: right;\">\n",
296
+ " <th></th>\n",
297
+ " <th>date</th>\n",
298
+ " <th>weight</th>\n",
299
+ " <th>display_name_x</th>\n",
300
+ " <th>actual_data</th>\n",
301
+ " <th>ticker</th>\n",
302
+ " <th>open</th>\n",
303
+ " <th>close</th>\n",
304
+ " <th>high</th>\n",
305
+ " <th>low</th>\n",
306
+ " <th>volume</th>\n",
307
+ " <th>money</th>\n",
308
+ " <th>display_name_y</th>\n",
309
+ " <th>name</th>\n",
310
+ " <th>sector</th>\n",
311
+ " <th>aggregate_sector</th>\n",
312
+ " <th>pct</th>\n",
313
+ " <th>return</th>\n",
314
+ " <th>norm_weight</th>\n",
315
+ " <th>weighted_pct</th>\n",
316
+ " </tr>\n",
317
+ " </thead>\n",
318
+ " <tbody>\n",
319
+ " <tr>\n",
320
+ " <th>0</th>\n",
321
+ " <td>2021-01-05</td>\n",
322
+ " <td>0.088</td>\n",
323
+ " <td>神州高铁</td>\n",
324
+ " <td>2020-12-31</td>\n",
325
+ " <td>000008.XSHE</td>\n",
326
+ " <td>2.52</td>\n",
327
+ " <td>2.57</td>\n",
328
+ " <td>2.67</td>\n",
329
+ " <td>2.49</td>\n",
330
+ " <td>33215803.0</td>\n",
331
+ " <td>85358605.99</td>\n",
332
+ " <td>神州高铁</td>\n",
333
+ " <td>SZGT</td>\n",
334
+ " <td>机械设备I 运输设备II 铁路设备III 铁路、船舶、航空航天和其他运输设备制造业 城轨铁路 工业</td>\n",
335
+ " <td>工业</td>\n",
336
+ " <td>NaN</td>\n",
337
+ " <td>0.0</td>\n",
338
+ " <td>0.00088</td>\n",
339
+ " <td>NaN</td>\n",
340
+ " </tr>\n",
341
+ " </tbody>\n",
342
+ "</table>\n",
343
+ "</div>"
344
+ ],
345
+ "text/plain": [
346
+ " date weight display_name_x actual_data ticker open close \\\n",
347
+ "0 2021-01-05 0.088 神州高铁 2020-12-31 000008.XSHE 2.52 2.57 \n",
348
+ "\n",
349
+ " high low volume money display_name_y name \\\n",
350
+ "0 2.67 2.49 33215803.0 85358605.99 神州高铁 SZGT \n",
351
+ "\n",
352
+ " sector aggregate_sector pct \\\n",
353
+ "0 机械设备I 运输设备II 铁路设备III 铁路、船舶、航空航天和其他运输设备制造业 城轨铁路 工业 工业 NaN \n",
354
+ "\n",
355
+ " return norm_weight weighted_pct \n",
356
+ "0 0.0 0.00088 NaN "
357
+ ]
358
+ },
359
+ "execution_count": 15,
360
+ "metadata": {},
361
+ "output_type": "execute_result"
362
+ }
363
+ ],
364
+ "source": [
365
+ "benchmark_df.head(1)"
366
+ ]
367
+ },
368
+ {
369
+ "cell_type": "code",
370
+ "execution_count": 16,
371
+ "metadata": {},
372
+ "outputs": [],
373
+ "source": [
374
+ "## aggregate as sector \n",
375
+ "\n",
376
+ "# weighted return in a sector, weight in a sector\n",
377
+ "portfolio_df['weight_in_sector'] = portfolio_df['norm_weight']/ portfolio_df.groupby(['aggregate_sector','date'])['weight'].transform('sum')\n",
378
+ "portfolio_df['weighted_s_return'] = portfolio_df['return'] * portfolio_df['weight_in_sector']\n",
379
+ "\n",
380
+ "# return and weight by sector\n",
381
+ "return_by_sector = portfolio_df.groupby(['aggregate_sector','date'])['weighted_s_return'].sum().reset_index()\n",
382
+ "normed_sector_weight = portfolio_df.groupby(['aggregate_sector','date'])['norm_weight'].sum().reset_index()\n",
383
+ "p_sector_result = pd.merge(return_by_sector, normed_sector_weight, on=['aggregate_sector','date'], how='left')\n",
384
+ "p_sector_result.rename(columns={'weighted_s_return':'return'}, inplace=True)\n",
385
+ "\n",
386
+ "# same thing for benchmark\n",
387
+ "benchmark_df['weight_in_sector'] = benchmark_df['norm_weight']/ benchmark_df.groupby(['aggregate_sector','date'])['norm_weight'].transform('sum')\n",
388
+ "benchmark_df['weighted_s_return'] = benchmark_df['return'] * benchmark_df['weight_in_sector']\n",
389
+ "return_by_sector = benchmark_df.groupby(['aggregate_sector','date'])['weighted_s_return'].sum().reset_index()\n",
390
+ "normed_sector_weight = benchmark_df.groupby(['aggregate_sector','date'])['norm_weight'].sum().reset_index()\n",
391
+ "b_sector_result = pd.merge(return_by_sector, normed_sector_weight, on=['aggregate_sector','date'], how='left')\n",
392
+ "b_sector_result.rename(columns={'weighted_s_return':'return'}, inplace=True)\n"
393
+ ]
394
+ },
395
+ {
396
+ "cell_type": "code",
397
+ "execution_count": 17,
398
+ "metadata": {},
399
+ "outputs": [],
400
+ "source": [
401
+ "## selection, allocation, interaction for sector\n",
402
+ "\n",
403
+ "\n",
404
+ "bnb_result_sector = pd.merge(\n",
405
+ " p_sector_result, b_sector_result,\n",
406
+ " on=['date','aggregate_sector',], how='outer', suffixes=('_p', '_b'))\n",
407
+ "\n",
408
+ "# replace nan norm_weight_b with 0\n",
409
+ "bnb_result_sector['norm_weight_b'] = bnb_result_sector['norm_weight_b'].fillna(0)\n",
410
+ "bnb_result_sector['norm_weight_p'] = bnb_result_sector['norm_weight_p'].fillna(0)\n",
411
+ "\n",
412
+ "# active weight\n",
413
+ "bnb_result_sector['active_weight'] = bnb_result_sector['norm_weight_p'] - bnb_result_sector['norm_weight_b']\n",
414
+ "\n",
415
+ "# allocation \n",
416
+ "bnb_result_sector['allocation'] = bnb_result_sector['active_weight'] * bnb_result_sector['return_b']\n",
417
+ "\n",
418
+ "#selection\n",
419
+ "bnb_result_sector['selection'] = bnb_result_sector['norm_weight_p'] * (bnb_result_sector['return_p'] - bnb_result_sector['return_b'])\n",
420
+ "\n",
421
+ "#inetration\n",
422
+ "bnb_result_sector['interaction'] = (bnb_result_sector['norm_weight_p'] - bnb_result_sector['norm_weight_b']) *\\\n",
423
+ " (bnb_result_sector['return_p'] - bnb_result_sector['return_b'])"
424
+ ]
425
+ },
426
+ {
427
+ "cell_type": "code",
428
+ "execution_count": 18,
429
+ "metadata": {},
430
+ "outputs": [],
431
+ "source": [
432
+ "## save result \n",
433
+ "bnb_result_sector.to_pickle('../data/bnb_result_sector.pkl')"
434
+ ]
435
+ },
436
+ {
437
+ "cell_type": "code",
438
+ "execution_count": 19,
439
+ "metadata": {},
440
+ "outputs": [],
441
+ "source": [
442
+ "## selection, allocation, interaction for stocks\n",
443
+ "\n",
444
+ "# I keep weight to exame the stock not in benchmark but in portfolio and vice versa\n",
445
+ "bnb_result = pd.merge(\n",
446
+ " portfolio_df[['date','ticker','norm_weight','weight','return','aggregate_sector']],\n",
447
+ " benchmark_df[['date','ticker','norm_weight','weight','return']], on=['date','ticker',], how='outer', suffixes=('_p', '_b'))\n",
448
+ "\n",
449
+ "# replace nan norm_weight_b with 0\n",
450
+ "bnb_result['norm_weight_b'] = bnb_result['norm_weight_b'].fillna(0)\n",
451
+ "bnb_result['norm_weight_p'] = bnb_result['norm_weight_p'].fillna(0)\n",
452
+ "\n",
453
+ "# active weight\n",
454
+ "bnb_result['active_weight'] = bnb_result['norm_weight_p'] - bnb_result['norm_weight_b']\n",
455
+ "\n",
456
+ "# allocation \n",
457
+ "bnb_result['allocation'] = bnb_result['active_weight'] * bnb_result['return_b']\n",
458
+ "\n",
459
+ "#selection\n",
460
+ "bnb_result['selection'] = bnb_result['norm_weight_p'] * (bnb_result['return_p'] - bnb_result['return_b'])\n",
461
+ "\n",
462
+ "#inetration\n",
463
+ "bnb_result['interaction'] = (bnb_result['norm_weight_p'] - bnb_result['norm_weight_b']) *\\\n",
464
+ " (bnb_result['return_p'] - bnb_result['return_b'])\n",
465
+ "\n"
466
+ ]
467
+ },
468
+ {
469
+ "cell_type": "code",
470
+ "execution_count": 20,
471
+ "metadata": {},
472
+ "outputs": [],
473
+ "source": [
474
+ "## svae\n",
475
+ "bnb_result.to_pickle('../data/bnb_result.pkl')"
476
+ ]
477
+ }
478
+ ],
479
+ "metadata": {
480
+ "kernelspec": {
481
+ "display_name": "portfolio_risk_assesment",
482
+ "language": "python",
483
+ "name": "python3"
484
+ },
485
+ "language_info": {
486
+ "codemirror_mode": {
487
+ "name": "ipython",
488
+ "version": 3
489
+ },
490
+ "file_extension": ".py",
491
+ "mimetype": "text/x-python",
492
+ "name": "python",
493
+ "nbconvert_exporter": "python",
494
+ "pygments_lexer": "ipython3",
495
+ "version": "3.11.4"
496
+ },
497
+ "orig_nbformat": 4
498
+ },
499
+ "nbformat": 4,
500
+ "nbformat_minor": 2
501
+ }
script/processing.py CHANGED
@@ -1,3 +1,369 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:c1e5a2142c83b558c112d0682f11907b6365d4873690c2e39d8ebc6a56a88c35
3
- size 16107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import math
3
+ from datetime import datetime
4
+ import hvplot.pandas
5
+ import math
6
+ import numpy as np
7
+ # load data
8
+
9
+
10
+ def get_processing_result_of_stocks_df(stock_df, profile_df):
11
+
12
+ # add sector_name display_name name
13
+ ticker_sector_map = dict(
14
+ zip(profile_df['ticker'], profile_df['aggregate_sector']))
15
+ ticker_display_name_map = dict(
16
+ zip(profile_df['ticker'], profile_df['display_name']))
17
+ ticker_name_map = dict(zip(profile_df['ticker'], profile_df['name']))
18
+ stock_df['display_name'] = stock_df['ticker'].map(ticker_display_name_map)
19
+ stock_df['name'] = stock_df['ticker'].map(ticker_name_map)
20
+ stock_df['aggregate_sector'] = stock_df['ticker'].map(ticker_sector_map)
21
+
22
+ # calculate pct using closing price
23
+ stock_df.sort_values(by=['date'], inplace=True)
24
+ stock_df['pct'] = stock_df.groupby('ticker')['close'].pct_change()
25
+
26
+ # calculate weight TODO: think about how to optimize this
27
+ stock_df = stock_df.merge(profile_df[['weight', 'date', 'ticker']], on=[
28
+ 'ticker', 'date'], how='outer')
29
+ stock_df.rename(columns={'weight': 'initial_weight'}, inplace=True)
30
+ # create if not in stock_df
31
+
32
+ stock_df['current_weight'] = float('nan')
33
+ stock_df['previous_weight'] = float('nan')
34
+ df_grouped = stock_df.groupby('ticker')
35
+ for _, group in df_grouped:
36
+ pre_w = float('nan')
37
+ ini_w = float('nan')
38
+ for index, row in group.iterrows():
39
+ cur_w = float('nan')
40
+
41
+ # if has initial weight, the following row all use this initial weight
42
+ if not pd.isna(row['initial_weight']):
43
+ ini_w = row['initial_weight']
44
+ pre_w = ini_w
45
+ cur_w = ini_w
46
+
47
+ # just calculate current weight based on previous weight
48
+ else:
49
+ cur_w = pre_w * (1 + row['pct'])
50
+
51
+ stock_df.loc[index, 'current_weight'] = cur_w
52
+ stock_df.loc[index, 'previous_weight'] = pre_w
53
+ stock_df.loc[index, 'initial_weight'] = ini_w
54
+ pre_w = cur_w
55
+
56
+ stock_df.rename(columns={'weight': 'initial_weight'}, inplace=True)
57
+ stock_df.dropna(subset=['close'], inplace=True)
58
+
59
+ # normalize weight
60
+ stock_df['prev_w_in_p'] = stock_df['previous_weight'] / \
61
+ stock_df.groupby('date')['previous_weight'].transform('sum')
62
+
63
+ stock_df['ini_w_in_p'] = stock_df['initial_weight'] / \
64
+ stock_df.groupby('date')['initial_weight'].transform('sum')
65
+
66
+ # calculate weighted pct in portfolio
67
+ stock_df['portfolio_pct'] = stock_df['pct'] * stock_df['prev_w_in_p']
68
+
69
+ # calculate weight in sector TODO: remove
70
+ stock_df['prev_w_in_sectore'] = stock_df['previous_weight'] / \
71
+ stock_df.groupby(['date', 'aggregate_sector'])[
72
+ 'previous_weight'].transform('sum')
73
+ stock_df['ini_w_in_sector'] = stock_df['initial_weight'] / \
74
+ stock_df.groupby(['date', 'aggregate_sector'])[
75
+ 'initial_weight'].transform('sum')
76
+ # weighted pct in sector TODO: remove
77
+ stock_df['sector_pct'] = stock_df['pct'] * stock_df['prev_w_in_sectore']
78
+
79
+ # portfolio return
80
+ stock_df['portfolio_return'] = (stock_df.groupby('ticker')['portfolio_pct'].cumprod() + 1) - 1
81
+ # stock_df['cum_p_pct'] = stock_df.groupby(
82
+ # 'ticker')['portfolio_pct'].cumsum()
83
+ # stock_df['portfolio_return'] = np.exp(stock_df['cum_p_pct']) - 1
84
+
85
+ # stock return
86
+ stock_df['return'] = (stock_df.groupby('ticker')['pct'].cumprod() + 1) - 1
87
+ # stock_df['cum_pct'] = stock_df.groupby(
88
+ # 'ticker')['pct'].cumsum()
89
+ # stock_df['return'] = np.exp(stock_df['cum_pct']) - 1
90
+
91
+ # drop intermediate columns
92
+ stock_df = stock_df.drop(columns=['cum_p_pct'])
93
+
94
+ # risk
95
+ stock_df['risk'] = stock_df.groupby('ticker')['pct']\
96
+ .transform(lambda x: x.rolling(len(x), min_periods=1).std() * math.sqrt(252))
97
+
98
+ # fill na aggregate_sector
99
+ stock_df['aggregate_sector'].fillna('其他', inplace=True)
100
+ # sector return
101
+ stock_df['sector_return'] = stock_df['ini_w_in_sector'] * \
102
+ stock_df['return']
103
+
104
+ return stock_df
105
+
106
+
107
+ # total return by date
108
+ def get_portfolio_evaluation(portfolio_stock, benchmark_stock, profile_df):
109
+ # agg by date
110
+ agg_p_stock = portfolio_stock\
111
+ .groupby('date', as_index=False)\
112
+ .agg({'portfolio_return': 'sum', 'portfolio_pct': 'sum'})
113
+ agg_b_stock = benchmark_stock\
114
+ .groupby('date', as_index=False)\
115
+ .agg({'portfolio_return': 'sum', 'portfolio_pct': 'sum'})
116
+
117
+ # add pct of benchmark
118
+ merged_df = pd.merge(agg_p_stock, agg_b_stock, on=[
119
+ 'date'], how='left', suffixes=('_p', '_b'))
120
+
121
+ # portfolio mkt cap
122
+ mkt_adjustment = pd.DataFrame(profile_df.groupby('date')['weight'].sum())
123
+ mkt_adjustment.rename(columns={'weight': 'mkt_cap'}, inplace=True)
124
+ merged_df = merged_df.merge(mkt_adjustment, on=['date'], how='outer')
125
+
126
+ for i in range(len(merged_df)):
127
+ if pd.isna(merged_df.loc[i, 'mkt_cap']) and i > 0:
128
+ merged_df.loc[i, 'mkt_cap'] = merged_df.loc[i-1,
129
+ 'mkt_cap'] * (1 + merged_df.loc[i, 'portfolio_pct_p'])
130
+ # drop where portfolio_return_p is nan
131
+ merged_df.dropna(subset=['portfolio_return_p'], inplace=True)
132
+ # portfolio pnl TODO seem I can just use current wegith to do this
133
+ merged_df['prev_mkt_cap'] = merged_df['mkt_cap'].shift(1)
134
+ merged_df['pnl'] = merged_df['prev_mkt_cap'] * merged_df['portfolio_pct_p']
135
+
136
+ # risk std(pct)
137
+ merged_df['risk'] = merged_df['portfolio_pct_p'].rolling(
138
+ len(merged_df), min_periods=1).std() * math.sqrt(252)
139
+
140
+ # active return
141
+ merged_df['active_return'] = merged_df['portfolio_pct_p'] - \
142
+ merged_df['portfolio_pct_b']
143
+
144
+ # tracking errro std(active return)
145
+ merged_df['tracking_error'] = merged_df['active_return'].rolling(
146
+ len(merged_df), min_periods=1).std() * math.sqrt(252)
147
+
148
+ # cum pnl
149
+ merged_df['cum_pnl'] = merged_df['pnl'].cumsum()
150
+
151
+ return merged_df
152
+
153
+
154
+ # TODO convert below to funciton
155
+
156
+ def get_portfolio_sector_evaluation(portfolio_stock, benchmark_df):
157
+ # aggregate on sector and day
158
+ p_sector_df = portfolio_stock.groupby(['date', 'aggregate_sector'], as_index=False)\
159
+ .agg({'prev_w_in_p': 'sum', 'ini_w_in_p': "sum", "current_weight": 'sum',
160
+ "portfolio_pct": "sum", 'sector_return': "sum", 'ini_w_in_sector': 'sum', "portfolio_return": "sum"})
161
+ b_sector_df = benchmark_df.groupby(['date', 'aggregate_sector'], as_index=False)\
162
+ .agg({'prev_w_in_p': 'sum', 'ini_w_in_p': "sum", "current_weight": 'sum',
163
+ "portfolio_pct": "sum", "portfolio_return": "sum", 'sector_return': "sum", 'ini_w_in_sector': 'sum'})
164
+
165
+ # merge portfolio and benchmark
166
+ merge_df = p_sector_df.merge(
167
+ b_sector_df, on=['date', 'aggregate_sector'], how='outer', suffixes=('_p', '_b'))
168
+
169
+ # to acomendate bhb result
170
+ merge_df.rename(columns={'sector_return_p': 'return_p',
171
+ 'sector_return_b': 'return_b'}, inplace=True)
172
+ # active return
173
+ merge_df['active_return'] = merge_df['portfolio_return_p'] - \
174
+ merge_df['portfolio_return_b']
175
+
176
+ # risk
177
+ merge_df['risk'] = merge_df.groupby('aggregate_sector')['portfolio_pct_p']\
178
+ .transform(lambda x: x.rolling(len(x), min_periods=1).std() * math.sqrt(252))
179
+
180
+ # tracking error
181
+ merge_df['tracking_error'] = merge_df.groupby('aggregate_sector')['active_return']\
182
+ .transform(lambda x: x.rolling(len(x), min_periods=1).std() * math.sqrt(252))
183
+ return merge_df
184
+
185
+
186
+ # sector_eval_df = get_portfolio_sector_evaluation(portfolio_stock, benchmark_stock)
187
+ # sector_eval_df[sector_eval_df.date == datetime(2021, 10,13)].hvplot.bar(x='aggregate_sector', y=['portfolio_pct_p','portfolio_pct_b'], stacked=True, rot=90, title='sector pct')
188
+
189
+
190
+ def merge_on_date(calculated_ps, calculated_bs):
191
+ p_selected = calculated_ps.reset_index(
192
+ )[['ini_w_in_p', 'portfolio_return', 'date', 'ticker', 'display_name', 'return']]
193
+ b_selected = calculated_bs.reset_index(
194
+ )[['ini_w_in_p', 'portfolio_return', 'date', 'ticker', 'return']]
195
+ merged_stock_df = pd.merge(p_selected, b_selected, on=[
196
+ 'date', 'ticker'], how='outer', suffixes=('_p', '_b'))
197
+ return merged_stock_df
198
+
199
+
200
+ # merged_df = merge_on_date(portfolio_stock, benchmark_stock)
201
+
202
+
203
+ def get_bhb_result(merged_stock_df):
204
+ # merged_stock_df['ini_w_in_p_p'].fillna(0, inplace=True)
205
+ # merged_stock_df['ini_w_in_p_b'].fillna(0, inplace=True)
206
+ # merged_stock_df['portfolio_return_b'].fillna(0, inplace=True)
207
+ # merged_stock_df['portfolio_return_p'].fillna(0, inplace=True)
208
+ # allocation
209
+ merged_stock_df['allocation'] = (merged_stock_df['ini_w_in_p_p'] - merged_stock_df['ini_w_in_p_b']) \
210
+ * merged_stock_df['return_b']
211
+
212
+ # selection
213
+ merged_stock_df['selection'] = merged_stock_df['ini_w_in_p_b'] * \
214
+ (merged_stock_df['return_p'] -
215
+ merged_stock_df['return_b'])
216
+
217
+ # interaction
218
+ merged_stock_df['interaction'] = (merged_stock_df['ini_w_in_p_p'] - merged_stock_df['ini_w_in_p_b']) * \
219
+ (merged_stock_df['return_p'] -
220
+ merged_stock_df['return_b'])
221
+
222
+ # excess
223
+ merged_stock_df['excess'] = merged_stock_df['portfolio_return_p'] - \
224
+ merged_stock_df['portfolio_return_b']
225
+
226
+ # replace inf with nan
227
+ # merged_stock_df.replace([np.inf, -np.inf], np.nan, inplace=True)
228
+ return merged_stock_df
229
+
230
+
231
+ def calculate_total_attribution_by_sector(calculated_p_stock, calculated_b_stock):
232
+ sector_view_p = calculated_p_stock.groupby(['date', 'aggregate_sector']).aggregate({
233
+ 'prev_w_in_p': 'sum', 'sector_pct': 'sum'})
234
+ sector_view_b = calculated_b_stock.groupby(['date', 'aggregate_sector']).aggregate({
235
+ 'prev_w_in_p': 'sum', 'sector_pct': 'sum'})
236
+
237
+ sector_view_p['weighted_return'] = sector_view_p.prev_w_in_p * \
238
+ sector_view_p.sector_pct
239
+ sector_view_b['weighted_return'] = sector_view_b.prev_w_in_p * \
240
+ sector_view_b.sector_pct
241
+
242
+ merged_df = pd.merge(sector_view_p, sector_view_b, left_index=True,
243
+ right_index=True, how='outer', suffixes=['_b', '_p'])
244
+ merged_df.fillna(0, inplace=True)
245
+ merged_df['active_return'] = merged_df['weighted_return_p'] - \
246
+ merged_df['weighted_return_b']
247
+ merged_df['allocation'] = (
248
+ merged_df.prev_w_in_p_p - merged_df.prev_w_in_p_b) * merged_df.sector_pct_b
249
+ merged_df['selection'] = (
250
+ merged_df.sector_pct_p - merged_df.sector_pct_b) * merged_df.prev_w_in_p_b
251
+ merged_df['interaction'] = (merged_df.sector_pct_p - merged_df.sector_pct_b) * (
252
+ merged_df.prev_w_in_p_p - merged_df.prev_w_in_p_b)
253
+ merged_df['notinal_return'] = merged_df.allocation + \
254
+ merged_df.selection + merged_df.interaction
255
+ return merged_df.reset_index()
256
+
257
+
258
+ def calculate_total_attribution(calculated_p_stock, calculated_b_stock):
259
+ '''
260
+ using pct between two row's data of ticker to calculate the attribute,
261
+ use this method if need to calculate weekly attribut, yearly attribut, etc.
262
+ '''
263
+ merged_df = pd.merge(calculated_b_stock, calculated_p_stock, on=[
264
+ 'date', 'ticker'], how='outer', suffixes=['_b', '_p'])
265
+ df = merged_df[['pct_p', 'pct_b', 'prev_w_in_p_p',
266
+ 'prev_w_in_p_b', 'ticker', 'date']]
267
+ df.fillna(0, inplace=True)
268
+ df['active_return'] = df.pct_p * \
269
+ df.prev_w_in_p_p - df.pct_b * df.prev_w_in_p_b
270
+ # allocation
271
+ df['allocation'] = (df.prev_w_in_p_p - df.prev_w_in_p_b) * df.pct_b
272
+ df['selection'] = (df.pct_p - df.pct_b) * df.prev_w_in_p_b
273
+ df['interaction'] = (df.pct_p - df.pct_b) * \
274
+ (df.prev_w_in_p_p - df.prev_w_in_p_b)
275
+ df['notional_return'] = df.allocation + df.selection + df.interaction
276
+
277
+ daily_bnb_result = df.groupby(['date']).aggregate(
278
+ {'allocation': 'sum', 'selection': 'sum', 'interaction': 'sum', 'notional_return': 'sum', 'active_return': 'sum'})
279
+ daily_bnb_result['date'] = daily_bnb_result.index
280
+
281
+ return daily_bnb_result.reset_index(drop=True)
282
+ # return df
283
+
284
+
285
+ def calculate_return(df, start, end):
286
+ '''
287
+ return a df consist of total return for each day,
288
+ the return at start date would be 0
289
+ '''
290
+ selected_df = df[df.date.between(start, end)].copy()
291
+ # set the pct of first row to null
292
+ selected_df.iloc[0, selected_df.columns.get_indexer(
293
+ ['portfolio_pct_p', 'portfolio_pct_b'])] = 0
294
+ selected_df['return_p'] = (1 + selected_df['portfolio_pct_p']).cumprod() - 1
295
+ selected_df['return_b'] = (1 + selected_df['portfolio_pct_b']).cumprod() - 1
296
+ selected_df['active_return'] = selected_df.return_p - selected_df.return_b
297
+ return selected_df
298
+
299
+
300
+ def calculate_attributes_between_dates(start, end, calculated_p_stock, calculated_b_stock):
301
+ '''
302
+ calculate the attributes to explain the active return between two time series entries, the time series entry
303
+ right after or at start and another time serie right before or at end
304
+ return a df with attributes to explain the active return between start and end time series
305
+ '''
306
+ p_ranged_df = calculated_p_stock[(calculated_p_stock.date >= start) & (
307
+ calculated_p_stock.date <= end)]
308
+ b_ranged_df = calculated_b_stock[(calculated_b_stock.date >= start) & (
309
+ calculated_b_stock.date <= end)]
310
+
311
+ # return and weight of portfolio
312
+ p_start_df = p_ranged_df[p_ranged_df.date == p_ranged_df.date.min()]
313
+ p_end_df = p_ranged_df[p_ranged_df.date == p_ranged_df.date.max()]
314
+ p_concat = pd.concat([p_start_df, p_end_df])
315
+ # pct is unweighted return
316
+ p_concat['pct'] = p_concat.groupby('ticker')['close'].pct_change()
317
+ p_concat = p_concat.dropna(subset=['pct'])
318
+ p_concat['prev_w_in_p'] = p_concat['ticker'].map(
319
+ lambda x: p_start_df[p_start_df.ticker == x]['prev_w_in_p'].values[0])
320
+ # p_concatp_concat[['date', 'display_name', 'pct',
321
+ # 'close', 'prev_w_in_p', 'ini_w_in_p']]
322
+ # return and weight of benchmark
323
+ b_start_df = b_ranged_df[b_ranged_df.date == b_ranged_df.date.min()]
324
+ b_end_df = b_ranged_df[b_ranged_df.date == b_ranged_df.date.max()]
325
+ b_concat = pd.concat([b_start_df, b_end_df])
326
+ b_concat['pct'] = b_concat.groupby('ticker')['close'].pct_change()
327
+ b_concat = b_concat.dropna(subset=['pct'])
328
+ b_concat['prev_w_in_p'] = b_concat['ticker'].map(
329
+ lambda x: b_concat[b_concat.ticker == x]['prev_w_in_p'].values[0])
330
+ # b_concat = b_concat[['date', 'display_name', 'pct',
331
+ # 'close', 'prev_w_in_p', 'ini_w_in_p']]
332
+ merged_df = pd.merge(b_concat, p_concat, on=[
333
+ 'ticker', 'date'], suffixes=('_b', '_p'), how='outer')
334
+ df = merged_df[['display_name_p', 'display_name_b', 'ticker',
335
+ 'pct_b', 'pct_p', 'prev_w_in_p_b', 'prev_w_in_p_p']].copy()
336
+
337
+ # indicate weather stock is in portfolio
338
+ df['in_portfolio'] = False
339
+ df.loc[df.display_name_p.notnull(), 'in_portfolio'] = True
340
+
341
+ # fill display_name
342
+ df['display_name_p'] = df['display_name_p'].fillna(df['display_name_b'])
343
+ df['display_name_b'] = df['display_name_b'].fillna(df['display_name_p'])
344
+
345
+ # treat nan weight and pct as 0
346
+ df.fillna(0, inplace=True)
347
+
348
+ # allocation, selection, interaction, notional return, active return
349
+ df['allocation'] = (df.prev_w_in_p_p - df.prev_w_in_p_b) * df.pct_b
350
+ df['selection'] = (df.pct_p - df.pct_b) * df.prev_w_in_p_b
351
+ df['interaction'] = (df.pct_p - df.pct_b) * \
352
+ (df.prev_w_in_p_p - df.prev_w_in_p_b)
353
+ df['notional_return'] = df.allocation + df.selection + df.interaction
354
+ # weighted return
355
+ df['return'] = df.prev_w_in_p_p * df.pct_p
356
+ # weight * prev_w is the weighted return
357
+ df['active_return'] = df.prev_w_in_p_p * \
358
+ df.pct_p - df.prev_w_in_p_b * df.pct_b
359
+
360
+ return df
361
+
362
+
363
+ def change_resolution(df, freq='W'):
364
+ '''
365
+ aggregate by keeping the first entry of the freq period,
366
+ the resolution of the df, default to weekly
367
+ '''
368
+ df['freq'] = pd.to_datetime(df['date']).dt.to_period(freq)
369
+ return df.groupby('freq').first().reset_index()
script/processing2.ipynb CHANGED
@@ -1,3 +1,1836 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:f2b581ac16ff53983fcf43922dd3896d186279b8868b1080cd274938babaf627
3
- size 59598
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 142,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "import pandas as pd\n",
10
+ "import math\n",
11
+ "from datetime import datetime\n",
12
+ "import hvplot.pandas\n",
13
+ "import math\n",
14
+ "# load data\n",
15
+ "profile_df = pd.read_pickle('../data/portfolio_portfile.pkl')\n",
16
+ "benchmark_df = pd.read_pickle('../data/benchmark_portfolio.pkl')\n",
17
+ "portfolio_df = pd.read_pickle('../data/portfolio_data.pkl')"
18
+ ]
19
+ },
20
+ {
21
+ "cell_type": "code",
22
+ "execution_count": 143,
23
+ "metadata": {},
24
+ "outputs": [],
25
+ "source": [
26
+ "# to acoomodate the current pipe line\n",
27
+ "min_dates = benchmark_df.groupby('ticker')['date'].min()\n",
28
+ "\n",
29
+ "for ticker, min_date in min_dates.items():\n",
30
+ " benchmark_df.loc[(benchmark_df['ticker'] == ticker) & (benchmark_df['date'] != min_date), 'weight'] = float('nan')\n",
31
+ "\n",
32
+ "benchmark_df['initial_weight'] = benchmark_df['weight']\n",
33
+ "# drop weight\n",
34
+ "benchmark_df = benchmark_df.drop(columns=['weight'])"
35
+ ]
36
+ },
37
+ {
38
+ "cell_type": "code",
39
+ "execution_count": 144,
40
+ "metadata": {},
41
+ "outputs": [
42
+ {
43
+ "data": {
44
+ "text/plain": [
45
+ "Series([], Name: initial_weight, dtype: int64)"
46
+ ]
47
+ },
48
+ "execution_count": 144,
49
+ "metadata": {},
50
+ "output_type": "execute_result"
51
+ }
52
+ ],
53
+ "source": [
54
+ "# check if all unique ticker has an weight\n",
55
+ "count_list = benchmark_df.groupby('ticker')['initial_weight'].count().sort_values(ascending=False)\n",
56
+ "count_list[count_list != 1]\n"
57
+ ]
58
+ },
59
+ {
60
+ "cell_type": "code",
61
+ "execution_count": 145,
62
+ "metadata": {},
63
+ "outputs": [],
64
+ "source": [
65
+ "update_profile_df = profile_df.copy()\n",
66
+ "update_profile_df['date'] = datetime(2021,1,10)\n",
67
+ "update_profile_df['weight'] = [50,100,200,300,400,500]\n",
68
+ "profile_df = pd.concat([profile_df, update_profile_df])\n"
69
+ ]
70
+ },
71
+ {
72
+ "cell_type": "code",
73
+ "execution_count": 146,
74
+ "metadata": {},
75
+ "outputs": [],
76
+ "source": [
77
+ "def calculate_pct(stock_df):\n",
78
+ " stock_df['pct'] = stock_df.groupby(['ticker'])['close'].pct_change()\n"
79
+ ]
80
+ },
81
+ {
82
+ "cell_type": "code",
83
+ "execution_count": 147,
84
+ "metadata": {},
85
+ "outputs": [],
86
+ "source": [
87
+ "# step 1 pct\n",
88
+ "calculate_pct(portfolio_df)\n",
89
+ "calculate_pct(benchmark_df)"
90
+ ]
91
+ },
92
+ {
93
+ "cell_type": "code",
94
+ "execution_count": 148,
95
+ "metadata": {},
96
+ "outputs": [],
97
+ "source": [
98
+ "def return_weighted_stock_df(stock_price_df, profile_df=None):\n",
99
+ " # TODO change later this a temporary solution\n",
100
+ " # initialize weight if profile_df is not none\n",
101
+ " merged_df = pd.DataFrame()\n",
102
+ " if profile_df is not None:\n",
103
+ " merged_df = stock_price_df.merge(profile_df[['weight', 'date', 'ticker']], on=['ticker', 'date'], how='outer')\n",
104
+ " merged_df.sort_values(by=['date'], inplace=True)\n",
105
+ " merged_df.rename(columns={'weight': 'initial_weight'}, inplace=True)\n",
106
+ " else:\n",
107
+ " merged_df = stock_price_df.copy()\n",
108
+ " merged_df['current_weight'] = float('nan')\n",
109
+ " merged_df['previous_weight'] = float('nan')\n",
110
+ " df_grouped = merged_df.groupby('ticker')\n",
111
+ " for _, group in df_grouped:\n",
112
+ " pre_w = float('nan')\n",
113
+ " ini_w = float('nan')\n",
114
+ " for index, row in group.iterrows():\n",
115
+ " cur_w = float('nan')\n",
116
+ " # if has initial weight, the following row all use this initial weight\n",
117
+ " if not pd.isna(row['initial_weight']):\n",
118
+ " ini_w = row['initial_weight']\n",
119
+ " cur_w = ini_w\n",
120
+ " # just calculate current weight based on previous weight\n",
121
+ " else:\n",
122
+ " cur_w = pre_w * (1 + row['pct'])\n",
123
+ "\n",
124
+ " merged_df.loc[index, 'current_weight'] = cur_w \n",
125
+ " merged_df.loc[index, 'previous_weight'] = pre_w\n",
126
+ " merged_df.loc[index, 'initial_weight'] = ini_w\n",
127
+ " pre_w = cur_w\n",
128
+ " \n",
129
+ " # drop row where closing price is none\n",
130
+ " merged_df = merged_df[~pd.isna(merged_df['close'])]\n",
131
+ " # drop index\n",
132
+ " return merged_df"
133
+ ]
134
+ },
135
+ {
136
+ "cell_type": "code",
137
+ "execution_count": 149,
138
+ "metadata": {},
139
+ "outputs": [],
140
+ "source": [
141
+ "# TODO consider save the weight calculation\n",
142
+ "portfolio_df = return_weighted_stock_df(portfolio_df, profile_df)\n",
143
+ "benchmark_df = return_weighted_stock_df(benchmark_df)"
144
+ ]
145
+ },
146
+ {
147
+ "cell_type": "code",
148
+ "execution_count": 150,
149
+ "metadata": {},
150
+ "outputs": [],
151
+ "source": [
152
+ "# benchmark_df[benchmark_df.ticker =='000008.XSHE']"
153
+ ]
154
+ },
155
+ {
156
+ "cell_type": "code",
157
+ "execution_count": 151,
158
+ "metadata": {},
159
+ "outputs": [],
160
+ "source": [
161
+ "## normalize all weight\n",
162
+ "def normalize_weight(stock_df):\n",
163
+ " stock_df['current_weight'] = stock_df['current_weight'] / \\\n",
164
+ " stock_df.groupby('date')['current_weight'].transform('sum')\n",
165
+ "\n",
166
+ " stock_df['previous_weight'] = stock_df['previous_weight'] / \\\n",
167
+ " stock_df.groupby('date')['previous_weight'].transform('sum')\n",
168
+ "\n",
169
+ " stock_df['initial_weight'] = stock_df['initial_weight'] / \\\n",
170
+ " stock_df.groupby('date')['initial_weight'].transform('sum')\n"
171
+ ]
172
+ },
173
+ {
174
+ "cell_type": "code",
175
+ "execution_count": 152,
176
+ "metadata": {},
177
+ "outputs": [],
178
+ "source": [
179
+ "normalize_weight(portfolio_df)\n",
180
+ "normalize_weight(benchmark_df)"
181
+ ]
182
+ },
183
+ {
184
+ "cell_type": "code",
185
+ "execution_count": 153,
186
+ "metadata": {},
187
+ "outputs": [
188
+ {
189
+ "data": {
190
+ "text/plain": [
191
+ "260 0.032258\n",
192
+ "258 0.064516\n",
193
+ "262 0.129032\n",
194
+ "263 0.258065\n",
195
+ "259 0.322581\n",
196
+ "261 0.193548\n",
197
+ "Name: initial_weight, dtype: float64"
198
+ ]
199
+ },
200
+ "execution_count": 153,
201
+ "metadata": {},
202
+ "output_type": "execute_result"
203
+ }
204
+ ],
205
+ "source": [
206
+ "portfolio_df[portfolio_df.date == datetime(2021, 3, 12)]['initial_weight']"
207
+ ]
208
+ },
209
+ {
210
+ "cell_type": "code",
211
+ "execution_count": 154,
212
+ "metadata": {},
213
+ "outputs": [
214
+ {
215
+ "name": "stdout",
216
+ "output_type": "stream",
217
+ "text": [
218
+ "1.0\n",
219
+ "1.0\n",
220
+ "1.0\n"
221
+ ]
222
+ }
223
+ ],
224
+ "source": [
225
+ "print(benchmark_df[benchmark_df.date == datetime(2021, 3, 12)]['initial_weight'].sum())\n",
226
+ "print(benchmark_df[benchmark_df.date == datetime(2021, 3, 12)]['current_weight'].sum())\n",
227
+ "print(benchmark_df[benchmark_df.date == datetime(2021, 3, 12)]['previous_weight'].sum())"
228
+ ]
229
+ },
230
+ {
231
+ "cell_type": "code",
232
+ "execution_count": 155,
233
+ "metadata": {},
234
+ "outputs": [],
235
+ "source": [
236
+ "# step 3 sector wegiht\n",
237
+ "\n",
238
+ "# add sector information first\n",
239
+ "def create_sector_weight(stock_df, profile_df=None):\n",
240
+ " # if profile_df is none assume the aggregate_sector stock info already in stock_df\n",
241
+ " merged_df = None\n",
242
+ " if profile_df is not None:\n",
243
+ " merged_df = stock_df.merge(profile_df[['ticker', 'aggregate_sector']], on='ticker', how='left')\n",
244
+ " else:\n",
245
+ " merged_df = stock_df.copy()\n",
246
+ " # set null to others\n",
247
+ " merged_df['aggregate_sector'] = merged_df['aggregate_sector'].fillna('其他')\n",
248
+ " # calculate previous_sector_weight\n",
249
+ " merged_df['previous_sector_weight'] = merged_df['previous_weight'] / \\\n",
250
+ " merged_df.groupby(['date', 'aggregate_sector'])['previous_weight'].transform('sum')\n",
251
+ " # calculate initial sectore weight\n",
252
+ " merged_df['initial_sector_weight'] = merged_df['initial_weight'] / \\\n",
253
+ " merged_df.groupby(['date', 'aggregate_sector'])['initial_weight'].transform('sum')\n",
254
+ " \n",
255
+ " return merged_df"
256
+ ]
257
+ },
258
+ {
259
+ "cell_type": "code",
260
+ "execution_count": 156,
261
+ "metadata": {},
262
+ "outputs": [],
263
+ "source": [
264
+ "portfolio_df = create_sector_weight(stock_df = portfolio_df, profile_df = profile_df)\n",
265
+ "benchmark_df = create_sector_weight(benchmark_df)\n"
266
+ ]
267
+ },
268
+ {
269
+ "cell_type": "code",
270
+ "execution_count": 157,
271
+ "metadata": {},
272
+ "outputs": [
273
+ {
274
+ "name": "stdout",
275
+ "output_type": "stream",
276
+ "text": [
277
+ "aggregate_sector\n",
278
+ "信息与通信 1.0\n",
279
+ "公用事业 1.0\n",
280
+ "其他 1.0\n",
281
+ "医药卫生 1.0\n",
282
+ "原料与能源 1.0\n",
283
+ "工业 1.0\n",
284
+ "消费 1.0\n",
285
+ "金融与地产 1.0\n",
286
+ "Name: previous_sector_weight, dtype: float64\n",
287
+ "aggregate_sector\n",
288
+ "信息与通信 1.0\n",
289
+ "公用事业 1.0\n",
290
+ "其他 1.0\n",
291
+ "医药卫生 1.0\n",
292
+ "原料与能源 1.0\n",
293
+ "工业 1.0\n",
294
+ "消费 1.0\n",
295
+ "金融与地产 1.0\n",
296
+ "Name: initial_sector_weight, dtype: float64\n"
297
+ ]
298
+ }
299
+ ],
300
+ "source": [
301
+ "# check result \n",
302
+ "print(benchmark_df[benchmark_df.date == datetime(2021, 3, 12)].groupby('aggregate_sector')['previous_sector_weight'].sum())\n",
303
+ "print(benchmark_df[benchmark_df.date == datetime(2021, 3, 12)].groupby('aggregate_sector')['initial_sector_weight'].sum())\n"
304
+ ]
305
+ },
306
+ {
307
+ "cell_type": "code",
308
+ "execution_count": 158,
309
+ "metadata": {},
310
+ "outputs": [
311
+ {
312
+ "name": "stdout",
313
+ "output_type": "stream",
314
+ "text": [
315
+ "aggregate_sector\n",
316
+ "信息与通信 1.0\n",
317
+ "医药卫生 1.0\n",
318
+ "原料与能源 1.0\n",
319
+ "工业 1.0\n",
320
+ "消费 1.0\n",
321
+ "Name: previous_sector_weight, dtype: float64\n",
322
+ "aggregate_sector\n",
323
+ "信息与通信 1.0\n",
324
+ "医药卫生 1.0\n",
325
+ "原料与能源 1.0\n",
326
+ "工业 1.0\n",
327
+ "消费 1.0\n",
328
+ "Name: initial_sector_weight, dtype: float64\n"
329
+ ]
330
+ }
331
+ ],
332
+ "source": [
333
+ "# check result \n",
334
+ "print(portfolio_df[portfolio_df.date == datetime(2021, 3, 12)].groupby('aggregate_sector')['previous_sector_weight'].sum())\n",
335
+ "print(portfolio_df[portfolio_df.date == datetime(2021, 3, 12)].groupby('aggregate_sector')['initial_sector_weight'].sum())\n"
336
+ ]
337
+ },
338
+ {
339
+ "cell_type": "code",
340
+ "execution_count": 159,
341
+ "metadata": {},
342
+ "outputs": [],
343
+ "source": [
344
+ "## return define as the total return since the portfolio created\n",
345
+ "def calcualte_return(stock_df):\n",
346
+ " stock_df['return'] = stock_df['close'] / stock_df.groupby(['ticker'])['close'].transform('first') - 1"
347
+ ]
348
+ },
349
+ {
350
+ "cell_type": "code",
351
+ "execution_count": 160,
352
+ "metadata": {},
353
+ "outputs": [],
354
+ "source": [
355
+ "calcualte_return(portfolio_df)\n",
356
+ "calcualte_return(benchmark_df)"
357
+ ]
358
+ },
359
+ {
360
+ "cell_type": "code",
361
+ "execution_count": 161,
362
+ "metadata": {},
363
+ "outputs": [],
364
+ "source": [
365
+ "def calculate_weighted_sector_return(stock_df):\n",
366
+ " stock_df['weighted_sectore_return'] = stock_df['return'] * stock_df['initial_sector_weight']"
367
+ ]
368
+ },
369
+ {
370
+ "cell_type": "code",
371
+ "execution_count": 162,
372
+ "metadata": {},
373
+ "outputs": [],
374
+ "source": [
375
+ "calculate_weighted_sector_return(portfolio_df)\n",
376
+ "calculate_weighted_sector_return(benchmark_df)"
377
+ ]
378
+ },
379
+ {
380
+ "cell_type": "code",
381
+ "execution_count": 163,
382
+ "metadata": {},
383
+ "outputs": [],
384
+ "source": [
385
+ "## weighted return and sector weighred return \n",
386
+ "def calculate_weighted_return(stock_df):\n",
387
+ " stock_df['weighted_return'] = stock_df['return'] * stock_df['initial_weight']"
388
+ ]
389
+ },
390
+ {
391
+ "cell_type": "code",
392
+ "execution_count": 164,
393
+ "metadata": {},
394
+ "outputs": [],
395
+ "source": [
396
+ "# step\n",
397
+ "calculate_weighted_return(portfolio_df)\n",
398
+ "calculate_weighted_return(benchmark_df)"
399
+ ]
400
+ },
401
+ {
402
+ "cell_type": "code",
403
+ "execution_count": 165,
404
+ "metadata": {},
405
+ "outputs": [],
406
+ "source": [
407
+ "def calculate_weighted_sector_return(stock_df):\n",
408
+ " stock_df['weighted_sector_return'] = stock_df['return'] * stock_df['initial_sector_weight']"
409
+ ]
410
+ },
411
+ {
412
+ "cell_type": "code",
413
+ "execution_count": 166,
414
+ "metadata": {},
415
+ "outputs": [],
416
+ "source": [
417
+ "calculate_weighted_sector_return(portfolio_df)\n",
418
+ "calculate_weighted_sector_return(benchmark_df)"
419
+ ]
420
+ },
421
+ {
422
+ "cell_type": "code",
423
+ "execution_count": 167,
424
+ "metadata": {},
425
+ "outputs": [],
426
+ "source": [
427
+ "## calcualte weighted pc\n",
428
+ "def calculate_weighted_pct(stock_df):\n",
429
+ " stock_df['weighted_pct'] = stock_df['pct'] * stock_df['previous_weight']\n",
430
+ "\n"
431
+ ]
432
+ },
433
+ {
434
+ "cell_type": "code",
435
+ "execution_count": 168,
436
+ "metadata": {},
437
+ "outputs": [],
438
+ "source": [
439
+ "def calculate_weighted_sector_pct(stock_df):\n",
440
+ " stock_df['weighted_sector_pct'] = stock_df['pct'] * stock_df['previous_sector_weight']\n",
441
+ " "
442
+ ]
443
+ },
444
+ {
445
+ "cell_type": "code",
446
+ "execution_count": 169,
447
+ "metadata": {},
448
+ "outputs": [],
449
+ "source": [
450
+ "calculate_weighted_sector_pct(portfolio_df)\n",
451
+ "calculate_weighted_sector_pct(benchmark_df)"
452
+ ]
453
+ },
454
+ {
455
+ "cell_type": "code",
456
+ "execution_count": 170,
457
+ "metadata": {},
458
+ "outputs": [],
459
+ "source": [
460
+ "calculate_weighted_pct(portfolio_df)\n",
461
+ "calculate_weighted_pct(benchmark_df)"
462
+ ]
463
+ },
464
+ {
465
+ "cell_type": "code",
466
+ "execution_count": 171,
467
+ "metadata": {},
468
+ "outputs": [],
469
+ "source": [
470
+ "calculate_weighted_sector_return(portfolio_df)\n",
471
+ "calculate_weighted_sector_return(benchmark_df)"
472
+ ]
473
+ },
474
+ {
475
+ "cell_type": "code",
476
+ "execution_count": 172,
477
+ "metadata": {},
478
+ "outputs": [],
479
+ "source": [
480
+ "## aggregate by date\n",
481
+ "\n",
482
+ "# pct and weighted_return\n",
483
+ "# def agg_by_date(stock_df)\n",
484
+ "def agg_by_date(stock_df):\n",
485
+ " agg_on_date_df = pd.DataFrame(stock_df.groupby('date')[['weighted_return','weighted_pct']].sum())\n",
486
+ " agg_on_date_df.rename(columns={'weighted_return': 'return', 'weighted_pct': 'pct'}, inplace=True)\n",
487
+ " return agg_on_date_df\n",
488
+ "\n",
489
+ "\n"
490
+ ]
491
+ },
492
+ {
493
+ "cell_type": "code",
494
+ "execution_count": 173,
495
+ "metadata": {},
496
+ "outputs": [],
497
+ "source": [
498
+ "p_total_view = agg_by_date(portfolio_df)\n",
499
+ "b_total_view = agg_by_date(benchmark_df)"
500
+ ]
501
+ },
502
+ {
503
+ "cell_type": "code",
504
+ "execution_count": 174,
505
+ "metadata": {},
506
+ "outputs": [],
507
+ "source": [
508
+ "## aggregate by sector\n",
509
+ "def agg_by_sector(stock_df):\n",
510
+ " agg_on_sector_df = pd.DataFrame(stock_df.groupby(['aggregate_sector','date'])[['weighted_sector_return','weighted_sector_pct']].sum())\n",
511
+ " agg_on_sector_df.rename(columns={'weighted_sector_return': 'return', 'weighted_sector_pct': 'pct'}, inplace=True)\n",
512
+ " return agg_on_sector_df"
513
+ ]
514
+ },
515
+ {
516
+ "cell_type": "code",
517
+ "execution_count": 175,
518
+ "metadata": {},
519
+ "outputs": [],
520
+ "source": [
521
+ "p_sector_view = agg_by_sector(portfolio_df)\n",
522
+ "b_sector_view = agg_by_sector(benchmark_df)"
523
+ ]
524
+ },
525
+ {
526
+ "cell_type": "code",
527
+ "execution_count": 200,
528
+ "metadata": {},
529
+ "outputs": [],
530
+ "source": [
531
+ "def create_risk_table(portfolio_summary, benchmark_summary):\n",
532
+ " # total risk tracking error \n",
533
+ " merged_df = pd.merge(portfolio_summary, benchmark_summary, on='date', how='outer', suffixes=('_p', '_b'))\n",
534
+ " merged_df['risk_p'] = merged_df['return_p'].expanding().std() * math.sqrt(252)\n",
535
+ " merged_df['risk_b'] = merged_df['return_b'].expanding().std() * math.sqrt(252)\n",
536
+ " merged_df['active_return'] = merged_df['return_p'] - merged_df['return_b']\n",
537
+ " merged_df['tracking_error'] = merged_df['active_return'].expanding().std() * math.sqrt(252)\n",
538
+ " merged_df['date'] = merged_df.index\n",
539
+ " # drop index\n",
540
+ " merged_df.reset_index(drop=True, inplace=True)\n",
541
+ " return merged_df"
542
+ ]
543
+ },
544
+ {
545
+ "cell_type": "code",
546
+ "execution_count": 201,
547
+ "metadata": {},
548
+ "outputs": [],
549
+ "source": [
550
+ "portfolio_risk_by_date_df = create_risk_table(p_total_view, b_total_view)"
551
+ ]
552
+ },
553
+ {
554
+ "cell_type": "code",
555
+ "execution_count": 202,
556
+ "metadata": {},
557
+ "outputs": [
558
+ {
559
+ "data": {
560
+ "text/html": [
561
+ "<div>\n",
562
+ "<style scoped>\n",
563
+ " .dataframe tbody tr th:only-of-type {\n",
564
+ " vertical-align: middle;\n",
565
+ " }\n",
566
+ "\n",
567
+ " .dataframe tbody tr th {\n",
568
+ " vertical-align: top;\n",
569
+ " }\n",
570
+ "\n",
571
+ " .dataframe thead th {\n",
572
+ " text-align: right;\n",
573
+ " }\n",
574
+ "</style>\n",
575
+ "<table border=\"1\" class=\"dataframe\">\n",
576
+ " <thead>\n",
577
+ " <tr style=\"text-align: right;\">\n",
578
+ " <th></th>\n",
579
+ " <th>return_p</th>\n",
580
+ " <th>pct_p</th>\n",
581
+ " <th>return_b</th>\n",
582
+ " <th>pct_b</th>\n",
583
+ " <th>risk_p</th>\n",
584
+ " <th>risk_b</th>\n",
585
+ " <th>active_return</th>\n",
586
+ " <th>tracking_error</th>\n",
587
+ " <th>date</th>\n",
588
+ " </tr>\n",
589
+ " </thead>\n",
590
+ " <tbody>\n",
591
+ " <tr>\n",
592
+ " <th>0</th>\n",
593
+ " <td>0.000000</td>\n",
594
+ " <td>0.000000</td>\n",
595
+ " <td>0.000000</td>\n",
596
+ " <td>0.000000</td>\n",
597
+ " <td>NaN</td>\n",
598
+ " <td>NaN</td>\n",
599
+ " <td>0.000000</td>\n",
600
+ " <td>NaN</td>\n",
601
+ " <td>2021-01-05</td>\n",
602
+ " </tr>\n",
603
+ " <tr>\n",
604
+ " <th>1</th>\n",
605
+ " <td>0.012146</td>\n",
606
+ " <td>0.012146</td>\n",
607
+ " <td>-0.001934</td>\n",
608
+ " <td>-0.001934</td>\n",
609
+ " <td>0.136341</td>\n",
610
+ " <td>0.021705</td>\n",
611
+ " <td>0.014080</td>\n",
612
+ " <td>0.158046</td>\n",
613
+ " <td>2021-01-06</td>\n",
614
+ " </tr>\n",
615
+ " <tr>\n",
616
+ " <th>2</th>\n",
617
+ " <td>0.086830</td>\n",
618
+ " <td>0.074233</td>\n",
619
+ " <td>-0.000811</td>\n",
620
+ " <td>0.001125</td>\n",
621
+ " <td>0.746402</td>\n",
622
+ " <td>0.015414</td>\n",
623
+ " <td>0.087641</td>\n",
624
+ " <td>0.747127</td>\n",
625
+ " <td>2021-01-07</td>\n",
626
+ " </tr>\n",
627
+ " <tr>\n",
628
+ " <th>3</th>\n",
629
+ " <td>0.089435</td>\n",
630
+ " <td>0.002496</td>\n",
631
+ " <td>0.002535</td>\n",
632
+ " <td>0.003349</td>\n",
633
+ " <td>0.756382</td>\n",
634
+ " <td>0.030137</td>\n",
635
+ " <td>0.086900</td>\n",
636
+ " <td>0.740979</td>\n",
637
+ " <td>2021-01-08</td>\n",
638
+ " </tr>\n",
639
+ " <tr>\n",
640
+ " <th>4</th>\n",
641
+ " <td>0.148063</td>\n",
642
+ " <td>0.029363</td>\n",
643
+ " <td>-0.013015</td>\n",
644
+ " <td>-0.015511</td>\n",
645
+ " <td>0.970984</td>\n",
646
+ " <td>0.095654</td>\n",
647
+ " <td>0.161078</td>\n",
648
+ " <td>1.032423</td>\n",
649
+ " <td>2021-01-11</td>\n",
650
+ " </tr>\n",
651
+ " <tr>\n",
652
+ " <th>...</th>\n",
653
+ " <td>...</td>\n",
654
+ " <td>...</td>\n",
655
+ " <td>...</td>\n",
656
+ " <td>...</td>\n",
657
+ " <td>...</td>\n",
658
+ " <td>...</td>\n",
659
+ " <td>...</td>\n",
660
+ " <td>...</td>\n",
661
+ " <td>...</td>\n",
662
+ " </tr>\n",
663
+ " <tr>\n",
664
+ " <th>242</th>\n",
665
+ " <td>0.028005</td>\n",
666
+ " <td>-0.071081</td>\n",
667
+ " <td>0.086827</td>\n",
668
+ " <td>0.000156</td>\n",
669
+ " <td>2.097631</td>\n",
670
+ " <td>0.886298</td>\n",
671
+ " <td>-0.058822</td>\n",
672
+ " <td>1.856213</td>\n",
673
+ " <td>2022-01-04</td>\n",
674
+ " </tr>\n",
675
+ " <tr>\n",
676
+ " <th>243</th>\n",
677
+ " <td>-0.033053</td>\n",
678
+ " <td>-0.059582</td>\n",
679
+ " <td>0.067931</td>\n",
680
+ " <td>-0.017386</td>\n",
681
+ " <td>2.099052</td>\n",
682
+ " <td>0.884891</td>\n",
683
+ " <td>-0.100984</td>\n",
684
+ " <td>1.861347</td>\n",
685
+ " <td>2022-01-05</td>\n",
686
+ " </tr>\n",
687
+ " <tr>\n",
688
+ " <th>244</th>\n",
689
+ " <td>-0.042238</td>\n",
690
+ " <td>-0.008112</td>\n",
691
+ " <td>0.069522</td>\n",
692
+ " <td>0.001490</td>\n",
693
+ " <td>2.101118</td>\n",
694
+ " <td>0.883542</td>\n",
695
+ " <td>-0.111761</td>\n",
696
+ " <td>1.867445</td>\n",
697
+ " <td>2022-01-06</td>\n",
698
+ " </tr>\n",
699
+ " <tr>\n",
700
+ " <th>245</th>\n",
701
+ " <td>-0.073118</td>\n",
702
+ " <td>-0.031015</td>\n",
703
+ " <td>0.062056</td>\n",
704
+ " <td>-0.006981</td>\n",
705
+ " <td>2.105760</td>\n",
706
+ " <td>0.881986</td>\n",
707
+ " <td>-0.135174</td>\n",
708
+ " <td>1.875959</td>\n",
709
+ " <td>2022-01-07</td>\n",
710
+ " </tr>\n",
711
+ " <tr>\n",
712
+ " <th>246</th>\n",
713
+ " <td>-0.029944</td>\n",
714
+ " <td>0.044300</td>\n",
715
+ " <td>0.064588</td>\n",
716
+ " <td>0.002384</td>\n",
717
+ " <td>2.106749</td>\n",
718
+ " <td>0.880502</td>\n",
719
+ " <td>-0.094532</td>\n",
720
+ " <td>1.880060</td>\n",
721
+ " <td>2022-01-10</td>\n",
722
+ " </tr>\n",
723
+ " </tbody>\n",
724
+ "</table>\n",
725
+ "<p>247 rows × 9 columns</p>\n",
726
+ "</div>"
727
+ ],
728
+ "text/plain": [
729
+ " return_p pct_p return_b pct_b risk_p risk_b \\\n",
730
+ "0 0.000000 0.000000 0.000000 0.000000 NaN NaN \n",
731
+ "1 0.012146 0.012146 -0.001934 -0.001934 0.136341 0.021705 \n",
732
+ "2 0.086830 0.074233 -0.000811 0.001125 0.746402 0.015414 \n",
733
+ "3 0.089435 0.002496 0.002535 0.003349 0.756382 0.030137 \n",
734
+ "4 0.148063 0.029363 -0.013015 -0.015511 0.970984 0.095654 \n",
735
+ ".. ... ... ... ... ... ... \n",
736
+ "242 0.028005 -0.071081 0.086827 0.000156 2.097631 0.886298 \n",
737
+ "243 -0.033053 -0.059582 0.067931 -0.017386 2.099052 0.884891 \n",
738
+ "244 -0.042238 -0.008112 0.069522 0.001490 2.101118 0.883542 \n",
739
+ "245 -0.073118 -0.031015 0.062056 -0.006981 2.105760 0.881986 \n",
740
+ "246 -0.029944 0.044300 0.064588 0.002384 2.106749 0.880502 \n",
741
+ "\n",
742
+ " active_return tracking_error date \n",
743
+ "0 0.000000 NaN 2021-01-05 \n",
744
+ "1 0.014080 0.158046 2021-01-06 \n",
745
+ "2 0.087641 0.747127 2021-01-07 \n",
746
+ "3 0.086900 0.740979 2021-01-08 \n",
747
+ "4 0.161078 1.032423 2021-01-11 \n",
748
+ ".. ... ... ... \n",
749
+ "242 -0.058822 1.856213 2022-01-04 \n",
750
+ "243 -0.100984 1.861347 2022-01-05 \n",
751
+ "244 -0.111761 1.867445 2022-01-06 \n",
752
+ "245 -0.135174 1.875959 2022-01-07 \n",
753
+ "246 -0.094532 1.880060 2022-01-10 \n",
754
+ "\n",
755
+ "[247 rows x 9 columns]"
756
+ ]
757
+ },
758
+ "execution_count": 202,
759
+ "metadata": {},
760
+ "output_type": "execute_result"
761
+ }
762
+ ],
763
+ "source": [
764
+ "# add mkt cap\n",
765
+ "portfolio_risk_by_date_df"
766
+ ]
767
+ },
768
+ {
769
+ "cell_type": "code",
770
+ "execution_count": 217,
771
+ "metadata": {},
772
+ "outputs": [
773
+ {
774
+ "data": {
775
+ "text/plain": [
776
+ "date\n",
777
+ "2021-01-05 600\n",
778
+ "2021-01-10 1550\n",
779
+ "Name: weight, dtype: int64"
780
+ ]
781
+ },
782
+ "execution_count": 217,
783
+ "metadata": {},
784
+ "output_type": "execute_result"
785
+ }
786
+ ],
787
+ "source": [
788
+ "profile_df.groupby('date')['weight'].sum()\n",
789
+ "\n",
790
+ "# for i in range(1, len(portfolio_risk_by_date_df)):\n",
791
+ "# cur_mkt = portfolio_risk_by_date_df.loc[i, 'mkt_cap']\n",
792
+ "# if pd.isna(cur_mkt):\n",
793
+ "# portfolio_risk_by_date_df.loc[i, 'mkt_cap'] = portfolio_risk_by_date_df.loc[i-1, 'mkt_cap'] * (1 + portfolio_risk_by_date_df.loc[i, 'pct_p'])\n",
794
+ " "
795
+ ]
796
+ },
797
+ {
798
+ "cell_type": "code",
799
+ "execution_count": 216,
800
+ "metadata": {},
801
+ "outputs": [
802
+ {
803
+ "data": {
804
+ "text/html": [
805
+ "<div>\n",
806
+ "<style scoped>\n",
807
+ " .dataframe tbody tr th:only-of-type {\n",
808
+ " vertical-align: middle;\n",
809
+ " }\n",
810
+ "\n",
811
+ " .dataframe tbody tr th {\n",
812
+ " vertical-align: top;\n",
813
+ " }\n",
814
+ "\n",
815
+ " .dataframe thead th {\n",
816
+ " text-align: right;\n",
817
+ " }\n",
818
+ "</style>\n",
819
+ "<table border=\"1\" class=\"dataframe\">\n",
820
+ " <thead>\n",
821
+ " <tr style=\"text-align: right;\">\n",
822
+ " <th></th>\n",
823
+ " <th>return_p</th>\n",
824
+ " <th>pct_p</th>\n",
825
+ " <th>return_b</th>\n",
826
+ " <th>pct_b</th>\n",
827
+ " <th>risk_p</th>\n",
828
+ " <th>risk_b</th>\n",
829
+ " <th>active_return</th>\n",
830
+ " <th>tracking_error</th>\n",
831
+ " <th>date</th>\n",
832
+ " <th>mkt_cap</th>\n",
833
+ " </tr>\n",
834
+ " </thead>\n",
835
+ " <tbody>\n",
836
+ " <tr>\n",
837
+ " <th>0</th>\n",
838
+ " <td>0.0</td>\n",
839
+ " <td>0.0</td>\n",
840
+ " <td>0.0</td>\n",
841
+ " <td>0.0</td>\n",
842
+ " <td>NaN</td>\n",
843
+ " <td>NaN</td>\n",
844
+ " <td>0.0</td>\n",
845
+ " <td>NaN</td>\n",
846
+ " <td>2021-01-05</td>\n",
847
+ " <td>600.0</td>\n",
848
+ " </tr>\n",
849
+ " </tbody>\n",
850
+ "</table>\n",
851
+ "</div>"
852
+ ],
853
+ "text/plain": [
854
+ " return_p pct_p return_b pct_b risk_p risk_b active_return \\\n",
855
+ "0 0.0 0.0 0.0 0.0 NaN NaN 0.0 \n",
856
+ "\n",
857
+ " tracking_error date mkt_cap \n",
858
+ "0 NaN 2021-01-05 600.0 "
859
+ ]
860
+ },
861
+ "execution_count": 216,
862
+ "metadata": {},
863
+ "output_type": "execute_result"
864
+ }
865
+ ],
866
+ "source": [
867
+ "# display row where mkt_cap is not nana\n",
868
+ "portfolio_risk_by_date_df[portfolio_risk_by_date_df['mkt_cap'].notna()]"
869
+ ]
870
+ },
871
+ {
872
+ "cell_type": "code",
873
+ "execution_count": null,
874
+ "metadata": {},
875
+ "outputs": [
876
+ {
877
+ "name": "stderr",
878
+ "output_type": "stream",
879
+ "text": [
880
+ "/var/folders/v5/2108rh5964q9j741wg_s8r1w0000gn/T/ipykernel_23255/2871737262.py:10: SettingWithCopyWarning: \n",
881
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
882
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
883
+ "\n",
884
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
885
+ " pct['weighted_pct'] = pct['pct'] * pct['norm_weight']\n"
886
+ ]
887
+ },
888
+ {
889
+ "data": {
890
+ "text/html": [
891
+ "<div>\n",
892
+ "<style scoped>\n",
893
+ " .dataframe tbody tr th:only-of-type {\n",
894
+ " vertical-align: middle;\n",
895
+ " }\n",
896
+ "\n",
897
+ " .dataframe tbody tr th {\n",
898
+ " vertical-align: top;\n",
899
+ " }\n",
900
+ "\n",
901
+ " .dataframe thead th {\n",
902
+ " text-align: right;\n",
903
+ " }\n",
904
+ "</style>\n",
905
+ "<table border=\"1\" class=\"dataframe\">\n",
906
+ " <thead>\n",
907
+ " <tr style=\"text-align: right;\">\n",
908
+ " <th></th>\n",
909
+ " <th>date</th>\n",
910
+ " <th>return</th>\n",
911
+ " <th>pct</th>\n",
912
+ " </tr>\n",
913
+ " </thead>\n",
914
+ " <tbody>\n",
915
+ " <tr>\n",
916
+ " <th>0</th>\n",
917
+ " <td>2021-01-05</td>\n",
918
+ " <td>0.000000</td>\n",
919
+ " <td>0.000000</td>\n",
920
+ " </tr>\n",
921
+ " <tr>\n",
922
+ " <th>1</th>\n",
923
+ " <td>2021-01-06</td>\n",
924
+ " <td>0.007011</td>\n",
925
+ " <td>0.036439</td>\n",
926
+ " </tr>\n",
927
+ " <tr>\n",
928
+ " <th>2</th>\n",
929
+ " <td>2021-01-07</td>\n",
930
+ " <td>0.047531</td>\n",
931
+ " <td>0.218707</td>\n",
932
+ " </tr>\n",
933
+ " <tr>\n",
934
+ " <th>3</th>\n",
935
+ " <td>2021-01-08</td>\n",
936
+ " <td>0.047111</td>\n",
937
+ " <td>0.013639</td>\n",
938
+ " </tr>\n",
939
+ " <tr>\n",
940
+ " <th>4</th>\n",
941
+ " <td>2021-01-11</td>\n",
942
+ " <td>0.052768</td>\n",
943
+ " <td>0.014559</td>\n",
944
+ " </tr>\n",
945
+ " <tr>\n",
946
+ " <th>...</th>\n",
947
+ " <td>...</td>\n",
948
+ " <td>...</td>\n",
949
+ " <td>...</td>\n",
950
+ " </tr>\n",
951
+ " <tr>\n",
952
+ " <th>242</th>\n",
953
+ " <td>2022-01-04</td>\n",
954
+ " <td>0.363845</td>\n",
955
+ " <td>-0.199827</td>\n",
956
+ " </tr>\n",
957
+ " <tr>\n",
958
+ " <th>243</th>\n",
959
+ " <td>2022-01-05</td>\n",
960
+ " <td>0.306697</td>\n",
961
+ " <td>-0.193598</td>\n",
962
+ " </tr>\n",
963
+ " <tr>\n",
964
+ " <th>244</th>\n",
965
+ " <td>2022-01-06</td>\n",
966
+ " <td>0.331291</td>\n",
967
+ " <td>0.023418</td>\n",
968
+ " </tr>\n",
969
+ " <tr>\n",
970
+ " <th>245</th>\n",
971
+ " <td>2022-01-07</td>\n",
972
+ " <td>0.313726</td>\n",
973
+ " <td>-0.080728</td>\n",
974
+ " </tr>\n",
975
+ " <tr>\n",
976
+ " <th>246</th>\n",
977
+ " <td>2022-01-10</td>\n",
978
+ " <td>0.313262</td>\n",
979
+ " <td>0.110254</td>\n",
980
+ " </tr>\n",
981
+ " </tbody>\n",
982
+ "</table>\n",
983
+ "<p>247 rows × 3 columns</p>\n",
984
+ "</div>"
985
+ ],
986
+ "text/plain": [
987
+ " date return pct\n",
988
+ "0 2021-01-05 0.000000 0.000000\n",
989
+ "1 2021-01-06 0.007011 0.036439\n",
990
+ "2 2021-01-07 0.047531 0.218707\n",
991
+ "3 2021-01-08 0.047111 0.013639\n",
992
+ "4 2021-01-11 0.052768 0.014559\n",
993
+ ".. ... ... ...\n",
994
+ "242 2022-01-04 0.363845 -0.199827\n",
995
+ "243 2022-01-05 0.306697 -0.193598\n",
996
+ "244 2022-01-06 0.331291 0.023418\n",
997
+ "245 2022-01-07 0.313726 -0.080728\n",
998
+ "246 2022-01-10 0.313262 0.110254\n",
999
+ "\n",
1000
+ "[247 rows x 3 columns]"
1001
+ ]
1002
+ },
1003
+ "execution_count": 191,
1004
+ "metadata": {},
1005
+ "output_type": "execute_result"
1006
+ }
1007
+ ],
1008
+ "source": [
1009
+ "## aggregate by date\n",
1010
+ "# step 7 aggregate (get portfolio return and pct(change of daily return))by date\n",
1011
+ "def create_agg_by_date(stock_df):\n",
1012
+ " # sum up weighted return to get return \n",
1013
+ " agg_return = stock_df.groupby(['date'])['weighted_return'].sum().reset_index()\n",
1014
+ " agg_return.rename(columns={'weighted_return':'return'}, inplace=True)\n",
1015
+ "\n",
1016
+ " # sum up weighted pct to get pct\n",
1017
+ " pct = stock_df[['date','pct','norm_weight','ticker']]\n",
1018
+ " pct['weighted_pct'] = pct['pct'] * pct['norm_weight']\n",
1019
+ " agg_pct = pct.groupby(['date'])['pct'].sum().reset_index()\n",
1020
+ "\n",
1021
+ " agg_df = pd.merge(agg_return, agg_pct, on='date', how='outer')\n",
1022
+ " return agg_df\n",
1023
+ "\n",
1024
+ "\n",
1025
+ "\n",
1026
+ "p_perform_result = create_agg_by_date(p_stock_df)\n",
1027
+ "p_perform_result"
1028
+ ]
1029
+ },
1030
+ {
1031
+ "cell_type": "code",
1032
+ "execution_count": null,
1033
+ "metadata": {},
1034
+ "outputs": [
1035
+ {
1036
+ "data": {
1037
+ "text/html": [
1038
+ "<div>\n",
1039
+ "<style scoped>\n",
1040
+ " .dataframe tbody tr th:only-of-type {\n",
1041
+ " vertical-align: middle;\n",
1042
+ " }\n",
1043
+ "\n",
1044
+ " .dataframe tbody tr th {\n",
1045
+ " vertical-align: top;\n",
1046
+ " }\n",
1047
+ "\n",
1048
+ " .dataframe thead th {\n",
1049
+ " text-align: right;\n",
1050
+ " }\n",
1051
+ "</style>\n",
1052
+ "<table border=\"1\" class=\"dataframe\">\n",
1053
+ " <thead>\n",
1054
+ " <tr style=\"text-align: right;\">\n",
1055
+ " <th></th>\n",
1056
+ " <th>ticker</th>\n",
1057
+ " <th>date</th>\n",
1058
+ " <th>open</th>\n",
1059
+ " <th>close</th>\n",
1060
+ " <th>high</th>\n",
1061
+ " <th>low</th>\n",
1062
+ " <th>volume</th>\n",
1063
+ " <th>money</th>\n",
1064
+ " <th>pct</th>\n",
1065
+ " <th>weight</th>\n",
1066
+ " <th>return</th>\n",
1067
+ " <th>norm_weight</th>\n",
1068
+ " <th>weighted_return</th>\n",
1069
+ " <th>aggregate_sector</th>\n",
1070
+ " <th>display_name</th>\n",
1071
+ " </tr>\n",
1072
+ " </thead>\n",
1073
+ " <tbody>\n",
1074
+ " <tr>\n",
1075
+ " <th>1452</th>\n",
1076
+ " <td>603882.XSHG</td>\n",
1077
+ " <td>2022-01-04</td>\n",
1078
+ " <td>106.89</td>\n",
1079
+ " <td>98.84</td>\n",
1080
+ " <td>106.89</td>\n",
1081
+ " <td>98.67</td>\n",
1082
+ " <td>5140406.0</td>\n",
1083
+ " <td>5.181929e+08</td>\n",
1084
+ " <td>-0.076262</td>\n",
1085
+ " <td>79.300385</td>\n",
1086
+ " <td>-0.206996</td>\n",
1087
+ " <td>0.107586</td>\n",
1088
+ " <td>-0.022270</td>\n",
1089
+ " <td>医药卫生</td>\n",
1090
+ " <td>金域医学</td>\n",
1091
+ " </tr>\n",
1092
+ " <tr>\n",
1093
+ " <th>1453</th>\n",
1094
+ " <td>002709.XSHE</td>\n",
1095
+ " <td>2022-01-04</td>\n",
1096
+ " <td>57.64</td>\n",
1097
+ " <td>54.64</td>\n",
1098
+ " <td>57.87</td>\n",
1099
+ " <td>54.29</td>\n",
1100
+ " <td>42150916.0</td>\n",
1101
+ " <td>2.333429e+09</td>\n",
1102
+ " <td>-0.028277</td>\n",
1103
+ " <td>161.227501</td>\n",
1104
+ " <td>0.612275</td>\n",
1105
+ " <td>0.218735</td>\n",
1106
+ " <td>0.133926</td>\n",
1107
+ " <td>工业</td>\n",
1108
+ " <td>天赐材料</td>\n",
1109
+ " </tr>\n",
1110
+ " <tr>\n",
1111
+ " <th>1454</th>\n",
1112
+ " <td>600409.XSHG</td>\n",
1113
+ " <td>2022-01-04</td>\n",
1114
+ " <td>8.16</td>\n",
1115
+ " <td>8.21</td>\n",
1116
+ " <td>8.25</td>\n",
1117
+ " <td>8.15</td>\n",
1118
+ " <td>27288613.0</td>\n",
1119
+ " <td>2.237925e+08</td>\n",
1120
+ " <td>0.007362</td>\n",
1121
+ " <td>85.788924</td>\n",
1122
+ " <td>-0.142111</td>\n",
1123
+ " <td>0.116389</td>\n",
1124
+ " <td>-0.016540</td>\n",
1125
+ " <td>原料与能源</td>\n",
1126
+ " <td>三友化工</td>\n",
1127
+ " </tr>\n",
1128
+ " <tr>\n",
1129
+ " <th>1455</th>\n",
1130
+ " <td>002920.XSHE</td>\n",
1131
+ " <td>2022-01-04</td>\n",
1132
+ " <td>139.71</td>\n",
1133
+ " <td>131.69</td>\n",
1134
+ " <td>140.91</td>\n",
1135
+ " <td>131.45</td>\n",
1136
+ " <td>5410083.0</td>\n",
1137
+ " <td>7.233361e+08</td>\n",
1138
+ " <td>-0.060833</td>\n",
1139
+ " <td>150.934097</td>\n",
1140
+ " <td>0.509341</td>\n",
1141
+ " <td>0.204770</td>\n",
1142
+ " <td>0.104298</td>\n",
1143
+ " <td>信息与通信</td>\n",
1144
+ " <td>德赛西威</td>\n",
1145
+ " </tr>\n",
1146
+ " <tr>\n",
1147
+ " <th>1456</th>\n",
1148
+ " <td>300274.XSHE</td>\n",
1149
+ " <td>2022-01-04</td>\n",
1150
+ " <td>146.52</td>\n",
1151
+ " <td>134.96</td>\n",
1152
+ " <td>148.46</td>\n",
1153
+ " <td>134.61</td>\n",
1154
+ " <td>24205007.0</td>\n",
1155
+ " <td>3.333125e+09</td>\n",
1156
+ " <td>-0.071291</td>\n",
1157
+ " <td>176.533682</td>\n",
1158
+ " <td>0.765337</td>\n",
1159
+ " <td>0.239501</td>\n",
1160
+ " <td>0.183299</td>\n",
1161
+ " <td>工业</td>\n",
1162
+ " <td>阳光电源</td>\n",
1163
+ " </tr>\n",
1164
+ " <tr>\n",
1165
+ " <th>1457</th>\n",
1166
+ " <td>600415.XSHG</td>\n",
1167
+ " <td>2022-01-04</td>\n",
1168
+ " <td>4.80</td>\n",
1169
+ " <td>4.89</td>\n",
1170
+ " <td>4.90</td>\n",
1171
+ " <td>4.78</td>\n",
1172
+ " <td>58291943.0</td>\n",
1173
+ " <td>2.832956e+08</td>\n",
1174
+ " <td>0.029474</td>\n",
1175
+ " <td>83.304940</td>\n",
1176
+ " <td>-0.166951</td>\n",
1177
+ " <td>0.113019</td>\n",
1178
+ " <td>-0.018869</td>\n",
1179
+ " <td>消费</td>\n",
1180
+ " <td>小商品城</td>\n",
1181
+ " </tr>\n",
1182
+ " </tbody>\n",
1183
+ "</table>\n",
1184
+ "</div>"
1185
+ ],
1186
+ "text/plain": [
1187
+ " ticker date open close high low volume \\\n",
1188
+ "1452 603882.XSHG 2022-01-04 106.89 98.84 106.89 98.67 5140406.0 \n",
1189
+ "1453 002709.XSHE 2022-01-04 57.64 54.64 57.87 54.29 42150916.0 \n",
1190
+ "1454 600409.XSHG 2022-01-04 8.16 8.21 8.25 8.15 27288613.0 \n",
1191
+ "1455 002920.XSHE 2022-01-04 139.71 131.69 140.91 131.45 5410083.0 \n",
1192
+ "1456 300274.XSHE 2022-01-04 146.52 134.96 148.46 134.61 24205007.0 \n",
1193
+ "1457 600415.XSHG 2022-01-04 4.80 4.89 4.90 4.78 58291943.0 \n",
1194
+ "\n",
1195
+ " money pct weight return norm_weight \\\n",
1196
+ "1452 5.181929e+08 -0.076262 79.300385 -0.206996 0.107586 \n",
1197
+ "1453 2.333429e+09 -0.028277 161.227501 0.612275 0.218735 \n",
1198
+ "1454 2.237925e+08 0.007362 85.788924 -0.142111 0.116389 \n",
1199
+ "1455 7.233361e+08 -0.060833 150.934097 0.509341 0.204770 \n",
1200
+ "1456 3.333125e+09 -0.071291 176.533682 0.765337 0.239501 \n",
1201
+ "1457 2.832956e+08 0.029474 83.304940 -0.166951 0.113019 \n",
1202
+ "\n",
1203
+ " weighted_return aggregate_sector display_name \n",
1204
+ "1452 -0.022270 医药卫生 金域医学 \n",
1205
+ "1453 0.133926 工业 天赐材料 \n",
1206
+ "1454 -0.016540 原料与能源 三友化工 \n",
1207
+ "1455 0.104298 信息与通信 德赛西威 \n",
1208
+ "1456 0.183299 工业 阳光电源 \n",
1209
+ "1457 -0.018869 消费 小商品城 "
1210
+ ]
1211
+ },
1212
+ "execution_count": 194,
1213
+ "metadata": {},
1214
+ "output_type": "execute_result"
1215
+ }
1216
+ ],
1217
+ "source": [
1218
+ "p_stock_df[p_stock_df.date==datetime(2022,1,4)]"
1219
+ ]
1220
+ },
1221
+ {
1222
+ "cell_type": "code",
1223
+ "execution_count": null,
1224
+ "metadata": {},
1225
+ "outputs": [
1226
+ {
1227
+ "data": {
1228
+ "text/html": [
1229
+ "<div>\n",
1230
+ "<style scoped>\n",
1231
+ " .dataframe tbody tr th:only-of-type {\n",
1232
+ " vertical-align: middle;\n",
1233
+ " }\n",
1234
+ "\n",
1235
+ " .dataframe tbody tr th {\n",
1236
+ " vertical-align: top;\n",
1237
+ " }\n",
1238
+ "\n",
1239
+ " .dataframe thead th {\n",
1240
+ " text-align: right;\n",
1241
+ " }\n",
1242
+ "</style>\n",
1243
+ "<table border=\"1\" class=\"dataframe\">\n",
1244
+ " <thead>\n",
1245
+ " <tr style=\"text-align: right;\">\n",
1246
+ " <th></th>\n",
1247
+ " <th>date</th>\n",
1248
+ " <th>mkt_cap</th>\n",
1249
+ " </tr>\n",
1250
+ " </thead>\n",
1251
+ " <tbody>\n",
1252
+ " <tr>\n",
1253
+ " <th>0</th>\n",
1254
+ " <td>2021-01-05</td>\n",
1255
+ " <td>600</td>\n",
1256
+ " </tr>\n",
1257
+ " <tr>\n",
1258
+ " <th>1</th>\n",
1259
+ " <td>2021-01-10</td>\n",
1260
+ " <td>1550</td>\n",
1261
+ " </tr>\n",
1262
+ " </tbody>\n",
1263
+ "</table>\n",
1264
+ "</div>"
1265
+ ],
1266
+ "text/plain": [
1267
+ " date mkt_cap\n",
1268
+ "0 2021-01-05 600\n",
1269
+ "1 2021-01-10 1550"
1270
+ ]
1271
+ },
1272
+ "execution_count": 102,
1273
+ "metadata": {},
1274
+ "output_type": "execute_result"
1275
+ }
1276
+ ],
1277
+ "source": [
1278
+ "mkt_cap_df = pd.DataFrame(profile_df.groupby(['date'])['weight'].sum()).reset_index()\n",
1279
+ "mkt_cap_df.rename(columns={'weight':'mkt_cap'}, inplace=True)\n",
1280
+ "mkt_cap_df"
1281
+ ]
1282
+ },
1283
+ {
1284
+ "cell_type": "code",
1285
+ "execution_count": null,
1286
+ "metadata": {},
1287
+ "outputs": [
1288
+ {
1289
+ "data": {
1290
+ "text/html": [
1291
+ "<div>\n",
1292
+ "<style scoped>\n",
1293
+ " .dataframe tbody tr th:only-of-type {\n",
1294
+ " vertical-align: middle;\n",
1295
+ " }\n",
1296
+ "\n",
1297
+ " .dataframe tbody tr th {\n",
1298
+ " vertical-align: top;\n",
1299
+ " }\n",
1300
+ "\n",
1301
+ " .dataframe thead th {\n",
1302
+ " text-align: right;\n",
1303
+ " }\n",
1304
+ "</style>\n",
1305
+ "<table border=\"1\" class=\"dataframe\">\n",
1306
+ " <thead>\n",
1307
+ " <tr style=\"text-align: right;\">\n",
1308
+ " <th></th>\n",
1309
+ " <th>date</th>\n",
1310
+ " <th>return</th>\n",
1311
+ " <th>pct</th>\n",
1312
+ " <th>mkt_cap</th>\n",
1313
+ " </tr>\n",
1314
+ " </thead>\n",
1315
+ " <tbody>\n",
1316
+ " <tr>\n",
1317
+ " <th>0</th>\n",
1318
+ " <td>2021-01-05</td>\n",
1319
+ " <td>0.000000</td>\n",
1320
+ " <td>0.000000</td>\n",
1321
+ " <td>600.000000</td>\n",
1322
+ " </tr>\n",
1323
+ " <tr>\n",
1324
+ " <th>1</th>\n",
1325
+ " <td>2021-01-06</td>\n",
1326
+ " <td>0.007011</td>\n",
1327
+ " <td>0.036439</td>\n",
1328
+ " <td>621.863161</td>\n",
1329
+ " </tr>\n",
1330
+ " <tr>\n",
1331
+ " <th>2</th>\n",
1332
+ " <td>2021-01-07</td>\n",
1333
+ " <td>0.047531</td>\n",
1334
+ " <td>0.218707</td>\n",
1335
+ " <td>757.869005</td>\n",
1336
+ " </tr>\n",
1337
+ " <tr>\n",
1338
+ " <th>3</th>\n",
1339
+ " <td>2021-01-08</td>\n",
1340
+ " <td>0.047111</td>\n",
1341
+ " <td>0.013639</td>\n",
1342
+ " <td>768.205269</td>\n",
1343
+ " </tr>\n",
1344
+ " </tbody>\n",
1345
+ "</table>\n",
1346
+ "</div>"
1347
+ ],
1348
+ "text/plain": [
1349
+ " date return pct mkt_cap\n",
1350
+ "0 2021-01-05 0.000000 0.000000 600.000000\n",
1351
+ "1 2021-01-06 0.007011 0.036439 621.863161\n",
1352
+ "2 2021-01-07 0.047531 0.218707 757.869005\n",
1353
+ "3 2021-01-08 0.047111 0.013639 768.205269"
1354
+ ]
1355
+ },
1356
+ "execution_count": 103,
1357
+ "metadata": {},
1358
+ "output_type": "execute_result"
1359
+ }
1360
+ ],
1361
+ "source": [
1362
+ "# get mkt adjustment (weight is the fund in a stock)\n",
1363
+ "mkt_adjustment = pd.DataFrame(profile_df.groupby(['date'])['weight'].sum()).reset_index()\n",
1364
+ "mkt_adjustment.rename(columns={'weight':'mkt_cap'}, inplace=True)\n",
1365
+ "merge_df = p_perform_result.merge(mkt_adjustment, on='date', how='outer')\n",
1366
+ "\n",
1367
+ "\n",
1368
+ "for i in range(1, len(merge_df)):\n",
1369
+ " merge_df.loc[i, 'mkt_cap'] = merge_df.loc[i-1, 'mkt_cap'] * (1 + merge_df.loc[i, 'pct'])\n",
1370
+ "\n",
1371
+ "# # calculate daily mkt_cap\n",
1372
+ "# # initial_mkt_cap = merge_df.loc[0, 'mkt_cap']\n",
1373
+ "# for i in range(1, len(merge_df)):\n",
1374
+ "# row = merge_df.loc[i]\n",
1375
+ "# if pd.isna(row['mkt_cap']):\n",
1376
+ "# merge_df.loc[i, 'mkt_cap'] = merge_df.loc[i-1, 'mkt_cap'] * (1 + merge_df.loc[i, 'pct_portfolio'])\n",
1377
+ " \n",
1378
+ "# # step 8 calculate daily mkt cap\n",
1379
+ "\n",
1380
+ "merge_df[merge_df.date < datetime(2021,1,10)]"
1381
+ ]
1382
+ },
1383
+ {
1384
+ "cell_type": "code",
1385
+ "execution_count": null,
1386
+ "metadata": {},
1387
+ "outputs": [
1388
+ {
1389
+ "data": {
1390
+ "text/html": [
1391
+ "<div>\n",
1392
+ "<style scoped>\n",
1393
+ " .dataframe tbody tr th:only-of-type {\n",
1394
+ " vertical-align: middle;\n",
1395
+ " }\n",
1396
+ "\n",
1397
+ " .dataframe tbody tr th {\n",
1398
+ " vertical-align: top;\n",
1399
+ " }\n",
1400
+ "\n",
1401
+ " .dataframe thead th {\n",
1402
+ " text-align: right;\n",
1403
+ " }\n",
1404
+ "</style>\n",
1405
+ "<table border=\"1\" class=\"dataframe\">\n",
1406
+ " <thead>\n",
1407
+ " <tr style=\"text-align: right;\">\n",
1408
+ " <th></th>\n",
1409
+ " <th>ticker</th>\n",
1410
+ " <th>date</th>\n",
1411
+ " <th>open</th>\n",
1412
+ " <th>close</th>\n",
1413
+ " <th>high</th>\n",
1414
+ " <th>low</th>\n",
1415
+ " <th>volume</th>\n",
1416
+ " <th>money</th>\n",
1417
+ " <th>pct</th>\n",
1418
+ " <th>weight</th>\n",
1419
+ " <th>return</th>\n",
1420
+ " <th>norm_weight</th>\n",
1421
+ " <th>weighted_return</th>\n",
1422
+ " <th>aggregate_sector</th>\n",
1423
+ " <th>display_name</th>\n",
1424
+ " </tr>\n",
1425
+ " </thead>\n",
1426
+ " <tbody>\n",
1427
+ " <tr>\n",
1428
+ " <th>0</th>\n",
1429
+ " <td>002709.XSHE</td>\n",
1430
+ " <td>2021-01-05</td>\n",
1431
+ " <td>32.54</td>\n",
1432
+ " <td>33.89</td>\n",
1433
+ " <td>34.22</td>\n",
1434
+ " <td>31.39</td>\n",
1435
+ " <td>59152352.0</td>\n",
1436
+ " <td>1.942406e+09</td>\n",
1437
+ " <td>NaN</td>\n",
1438
+ " <td>100.000000</td>\n",
1439
+ " <td>0.000000</td>\n",
1440
+ " <td>0.166667</td>\n",
1441
+ " <td>0.000000</td>\n",
1442
+ " <td>工业</td>\n",
1443
+ " <td>天赐材料</td>\n",
1444
+ " </tr>\n",
1445
+ " <tr>\n",
1446
+ " <th>1</th>\n",
1447
+ " <td>600415.XSHG</td>\n",
1448
+ " <td>2021-01-05</td>\n",
1449
+ " <td>5.33</td>\n",
1450
+ " <td>5.87</td>\n",
1451
+ " <td>5.87</td>\n",
1452
+ " <td>5.22</td>\n",
1453
+ " <td>180936477.0</td>\n",
1454
+ " <td>1.010225e+09</td>\n",
1455
+ " <td>NaN</td>\n",
1456
+ " <td>100.000000</td>\n",
1457
+ " <td>0.000000</td>\n",
1458
+ " <td>0.166667</td>\n",
1459
+ " <td>0.000000</td>\n",
1460
+ " <td>消费</td>\n",
1461
+ " <td>小商品城</td>\n",
1462
+ " </tr>\n",
1463
+ " <tr>\n",
1464
+ " <th>2</th>\n",
1465
+ " <td>600409.XSHG</td>\n",
1466
+ " <td>2021-01-05</td>\n",
1467
+ " <td>9.23</td>\n",
1468
+ " <td>9.57</td>\n",
1469
+ " <td>9.66</td>\n",
1470
+ " <td>9.08</td>\n",
1471
+ " <td>82669289.0</td>\n",
1472
+ " <td>7.803391e+08</td>\n",
1473
+ " <td>NaN</td>\n",
1474
+ " <td>100.000000</td>\n",
1475
+ " <td>0.000000</td>\n",
1476
+ " <td>0.166667</td>\n",
1477
+ " <td>0.000000</td>\n",
1478
+ " <td>原料与能源</td>\n",
1479
+ " <td>三友化工</td>\n",
1480
+ " </tr>\n",
1481
+ " <tr>\n",
1482
+ " <th>3</th>\n",
1483
+ " <td>300274.XSHE</td>\n",
1484
+ " <td>2021-01-05</td>\n",
1485
+ " <td>76.03</td>\n",
1486
+ " <td>76.45</td>\n",
1487
+ " <td>80.20</td>\n",
1488
+ " <td>75.27</td>\n",
1489
+ " <td>51384827.0</td>\n",
1490
+ " <td>3.961995e+09</td>\n",
1491
+ " <td>NaN</td>\n",
1492
+ " <td>100.000000</td>\n",
1493
+ " <td>0.000000</td>\n",
1494
+ " <td>0.166667</td>\n",
1495
+ " <td>0.000000</td>\n",
1496
+ " <td>工业</td>\n",
1497
+ " <td>阳光电源</td>\n",
1498
+ " </tr>\n",
1499
+ " <tr>\n",
1500
+ " <th>4</th>\n",
1501
+ " <td>002920.XSHE</td>\n",
1502
+ " <td>2021-01-05</td>\n",
1503
+ " <td>85.44</td>\n",
1504
+ " <td>87.25</td>\n",
1505
+ " <td>87.95</td>\n",
1506
+ " <td>84.07</td>\n",
1507
+ " <td>3852674.0</td>\n",
1508
+ " <td>3.322598e+08</td>\n",
1509
+ " <td>NaN</td>\n",
1510
+ " <td>100.000000</td>\n",
1511
+ " <td>0.000000</td>\n",
1512
+ " <td>0.166667</td>\n",
1513
+ " <td>0.000000</td>\n",
1514
+ " <td>信息与通信</td>\n",
1515
+ " <td>德赛西威</td>\n",
1516
+ " </tr>\n",
1517
+ " <tr>\n",
1518
+ " <th>...</th>\n",
1519
+ " <td>...</td>\n",
1520
+ " <td>...</td>\n",
1521
+ " <td>...</td>\n",
1522
+ " <td>...</td>\n",
1523
+ " <td>...</td>\n",
1524
+ " <td>...</td>\n",
1525
+ " <td>...</td>\n",
1526
+ " <td>...</td>\n",
1527
+ " <td>...</td>\n",
1528
+ " <td>...</td>\n",
1529
+ " <td>...</td>\n",
1530
+ " <td>...</td>\n",
1531
+ " <td>...</td>\n",
1532
+ " <td>...</td>\n",
1533
+ " <td>...</td>\n",
1534
+ " </tr>\n",
1535
+ " <tr>\n",
1536
+ " <th>1477</th>\n",
1537
+ " <td>600409.XSHG</td>\n",
1538
+ " <td>2022-01-10</td>\n",
1539
+ " <td>8.24</td>\n",
1540
+ " <td>8.35</td>\n",
1541
+ " <td>8.39</td>\n",
1542
+ " <td>8.21</td>\n",
1543
+ " <td>32516017.0</td>\n",
1544
+ " <td>2.699300e+08</td>\n",
1545
+ " <td>0.015815</td>\n",
1546
+ " <td>87.251829</td>\n",
1547
+ " <td>-0.127482</td>\n",
1548
+ " <td>0.121949</td>\n",
1549
+ " <td>-0.015546</td>\n",
1550
+ " <td>原料与能源</td>\n",
1551
+ " <td>三友化工</td>\n",
1552
+ " </tr>\n",
1553
+ " <tr>\n",
1554
+ " <th>1478</th>\n",
1555
+ " <td>002920.XSHE</td>\n",
1556
+ " <td>2022-01-10</td>\n",
1557
+ " <td>130.36</td>\n",
1558
+ " <td>138.43</td>\n",
1559
+ " <td>141.96</td>\n",
1560
+ " <td>130.11</td>\n",
1561
+ " <td>5005400.0</td>\n",
1562
+ " <td>6.901614e+08</td>\n",
1563
+ " <td>0.046888</td>\n",
1564
+ " <td>158.659026</td>\n",
1565
+ " <td>0.586590</td>\n",
1566
+ " <td>0.221752</td>\n",
1567
+ " <td>0.130077</td>\n",
1568
+ " <td>信息与通信</td>\n",
1569
+ " <td>德赛西威</td>\n",
1570
+ " </tr>\n",
1571
+ " <tr>\n",
1572
+ " <th>1479</th>\n",
1573
+ " <td>002709.XSHE</td>\n",
1574
+ " <td>2022-01-10</td>\n",
1575
+ " <td>51.63</td>\n",
1576
+ " <td>50.73</td>\n",
1577
+ " <td>51.93</td>\n",
1578
+ " <td>50.03</td>\n",
1579
+ " <td>29821246.0</td>\n",
1580
+ " <td>1.518902e+09</td>\n",
1581
+ " <td>-0.019142</td>\n",
1582
+ " <td>149.690174</td>\n",
1583
+ " <td>0.496902</td>\n",
1584
+ " <td>0.209216</td>\n",
1585
+ " <td>0.103960</td>\n",
1586
+ " <td>工业</td>\n",
1587
+ " <td>天赐材料</td>\n",
1588
+ " </tr>\n",
1589
+ " <tr>\n",
1590
+ " <th>1480</th>\n",
1591
+ " <td>600415.XSHG</td>\n",
1592
+ " <td>2022-01-10</td>\n",
1593
+ " <td>4.70</td>\n",
1594
+ " <td>4.75</td>\n",
1595
+ " <td>4.85</td>\n",
1596
+ " <td>4.67</td>\n",
1597
+ " <td>39278041.0</td>\n",
1598
+ " <td>1.859827e+08</td>\n",
1599
+ " <td>0.010638</td>\n",
1600
+ " <td>80.919932</td>\n",
1601
+ " <td>-0.190801</td>\n",
1602
+ " <td>0.113099</td>\n",
1603
+ " <td>-0.021579</td>\n",
1604
+ " <td>消费</td>\n",
1605
+ " <td>小商品城</td>\n",
1606
+ " </tr>\n",
1607
+ " <tr>\n",
1608
+ " <th>1481</th>\n",
1609
+ " <td>603882.XSHG</td>\n",
1610
+ " <td>2022-01-10</td>\n",
1611
+ " <td>88.45</td>\n",
1612
+ " <td>95.53</td>\n",
1613
+ " <td>95.59</td>\n",
1614
+ " <td>88.39</td>\n",
1615
+ " <td>6991445.0</td>\n",
1616
+ " <td>6.468392e+08</td>\n",
1617
+ " <td>0.085692</td>\n",
1618
+ " <td>76.644737</td>\n",
1619
+ " <td>-0.233553</td>\n",
1620
+ " <td>0.107123</td>\n",
1621
+ " <td>-0.025019</td>\n",
1622
+ " <td>医药卫生</td>\n",
1623
+ " <td>金域医学</td>\n",
1624
+ " </tr>\n",
1625
+ " </tbody>\n",
1626
+ "</table>\n",
1627
+ "<p>1482 rows × 15 columns</p>\n",
1628
+ "</div>"
1629
+ ],
1630
+ "text/plain": [
1631
+ " ticker date open close high low volume \\\n",
1632
+ "0 002709.XSHE 2021-01-05 32.54 33.89 34.22 31.39 59152352.0 \n",
1633
+ "1 600415.XSHG 2021-01-05 5.33 5.87 5.87 5.22 180936477.0 \n",
1634
+ "2 600409.XSHG 2021-01-05 9.23 9.57 9.66 9.08 82669289.0 \n",
1635
+ "3 300274.XSHE 2021-01-05 76.03 76.45 80.20 75.27 51384827.0 \n",
1636
+ "4 002920.XSHE 2021-01-05 85.44 87.25 87.95 84.07 3852674.0 \n",
1637
+ "... ... ... ... ... ... ... ... \n",
1638
+ "1477 600409.XSHG 2022-01-10 8.24 8.35 8.39 8.21 32516017.0 \n",
1639
+ "1478 002920.XSHE 2022-01-10 130.36 138.43 141.96 130.11 5005400.0 \n",
1640
+ "1479 002709.XSHE 2022-01-10 51.63 50.73 51.93 50.03 29821246.0 \n",
1641
+ "1480 600415.XSHG 2022-01-10 4.70 4.75 4.85 4.67 39278041.0 \n",
1642
+ "1481 603882.XSHG 2022-01-10 88.45 95.53 95.59 88.39 6991445.0 \n",
1643
+ "\n",
1644
+ " money pct weight return norm_weight \\\n",
1645
+ "0 1.942406e+09 NaN 100.000000 0.000000 0.166667 \n",
1646
+ "1 1.010225e+09 NaN 100.000000 0.000000 0.166667 \n",
1647
+ "2 7.803391e+08 NaN 100.000000 0.000000 0.166667 \n",
1648
+ "3 3.961995e+09 NaN 100.000000 0.000000 0.166667 \n",
1649
+ "4 3.322598e+08 NaN 100.000000 0.000000 0.166667 \n",
1650
+ "... ... ... ... ... ... \n",
1651
+ "1477 2.699300e+08 0.015815 87.251829 -0.127482 0.121949 \n",
1652
+ "1478 6.901614e+08 0.046888 158.659026 0.586590 0.221752 \n",
1653
+ "1479 1.518902e+09 -0.019142 149.690174 0.496902 0.209216 \n",
1654
+ "1480 1.859827e+08 0.010638 80.919932 -0.190801 0.113099 \n",
1655
+ "1481 6.468392e+08 0.085692 76.644737 -0.233553 0.107123 \n",
1656
+ "\n",
1657
+ " weighted_return aggregate_sector display_name \n",
1658
+ "0 0.000000 工业 天赐材料 \n",
1659
+ "1 0.000000 消费 小商品城 \n",
1660
+ "2 0.000000 原料与能源 三友化工 \n",
1661
+ "3 0.000000 ��业 阳光电源 \n",
1662
+ "4 0.000000 信息与通信 德赛西威 \n",
1663
+ "... ... ... ... \n",
1664
+ "1477 -0.015546 原料与能源 三友化工 \n",
1665
+ "1478 0.130077 信息与通信 德赛西威 \n",
1666
+ "1479 0.103960 工业 天赐材料 \n",
1667
+ "1480 -0.021579 消费 小商品城 \n",
1668
+ "1481 -0.025019 医药卫生 金域医学 \n",
1669
+ "\n",
1670
+ "[1482 rows x 15 columns]"
1671
+ ]
1672
+ },
1673
+ "execution_count": 127,
1674
+ "metadata": {},
1675
+ "output_type": "execute_result"
1676
+ }
1677
+ ],
1678
+ "source": [
1679
+ "## agg by sector and day\n",
1680
+ "p_stock_df['weight_in_sector'] = p_stock_df.groupby"
1681
+ ]
1682
+ },
1683
+ {
1684
+ "cell_type": "code",
1685
+ "execution_count": null,
1686
+ "metadata": {},
1687
+ "outputs": [],
1688
+ "source": [
1689
+ "def creaet_portfolio_return(stock_df):\n",
1690
+ " portfolio_df = stock_df.groupby(['date'])['weighted_return'].sum().reset_index()\n",
1691
+ " portfolio_df.rename(columns={'weighted_return':'portfolio_return'}, inplace=True)\n",
1692
+ " return portfolio_df"
1693
+ ]
1694
+ },
1695
+ {
1696
+ "cell_type": "code",
1697
+ "execution_count": null,
1698
+ "metadata": {},
1699
+ "outputs": [
1700
+ {
1701
+ "data": {
1702
+ "text/html": [
1703
+ "<div>\n",
1704
+ "<style scoped>\n",
1705
+ " .dataframe tbody tr th:only-of-type {\n",
1706
+ " vertical-align: middle;\n",
1707
+ " }\n",
1708
+ "\n",
1709
+ " .dataframe tbody tr th {\n",
1710
+ " vertical-align: top;\n",
1711
+ " }\n",
1712
+ "\n",
1713
+ " .dataframe thead th {\n",
1714
+ " text-align: right;\n",
1715
+ " }\n",
1716
+ "</style>\n",
1717
+ "<table border=\"1\" class=\"dataframe\">\n",
1718
+ " <thead>\n",
1719
+ " <tr style=\"text-align: right;\">\n",
1720
+ " <th></th>\n",
1721
+ " <th>date</th>\n",
1722
+ " <th>portfolio_return</th>\n",
1723
+ " </tr>\n",
1724
+ " </thead>\n",
1725
+ " <tbody>\n",
1726
+ " <tr>\n",
1727
+ " <th>0</th>\n",
1728
+ " <td>2021-01-05</td>\n",
1729
+ " <td>0.000000</td>\n",
1730
+ " </tr>\n",
1731
+ " <tr>\n",
1732
+ " <th>1</th>\n",
1733
+ " <td>2021-01-06</td>\n",
1734
+ " <td>0.007011</td>\n",
1735
+ " </tr>\n",
1736
+ " <tr>\n",
1737
+ " <th>2</th>\n",
1738
+ " <td>2021-01-07</td>\n",
1739
+ " <td>0.047531</td>\n",
1740
+ " </tr>\n",
1741
+ " <tr>\n",
1742
+ " <th>3</th>\n",
1743
+ " <td>2021-01-08</td>\n",
1744
+ " <td>0.047111</td>\n",
1745
+ " </tr>\n",
1746
+ " <tr>\n",
1747
+ " <th>4</th>\n",
1748
+ " <td>2021-01-11</td>\n",
1749
+ " <td>0.052768</td>\n",
1750
+ " </tr>\n",
1751
+ " <tr>\n",
1752
+ " <th>...</th>\n",
1753
+ " <td>...</td>\n",
1754
+ " <td>...</td>\n",
1755
+ " </tr>\n",
1756
+ " <tr>\n",
1757
+ " <th>242</th>\n",
1758
+ " <td>2022-01-04</td>\n",
1759
+ " <td>0.363845</td>\n",
1760
+ " </tr>\n",
1761
+ " <tr>\n",
1762
+ " <th>243</th>\n",
1763
+ " <td>2022-01-05</td>\n",
1764
+ " <td>0.306697</td>\n",
1765
+ " </tr>\n",
1766
+ " <tr>\n",
1767
+ " <th>244</th>\n",
1768
+ " <td>2022-01-06</td>\n",
1769
+ " <td>0.331291</td>\n",
1770
+ " </tr>\n",
1771
+ " <tr>\n",
1772
+ " <th>245</th>\n",
1773
+ " <td>2022-01-07</td>\n",
1774
+ " <td>0.313726</td>\n",
1775
+ " </tr>\n",
1776
+ " <tr>\n",
1777
+ " <th>246</th>\n",
1778
+ " <td>2022-01-10</td>\n",
1779
+ " <td>0.313262</td>\n",
1780
+ " </tr>\n",
1781
+ " </tbody>\n",
1782
+ "</table>\n",
1783
+ "<p>247 rows × 2 columns</p>\n",
1784
+ "</div>"
1785
+ ],
1786
+ "text/plain": [
1787
+ " date portfolio_return\n",
1788
+ "0 2021-01-05 0.000000\n",
1789
+ "1 2021-01-06 0.007011\n",
1790
+ "2 2021-01-07 0.047531\n",
1791
+ "3 2021-01-08 0.047111\n",
1792
+ "4 2021-01-11 0.052768\n",
1793
+ ".. ... ...\n",
1794
+ "242 2022-01-04 0.363845\n",
1795
+ "243 2022-01-05 0.306697\n",
1796
+ "244 2022-01-06 0.331291\n",
1797
+ "245 2022-01-07 0.313726\n",
1798
+ "246 2022-01-10 0.313262\n",
1799
+ "\n",
1800
+ "[247 rows x 2 columns]"
1801
+ ]
1802
+ },
1803
+ "execution_count": 58,
1804
+ "metadata": {},
1805
+ "output_type": "execute_result"
1806
+ }
1807
+ ],
1808
+ "source": [
1809
+ "portfolio_df = creaet_portfolio_return(p_stock_df)\n",
1810
+ "portfolio_df"
1811
+ ]
1812
+ }
1813
+ ],
1814
+ "metadata": {
1815
+ "kernelspec": {
1816
+ "display_name": "portfolio_risk_assesment",
1817
+ "language": "python",
1818
+ "name": "python3"
1819
+ },
1820
+ "language_info": {
1821
+ "codemirror_mode": {
1822
+ "name": "ipython",
1823
+ "version": 3
1824
+ },
1825
+ "file_extension": ".py",
1826
+ "mimetype": "text/x-python",
1827
+ "name": "python",
1828
+ "nbconvert_exporter": "python",
1829
+ "pygments_lexer": "ipython3",
1830
+ "version": "3.11.4"
1831
+ },
1832
+ "orig_nbformat": 4
1833
+ },
1834
+ "nbformat": 4,
1835
+ "nbformat_minor": 2
1836
+ }
script/stream_pricessing.ipynb CHANGED
@@ -1,3 +1,617 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:a654077c9f8cfd02c5894f6905f2156c814e6421b612c94f170363b8ee793e85
3
- size 41951
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 2,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "data": {
10
+ "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n var py_version = '3.1.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n var reloading = false;\n var Bokeh = root.Bokeh;\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n run_callbacks();\n return null;\n }\n if (!reloading) {\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n var skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n require([\"jspanel\"], function(jsPanel) {\n\twindow.jsPanel = jsPanel\n\ton_load()\n })\n require([\"jspanel-modal\"], function() {\n\ton_load()\n })\n require([\"jspanel-tooltip\"], function() {\n\ton_load()\n })\n require([\"jspanel-hint\"], function() {\n\ton_load()\n })\n require([\"jspanel-layout\"], function() {\n\ton_load()\n })\n require([\"jspanel-contextmenu\"], function() {\n\ton_load()\n })\n require([\"jspanel-dock\"], function() {\n\ton_load()\n })\n require([\"gridstack\"], function(GridStack) {\n\twindow.GridStack = GridStack\n\ton_load()\n })\n require([\"notyf\"], function() {\n\ton_load()\n })\n root._bokeh_is_loading = css_urls.length + 9;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n var existing_stylesheets = []\n var links = document.getElementsByTagName('link')\n for (var i = 0; i < links.length; i++) {\n var link = links[i]\n if (link.href != null) {\n\texisting_stylesheets.push(link.href)\n }\n }\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n if (existing_stylesheets.indexOf(url) !== -1) {\n\ton_load()\n\tcontinue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } var existing_scripts = []\n var scripts = document.getElementsByTagName('script')\n for (var i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n\texisting_scripts.push(script.src)\n }\n }\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (var i = 0; i < js_modules.length; i++) {\n var url = js_modules[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n var url = js_exports[name];\n if (skip.indexOf(url) >= 0 || root[name] != null) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js\", \"https://cdn.holoviz.org/panel/1.1.1/dist/panel.min.js\"];\n var js_modules = [];\n var js_exports = {};\n var css_urls = [];\n var inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n\tvar NewBokeh = root.Bokeh;\n\tif (Bokeh.versions === undefined) {\n\t Bokeh.versions = new Map();\n\t}\n\tif (NewBokeh.version !== Bokeh.version) {\n\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n\t}\n\troot.Bokeh = Bokeh;\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n Bokeh = root.Bokeh;\n bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n if (!reloading && (!bokeh_loaded || is_dev)) {\n\troot.Bokeh = undefined;\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n\trun_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));",
11
+ "application/vnd.holoviews_load.v0+json": ""
12
+ },
13
+ "metadata": {},
14
+ "output_type": "display_data"
15
+ },
16
+ {
17
+ "data": {
18
+ "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n",
19
+ "application/vnd.holoviews_load.v0+json": ""
20
+ },
21
+ "metadata": {},
22
+ "output_type": "display_data"
23
+ },
24
+ {
25
+ "data": {
26
+ "text/html": [
27
+ "<style>*[data-root-id],\n",
28
+ "*[data-root-id] > * {\n",
29
+ " box-sizing: border-box;\n",
30
+ " font-family: var(--jp-ui-font-family);\n",
31
+ " font-size: var(--jp-ui-font-size1);\n",
32
+ " color: var(--vscode-editor-foreground, var(--jp-ui-font-color1));\n",
33
+ "}\n",
34
+ "\n",
35
+ "/* Override VSCode background color */\n",
36
+ ".cell-output-ipywidget-background:has(> .cell-output-ipywidget-background\n",
37
+ " > .lm-Widget\n",
38
+ " > *[data-root-id]),\n",
39
+ ".cell-output-ipywidget-background:has(> .lm-Widget > *[data-root-id]) {\n",
40
+ " background-color: transparent !important;\n",
41
+ "}\n",
42
+ "</style>"
43
+ ]
44
+ },
45
+ "metadata": {},
46
+ "output_type": "display_data"
47
+ }
48
+ ],
49
+ "source": [
50
+ "import pandas as pd\n",
51
+ "import math\n",
52
+ "from datetime import datetime\n",
53
+ "import hvplot.pandas\n",
54
+ "import math\n",
55
+ "import numpy as np\n",
56
+ "from streamz import Stream"
57
+ ]
58
+ },
59
+ {
60
+ "cell_type": "code",
61
+ "execution_count": 3,
62
+ "metadata": {},
63
+ "outputs": [],
64
+ "source": [
65
+ "b_stocks = pd.read_pickle('../data/b_stocks.pkl')\n",
66
+ "p_stocks = pd.read_pickle('../data/p_stocks.pkl')\n",
67
+ "p_profile = pd.read_pickle('../data/p_profile.pkl')\n",
68
+ "b_profile = pd.read_pickle('../data/b_profile.pkl')"
69
+ ]
70
+ },
71
+ {
72
+ "cell_type": "code",
73
+ "execution_count": 20,
74
+ "metadata": {},
75
+ "outputs": [
76
+ {
77
+ "data": {
78
+ "text/html": [
79
+ "<div>\n",
80
+ "<style scoped>\n",
81
+ " .dataframe tbody tr th:only-of-type {\n",
82
+ " vertical-align: middle;\n",
83
+ " }\n",
84
+ "\n",
85
+ " .dataframe tbody tr th {\n",
86
+ " vertical-align: top;\n",
87
+ " }\n",
88
+ "\n",
89
+ " .dataframe thead th {\n",
90
+ " text-align: right;\n",
91
+ " }\n",
92
+ "</style>\n",
93
+ "<table border=\"1\" class=\"dataframe\">\n",
94
+ " <thead>\n",
95
+ " <tr style=\"text-align: right;\">\n",
96
+ " <th></th>\n",
97
+ " <th>date</th>\n",
98
+ " <th>ticker</th>\n",
99
+ " <th>open</th>\n",
100
+ " <th>close</th>\n",
101
+ " <th>high</th>\n",
102
+ " <th>low</th>\n",
103
+ " <th>volume</th>\n",
104
+ " <th>money</th>\n",
105
+ " </tr>\n",
106
+ " </thead>\n",
107
+ " <tbody>\n",
108
+ " <tr>\n",
109
+ " <th>1</th>\n",
110
+ " <td>2021-01-04</td>\n",
111
+ " <td>002233.XSHE</td>\n",
112
+ " <td>11.27</td>\n",
113
+ " <td>11.28</td>\n",
114
+ " <td>11.34</td>\n",
115
+ " <td>11.17</td>\n",
116
+ " <td>16262377.0</td>\n",
117
+ " <td>1.829668e+08</td>\n",
118
+ " </tr>\n",
119
+ " <tr>\n",
120
+ " <th>247</th>\n",
121
+ " <td>2021-01-04</td>\n",
122
+ " <td>601778.XSHG</td>\n",
123
+ " <td>7.27</td>\n",
124
+ " <td>7.65</td>\n",
125
+ " <td>7.77</td>\n",
126
+ " <td>7.25</td>\n",
127
+ " <td>59723781.0</td>\n",
128
+ " <td>4.523540e+08</td>\n",
129
+ " </tr>\n",
130
+ " <tr>\n",
131
+ " <th>493</th>\n",
132
+ " <td>2021-01-04</td>\n",
133
+ " <td>002368.XSHE</td>\n",
134
+ " <td>25.41</td>\n",
135
+ " <td>28.18</td>\n",
136
+ " <td>28.18</td>\n",
137
+ " <td>25.27</td>\n",
138
+ " <td>17448308.0</td>\n",
139
+ " <td>4.729692e+08</td>\n",
140
+ " </tr>\n",
141
+ " <tr>\n",
142
+ " <th>739</th>\n",
143
+ " <td>2021-01-04</td>\n",
144
+ " <td>001914.XSHE</td>\n",
145
+ " <td>21.21</td>\n",
146
+ " <td>20.39</td>\n",
147
+ " <td>21.33</td>\n",
148
+ " <td>20.26</td>\n",
149
+ " <td>6619778.0</td>\n",
150
+ " <td>1.366024e+08</td>\n",
151
+ " </tr>\n",
152
+ " <tr>\n",
153
+ " <th>985</th>\n",
154
+ " <td>2021-01-04</td>\n",
155
+ " <td>002384.XSHE</td>\n",
156
+ " <td>25.66</td>\n",
157
+ " <td>25.98</td>\n",
158
+ " <td>26.00</td>\n",
159
+ " <td>25.17</td>\n",
160
+ " <td>50695885.0</td>\n",
161
+ " <td>1.296706e+09</td>\n",
162
+ " </tr>\n",
163
+ " <tr>\n",
164
+ " <th>...</th>\n",
165
+ " <td>...</td>\n",
166
+ " <td>...</td>\n",
167
+ " <td>...</td>\n",
168
+ " <td>...</td>\n",
169
+ " <td>...</td>\n",
170
+ " <td>...</td>\n",
171
+ " <td>...</td>\n",
172
+ " <td>...</td>\n",
173
+ " </tr>\n",
174
+ " <tr>\n",
175
+ " <th>147355</th>\n",
176
+ " <td>2021-01-04</td>\n",
177
+ " <td>600511.XSHG</td>\n",
178
+ " <td>46.39</td>\n",
179
+ " <td>45.10</td>\n",
180
+ " <td>46.55</td>\n",
181
+ " <td>44.32</td>\n",
182
+ " <td>45375375.0</td>\n",
183
+ " <td>2.043297e+09</td>\n",
184
+ " </tr>\n",
185
+ " <tr>\n",
186
+ " <th>147601</th>\n",
187
+ " <td>2021-01-04</td>\n",
188
+ " <td>600236.XSHG</td>\n",
189
+ " <td>4.05</td>\n",
190
+ " <td>4.05</td>\n",
191
+ " <td>4.05</td>\n",
192
+ " <td>4.02</td>\n",
193
+ " <td>5788783.0</td>\n",
194
+ " <td>2.339606e+07</td>\n",
195
+ " </tr>\n",
196
+ " <tr>\n",
197
+ " <th>147847</th>\n",
198
+ " <td>2021-01-04</td>\n",
199
+ " <td>000807.XSHE</td>\n",
200
+ " <td>7.32</td>\n",
201
+ " <td>7.58</td>\n",
202
+ " <td>7.71</td>\n",
203
+ " <td>7.13</td>\n",
204
+ " <td>136647514.0</td>\n",
205
+ " <td>1.027073e+09</td>\n",
206
+ " </tr>\n",
207
+ " <tr>\n",
208
+ " <th>148093</th>\n",
209
+ " <td>2021-01-04</td>\n",
210
+ " <td>002815.XSHE</td>\n",
211
+ " <td>13.41</td>\n",
212
+ " <td>13.55</td>\n",
213
+ " <td>13.67</td>\n",
214
+ " <td>13.29</td>\n",
215
+ " <td>5410989.0</td>\n",
216
+ " <td>7.290842e+07</td>\n",
217
+ " </tr>\n",
218
+ " <tr>\n",
219
+ " <th>148339</th>\n",
220
+ " <td>2021-01-04</td>\n",
221
+ " <td>002690.XSHE</td>\n",
222
+ " <td>31.68</td>\n",
223
+ " <td>31.68</td>\n",
224
+ " <td>32.12</td>\n",
225
+ " <td>31.26</td>\n",
226
+ " <td>3641409.0</td>\n",
227
+ " <td>1.153387e+08</td>\n",
228
+ " </tr>\n",
229
+ " </tbody>\n",
230
+ "</table>\n",
231
+ "<p>604 rows × 8 columns</p>\n",
232
+ "</div>"
233
+ ],
234
+ "text/plain": [
235
+ " date ticker open close high low volume \\\n",
236
+ "1 2021-01-04 002233.XSHE 11.27 11.28 11.34 11.17 16262377.0 \n",
237
+ "247 2021-01-04 601778.XSHG 7.27 7.65 7.77 7.25 59723781.0 \n",
238
+ "493 2021-01-04 002368.XSHE 25.41 28.18 28.18 25.27 17448308.0 \n",
239
+ "739 2021-01-04 001914.XSHE 21.21 20.39 21.33 20.26 6619778.0 \n",
240
+ "985 2021-01-04 002384.XSHE 25.66 25.98 26.00 25.17 50695885.0 \n",
241
+ "... ... ... ... ... ... ... ... \n",
242
+ "147355 2021-01-04 600511.XSHG 46.39 45.10 46.55 44.32 45375375.0 \n",
243
+ "147601 2021-01-04 600236.XSHG 4.05 4.05 4.05 4.02 5788783.0 \n",
244
+ "147847 2021-01-04 000807.XSHE 7.32 7.58 7.71 7.13 136647514.0 \n",
245
+ "148093 2021-01-04 002815.XSHE 13.41 13.55 13.67 13.29 5410989.0 \n",
246
+ "148339 2021-01-04 002690.XSHE 31.68 31.68 32.12 31.26 3641409.0 \n",
247
+ "\n",
248
+ " money \n",
249
+ "1 1.829668e+08 \n",
250
+ "247 4.523540e+08 \n",
251
+ "493 4.729692e+08 \n",
252
+ "739 1.366024e+08 \n",
253
+ "985 1.296706e+09 \n",
254
+ "... ... \n",
255
+ "147355 2.043297e+09 \n",
256
+ "147601 2.339606e+07 \n",
257
+ "147847 1.027073e+09 \n",
258
+ "148093 7.290842e+07 \n",
259
+ "148339 1.153387e+08 \n",
260
+ "\n",
261
+ "[604 rows x 8 columns]"
262
+ ]
263
+ },
264
+ "execution_count": 20,
265
+ "metadata": {},
266
+ "output_type": "execute_result"
267
+ }
268
+ ],
269
+ "source": [
270
+ "# start stream here\n",
271
+ "dates = b_stocks.date.unique()\n",
272
+ "b_stocks[b_stocks.date == dates[1]]"
273
+ ]
274
+ },
275
+ {
276
+ "cell_type": "code",
277
+ "execution_count": 32,
278
+ "metadata": {},
279
+ "outputs": [],
280
+ "source": [
281
+ "data1 = b_stocks[b_stocks.date == dates[0]]\n",
282
+ "data2 = b_stocks[b_stocks.date == dates[1]]\n",
283
+ "data3 = b_stocks[b_stocks.date == dates[2]]"
284
+ ]
285
+ },
286
+ {
287
+ "cell_type": "code",
288
+ "execution_count": 34,
289
+ "metadata": {},
290
+ "outputs": [
291
+ {
292
+ "data": {
293
+ "text/html": [
294
+ "<div>\n",
295
+ "<style scoped>\n",
296
+ " .dataframe tbody tr th:only-of-type {\n",
297
+ " vertical-align: middle;\n",
298
+ " }\n",
299
+ "\n",
300
+ " .dataframe tbody tr th {\n",
301
+ " vertical-align: top;\n",
302
+ " }\n",
303
+ "\n",
304
+ " .dataframe thead th {\n",
305
+ " text-align: right;\n",
306
+ " }\n",
307
+ "</style>\n",
308
+ "<table border=\"1\" class=\"dataframe\">\n",
309
+ " <thead>\n",
310
+ " <tr style=\"text-align: right;\">\n",
311
+ " <th></th>\n",
312
+ " <th>date</th>\n",
313
+ " <th>ticker</th>\n",
314
+ " <th>open</th>\n",
315
+ " <th>close</th>\n",
316
+ " <th>high</th>\n",
317
+ " <th>low</th>\n",
318
+ " <th>volume</th>\n",
319
+ " <th>money</th>\n",
320
+ " </tr>\n",
321
+ " </thead>\n",
322
+ " <tbody>\n",
323
+ " <tr>\n",
324
+ " <th>0</th>\n",
325
+ " <td>2020-12-31</td>\n",
326
+ " <td>002233.XSHE</td>\n",
327
+ " <td>11.23</td>\n",
328
+ " <td>11.30</td>\n",
329
+ " <td>11.38</td>\n",
330
+ " <td>11.19</td>\n",
331
+ " <td>9712496.0</td>\n",
332
+ " <td>1.096390e+08</td>\n",
333
+ " </tr>\n",
334
+ " <tr>\n",
335
+ " <th>246</th>\n",
336
+ " <td>2020-12-31</td>\n",
337
+ " <td>601778.XSHG</td>\n",
338
+ " <td>7.28</td>\n",
339
+ " <td>7.23</td>\n",
340
+ " <td>7.39</td>\n",
341
+ " <td>7.20</td>\n",
342
+ " <td>29971398.0</td>\n",
343
+ " <td>2.181267e+08</td>\n",
344
+ " </tr>\n",
345
+ " <tr>\n",
346
+ " <th>492</th>\n",
347
+ " <td>2020-12-31</td>\n",
348
+ " <td>002368.XSHE</td>\n",
349
+ " <td>24.96</td>\n",
350
+ " <td>25.62</td>\n",
351
+ " <td>25.92</td>\n",
352
+ " <td>24.96</td>\n",
353
+ " <td>7090839.0</td>\n",
354
+ " <td>1.811902e+08</td>\n",
355
+ " </tr>\n",
356
+ " <tr>\n",
357
+ " <th>738</th>\n",
358
+ " <td>2020-12-31</td>\n",
359
+ " <td>001914.XSHE</td>\n",
360
+ " <td>20.52</td>\n",
361
+ " <td>21.26</td>\n",
362
+ " <td>21.36</td>\n",
363
+ " <td>20.52</td>\n",
364
+ " <td>5598757.0</td>\n",
365
+ " <td>1.171876e+08</td>\n",
366
+ " </tr>\n",
367
+ " <tr>\n",
368
+ " <th>984</th>\n",
369
+ " <td>2020-12-31</td>\n",
370
+ " <td>002384.XSHE</td>\n",
371
+ " <td>25.67</td>\n",
372
+ " <td>25.65</td>\n",
373
+ " <td>25.90</td>\n",
374
+ " <td>25.15</td>\n",
375
+ " <td>49307624.0</td>\n",
376
+ " <td>1.256593e+09</td>\n",
377
+ " </tr>\n",
378
+ " <tr>\n",
379
+ " <th>...</th>\n",
380
+ " <td>...</td>\n",
381
+ " <td>...</td>\n",
382
+ " <td>...</td>\n",
383
+ " <td>...</td>\n",
384
+ " <td>...</td>\n",
385
+ " <td>...</td>\n",
386
+ " <td>...</td>\n",
387
+ " <td>...</td>\n",
388
+ " </tr>\n",
389
+ " <tr>\n",
390
+ " <th>147355</th>\n",
391
+ " <td>2021-01-04</td>\n",
392
+ " <td>600511.XSHG</td>\n",
393
+ " <td>46.39</td>\n",
394
+ " <td>45.10</td>\n",
395
+ " <td>46.55</td>\n",
396
+ " <td>44.32</td>\n",
397
+ " <td>45375375.0</td>\n",
398
+ " <td>2.043297e+09</td>\n",
399
+ " </tr>\n",
400
+ " <tr>\n",
401
+ " <th>147601</th>\n",
402
+ " <td>2021-01-04</td>\n",
403
+ " <td>600236.XSHG</td>\n",
404
+ " <td>4.05</td>\n",
405
+ " <td>4.05</td>\n",
406
+ " <td>4.05</td>\n",
407
+ " <td>4.02</td>\n",
408
+ " <td>5788783.0</td>\n",
409
+ " <td>2.339606e+07</td>\n",
410
+ " </tr>\n",
411
+ " <tr>\n",
412
+ " <th>147847</th>\n",
413
+ " <td>2021-01-04</td>\n",
414
+ " <td>000807.XSHE</td>\n",
415
+ " <td>7.32</td>\n",
416
+ " <td>7.58</td>\n",
417
+ " <td>7.71</td>\n",
418
+ " <td>7.13</td>\n",
419
+ " <td>136647514.0</td>\n",
420
+ " <td>1.027073e+09</td>\n",
421
+ " </tr>\n",
422
+ " <tr>\n",
423
+ " <th>148093</th>\n",
424
+ " <td>2021-01-04</td>\n",
425
+ " <td>002815.XSHE</td>\n",
426
+ " <td>13.41</td>\n",
427
+ " <td>13.55</td>\n",
428
+ " <td>13.67</td>\n",
429
+ " <td>13.29</td>\n",
430
+ " <td>5410989.0</td>\n",
431
+ " <td>7.290842e+07</td>\n",
432
+ " </tr>\n",
433
+ " <tr>\n",
434
+ " <th>148339</th>\n",
435
+ " <td>2021-01-04</td>\n",
436
+ " <td>002690.XSHE</td>\n",
437
+ " <td>31.68</td>\n",
438
+ " <td>31.68</td>\n",
439
+ " <td>32.12</td>\n",
440
+ " <td>31.26</td>\n",
441
+ " <td>3641409.0</td>\n",
442
+ " <td>1.153387e+08</td>\n",
443
+ " </tr>\n",
444
+ " </tbody>\n",
445
+ "</table>\n",
446
+ "<p>1208 rows × 8 columns</p>\n",
447
+ "</div>"
448
+ ],
449
+ "text/plain": [
450
+ " date ticker open close high low volume \\\n",
451
+ "0 2020-12-31 002233.XSHE 11.23 11.30 11.38 11.19 9712496.0 \n",
452
+ "246 2020-12-31 601778.XSHG 7.28 7.23 7.39 7.20 29971398.0 \n",
453
+ "492 2020-12-31 002368.XSHE 24.96 25.62 25.92 24.96 7090839.0 \n",
454
+ "738 2020-12-31 001914.XSHE 20.52 21.26 21.36 20.52 5598757.0 \n",
455
+ "984 2020-12-31 002384.XSHE 25.67 25.65 25.90 25.15 49307624.0 \n",
456
+ "... ... ... ... ... ... ... ... \n",
457
+ "147355 2021-01-04 600511.XSHG 46.39 45.10 46.55 44.32 45375375.0 \n",
458
+ "147601 2021-01-04 600236.XSHG 4.05 4.05 4.05 4.02 5788783.0 \n",
459
+ "147847 2021-01-04 000807.XSHE 7.32 7.58 7.71 7.13 136647514.0 \n",
460
+ "148093 2021-01-04 002815.XSHE 13.41 13.55 13.67 13.29 5410989.0 \n",
461
+ "148339 2021-01-04 002690.XSHE 31.68 31.68 32.12 31.26 3641409.0 \n",
462
+ "\n",
463
+ " money \n",
464
+ "0 1.096390e+08 \n",
465
+ "246 2.181267e+08 \n",
466
+ "492 1.811902e+08 \n",
467
+ "738 1.171876e+08 \n",
468
+ "984 1.256593e+09 \n",
469
+ "... ... \n",
470
+ "147355 2.043297e+09 \n",
471
+ "147601 2.339606e+07 \n",
472
+ "147847 1.027073e+09 \n",
473
+ "148093 7.290842e+07 \n",
474
+ "148339 1.153387e+08 \n",
475
+ "\n",
476
+ "[1208 rows x 8 columns]"
477
+ ]
478
+ },
479
+ "execution_count": 34,
480
+ "metadata": {},
481
+ "output_type": "execute_result"
482
+ }
483
+ ],
484
+ "source": [
485
+ "merged_df = pd.concat([data1, data2])\n",
486
+ "merged_df"
487
+ ]
488
+ },
489
+ {
490
+ "cell_type": "code",
491
+ "execution_count": 31,
492
+ "metadata": {},
493
+ "outputs": [
494
+ {
495
+ "name": "stdout",
496
+ "output_type": "stream",
497
+ "text": [
498
+ " date ticker open close high low volume \\\n",
499
+ "0 2020-12-31 002233.XSHE 11.23 11.30 11.38 11.19 9712496.0 \n",
500
+ "246 2020-12-31 601778.XSHG 7.28 7.23 7.39 7.20 29971398.0 \n",
501
+ "492 2020-12-31 002368.XSHE 24.96 25.62 25.92 24.96 7090839.0 \n",
502
+ "738 2020-12-31 001914.XSHE 20.52 21.26 21.36 20.52 5598757.0 \n",
503
+ "984 2020-12-31 002384.XSHE 25.67 25.65 25.90 25.15 49307624.0 \n",
504
+ "... ... ... ... ... ... ... ... \n",
505
+ "147354 2020-12-31 600511.XSHG 44.43 46.33 48.45 44.16 69779041.0 \n",
506
+ "147600 2020-12-31 600236.XSHG 4.07 4.05 4.10 4.02 6542015.0 \n",
507
+ "147846 2020-12-31 000807.XSHE 7.34 7.35 7.48 7.20 72260375.0 \n",
508
+ "148092 2020-12-31 002815.XSHE 13.22 13.41 13.51 13.19 4198958.0 \n",
509
+ "148338 2020-12-31 002690.XSHE 31.14 31.48 31.60 30.79 4010199.0 \n",
510
+ "\n",
511
+ " money \n",
512
+ "0 1.096390e+08 \n",
513
+ "246 2.181267e+08 \n",
514
+ "492 1.811902e+08 \n",
515
+ "738 1.171876e+08 \n",
516
+ "984 1.256593e+09 \n",
517
+ "... ... \n",
518
+ "147354 3.258608e+09 \n",
519
+ "147600 2.649244e+07 \n",
520
+ "147846 5.305392e+08 \n",
521
+ "148092 5.621386e+07 \n",
522
+ "148338 1.257796e+08 \n",
523
+ "\n",
524
+ "[604 rows x 8 columns]\n",
525
+ " date ticker open close high low volume \\\n",
526
+ "0 2020-12-31 002233.XSHE 11.23 11.30 11.38 11.19 9712496.0 \n",
527
+ "246 2020-12-31 601778.XSHG 7.28 7.23 7.39 7.20 29971398.0 \n",
528
+ "492 2020-12-31 002368.XSHE 24.96 25.62 25.92 24.96 7090839.0 \n",
529
+ "738 2020-12-31 001914.XSHE 20.52 21.26 21.36 20.52 5598757.0 \n",
530
+ "984 2020-12-31 002384.XSHE 25.67 25.65 25.90 25.15 49307624.0 \n",
531
+ "... ... ... ... ... ... ... ... \n",
532
+ "147355 2021-01-04 600511.XSHG 46.39 45.10 46.55 44.32 45375375.0 \n",
533
+ "147601 2021-01-04 600236.XSHG 4.05 4.05 4.05 4.02 5788783.0 \n",
534
+ "147847 2021-01-04 000807.XSHE 7.32 7.58 7.71 7.13 136647514.0 \n",
535
+ "148093 2021-01-04 002815.XSHE 13.41 13.55 13.67 13.29 5410989.0 \n",
536
+ "148339 2021-01-04 002690.XSHE 31.68 31.68 32.12 31.26 3641409.0 \n",
537
+ "\n",
538
+ " money \n",
539
+ "0 1.096390e+08 \n",
540
+ "246 2.181267e+08 \n",
541
+ "492 1.811902e+08 \n",
542
+ "738 1.171876e+08 \n",
543
+ "984 1.256593e+09 \n",
544
+ "... ... \n",
545
+ "147355 2.043297e+09 \n",
546
+ "147601 2.339606e+07 \n",
547
+ "147847 1.027073e+09 \n",
548
+ "148093 7.290842e+07 \n",
549
+ "148339 1.153387e+08 \n",
550
+ "\n",
551
+ "[1208 rows x 8 columns]\n",
552
+ " date ticker open close high low volume \\\n",
553
+ "0 2020-12-31 002233.XSHE 11.23 11.30 11.38 11.19 9712496.0 \n",
554
+ "246 2020-12-31 601778.XSHG 7.28 7.23 7.39 7.20 29971398.0 \n",
555
+ "492 2020-12-31 002368.XSHE 24.96 25.62 25.92 24.96 7090839.0 \n",
556
+ "738 2020-12-31 001914.XSHE 20.52 21.26 21.36 20.52 5598757.0 \n",
557
+ "984 2020-12-31 002384.XSHE 25.67 25.65 25.90 25.15 49307624.0 \n",
558
+ "... ... ... ... ... ... ... ... \n",
559
+ "147356 2021-01-05 600511.XSHG 45.00 45.58 46.79 43.58 45296155.0 \n",
560
+ "147602 2021-01-05 600236.XSHG 4.04 4.01 4.05 4.00 4302703.0 \n",
561
+ "147848 2021-01-05 000807.XSHE 7.61 7.99 8.21 7.48 177268867.0 \n",
562
+ "148094 2021-01-05 002815.XSHE 13.52 13.63 13.65 13.44 5800866.0 \n",
563
+ "148340 2021-01-05 002690.XSHE 31.68 31.85 32.14 31.42 5082815.0 \n",
564
+ "\n",
565
+ " money \n",
566
+ "0 1.096390e+08 \n",
567
+ "246 2.181267e+08 \n",
568
+ "492 1.811902e+08 \n",
569
+ "738 1.171876e+08 \n",
570
+ "984 1.256593e+09 \n",
571
+ "... ... \n",
572
+ "147356 2.035299e+09 \n",
573
+ "147602 1.726972e+07 \n",
574
+ "147848 1.383377e+09 \n",
575
+ "148094 7.856429e+07 \n",
576
+ "148340 1.612986e+08 \n",
577
+ "\n",
578
+ "[1812 rows x 8 columns]\n"
579
+ ]
580
+ }
581
+ ],
582
+ "source": [
583
+ "def add(prev_df, new_df):\n",
584
+ " merged_df = pd.concat([prev_df, new_df])\n",
585
+ " return merged_df\n",
586
+ "\n",
587
+ "source = Stream()\n",
588
+ "source.accumulate(add).sink(print)\n",
589
+ "source.emit(b_stocks[b_stocks.date == dates[0]])\n",
590
+ "source.emit(b_stocks[b_stocks.date == dates[1]])\n",
591
+ "source.emit(b_stocks[b_stocks.date == dates[2]])\n"
592
+ ]
593
+ }
594
+ ],
595
+ "metadata": {
596
+ "kernelspec": {
597
+ "display_name": "portfolio_risk_assesment",
598
+ "language": "python",
599
+ "name": "python3"
600
+ },
601
+ "language_info": {
602
+ "codemirror_mode": {
603
+ "name": "ipython",
604
+ "version": 3
605
+ },
606
+ "file_extension": ".py",
607
+ "mimetype": "text/x-python",
608
+ "name": "python",
609
+ "nbconvert_exporter": "python",
610
+ "pygments_lexer": "ipython3",
611
+ "version": "3.11.4"
612
+ },
613
+ "orig_nbformat": 4
614
+ },
615
+ "nbformat": 4,
616
+ "nbformat_minor": 2
617
+ }
script/stream_processing.py CHANGED
@@ -1,3 +1,32 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:e193f46fa91cc5f42ea6117dd54ae0aebe6ff173ed452f505915d10089ebabf8
3
- size 948
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import math
3
+ from datetime import datetime
4
+ import hvplot.pandas
5
+ import math
6
+ import numpy as np
7
+ import time
8
+ from streamz import Stream
9
+
10
+ b_stocks = pd.read_pickle('../data/b_stocks.pkl')
11
+ p_stocks = pd.read_pickle('../data/p_stocks.pkl')
12
+ p_profile = pd.read_pickle('../data/p_profile.pkl')
13
+ b_profile = pd.read_pickle('../data/b_profile.pkl')
14
+
15
+ # start stream here
16
+ dates = b_stocks.date.unique()
17
+ b_stocks[b_stocks.date == dates[1]]
18
+
19
+ def add(prev_df, new_df):
20
+ merged_df = pd.concat([prev_df, new_df])
21
+ merged_df.sort_values(by=['date'], inplace=True)
22
+ merged_df['pct'] = merged_df.groupby('ticker')['close'].pct_change()
23
+
24
+ # remove prev_df
25
+ merged_df = merged_df[~merged_df.isin(prev_df)].dropna()
26
+ return merged_df
27
+
28
+ source = Stream()
29
+ source.accumulate(add).sink(print)
30
+ source.emit(b_stocks[b_stocks.date == dates[0]])
31
+ source.emit(b_stocks[b_stocks.date == dates[1]])
32
+ source.emit(b_stocks[b_stocks.date == dates[2]])
script/styling.py CHANGED
@@ -1,3 +1,15 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:455ecf338b18610c747777df147049b6b7c4e01f63fd0ca35c5fb20d79c00a41
3
- size 268
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ plot_layout = dict(
2
+ legend=dict(
3
+ orientation="h",
4
+ ),
5
+ yaxis_title=None,
6
+ xaxis_title=None,
7
+ margin=dict(l=0, r=0, t=30, b=0),
8
+ uniformtext_mode='hide',
9
+
10
+ )
11
+
12
+ barplot_trace = dict(
13
+ marker_line_width=0,
14
+ selector=dict(type="bar"),
15
+ )
settings.py CHANGED
@@ -1,3 +1,9 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:0fd4fd43c9eee777f8959e01aded629a34384181af34f1532a27409682460b81
3
- size 212
 
 
 
 
 
 
 
1
+ from datetime import timedelta
2
+ stream_frequency = timedelta(seconds=60)
3
+
4
+ TABLE_NAME_AND_FREQ = [
5
+ ('benchmark_profile', timedelta(days=1)),
6
+ ('portfolio_profile', timedelta(days=1))
7
+ ]
8
+
9
+ COMPONENT_WIDTH = 375
styling.py CHANGED
@@ -1,3 +1,15 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:455ecf338b18610c747777df147049b6b7c4e01f63fd0ca35c5fb20d79c00a41
3
- size 268
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ plot_layout = dict(
2
+ legend=dict(
3
+ orientation="h",
4
+ ),
5
+ yaxis_title=None,
6
+ xaxis_title=None,
7
+ margin=dict(l=0, r=0, t=30, b=0),
8
+ uniformtext_mode='hide',
9
+
10
+ )
11
+
12
+ barplot_trace = dict(
13
+ marker_line_width=0,
14
+ selector=dict(type="bar"),
15
+ )
table_schema.py CHANGED
@@ -1,3 +1,28 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:d7f88f15456c20d7ebef37e45f78ecb96276acdc26f696572c4d867a3b7b5674
3
- size 590
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ '''
3
+ create df schema for db
4
+ '''
5
+ PORTFOLIO_TABLE = 'portfolio_profile'
6
+ PORTFOLIO_TABLE_SCHEMA = {
7
+ 'ticker': str,
8
+ 'shares': int,
9
+ 'date': 'datetime64[ns]',
10
+ 'sector': str,
11
+ 'aggregate_sector': str,
12
+ 'display_name': str,
13
+ 'name': str,
14
+ 'cash': float,
15
+ 'weight': float,
16
+ 'ave_price': float
17
+ }
18
+ STOCKS_DETAILS_TABLE = 'all_stock_info'
19
+ STOCKS_DETAILS_TABLE_SCHEMA = {
20
+ 'display_name': str,
21
+ 'name': str,
22
+ 'start_date': 'datetime64[ns]',
23
+ 'end_date': 'datetime64[ns]',
24
+ 'type': str,
25
+ 'ticker': str,
26
+ 'sector': str,
27
+ 'aggregate_sector': str
28
+ }
test.ipynb CHANGED
@@ -1,3 +1,47 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:a7aa0baf72e8e836a10321925eccc7cad01f6f6f0062a811f571486ef0176060
3
- size 1171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "import numpy as np\n",
10
+ "import pandas as pd\n",
11
+ "import panel as pn\n",
12
+ "\n",
13
+ "pn.extension('perspective', template='fast', sizing_mode='stretch_width')\n",
14
+ "df = pd.DataFrame(np.random.randn(10, 4), columns=list('ABCD')).cumsum()\n",
15
+ "\n",
16
+ "rollover = pn.widgets.IntInput(name='Rollover', value=15)\n",
17
+ "\n",
18
+ "perspective = pn.pane.Perspective(df, height=400)\n",
19
+ "\n",
20
+ "def stream():\n",
21
+ " data = df.iloc[-1] + np.random.randn(4)\n",
22
+ " perspective.stream(data, rollover=rollover.value)\n",
23
+ "\\\n",
24
+ "cb = pn.state.add_periodic_callback(stream, 50)\n",
25
+ "\n",
26
+ "pn.Column(\n",
27
+ " pn.Row(cb.param.period, rollover, perspective.param.theme),\n",
28
+ " perspective\n",
29
+ ").servable()"
30
+ ]
31
+ }
32
+ ],
33
+ "metadata": {
34
+ "kernelspec": {
35
+ "display_name": "portfolio_risk_assesment",
36
+ "language": "python",
37
+ "name": "python3"
38
+ },
39
+ "language_info": {
40
+ "name": "python",
41
+ "version": "3.11.4"
42
+ },
43
+ "orig_nbformat": 4
44
+ },
45
+ "nbformat": 4,
46
+ "nbformat_minor": 2
47
+ }
test_background_task.py CHANGED
@@ -1,3 +1,26 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:045734bbc99c4c78abfb10c850ed49a55e4cf7434595137022446074f3e44802
3
- size 617
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pipeline
2
+ import settings
3
+
4
+ def test_need_to_update():
5
+ table_list = settings.TABLE_NAME_AND_FREQ
6
+ for table, freq in table_list:
7
+ result = pipeline.need_to_update(table, freq)
8
+ print(result)
9
+
10
+ # test_need_to_update()
11
+
12
+ def test_fetch_stock_price():
13
+ df = pipeline.fetch_stock_price()
14
+ return df
15
+
16
+ def test_need_to_update_stocks_price():
17
+ print(pipeline.need_to_update_stocks_price())
18
+
19
+
20
+ def test_add_details_to_stock_df():
21
+ stock_df = pipeline.update_stocks_price()
22
+ stock_df = pipeline.add_details_to_stock_df(stock_df)
23
+
24
+ return stock_df
25
+
26
+ print(test_add_details_to_stock_df())
test_responsive.py CHANGED
@@ -1,3 +1,14 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:a935ade97cc93f866e1d21a344bd4986d8ac6a8656ae4d356a40a64c0fb27d98
3
- size 398
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import panel.widgets as pnw
3
+ import panel as pn
4
+ pn.extension()
5
+
6
+
7
+ def rcolor(): return "#%06x" % random.randint(0, 0xFFFFFF)
8
+
9
+
10
+ box = pn.FlexBox(*[pn.pane.HTML(str(i), styles=dict(background=rcolor()),
11
+ width=370, height=100, sizing_mode='fixed') for i in range(24)],
12
+ align_conten='start', styles={'background': 'black'}
13
+ )
14
+ box.servable()
testing_pipeline.ipynb CHANGED
@@ -1,3 +1,300 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:3333498618a0ef539b89cb37d51d165ec901c3e058225ba511b1ed4a6f258d90
3
- size 8920
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "import pipeline\n",
10
+ "import settings\n",
11
+ "from sqlalchemy import create_engine\n",
12
+ "import pandas as pd\n",
13
+ "import datetime as dt\n",
14
+ "import api\n",
15
+ "db_url = 'sqlite:///local.db'"
16
+ ]
17
+ },
18
+ {
19
+ "cell_type": "code",
20
+ "execution_count": 2,
21
+ "metadata": {},
22
+ "outputs": [
23
+ {
24
+ "name": "stdout",
25
+ "output_type": "stream",
26
+ "text": [
27
+ "auth success \n"
28
+ ]
29
+ }
30
+ ],
31
+ "source": [
32
+ "pipeline.update_stocks_details_to_db()"
33
+ ]
34
+ },
35
+ {
36
+ "cell_type": "code",
37
+ "execution_count": 1,
38
+ "metadata": {},
39
+ "outputs": [],
40
+ "source": [
41
+ "%load_ext autoreload\n",
42
+ "%autoreload 2"
43
+ ]
44
+ },
45
+ {
46
+ "cell_type": "code",
47
+ "execution_count": 147,
48
+ "metadata": {},
49
+ "outputs": [],
50
+ "source": [
51
+ "## calculate weight for benchmark and portfolio \n",
52
+ "with create_engine(db_url).connect() as conn:\n",
53
+ " p_profile = pd.read_sql('portfolio_profile', con=conn)\n",
54
+ " b_profile = pd.read_sql('benchmark_profile', con=conn)"
55
+ ]
56
+ },
57
+ {
58
+ "cell_type": "code",
59
+ "execution_count": 167,
60
+ "metadata": {},
61
+ "outputs": [],
62
+ "source": [
63
+ "date = dt.datetime(2023, 8, 2, 15, 0,0)\n"
64
+ ]
65
+ },
66
+ {
67
+ "cell_type": "code",
68
+ "execution_count": 168,
69
+ "metadata": {},
70
+ "outputs": [
71
+ {
72
+ "data": {
73
+ "text/html": [
74
+ "<div>\n",
75
+ "<style scoped>\n",
76
+ " .dataframe tbody tr th:only-of-type {\n",
77
+ " vertical-align: middle;\n",
78
+ " }\n",
79
+ "\n",
80
+ " .dataframe tbody tr th {\n",
81
+ " vertical-align: top;\n",
82
+ " }\n",
83
+ "\n",
84
+ " .dataframe thead th {\n",
85
+ " text-align: right;\n",
86
+ " }\n",
87
+ "</style>\n",
88
+ "<table border=\"1\" class=\"dataframe\">\n",
89
+ " <thead>\n",
90
+ " <tr style=\"text-align: right;\">\n",
91
+ " <th></th>\n",
92
+ " <th>time</th>\n",
93
+ " <th>ticker</th>\n",
94
+ " <th>open</th>\n",
95
+ " <th>close</th>\n",
96
+ " <th>high</th>\n",
97
+ " <th>low</th>\n",
98
+ " <th>volume</th>\n",
99
+ " <th>money</th>\n",
100
+ " </tr>\n",
101
+ " </thead>\n",
102
+ " <tbody>\n",
103
+ " <tr>\n",
104
+ " <th>0</th>\n",
105
+ " <td>2023-08-02 15:00:00</td>\n",
106
+ " <td>603288.XSHG</td>\n",
107
+ " <td>45.85</td>\n",
108
+ " <td>45.85</td>\n",
109
+ " <td>45.85</td>\n",
110
+ " <td>45.85</td>\n",
111
+ " <td>156700.0</td>\n",
112
+ " <td>7184695.0</td>\n",
113
+ " </tr>\n",
114
+ " </tbody>\n",
115
+ "</table>\n",
116
+ "</div>"
117
+ ],
118
+ "text/plain": [
119
+ " time ticker open close high low volume \\\n",
120
+ "0 2023-08-02 15:00:00 603288.XSHG 45.85 45.85 45.85 45.85 156700.0 \n",
121
+ "\n",
122
+ " money \n",
123
+ "0 7184695.0 "
124
+ ]
125
+ },
126
+ "execution_count": 168,
127
+ "metadata": {},
128
+ "output_type": "execute_result"
129
+ }
130
+ ],
131
+ "source": [
132
+ "api.fetch_stocks_price(security=['603288.XSHG'],end_date=date,count=1, frequency='minute')"
133
+ ]
134
+ },
135
+ {
136
+ "cell_type": "code",
137
+ "execution_count": 151,
138
+ "metadata": {},
139
+ "outputs": [
140
+ {
141
+ "data": {
142
+ "text/plain": [
143
+ "Timestamp('2023-08-02 21:20:02')"
144
+ ]
145
+ },
146
+ "execution_count": 151,
147
+ "metadata": {},
148
+ "output_type": "execute_result"
149
+ }
150
+ ],
151
+ "source": [
152
+ "date = dt.datetime(2023, 8, 2)\n",
153
+ "p_profile.iloc[0].date"
154
+ ]
155
+ },
156
+ {
157
+ "cell_type": "code",
158
+ "execution_count": 149,
159
+ "metadata": {},
160
+ "outputs": [
161
+ {
162
+ "data": {
163
+ "text/html": [
164
+ "<div>\n",
165
+ "<style scoped>\n",
166
+ " .dataframe tbody tr th:only-of-type {\n",
167
+ " vertical-align: middle;\n",
168
+ " }\n",
169
+ "\n",
170
+ " .dataframe tbody tr th {\n",
171
+ " vertical-align: top;\n",
172
+ " }\n",
173
+ "\n",
174
+ " .dataframe thead th {\n",
175
+ " text-align: right;\n",
176
+ " }\n",
177
+ "</style>\n",
178
+ "<table border=\"1\" class=\"dataframe\">\n",
179
+ " <thead>\n",
180
+ " <tr style=\"text-align: right;\">\n",
181
+ " <th></th>\n",
182
+ " <th>index</th>\n",
183
+ " <th>date</th>\n",
184
+ " <th>weight</th>\n",
185
+ " <th>display_name</th>\n",
186
+ " <th>ticker</th>\n",
187
+ " <th>sector</th>\n",
188
+ " <th>aggregate_sector</th>\n",
189
+ " <th>name</th>\n",
190
+ " </tr>\n",
191
+ " </thead>\n",
192
+ " <tbody>\n",
193
+ " <tr>\n",
194
+ " <th>0</th>\n",
195
+ " <td>1500</td>\n",
196
+ " <td>2020-12-31</td>\n",
197
+ " <td>0.088</td>\n",
198
+ " <td>神州高铁</td>\n",
199
+ " <td>000008.XSHE</td>\n",
200
+ " <td>机械设备I 运输设备II 铁路设备III 铁路、船舶、航空航天和其他运输设备制造业 城轨铁路 工业</td>\n",
201
+ " <td>工业</td>\n",
202
+ " <td>SZGT</td>\n",
203
+ " </tr>\n",
204
+ " <tr>\n",
205
+ " <th>1</th>\n",
206
+ " <td>1501</td>\n",
207
+ " <td>2020-12-31</td>\n",
208
+ " <td>0.344</td>\n",
209
+ " <td>中国宝安</td>\n",
210
+ " <td>000009.XSHE</td>\n",
211
+ " <td>电气设备I 电池II 电池化学品III 综合 工业集团企业 工业</td>\n",
212
+ " <td>工业</td>\n",
213
+ " <td>ZGBA</td>\n",
214
+ " </tr>\n",
215
+ " <tr>\n",
216
+ " <th>2</th>\n",
217
+ " <td>1502</td>\n",
218
+ " <td>2020-12-31</td>\n",
219
+ " <td>0.180</td>\n",
220
+ " <td>南玻A</td>\n",
221
+ " <td>000012.XSHE</td>\n",
222
+ " <td>建筑材料I 玻璃制造II 玻璃制造III 非金属矿物制品业 玻璃 原材料</td>\n",
223
+ " <td>原料与能源</td>\n",
224
+ " <td>NBA</td>\n",
225
+ " </tr>\n",
226
+ " <tr>\n",
227
+ " <th>3</th>\n",
228
+ " <td>1503</td>\n",
229
+ " <td>2020-12-31</td>\n",
230
+ " <td>0.297</td>\n",
231
+ " <td>深科技</td>\n",
232
+ " <td>000021.XSHE</td>\n",
233
+ " <td>电子I 电子制造II 消费电子零部件及组装III 计算机、通信和其他电子设备制造业 安防设备...</td>\n",
234
+ " <td>信息与通信</td>\n",
235
+ " <td>SKJ</td>\n",
236
+ " </tr>\n",
237
+ " <tr>\n",
238
+ " <th>4</th>\n",
239
+ " <td>1504</td>\n",
240
+ " <td>2020-12-31</td>\n",
241
+ " <td>0.030</td>\n",
242
+ " <td>招商港口</td>\n",
243
+ " <td>001872.XSHE</td>\n",
244
+ " <td>交通运输I 航运港口II 港口III 水上运输业 港口 工业</td>\n",
245
+ " <td>工业</td>\n",
246
+ " <td>ZSGK</td>\n",
247
+ " </tr>\n",
248
+ " </tbody>\n",
249
+ "</table>\n",
250
+ "</div>"
251
+ ],
252
+ "text/plain": [
253
+ " index date weight display_name ticker \\\n",
254
+ "0 1500 2020-12-31 0.088 神州高铁 000008.XSHE \n",
255
+ "1 1501 2020-12-31 0.344 中国宝安 000009.XSHE \n",
256
+ "2 1502 2020-12-31 0.180 南玻A 000012.XSHE \n",
257
+ "3 1503 2020-12-31 0.297 深科技 000021.XSHE \n",
258
+ "4 1504 2020-12-31 0.030 招商港口 001872.XSHE \n",
259
+ "\n",
260
+ " sector aggregate_sector name \n",
261
+ "0 机械设备I 运输设备II 铁路设备III 铁路、船舶、航空航天和其他运输设备制造业 城轨铁路 工业 工业 SZGT \n",
262
+ "1 电气设备I 电池II 电池化学品III 综合 工业集团企业 工业 工业 ZGBA \n",
263
+ "2 建筑材料I 玻璃制造II 玻璃制造III 非金属矿物制品业 玻璃 原材料 原料与能源 NBA \n",
264
+ "3 电子I 电子制造II 消费电子零部件及组装III 计算机、通信和其他电子设备制造业 安防设备... 信息与通信 SKJ \n",
265
+ "4 交通运输I 航运港口II 港口III 水上运输业 港口 工业 工业 ZSGK "
266
+ ]
267
+ },
268
+ "execution_count": 149,
269
+ "metadata": {},
270
+ "output_type": "execute_result"
271
+ }
272
+ ],
273
+ "source": [
274
+ "b_profile.head()"
275
+ ]
276
+ }
277
+ ],
278
+ "metadata": {
279
+ "kernelspec": {
280
+ "display_name": "risk-dashboard",
281
+ "language": "python",
282
+ "name": "python3"
283
+ },
284
+ "language_info": {
285
+ "codemirror_mode": {
286
+ "name": "ipython",
287
+ "version": 3
288
+ },
289
+ "file_extension": ".py",
290
+ "mimetype": "text/x-python",
291
+ "name": "python",
292
+ "nbconvert_exporter": "python",
293
+ "pygments_lexer": "ipython3",
294
+ "version": "3.11.4"
295
+ },
296
+ "orig_nbformat": 4
297
+ },
298
+ "nbformat": 4,
299
+ "nbformat_minor": 2
300
+ }
total_return.ipynb CHANGED
@@ -1,3 +1,713 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:652c33dc1d1fad4473c1bfd23f19bc11b2e718e2dbaf5057a2716a958de2f7f3
3
- size 84782
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "data": {
10
+ "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n var py_version = '3.1.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n var reloading = false;\n var Bokeh = root.Bokeh;\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n run_callbacks();\n return null;\n }\n if (!reloading) {\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n var skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n require([\"jspanel\"], function(jsPanel) {\n\twindow.jsPanel = jsPanel\n\ton_load()\n })\n require([\"jspanel-modal\"], function() {\n\ton_load()\n })\n require([\"jspanel-tooltip\"], function() {\n\ton_load()\n })\n require([\"jspanel-hint\"], function() {\n\ton_load()\n })\n require([\"jspanel-layout\"], function() {\n\ton_load()\n })\n require([\"jspanel-contextmenu\"], function() {\n\ton_load()\n })\n require([\"jspanel-dock\"], function() {\n\ton_load()\n })\n require([\"gridstack\"], function(GridStack) {\n\twindow.GridStack = GridStack\n\ton_load()\n })\n require([\"notyf\"], function() {\n\ton_load()\n })\n root._bokeh_is_loading = css_urls.length + 9;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n var existing_stylesheets = []\n var links = document.getElementsByTagName('link')\n for (var i = 0; i < links.length; i++) {\n var link = links[i]\n if (link.href != null) {\n\texisting_stylesheets.push(link.href)\n }\n }\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n if (existing_stylesheets.indexOf(url) !== -1) {\n\ton_load()\n\tcontinue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } var existing_scripts = []\n var scripts = document.getElementsByTagName('script')\n for (var i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n\texisting_scripts.push(script.src)\n }\n }\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (var i = 0; i < js_modules.length; i++) {\n var url = js_modules[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n var url = js_exports[name];\n if (skip.indexOf(url) >= 0 || root[name] != null) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js\", \"https://cdn.holoviz.org/panel/1.1.1/dist/panel.min.js\"];\n var js_modules = [];\n var js_exports = {};\n var css_urls = [];\n var inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n\tvar NewBokeh = root.Bokeh;\n\tif (Bokeh.versions === undefined) {\n\t Bokeh.versions = new Map();\n\t}\n\tif (NewBokeh.version !== Bokeh.version) {\n\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n\t}\n\troot.Bokeh = Bokeh;\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n Bokeh = root.Bokeh;\n bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n if (!reloading && (!bokeh_loaded || is_dev)) {\n\troot.Bokeh = undefined;\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n\trun_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));",
11
+ "application/vnd.holoviews_load.v0+json": ""
12
+ },
13
+ "metadata": {},
14
+ "output_type": "display_data"
15
+ },
16
+ {
17
+ "data": {
18
+ "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n",
19
+ "application/vnd.holoviews_load.v0+json": ""
20
+ },
21
+ "metadata": {},
22
+ "output_type": "display_data"
23
+ },
24
+ {
25
+ "data": {
26
+ "text/html": [
27
+ "<style>*[data-root-id],\n",
28
+ "*[data-root-id] > * {\n",
29
+ " box-sizing: border-box;\n",
30
+ " font-family: var(--jp-ui-font-family);\n",
31
+ " font-size: var(--jp-ui-font-size1);\n",
32
+ " color: var(--vscode-editor-foreground, var(--jp-ui-font-color1));\n",
33
+ "}\n",
34
+ "\n",
35
+ "/* Override VSCode background color */\n",
36
+ ".cell-output-ipywidget-background:has(> .cell-output-ipywidget-background\n",
37
+ " > .lm-Widget\n",
38
+ " > *[data-root-id]),\n",
39
+ ".cell-output-ipywidget-background:has(> .lm-Widget > *[data-root-id]) {\n",
40
+ " background-color: transparent !important;\n",
41
+ "}\n",
42
+ "</style>"
43
+ ]
44
+ },
45
+ "metadata": {},
46
+ "output_type": "display_data"
47
+ },
48
+ {
49
+ "data": {
50
+ "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n var py_version = '3.1.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n var reloading = true;\n var Bokeh = root.Bokeh;\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n run_callbacks();\n return null;\n }\n if (!reloading) {\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n var skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n require([\"jspanel\"], function(jsPanel) {\n\twindow.jsPanel = jsPanel\n\ton_load()\n })\n require([\"jspanel-modal\"], function() {\n\ton_load()\n })\n require([\"jspanel-tooltip\"], function() {\n\ton_load()\n })\n require([\"jspanel-hint\"], function() {\n\ton_load()\n })\n require([\"jspanel-layout\"], function() {\n\ton_load()\n })\n require([\"jspanel-contextmenu\"], function() {\n\ton_load()\n })\n require([\"jspanel-dock\"], function() {\n\ton_load()\n })\n require([\"gridstack\"], function(GridStack) {\n\twindow.GridStack = GridStack\n\ton_load()\n })\n require([\"notyf\"], function() {\n\ton_load()\n })\n root._bokeh_is_loading = css_urls.length + 9;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n var existing_stylesheets = []\n var links = document.getElementsByTagName('link')\n for (var i = 0; i < links.length; i++) {\n var link = links[i]\n if (link.href != null) {\n\texisting_stylesheets.push(link.href)\n }\n }\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n if (existing_stylesheets.indexOf(url) !== -1) {\n\ton_load()\n\tcontinue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } var existing_scripts = []\n var scripts = document.getElementsByTagName('script')\n for (var i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n\texisting_scripts.push(script.src)\n }\n }\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (var i = 0; i < js_modules.length; i++) {\n var url = js_modules[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n var url = js_exports[name];\n if (skip.indexOf(url) >= 0 || root[name] != null) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [];\n var js_modules = [];\n var js_exports = {};\n var css_urls = [];\n var inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n\tvar NewBokeh = root.Bokeh;\n\tif (Bokeh.versions === undefined) {\n\t Bokeh.versions = new Map();\n\t}\n\tif (NewBokeh.version !== Bokeh.version) {\n\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n\t}\n\troot.Bokeh = Bokeh;\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n Bokeh = root.Bokeh;\n bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n if (!reloading && (!bokeh_loaded || is_dev)) {\n\troot.Bokeh = undefined;\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n\trun_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));",
51
+ "application/vnd.holoviews_load.v0+json": ""
52
+ },
53
+ "metadata": {},
54
+ "output_type": "display_data"
55
+ },
56
+ {
57
+ "data": {
58
+ "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n",
59
+ "application/vnd.holoviews_load.v0+json": ""
60
+ },
61
+ "metadata": {},
62
+ "output_type": "display_data"
63
+ },
64
+ {
65
+ "data": {
66
+ "text/html": [
67
+ "<style>*[data-root-id],\n",
68
+ "*[data-root-id] > * {\n",
69
+ " box-sizing: border-box;\n",
70
+ " font-family: var(--jp-ui-font-family);\n",
71
+ " font-size: var(--jp-ui-font-size1);\n",
72
+ " color: var(--vscode-editor-foreground, var(--jp-ui-font-color1));\n",
73
+ "}\n",
74
+ "\n",
75
+ "/* Override VSCode background color */\n",
76
+ ".cell-output-ipywidget-background:has(> .cell-output-ipywidget-background\n",
77
+ " > .lm-Widget\n",
78
+ " > *[data-root-id]),\n",
79
+ ".cell-output-ipywidget-background:has(> .lm-Widget > *[data-root-id]) {\n",
80
+ " background-color: transparent !important;\n",
81
+ "}\n",
82
+ "</style>"
83
+ ]
84
+ },
85
+ "metadata": {},
86
+ "output_type": "display_data"
87
+ },
88
+ {
89
+ "data": {
90
+ "application/javascript": "(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n var py_version = '3.1.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n var reloading = true;\n var Bokeh = root.Bokeh;\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n run_callbacks();\n return null;\n }\n if (!reloading) {\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n var skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {'mathjax': '//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS_HTML', 'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'mathjax': {'exports': 'MathJax'}, 'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n require([\"mathjax\"], function() {\n\ton_load()\n })\n require([\"jspanel\"], function(jsPanel) {\n\twindow.jsPanel = jsPanel\n\ton_load()\n })\n require([\"jspanel-modal\"], function() {\n\ton_load()\n })\n require([\"jspanel-tooltip\"], function() {\n\ton_load()\n })\n require([\"jspanel-hint\"], function() {\n\ton_load()\n })\n require([\"jspanel-layout\"], function() {\n\ton_load()\n })\n require([\"jspanel-contextmenu\"], function() {\n\ton_load()\n })\n require([\"jspanel-dock\"], function() {\n\ton_load()\n })\n require([\"gridstack\"], function(GridStack) {\n\twindow.GridStack = GridStack\n\ton_load()\n })\n require([\"notyf\"], function() {\n\ton_load()\n })\n root._bokeh_is_loading = css_urls.length + 10;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n var existing_stylesheets = []\n var links = document.getElementsByTagName('link')\n for (var i = 0; i < links.length; i++) {\n var link = links[i]\n if (link.href != null) {\n\texisting_stylesheets.push(link.href)\n }\n }\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n if (existing_stylesheets.indexOf(url) !== -1) {\n\ton_load()\n\tcontinue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } if (((window['MathJax'] !== undefined) && (!(window['MathJax'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.1/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.1/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } var existing_scripts = []\n var scripts = document.getElementsByTagName('script')\n for (var i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n\texisting_scripts.push(script.src)\n }\n }\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (var i = 0; i < js_modules.length; i++) {\n var url = js_modules[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n var url = js_exports[name];\n if (skip.indexOf(url) >= 0 || root[name] != null) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [\"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML\"];\n var js_modules = [];\n var js_exports = {};\n var css_urls = [];\n var inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n\tvar NewBokeh = root.Bokeh;\n\tif (Bokeh.versions === undefined) {\n\t Bokeh.versions = new Map();\n\t}\n\tif (NewBokeh.version !== Bokeh.version) {\n\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n\t}\n\troot.Bokeh = Bokeh;\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n Bokeh = root.Bokeh;\n bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n if (!reloading && (!bokeh_loaded || is_dev)) {\n\troot.Bokeh = undefined;\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n\trun_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));",
91
+ "application/vnd.holoviews_load.v0+json": ""
92
+ },
93
+ "metadata": {},
94
+ "output_type": "display_data"
95
+ },
96
+ {
97
+ "data": {
98
+ "application/javascript": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n",
99
+ "application/vnd.holoviews_load.v0+json": ""
100
+ },
101
+ "metadata": {},
102
+ "output_type": "display_data"
103
+ },
104
+ {
105
+ "data": {
106
+ "text/html": [
107
+ "<style>*[data-root-id],\n",
108
+ "*[data-root-id] > * {\n",
109
+ " box-sizing: border-box;\n",
110
+ " font-family: var(--jp-ui-font-family);\n",
111
+ " font-size: var(--jp-ui-font-size1);\n",
112
+ " color: var(--vscode-editor-foreground, var(--jp-ui-font-color1));\n",
113
+ "}\n",
114
+ "\n",
115
+ "/* Override VSCode background color */\n",
116
+ ".cell-output-ipywidget-background:has(> .cell-output-ipywidget-background\n",
117
+ " > .lm-Widget\n",
118
+ " > *[data-root-id]),\n",
119
+ ".cell-output-ipywidget-background:has(> .lm-Widget > *[data-root-id]) {\n",
120
+ " background-color: transparent !important;\n",
121
+ "}\n",
122
+ "</style>"
123
+ ]
124
+ },
125
+ "metadata": {},
126
+ "output_type": "display_data"
127
+ }
128
+ ],
129
+ "source": [
130
+ "import panel as pn\n",
131
+ "import pandas as pd\n",
132
+ "import scipy.stats as stats\n",
133
+ "from sqlalchemy import create_engine\n",
134
+ "import hvplot.pandas\n",
135
+ "from datetime import datetime, timedelta\n",
136
+ "from script import processing\n",
137
+ "import numpy as np\n",
138
+ "pn.extension()\n",
139
+ "db_url = 'sqlite:///local.db'\n",
140
+ "engine = create_engine(db_url)\n",
141
+ "import plotly.express as px\n",
142
+ "pn.extension('mathjax')"
143
+ ]
144
+ },
145
+ {
146
+ "cell_type": "code",
147
+ "execution_count": 2,
148
+ "metadata": {},
149
+ "outputs": [],
150
+ "source": [
151
+ "%load_ext autoreload\n",
152
+ "%autoreload 2"
153
+ ]
154
+ },
155
+ {
156
+ "cell_type": "code",
157
+ "execution_count": 3,
158
+ "metadata": {},
159
+ "outputs": [],
160
+ "source": [
161
+ "p_eval_df = None\n",
162
+ "calculated_b_stock = None\n",
163
+ "calculated_p_stock = None\n",
164
+ "# load benchmark stock\n",
165
+ "with engine.connect() as connection:\n",
166
+ " calculated_b_stock = pd.read_sql('calculated_b_stock', con=connection)\n",
167
+ " calculated_p_stock = pd.read_sql('calculated_p_stock', con=connection)\n",
168
+ " p_eval_df = pd.read_sql('p_eval_result', con=connection)"
169
+ ]
170
+ },
171
+ {
172
+ "cell_type": "code",
173
+ "execution_count": 139,
174
+ "metadata": {},
175
+ "outputs": [
176
+ {
177
+ "name": "stdout",
178
+ "output_type": "stream",
179
+ "text": [
180
+ "2023-06-29 00:00:00\n",
181
+ "interaction 0.012116\n",
182
+ "allocation 0.002211\n",
183
+ "selection -0.012116\n",
184
+ "active_return 0.002211\n",
185
+ "notional_return 0.002211\n",
186
+ "dtype: float64\n"
187
+ ]
188
+ },
189
+ {
190
+ "data": {
191
+ "text/html": [
192
+ "<div>\n",
193
+ "<style scoped>\n",
194
+ " .dataframe tbody tr th:only-of-type {\n",
195
+ " vertical-align: middle;\n",
196
+ " }\n",
197
+ "\n",
198
+ " .dataframe tbody tr th {\n",
199
+ " vertical-align: top;\n",
200
+ " }\n",
201
+ "\n",
202
+ " .dataframe thead th {\n",
203
+ " text-align: right;\n",
204
+ " }\n",
205
+ "</style>\n",
206
+ "<table border=\"1\" class=\"dataframe\">\n",
207
+ " <thead>\n",
208
+ " <tr style=\"text-align: right;\">\n",
209
+ " <th></th>\n",
210
+ " <th>date</th>\n",
211
+ " <th>portfolio_return_p</th>\n",
212
+ " <th>portfolio_pct_p</th>\n",
213
+ " <th>portfolio_return_b</th>\n",
214
+ " <th>portfolio_pct_b</th>\n",
215
+ " <th>mkt_cap</th>\n",
216
+ " <th>prev_mkt_cap</th>\n",
217
+ " <th>pnl</th>\n",
218
+ " <th>risk</th>\n",
219
+ " <th>active_return</th>\n",
220
+ " <th>tracking_error</th>\n",
221
+ " <th>cum_pnl</th>\n",
222
+ " <th>return_p</th>\n",
223
+ " <th>return_b</th>\n",
224
+ " </tr>\n",
225
+ " </thead>\n",
226
+ " <tbody>\n",
227
+ " <tr>\n",
228
+ " <th>600</th>\n",
229
+ " <td>2023-06-29</td>\n",
230
+ " <td>0.343213</td>\n",
231
+ " <td>0.006552</td>\n",
232
+ " <td>-0.017097</td>\n",
233
+ " <td>-0.000125</td>\n",
234
+ " <td>1219.334595</td>\n",
235
+ " <td>1211.396987</td>\n",
236
+ " <td>7.937608</td>\n",
237
+ " <td>0.309501</td>\n",
238
+ " <td>0.002785</td>\n",
239
+ " <td>0.218361</td>\n",
240
+ " <td>140.338486</td>\n",
241
+ " <td>0.014219</td>\n",
242
+ " <td>0.011434</td>\n",
243
+ " </tr>\n",
244
+ " </tbody>\n",
245
+ "</table>\n",
246
+ "</div>"
247
+ ],
248
+ "text/plain": [
249
+ " date portfolio_return_p portfolio_pct_p portfolio_return_b \\\n",
250
+ "600 2023-06-29 0.343213 0.006552 -0.017097 \n",
251
+ "\n",
252
+ " portfolio_pct_b mkt_cap prev_mkt_cap pnl risk \\\n",
253
+ "600 -0.000125 1219.334595 1211.396987 7.937608 0.309501 \n",
254
+ "\n",
255
+ " active_return tracking_error cum_pnl return_p return_b \n",
256
+ "600 0.002785 0.218361 140.338486 0.014219 0.011434 "
257
+ ]
258
+ },
259
+ "execution_count": 139,
260
+ "metadata": {},
261
+ "output_type": "execute_result"
262
+ }
263
+ ],
264
+ "source": [
265
+ "## check why activate return and nominal return is not same \n",
266
+ "# start_date = p_eval_df['date'].min()\n",
267
+ "# start_date = datetime(2022, 12, 14)\n",
268
+ "start_date = datetime(2023,6,25)\n",
269
+ "end_date = p_eval_df['date'].max()\n",
270
+ "print(end_date)\n",
271
+ "ticker = \"601117.XSHG\"\n",
272
+ "attributes_df = processing.calculate_attributes_between_dates(\n",
273
+ " start_date, end_date, calculated_b_stock=calculated_b_stock, calculated_p_stock=calculated_p_stock)\n",
274
+ "total_attributes = attributes_df.aggregate({\n",
275
+ " 'interaction': 'sum',\n",
276
+ " 'allocation': 'sum',\n",
277
+ " 'selection': 'sum',\n",
278
+ " 'active_return': 'sum',\n",
279
+ " 'notional_return': 'sum'\n",
280
+ " }) \n",
281
+ "# print(attributes_df.columns)\n",
282
+ "print(total_attributes)\n",
283
+ "return_df = processing.calculate_return(p_eval_df, start_date, end_date)\n",
284
+ "return_df\n",
285
+ "most_recent_row = return_df.tail(1)\n",
286
+ "# print(return_df.head(1))\n",
287
+ "most_recent_row\n",
288
+ "\n",
289
+ "\n"
290
+ ]
291
+ },
292
+ {
293
+ "cell_type": "code",
294
+ "execution_count": 124,
295
+ "metadata": {},
296
+ "outputs": [
297
+ {
298
+ "data": {
299
+ "text/plain": [
300
+ "1219.334714348576"
301
+ ]
302
+ },
303
+ "execution_count": 124,
304
+ "metadata": {},
305
+ "output_type": "execute_result"
306
+ }
307
+ ],
308
+ "source": [
309
+ "1038.573137 * (1 + 0.174048)"
310
+ ]
311
+ },
312
+ {
313
+ "cell_type": "code",
314
+ "execution_count": 137,
315
+ "metadata": {},
316
+ "outputs": [
317
+ {
318
+ "data": {
319
+ "text/plain": [
320
+ "0.01448137201285984"
321
+ ]
322
+ },
323
+ "execution_count": 137,
324
+ "metadata": {},
325
+ "output_type": "execute_result"
326
+ }
327
+ ],
328
+ "source": [
329
+ "# portfolio return\n",
330
+ "attributes_df['w_return_p'] = attributes_df.pct_p * attributes_df.prev_w_in_p_p\n",
331
+ "attributes_df.w_return_p.sum()"
332
+ ]
333
+ },
334
+ {
335
+ "cell_type": "code",
336
+ "execution_count": 129,
337
+ "metadata": {},
338
+ "outputs": [
339
+ {
340
+ "data": {
341
+ "text/plain": [
342
+ "0.09551009084622147"
343
+ ]
344
+ },
345
+ "execution_count": 129,
346
+ "metadata": {},
347
+ "output_type": "execute_result"
348
+ }
349
+ ],
350
+ "source": [
351
+ "0.17190200433908703 - 0.07639191349286556"
352
+ ]
353
+ },
354
+ {
355
+ "cell_type": "code",
356
+ "execution_count": 138,
357
+ "metadata": {},
358
+ "outputs": [
359
+ {
360
+ "data": {
361
+ "text/plain": [
362
+ "0.011250737770502067"
363
+ ]
364
+ },
365
+ "execution_count": 138,
366
+ "metadata": {},
367
+ "output_type": "execute_result"
368
+ }
369
+ ],
370
+ "source": [
371
+ "# benchmark return\n",
372
+ "attributes_df['w_return_b'] = attributes_df.pct_b * attributes_df.prev_w_in_p_b\n",
373
+ "attributes_df.w_return_b.sum()"
374
+ ]
375
+ },
376
+ {
377
+ "cell_type": "code",
378
+ "execution_count": 120,
379
+ "metadata": {},
380
+ "outputs": [
381
+ {
382
+ "name": "stderr",
383
+ "output_type": "stream",
384
+ "text": [
385
+ "/var/folders/v5/2108rh5964q9j741wg_s8r1w0000gn/T/ipykernel_87460/2230555833.py:4: SettingWithCopyWarning: \n",
386
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
387
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
388
+ "\n",
389
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
390
+ " selected_df['return_p'] = (1 + selected_df.portfolio_pct_p).cumprod()\n"
391
+ ]
392
+ },
393
+ {
394
+ "data": {
395
+ "text/html": [
396
+ "<div>\n",
397
+ "<style scoped>\n",
398
+ " .dataframe tbody tr th:only-of-type {\n",
399
+ " vertical-align: middle;\n",
400
+ " }\n",
401
+ "\n",
402
+ " .dataframe tbody tr th {\n",
403
+ " vertical-align: top;\n",
404
+ " }\n",
405
+ "\n",
406
+ " .dataframe thead th {\n",
407
+ " text-align: right;\n",
408
+ " }\n",
409
+ "</style>\n",
410
+ "<table border=\"1\" class=\"dataframe\">\n",
411
+ " <thead>\n",
412
+ " <tr style=\"text-align: right;\">\n",
413
+ " <th></th>\n",
414
+ " <th>date</th>\n",
415
+ " <th>portfolio_return_p</th>\n",
416
+ " <th>portfolio_pct_p</th>\n",
417
+ " <th>portfolio_return_b</th>\n",
418
+ " <th>portfolio_pct_b</th>\n",
419
+ " <th>mkt_cap</th>\n",
420
+ " <th>prev_mkt_cap</th>\n",
421
+ " <th>pnl</th>\n",
422
+ " <th>risk</th>\n",
423
+ " <th>active_return</th>\n",
424
+ " <th>tracking_error</th>\n",
425
+ " <th>cum_pnl</th>\n",
426
+ " <th>return_p</th>\n",
427
+ " </tr>\n",
428
+ " </thead>\n",
429
+ " <tbody>\n",
430
+ " <tr>\n",
431
+ " <th>471</th>\n",
432
+ " <td>2022-12-14</td>\n",
433
+ " <td>0.151775</td>\n",
434
+ " <td>0.000000</td>\n",
435
+ " <td>0.002796</td>\n",
436
+ " <td>0.000000</td>\n",
437
+ " <td>1038.573137</td>\n",
438
+ " <td>1043.453255</td>\n",
439
+ " <td>-4.880117</td>\n",
440
+ " <td>0.327324</td>\n",
441
+ " <td>-0.000508</td>\n",
442
+ " <td>0.225463</td>\n",
443
+ " <td>-40.422972</td>\n",
444
+ " <td>1.000000</td>\n",
445
+ " </tr>\n",
446
+ " <tr>\n",
447
+ " <th>472</th>\n",
448
+ " <td>2022-12-15</td>\n",
449
+ " <td>0.166816</td>\n",
450
+ " <td>0.013638</td>\n",
451
+ " <td>0.004505</td>\n",
452
+ " <td>0.001707</td>\n",
453
+ " <td>1052.737107</td>\n",
454
+ " <td>1038.573137</td>\n",
455
+ " <td>14.163970</td>\n",
456
+ " <td>0.327122</td>\n",
457
+ " <td>0.011931</td>\n",
458
+ " <td>0.225384</td>\n",
459
+ " <td>-26.259002</td>\n",
460
+ " <td>1.013638</td>\n",
461
+ " </tr>\n",
462
+ " <tr>\n",
463
+ " <th>473</th>\n",
464
+ " <td>2022-12-16</td>\n",
465
+ " <td>0.156618</td>\n",
466
+ " <td>-0.009792</td>\n",
467
+ " <td>-0.000671</td>\n",
468
+ " <td>-0.005172</td>\n",
469
+ " <td>1042.429190</td>\n",
470
+ " <td>1052.737107</td>\n",
471
+ " <td>-10.307917</td>\n",
472
+ " <td>0.326859</td>\n",
473
+ " <td>-0.004619</td>\n",
474
+ " <td>0.225175</td>\n",
475
+ " <td>-36.566919</td>\n",
476
+ " <td>1.003713</td>\n",
477
+ " </tr>\n",
478
+ " <tr>\n",
479
+ " <th>474</th>\n",
480
+ " <td>2022-12-19</td>\n",
481
+ " <td>0.144766</td>\n",
482
+ " <td>-0.012217</td>\n",
483
+ " <td>-0.022720</td>\n",
484
+ " <td>-0.022046</td>\n",
485
+ " <td>1029.693832</td>\n",
486
+ " <td>1042.429190</td>\n",
487
+ " <td>-12.735359</td>\n",
488
+ " <td>0.326642</td>\n",
489
+ " <td>0.009829</td>\n",
490
+ " <td>0.225043</td>\n",
491
+ " <td>-49.302277</td>\n",
492
+ " <td>0.991450</td>\n",
493
+ " </tr>\n",
494
+ " <tr>\n",
495
+ " <th>475</th>\n",
496
+ " <td>2022-12-20</td>\n",
497
+ " <td>0.140936</td>\n",
498
+ " <td>-0.003925</td>\n",
499
+ " <td>-0.032668</td>\n",
500
+ " <td>-0.009947</td>\n",
501
+ " <td>1025.652205</td>\n",
502
+ " <td>1029.693832</td>\n",
503
+ " <td>-4.041626</td>\n",
504
+ " <td>0.326312</td>\n",
505
+ " <td>0.006022</td>\n",
506
+ " <td>0.224844</td>\n",
507
+ " <td>-53.343904</td>\n",
508
+ " <td>0.987559</td>\n",
509
+ " </tr>\n",
510
+ " <tr>\n",
511
+ " <th>...</th>\n",
512
+ " <td>...</td>\n",
513
+ " <td>...</td>\n",
514
+ " <td>...</td>\n",
515
+ " <td>...</td>\n",
516
+ " <td>...</td>\n",
517
+ " <td>...</td>\n",
518
+ " <td>...</td>\n",
519
+ " <td>...</td>\n",
520
+ " <td>...</td>\n",
521
+ " <td>...</td>\n",
522
+ " <td>...</td>\n",
523
+ " <td>...</td>\n",
524
+ " <td>...</td>\n",
525
+ " </tr>\n",
526
+ " <tr>\n",
527
+ " <th>596</th>\n",
528
+ " <td>2023-06-21</td>\n",
529
+ " <td>0.338226</td>\n",
530
+ " <td>-0.012860</td>\n",
531
+ " <td>-0.012265</td>\n",
532
+ " <td>-0.020746</td>\n",
533
+ " <td>1213.343064</td>\n",
534
+ " <td>1229.149762</td>\n",
535
+ " <td>-15.806698</td>\n",
536
+ " <td>0.310372</td>\n",
537
+ " <td>0.007886</td>\n",
538
+ " <td>0.219003</td>\n",
539
+ " <td>134.346955</td>\n",
540
+ " <td>1.168279</td>\n",
541
+ " </tr>\n",
542
+ " <tr>\n",
543
+ " <th>597</th>\n",
544
+ " <td>2023-06-26</td>\n",
545
+ " <td>0.327466</td>\n",
546
+ " <td>-0.009151</td>\n",
547
+ " <td>-0.028546</td>\n",
548
+ " <td>-0.016269</td>\n",
549
+ " <td>1202.239760</td>\n",
550
+ " <td>1213.343064</td>\n",
551
+ " <td>-11.103304</td>\n",
552
+ " <td>0.310175</td>\n",
553
+ " <td>0.007118</td>\n",
554
+ " <td>0.218860</td>\n",
555
+ " <td>123.243651</td>\n",
556
+ " <td>1.157588</td>\n",
557
+ " </tr>\n",
558
+ " <tr>\n",
559
+ " <th>598</th>\n",
560
+ " <td>2023-06-27</td>\n",
561
+ " <td>0.339179</td>\n",
562
+ " <td>0.010630</td>\n",
563
+ " <td>-0.016308</td>\n",
564
+ " <td>0.012234</td>\n",
565
+ " <td>1215.019735</td>\n",
566
+ " <td>1202.239760</td>\n",
567
+ " <td>12.779975</td>\n",
568
+ " <td>0.309985</td>\n",
569
+ " <td>-0.001604</td>\n",
570
+ " <td>0.218682</td>\n",
571
+ " <td>136.023626</td>\n",
572
+ " <td>1.169893</td>\n",
573
+ " </tr>\n",
574
+ " <tr>\n",
575
+ " <th>599</th>\n",
576
+ " <td>2023-06-28</td>\n",
577
+ " <td>0.335389</td>\n",
578
+ " <td>-0.002982</td>\n",
579
+ " <td>-0.016974</td>\n",
580
+ " <td>-0.000665</td>\n",
581
+ " <td>1211.396987</td>\n",
582
+ " <td>1215.019735</td>\n",
583
+ " <td>-3.622748</td>\n",
584
+ " <td>0.309735</td>\n",
585
+ " <td>-0.002316</td>\n",
586
+ " <td>0.218507</td>\n",
587
+ " <td>132.400878</td>\n",
588
+ " <td>1.166405</td>\n",
589
+ " </tr>\n",
590
+ " <tr>\n",
591
+ " <th>600</th>\n",
592
+ " <td>2023-06-29</td>\n",
593
+ " <td>0.343213</td>\n",
594
+ " <td>0.006552</td>\n",
595
+ " <td>-0.017097</td>\n",
596
+ " <td>-0.000125</td>\n",
597
+ " <td>1219.334595</td>\n",
598
+ " <td>1211.396987</td>\n",
599
+ " <td>7.937608</td>\n",
600
+ " <td>0.309501</td>\n",
601
+ " <td>0.006678</td>\n",
602
+ " <td>0.218361</td>\n",
603
+ " <td>140.338486</td>\n",
604
+ " <td>1.174048</td>\n",
605
+ " </tr>\n",
606
+ " </tbody>\n",
607
+ "</table>\n",
608
+ "<p>130 rows × 13 columns</p>\n",
609
+ "</div>"
610
+ ],
611
+ "text/plain": [
612
+ " date portfolio_return_p portfolio_pct_p portfolio_return_b \\\n",
613
+ "471 2022-12-14 0.151775 0.000000 0.002796 \n",
614
+ "472 2022-12-15 0.166816 0.013638 0.004505 \n",
615
+ "473 2022-12-16 0.156618 -0.009792 -0.000671 \n",
616
+ "474 2022-12-19 0.144766 -0.012217 -0.022720 \n",
617
+ "475 2022-12-20 0.140936 -0.003925 -0.032668 \n",
618
+ ".. ... ... ... ... \n",
619
+ "596 2023-06-21 0.338226 -0.012860 -0.012265 \n",
620
+ "597 2023-06-26 0.327466 -0.009151 -0.028546 \n",
621
+ "598 2023-06-27 0.339179 0.010630 -0.016308 \n",
622
+ "599 2023-06-28 0.335389 -0.002982 -0.016974 \n",
623
+ "600 2023-06-29 0.343213 0.006552 -0.017097 \n",
624
+ "\n",
625
+ " portfolio_pct_b mkt_cap prev_mkt_cap pnl risk \\\n",
626
+ "471 0.000000 1038.573137 1043.453255 -4.880117 0.327324 \n",
627
+ "472 0.001707 1052.737107 1038.573137 14.163970 0.327122 \n",
628
+ "473 -0.005172 1042.429190 1052.737107 -10.307917 0.326859 \n",
629
+ "474 -0.022046 1029.693832 1042.429190 -12.735359 0.326642 \n",
630
+ "475 -0.009947 1025.652205 1029.693832 -4.041626 0.326312 \n",
631
+ ".. ... ... ... ... ... \n",
632
+ "596 -0.020746 1213.343064 1229.149762 -15.806698 0.310372 \n",
633
+ "597 -0.016269 1202.239760 1213.343064 -11.103304 0.310175 \n",
634
+ "598 0.012234 1215.019735 1202.239760 12.779975 0.309985 \n",
635
+ "599 -0.000665 1211.396987 1215.019735 -3.622748 0.309735 \n",
636
+ "600 -0.000125 1219.334595 1211.396987 7.937608 0.309501 \n",
637
+ "\n",
638
+ " active_return tracking_error cum_pnl return_p \n",
639
+ "471 -0.000508 0.225463 -40.422972 1.000000 \n",
640
+ "472 0.011931 0.225384 -26.259002 1.013638 \n",
641
+ "473 -0.004619 0.225175 -36.566919 1.003713 \n",
642
+ "474 0.009829 0.225043 -49.302277 0.991450 \n",
643
+ "475 0.006022 0.224844 -53.343904 0.987559 \n",
644
+ ".. ... ... ... ... \n",
645
+ "596 0.007886 0.219003 134.346955 1.168279 \n",
646
+ "597 0.007118 0.218860 123.243651 1.157588 \n",
647
+ "598 -0.001604 0.218682 136.023626 1.169893 \n",
648
+ "599 -0.002316 0.218507 132.400878 1.166405 \n",
649
+ "600 0.006678 0.218361 140.338486 1.174048 \n",
650
+ "\n",
651
+ "[130 rows x 13 columns]"
652
+ ]
653
+ },
654
+ "execution_count": 120,
655
+ "metadata": {},
656
+ "output_type": "execute_result"
657
+ }
658
+ ],
659
+ "source": [
660
+ "selected_df = p_eval_df[p_eval_df.date.between(start_date, end_date)]\n",
661
+ "selected_df.iloc[0, selected_df.columns.get_indexer(\n",
662
+ " ['portfolio_pct_p', 'portfolio_pct_b'])] = 0\n",
663
+ "selected_df['return_p'] = (1 + selected_df.portfolio_pct_p).cumprod()\n",
664
+ "selected_df"
665
+ ]
666
+ },
667
+ {
668
+ "cell_type": "code",
669
+ "execution_count": 140,
670
+ "metadata": {},
671
+ "outputs": [
672
+ {
673
+ "data": {
674
+ "text/plain": [
675
+ "Index(['date', 'portfolio_return_p', 'portfolio_pct_p', 'portfolio_return_b',\n",
676
+ " 'portfolio_pct_b', 'mkt_cap', 'prev_mkt_cap', 'pnl', 'risk',\n",
677
+ " 'active_return', 'tracking_error', 'cum_pnl'],\n",
678
+ " dtype='object')"
679
+ ]
680
+ },
681
+ "execution_count": 140,
682
+ "metadata": {},
683
+ "output_type": "execute_result"
684
+ }
685
+ ],
686
+ "source": [
687
+ "p_eval_df.columns"
688
+ ]
689
+ }
690
+ ],
691
+ "metadata": {
692
+ "kernelspec": {
693
+ "display_name": "portfolio_risk_assesment",
694
+ "language": "python",
695
+ "name": "python3"
696
+ },
697
+ "language_info": {
698
+ "codemirror_mode": {
699
+ "name": "ipython",
700
+ "version": 3
701
+ },
702
+ "file_extension": ".py",
703
+ "mimetype": "text/x-python",
704
+ "name": "python",
705
+ "nbconvert_exporter": "python",
706
+ "pygments_lexer": "ipython3",
707
+ "version": "3.11.4"
708
+ },
709
+ "orig_nbformat": 4
710
+ },
711
+ "nbformat": 4,
712
+ "nbformat_minor": 2
713
+ }
utils.py CHANGED
@@ -1,3 +1,110 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:873819e905c0ff108acd3da231a5b6c00c8830d4ae8cbe99b280899c7893e026
3
- size 3697
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytz
2
+ import datetime
3
+ import io
4
+ import pandas as pd
5
+
6
+
7
+ def time_in_beijing():
8
+ '''
9
+ return current time in Beijing as datetime object
10
+ '''
11
+ tz = pytz.timezone('Asia/Shanghai')
12
+ dt = datetime.datetime.now(tz)
13
+ return dt
14
+
15
+
16
+ def convert_string_to_datetime(date_string, time_zone="Asia/Shanghai"):
17
+ '''
18
+ Convert a string to a datetime object with the timezone by default,
19
+ Shanghai
20
+ '''
21
+ dt = datetime.datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S.%f')
22
+ tz = pytz.timezone(time_zone)
23
+ dt = tz.localize(dt)
24
+ return dt
25
+
26
+
27
+ def create_stocks_entry_from_excel(byte_string):
28
+ '''create stock entry from excel file
29
+ Parameters
30
+ ----------
31
+ byte_string: bytes
32
+ the byte string of the excel file
33
+ Returns
34
+ -------
35
+ new_stock_entry: list
36
+ [{ticker:str, shares:int, mean_price: float, date:datetime.datetime}]
37
+ the list of stock entry
38
+ '''
39
+ uploaded_df = None
40
+ with io.BytesIO(byte_string) as f:
41
+ uploaded_df = pd.read_excel(f)
42
+
43
+ # throw exception if doesn't have required columns
44
+ if not set(['证券代码', '持仓数量', '平均建仓成本', 'time_stamp']).issubset(uploaded_df.columns):
45
+ raise Exception('Missing required columns')
46
+ # print(uploaded_df)
47
+ # uploaded_df = pd.read_excel()
48
+ uploaded_df.drop(columns='Unnamed: 0', inplace=True)
49
+ # Define the regular expression pattern to match the string endings
50
+ pattern = r'\.(sz|sh)$'
51
+ # Define the replacement strings for each match group
52
+ replacements = {'.sz': '.XSHE', '.sh': '.XSHG'}
53
+ # Use the str.replace method with the pattern and replacements
54
+ uploaded_df['证券代码'] = uploaded_df['证券代码'].str.lower()
55
+ uploaded_df['证券代码'] = uploaded_df['证券代码'].str.replace(
56
+ pattern, lambda m: replacements[m.group()], regex=True)
57
+ new_stock_entry = [
58
+ dict(ticker=ticker, shares=shares, date=time, mean_price=mean_price)
59
+ for ticker, shares, mean_price, time in zip(
60
+ uploaded_df['证券代码'],
61
+ uploaded_df['持仓数量'],
62
+ uploaded_df['平均建仓成本'],
63
+ pd.to_datetime(uploaded_df['time_stamp']))]
64
+ # new_profile, error = api.update_portfolio_profile(new_stock_entry)
65
+ print(new_stock_entry)
66
+ return new_stock_entry
67
+
68
+
69
+ def style_number(vals):
70
+ '''color negative number as red, positive as green
71
+ Parameters
72
+ ----------
73
+ vals: df columns
74
+ the columns to be styled
75
+ Returns
76
+ -------
77
+ list
78
+ the list of style
79
+
80
+ '''
81
+ return ['color: red' if v < 0 else 'color: green' for v in vals]
82
+
83
+
84
+ def create_share_changes_report(df):
85
+ '''Create a markdown report of the share changes for certain date
86
+ Parameters
87
+ ----------
88
+ df: pd.DataFrame
89
+ the dataframe of profile for a specific date
90
+ Returns
91
+ -------
92
+ markdown: str
93
+ '''
94
+
95
+ date_str = df.date.to_list()[0].strftime('%Y-%m-%d %H:%M:%S')
96
+ markdown = f"### {date_str}\n\n"
97
+ markdown += 'Ticker | Display Name | Share Changes\n'
98
+ markdown += '--- | --- | ---\n'
99
+ for _, row in df.iterrows():
100
+ share_changes = row['share_changes']
101
+ # Apply green color to positive numbers and red color to negative numbers
102
+ if share_changes > 0:
103
+ share_changes_str = f'<span style="color:green">{share_changes}</span>'
104
+ elif share_changes < 0:
105
+ share_changes_str = f'<span style="color:red">{share_changes}</span>'
106
+ else:
107
+ share_changes_str = str(share_changes)
108
+ markdown += '{} | {} | {}\n'.format(row['ticker'],
109
+ row['display_name'], share_changes_str)
110
+ return markdown