Spaces:
Runtime error
Runtime error
huggingface112
commited on
Commit
•
976166f
1
Parent(s):
fec3fd9
move files to normal tracking except .db
Browse files- .gitattributes +0 -6
- Dockerfile +16 -3
- README.md +9 -3
- api.py +346 -3
- apiMonitorPage.py +40 -3
- api_data_samples.ipynb +887 -3
- app.ipynb +0 -0
- appComponents.py +817 -3
- app_ini.ipynb +723 -3
- find_outlier.ipynb +120 -3
- index_page.py +73 -3
- initialize_db.py +28 -3
- newBackgroundTask.py +12 -3
- pipeline.py +337 -3
- pipeline/bhb.ipynb +0 -0
- pipeline/create_dumpy_data.ipynb +36 -3
- pipeline/db_operation.py +35 -3
- pipeline/model.py +41 -3
- pipeline/test_db_peration.py +107 -3
- portfolioEditingPage.ipynb +0 -0
- portfolioEditingPage.py +377 -3
- portfolio_page.py +4 -3
- requirements.txt +136 -3
- script/api_test.ipynb +0 -0
- script/description.py +14 -3
- script/downloadData.ipynb +751 -3
- script/downloadData.py +10 -3
- script/pipeline.ipynb +521 -3
- script/processing.ipynb +501 -3
- script/processing.py +369 -3
- script/processing2.ipynb +1836 -3
- script/stream_pricessing.ipynb +617 -3
- script/stream_processing.py +32 -3
- script/styling.py +15 -3
- settings.py +9 -3
- styling.py +15 -3
- table_schema.py +28 -3
- test.ipynb +47 -3
- test_background_task.py +26 -3
- test_responsive.py +14 -3
- testing_pipeline.ipynb +300 -3
- total_return.ipynb +713 -3
- utils.py +110 -3
.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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
1 |
+
'''
|
2 |
+
A page to view history of portfolio and update portfolio
|
3 |
+
'''
|
4 |
+
|
requirements.txt
CHANGED
@@ -1,3 +1,136 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|