neurostats-API 0.0.22__py3-none-any.whl → 0.0.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- neurostats_API/__init__.py +1 -1
- neurostats_API/fetchers/balance_sheet.py +59 -36
- neurostats_API/fetchers/base.py +6 -2
- neurostats_API/fetchers/cash_flow.py +87 -66
- neurostats_API/fetchers/finance_overview.py +26 -26
- neurostats_API/fetchers/institution.py +211 -97
- neurostats_API/fetchers/margin_trading.py +121 -94
- neurostats_API/fetchers/month_revenue.py +139 -105
- neurostats_API/fetchers/profit_lose.py +100 -80
- neurostats_API/fetchers/tech.py +61 -26
- neurostats_API/fetchers/tej_finance_report.py +23 -8
- neurostats_API/fetchers/value_invest.py +32 -12
- neurostats_API/tools/tej_db/tej_db_percent_index.yaml +0 -3
- neurostats_API/tools/tej_db/tej_db_skip_index.yaml +12 -1
- neurostats_API/tools/tej_db/tej_db_thousand_index.yaml +0 -4
- neurostats_API/utils/calculate_value.py +5 -2
- neurostats_API/utils/data_process.py +11 -5
- neurostats_API/utils/logger.py +21 -0
- {neurostats_API-0.0.22.dist-info → neurostats_API-0.0.23.dist-info}/METADATA +2 -2
- neurostats_API-0.0.23.dist-info/RECORD +35 -0
- neurostats_API-0.0.22.dist-info/RECORD +0 -34
- {neurostats_API-0.0.22.dist-info → neurostats_API-0.0.23.dist-info}/WHEEL +0 -0
- {neurostats_API-0.0.22.dist-info → neurostats_API-0.0.23.dist-info}/top_level.txt +0 -0
@@ -22,6 +22,16 @@ class ProfitLoseFetcher(StatsFetcher):
|
|
22
22
|
"us_stats": self.process_data_us
|
23
23
|
}
|
24
24
|
|
25
|
+
self.return_keys = [
|
26
|
+
'profit_lose', 'grand_total_profit_lose', 'revenue', 'grand_total_revenue',
|
27
|
+
'gross_profit', 'grand_total_gross_profit', 'gross_profit_percentage',
|
28
|
+
'grand_total_gross_profit_percentage', 'operating_income', 'grand_total_operating_income', 'operating_income_percentage',
|
29
|
+
'grand_total_operating_income_percentage', 'net_income_before_tax', 'grand_total_net_income_before_tax', 'net_income_before_tax_percentage',
|
30
|
+
'grand_total_net_income_before_tax_percentage', 'net_income', 'grand_total_net_income', 'net_income_percentage',
|
31
|
+
'grand_total_income_percentage', 'EPS', 'EPS_growth', 'grand_total_EPS',
|
32
|
+
'grand_total_EPS_growth', 'profit_lose_all', 'profit_lose_YoY'
|
33
|
+
]
|
34
|
+
|
25
35
|
def prepare_query(self):
|
26
36
|
pipeline = super().prepare_query()
|
27
37
|
|
@@ -30,28 +40,25 @@ class ProfitLoseFetcher(StatsFetcher):
|
|
30
40
|
chart_name = name_map.get(self.collection_name, "income_statement")
|
31
41
|
|
32
42
|
append_pipeline = [
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
"
|
38
|
-
|
39
|
-
"
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
{
|
49
|
-
"$sort": {
|
50
|
-
"year": -1,
|
51
|
-
"season": -1
|
43
|
+
{
|
44
|
+
"$project": {
|
45
|
+
"_id": 0,
|
46
|
+
"ticker": 1,
|
47
|
+
"company_name": 1,
|
48
|
+
"seasonal_data": {
|
49
|
+
"$map": {
|
50
|
+
"input": {"$ifNull": ["$seasonal_data", []]},
|
51
|
+
"as": "season",
|
52
|
+
"in": {
|
53
|
+
"year": "$$season.year",
|
54
|
+
"season": "$$season.season",
|
55
|
+
"data": {"$ifNull": [f"$$season.{chart_name}", []]}
|
56
|
+
}
|
57
|
+
}
|
52
58
|
}
|
53
59
|
}
|
54
|
-
|
60
|
+
}
|
61
|
+
]
|
55
62
|
|
56
63
|
pipeline = pipeline + append_pipeline
|
57
64
|
|
@@ -63,6 +70,8 @@ class ProfitLoseFetcher(StatsFetcher):
|
|
63
70
|
def query_data(self):
|
64
71
|
fetched_data = self.collect_data()
|
65
72
|
|
73
|
+
fetched_data = fetched_data[0]
|
74
|
+
|
66
75
|
process_fn = self.process_function_map.get(
|
67
76
|
self.collection_name, self.process_data_us
|
68
77
|
)
|
@@ -79,101 +88,108 @@ class ProfitLoseFetcher(StatsFetcher):
|
|
79
88
|
'latest_target_year',
|
80
89
|
StatsDateTime.get_today().year - 1
|
81
90
|
)
|
82
|
-
target_season = latest_time.get(
|
83
|
-
|
84
|
-
).get('latest_season', 4)
|
91
|
+
target_season = latest_time.get('seasonal_data',
|
92
|
+
{}).get('latest_season', 4)
|
85
93
|
|
86
94
|
return_dict = {
|
87
95
|
"ticker": self.ticker,
|
88
|
-
"company_name": fetched_data[
|
96
|
+
"company_name": fetched_data['company_name'],
|
89
97
|
}
|
90
98
|
|
99
|
+
seasonal_data = fetched_data.get('seasonal_data', [])
|
100
|
+
|
101
|
+
if (not seasonal_data):
|
102
|
+
return_dict.update(self._get_empty_structure())
|
103
|
+
return return_dict
|
104
|
+
|
91
105
|
profit_lose_dict = {
|
92
|
-
f"{data['year']}Q{data['season']}": data['
|
93
|
-
for data in
|
106
|
+
f"{data['year']}Q{data['season']}": data['data']
|
107
|
+
for data in seasonal_data
|
94
108
|
}
|
95
109
|
|
110
|
+
profit_lose_dict = YoY_Calculator.cal_QoQ(profit_lose_dict)
|
96
111
|
profit_lose_df = pd.DataFrame.from_dict(profit_lose_dict)
|
97
112
|
target_season_col = profit_lose_df.columns.str.endswith(
|
98
113
|
f"Q{target_season}"
|
99
114
|
)
|
100
115
|
profit_lose_df = profit_lose_df.loc[:, target_season_col]
|
101
|
-
|
116
|
+
|
117
|
+
old_profit_lose_df = StatsProcessor.expand_value_percentage(
|
102
118
|
profit_lose_df
|
103
119
|
)
|
104
|
-
|
105
|
-
value_col =
|
106
|
-
percentage_col =
|
107
|
-
|
108
|
-
grand_total_value_col =
|
120
|
+
# OLD: 回傳包含value & percentage
|
121
|
+
value_col = old_profit_lose_df.columns.str.endswith(f"_value")
|
122
|
+
percentage_col = old_profit_lose_df.columns.str.endswith(f"_percentage")
|
123
|
+
# OLD: 回傳剔除grand_total
|
124
|
+
grand_total_value_col = old_profit_lose_df.columns.str.endswith(
|
109
125
|
f"grand_total_value"
|
110
126
|
)
|
111
|
-
grand_total_percentage_col =
|
127
|
+
grand_total_percentage_col = old_profit_lose_df.columns.str.endswith(
|
112
128
|
f"grand_total_percentage"
|
113
129
|
)
|
114
130
|
|
115
|
-
|
131
|
+
old_profit_lose_df = old_profit_lose_df.loc[:, (
|
116
132
|
(value_col & ~grand_total_value_col) |
|
117
133
|
(percentage_col & ~grand_total_percentage_col)
|
118
134
|
)]
|
119
135
|
|
120
|
-
for time_index,
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
"
|
128
|
-
"
|
129
|
-
"YoY_3",
|
130
|
-
"YoY_5",
|
131
|
-
"YoY_10",
|
132
|
-
"grand_total_YoY_1",
|
133
|
-
"grand_total_YoY_3",
|
134
|
-
"grand_total_YoY_5",
|
135
|
-
"grand_total_YoY_10",
|
136
|
-
]
|
137
|
-
# flatten dict
|
138
|
-
new_profit_lose = self.flatten_dict(
|
139
|
-
profit_lose, index_names, target_keys
|
136
|
+
for time_index, data_dict in profit_lose_dict.items():
|
137
|
+
profit_lose_dict[time_index] = self.flatten_dict(
|
138
|
+
value_dict=data_dict,
|
139
|
+
indexes=list(data_dict.keys()),
|
140
|
+
target_keys=[
|
141
|
+
"value", "growth", "percentage", "grand_total",
|
142
|
+
"grand_total_percentage"
|
143
|
+
] + [f"YoY_{i}" for i in [1, 3, 5, 10]] +
|
144
|
+
[f"grand_total_YoY_{i}" for i in [1, 3, 5, 10]]
|
140
145
|
)
|
141
|
-
profit_lose_dict[time_index] = new_profit_lose
|
142
|
-
|
143
|
-
profit_lose_df = pd.DataFrame.from_dict(profit_lose_dict)
|
144
146
|
|
147
|
+
profit_lose_df = pd.DataFrame.from_dict(profit_lose_dict).T
|
145
148
|
# EPS的value用元計算
|
146
|
-
eps_index =
|
147
|
-
"_value"
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
149
|
+
eps_index = (
|
150
|
+
profit_lose_df.columns.str.endswith("_value")
|
151
|
+
& profit_lose_df.columns.str.contains("每股盈餘")
|
152
|
+
)
|
153
|
+
eps_copy = profit_lose_df.loc[:, eps_index].copy()
|
154
|
+
eps_mask_index = eps_copy.columns
|
155
|
+
profit_lose_df[eps_mask_index] = profit_lose_df[eps_mask_index].map(
|
156
|
+
lambda x: StatsProcessor.cal_non_percentage(x, postfix="元")
|
157
|
+
)
|
153
158
|
|
154
159
|
# percentage處理
|
155
|
-
percentage_index = profit_lose_df.
|
156
|
-
|
157
|
-
|
160
|
+
percentage_index = profit_lose_df.columns.str.endswith("percentage")
|
161
|
+
growth_index = profit_lose_df.columns.str.endswith("growth")
|
162
|
+
percentage_mask = (percentage_index | growth_index)
|
163
|
+
percentage_copy = profit_lose_df.loc[:, percentage_mask]
|
164
|
+
percentage_mask_index = percentage_copy.columns
|
165
|
+
|
166
|
+
profit_lose_df[percentage_mask_index] = profit_lose_df[
|
167
|
+
percentage_mask_index].map(
|
158
168
|
lambda x: StatsProcessor.
|
159
169
|
cal_non_percentage(x, to_str=True, postfix="%")
|
160
170
|
)
|
161
171
|
|
162
172
|
# YoY處理: 乘以100
|
163
|
-
YoY_index = profit_lose_df.
|
164
|
-
|
165
|
-
|
173
|
+
YoY_index = profit_lose_df.columns.str.contains("YoY")
|
174
|
+
YoY_mask = YoY_index
|
175
|
+
YoY_copy = profit_lose_df.loc[:, YoY_mask]
|
176
|
+
YoY_mask_cols = YoY_copy.columns
|
177
|
+
|
178
|
+
profit_lose_df[YoY_mask_cols] = profit_lose_df[YoY_mask_cols].map(
|
179
|
+
lambda x: StatsProcessor.cal_percentage(x)
|
180
|
+
)
|
166
181
|
|
167
182
|
# 剩下的處理: 乘以千元
|
168
183
|
value_index = ~(
|
169
|
-
percentage_index | YoY_index |
|
184
|
+
percentage_index | growth_index | YoY_index | eps_index
|
170
185
|
) # 除了上述以外的 index
|
171
|
-
profit_lose_df.loc[value_index] = profit_lose_df.loc[
|
172
|
-
value_index].apply(
|
173
|
-
lambda x: StatsProcessor.cal_non_percentage(x, postfix="千元")
|
174
|
-
)
|
175
186
|
|
176
|
-
|
187
|
+
value_col = profit_lose_df.loc[:, value_index].columns
|
188
|
+
profit_lose_df[value_col] = profit_lose_df[value_col].map(
|
189
|
+
lambda x: StatsProcessor.cal_non_percentage(x, postfix="千元")
|
190
|
+
)
|
191
|
+
|
192
|
+
total_table = profit_lose_df.replace("N/A", None).T
|
177
193
|
|
178
194
|
# 取特定季度
|
179
195
|
target_season_columns = total_table.columns.str.endswith(
|
@@ -192,12 +208,11 @@ class ProfitLoseFetcher(StatsFetcher):
|
|
192
208
|
)
|
193
209
|
break
|
194
210
|
except Exception as e:
|
195
|
-
print(str(e))
|
196
211
|
continue
|
197
212
|
|
198
213
|
return_dict.update(
|
199
214
|
{
|
200
|
-
"profit_lose":
|
215
|
+
"profit_lose": old_profit_lose_df,
|
201
216
|
"profit_lose_all": total_table.copy(),
|
202
217
|
"profit_lose_YoY": total_table_YoY
|
203
218
|
}
|
@@ -218,8 +233,8 @@ class ProfitLoseFetcher(StatsFetcher):
|
|
218
233
|
table_dict[time_index] = self.flatten_dict(
|
219
234
|
value_dict=data_dict,
|
220
235
|
indexes=list(data_dict.keys()),
|
221
|
-
target_keys=["value", "growth"] +
|
222
|
-
|
236
|
+
target_keys=["value", "growth"] +
|
237
|
+
[f"YoY_{i}" for i in [1, 3, 5, 10]]
|
223
238
|
)
|
224
239
|
|
225
240
|
# 計算QoQ
|
@@ -231,3 +246,8 @@ class ProfitLoseFetcher(StatsFetcher):
|
|
231
246
|
}
|
232
247
|
|
233
248
|
return return_dict
|
249
|
+
|
250
|
+
def _get_empty_structure(self):
|
251
|
+
return {
|
252
|
+
key: pd.DataFrame(columns= pd.Index([], name = 'date')) for key in self.return_keys
|
253
|
+
}
|
neurostats_API/fetchers/tech.py
CHANGED
@@ -13,7 +13,8 @@ class TechFetcher(StatsFetcher):
|
|
13
13
|
|
14
14
|
super().__init__(ticker, db_client)
|
15
15
|
if (ticker in self.tw_company_list.keys()):
|
16
|
-
self.
|
16
|
+
self.twse_collection = self.db['twse_stats']
|
17
|
+
self.tej_collection = self.db["TWN/APIPRCD"]
|
17
18
|
|
18
19
|
self.full_ohlcv = self._get_ohlcv()
|
19
20
|
self.basic_indexes = [
|
@@ -53,35 +54,47 @@ class TechFetcher(StatsFetcher):
|
|
53
54
|
|
54
55
|
required_cols = ['date', 'open', 'high', 'low', 'close', 'volume']
|
55
56
|
|
56
|
-
|
57
|
-
|
58
|
-
if self.ticker in ['GSPC', 'IXIC', 'DJI', 'TWII']:
|
59
|
-
full_tick = f'^{self.ticker}'
|
60
|
-
elif(self.ticker in self.tw_company_list.keys()):
|
61
|
-
full_tick = f'{self.ticker}.tw'
|
62
|
-
else:
|
63
|
-
full_tick = f"{self.ticker}"
|
64
|
-
|
57
|
+
if self.ticker in ['GSPC', 'IXIC', 'DJI', 'TWII']:
|
58
|
+
full_tick = f'^{self.ticker}'
|
65
59
|
df = self.conduct_yf_search(full_tick)
|
66
60
|
|
67
|
-
|
68
|
-
self.ticker in self.tw_company_list.keys() and
|
69
|
-
not self.has_required_columns(df, required_cols)
|
70
|
-
):
|
71
|
-
full_tick = f'{self.ticker}.two'
|
61
|
+
return df[required_cols]
|
72
62
|
|
73
|
-
|
63
|
+
elif(self.ticker in self.tw_company_list.keys()):
|
64
|
+
search_fns = [
|
65
|
+
self.conduct_db_search_twse,
|
66
|
+
self.conduct_db_search_tej,
|
67
|
+
lambda: self.conduct_yf_search(f'{self.ticker}.tw'),
|
68
|
+
lambda: self.conduct_yf_search(f'{self.ticker}.two')
|
69
|
+
]
|
70
|
+
|
71
|
+
for search_method in search_fns:
|
72
|
+
try:
|
73
|
+
df = search_method()
|
74
|
+
break
|
75
|
+
except (KeyError, ValueError, TypeError):
|
76
|
+
continue
|
77
|
+
else:
|
78
|
+
return pd.DataFrame(columns=required_cols)
|
74
79
|
|
75
|
-
|
76
|
-
raise ValueError(f"No data found for ticker: {self.ticker}")\
|
77
|
-
|
80
|
+
# break跳出後
|
78
81
|
return df[required_cols]
|
82
|
+
else: # 美股
|
83
|
+
search_fns = [
|
84
|
+
self.conduct_db_search_us,
|
85
|
+
lambda : self.conduct_yf_search(f"{self.ticker}")
|
86
|
+
]
|
87
|
+
for search_method in search_fns:
|
88
|
+
try:
|
89
|
+
df = search_method()
|
90
|
+
break
|
91
|
+
except (KeyError, ValueError, TypeError):
|
92
|
+
continue
|
93
|
+
else:
|
94
|
+
df = pd.DataFrame()
|
79
95
|
|
80
|
-
|
81
|
-
|
82
|
-
return self.conduct_db_search_tej()
|
83
|
-
elif (self.collection_name == 'us_stats'):
|
84
|
-
return self.conduct_db_search_us()
|
96
|
+
return df
|
97
|
+
|
85
98
|
|
86
99
|
def get_daily(self):
|
87
100
|
|
@@ -175,13 +188,35 @@ class TechFetcher(StatsFetcher):
|
|
175
188
|
if not self.has_required_columns(df, required_cols):
|
176
189
|
missing_cols = [col for col in required_cols if col not in df.columns]
|
177
190
|
missing_cols = ",".join(missing_cols)
|
178
|
-
print(Warning(f"{missing_cols} not in columns"))
|
179
191
|
for col in missing_cols:
|
180
192
|
df[col] = pd.NA
|
181
|
-
# raise KeyError(f"Missing required columns")
|
182
193
|
|
183
194
|
return df[required_cols]
|
184
195
|
|
196
|
+
|
197
|
+
def conduct_db_search_twse(self):
|
198
|
+
required_cols = ['date', 'open', 'high', 'low', 'close', 'volume']
|
199
|
+
match_query = {"ticker" : self.ticker}
|
200
|
+
proj_query = {"_id": 0, "daily_data": 1}
|
201
|
+
|
202
|
+
full_data = self.twse_collection.find_one(match_query, proj_query)
|
203
|
+
|
204
|
+
if (not full_data):
|
205
|
+
raise ValueError("No ticker found in database twse_stats")
|
206
|
+
|
207
|
+
daily_data = full_data.get("daily_data", [])
|
208
|
+
|
209
|
+
if (not isinstance(daily_data, list)):
|
210
|
+
raise ValueError("No ticker found in database twse_stats")
|
211
|
+
|
212
|
+
df = pd.DataFrame(daily_data)
|
213
|
+
if not self.has_required_columns(df, required_cols):
|
214
|
+
raise KeyError(f"Missing required columns")
|
215
|
+
|
216
|
+
df = df[required_cols]
|
217
|
+
df = df.sort_values(by = 'date').drop_duplicates(subset=['date'])
|
218
|
+
|
219
|
+
return df
|
185
220
|
|
186
221
|
class TechProcessor:
|
187
222
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from .base import BaseTEJFetcher
|
2
2
|
from datetime import datetime
|
3
3
|
from enum import Enum
|
4
|
+
import numpy as np
|
4
5
|
import pandas as pd
|
5
6
|
from pymongo import MongoClient
|
6
7
|
from .tech import TechProcessor
|
@@ -176,7 +177,10 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
176
177
|
fetched_data = self.collection.aggregate(pipeline).to_list()
|
177
178
|
data_dict = self.transform_value(
|
178
179
|
StatsProcessor.list_of_dict_to_dict(
|
179
|
-
fetched_data,
|
180
|
+
data_list=fetched_data,
|
181
|
+
keys=["year", "season"],
|
182
|
+
delimeter="Q",
|
183
|
+
data_key=report_type
|
180
184
|
)
|
181
185
|
)
|
182
186
|
|
@@ -195,9 +199,9 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
195
199
|
(self.percent_index_list, "%")]:
|
196
200
|
process_list = list(set(data_df.index) & set(category))
|
197
201
|
if postfix == "%":
|
198
|
-
data_df
|
199
|
-
|
200
|
-
|
202
|
+
data_df = data_df.T
|
203
|
+
data_df[process_list] = data_df[process_list].map(lambda x: f"{x}%") # if (not np.isnan(x)) else None)
|
204
|
+
data_df = data_df.T
|
201
205
|
else:
|
202
206
|
data_df.loc[process_list] = data_df.loc[process_list].map(
|
203
207
|
lambda x: StatsProcessor.
|
@@ -241,7 +245,7 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
241
245
|
"$gt": start_year,
|
242
246
|
"$lt": end_year
|
243
247
|
},
|
244
|
-
"data.season":
|
248
|
+
"data.season": start_season
|
245
249
|
}
|
246
250
|
else:
|
247
251
|
match_stage = {
|
@@ -327,7 +331,10 @@ class TEJStockPriceFetcher(BaseTEJFetcher):
|
|
327
331
|
|
328
332
|
if (period is not None):
|
329
333
|
latest_date = self.get_latest_data_time(ticker)
|
330
|
-
|
334
|
+
if (latest_date):
|
335
|
+
start_date = self.set_time_shift(date=latest_date, period=period)
|
336
|
+
else:
|
337
|
+
start_date = datetime.strptime(start_date, "%Y-%m-%d")
|
331
338
|
else:
|
332
339
|
start_date = datetime.strptime(start_date, "%Y-%m-%d")
|
333
340
|
|
@@ -355,7 +362,15 @@ class TEJStockPriceFetcher(BaseTEJFetcher):
|
|
355
362
|
datas = self.collection.aggregate(pipeline).to_list()
|
356
363
|
|
357
364
|
elements = [element['data'] for element in datas]
|
358
|
-
|
359
|
-
|
365
|
+
try:
|
366
|
+
data_df = pd.DataFrame(elements).set_index('mdate')
|
367
|
+
except:
|
368
|
+
column_names = [
|
369
|
+
"coid", "mdate", "mkt", "open_d", "high_d", "low_d", "close_d",
|
370
|
+
"adjfac", "vol", "amt", "trn", "bid", "offer", "avgprc", "roi",
|
371
|
+
"hmlpct", "turnover", "shares", "mktcap", "mktcap_pct",
|
372
|
+
"amt_pct", "per", "pbr", "div_yid", "cdiv_yid"
|
373
|
+
]
|
374
|
+
data_df = pd.DataFrame(columns = column_names)
|
360
375
|
|
361
376
|
return data_df
|
@@ -8,6 +8,11 @@ class ValueFetcher(StatsFetcher):
|
|
8
8
|
|
9
9
|
def __init__(self, ticker: str, db_client):
|
10
10
|
super().__init__(ticker, db_client)
|
11
|
+
self.value_keys = [
|
12
|
+
"P_E", "P_FCF", "P_B", "P_S",
|
13
|
+
"EV_OPI", "EV_EBIT", "EV_EBITDA", "EV_S",
|
14
|
+
"Yield"
|
15
|
+
]
|
11
16
|
|
12
17
|
def prepare_query(self, start_date, end_date):
|
13
18
|
pipeline = super().prepare_query()
|
@@ -54,7 +59,8 @@ class ValueFetcher(StatsFetcher):
|
|
54
59
|
"EV_OPI": "$$daily_item.EV_OPI",
|
55
60
|
"EV_EBIT": "$$daily_item.EV_EBIT",
|
56
61
|
"EV_EBITDA": "$$daily_item.EV_EBITDA",
|
57
|
-
"EV_S": "$$daily_item.EV_S"
|
62
|
+
"EV_S": "$$daily_item.EV_S",
|
63
|
+
"Yield": "$$daily_item.Yield"
|
58
64
|
}
|
59
65
|
}
|
60
66
|
},
|
@@ -89,10 +95,13 @@ class ValueFetcher(StatsFetcher):
|
|
89
95
|
fetched_data = self.collect_data(start_date, end_date)
|
90
96
|
|
91
97
|
fetched_data['daily_data'] = fetched_data['daily_data'][-1]
|
98
|
+
for key in self.value_keys:
|
99
|
+
fetched_data['daily_data'][key] = fetched_data['daily_data'].get(key, None)
|
92
100
|
|
93
101
|
fetched_data['yearly_data'] = ValueProcessor.transform_to_df(
|
94
102
|
fetched_data['daily_data'],
|
95
103
|
fetched_data['yearly_data'],
|
104
|
+
required_keys=self.value_keys
|
96
105
|
)
|
97
106
|
|
98
107
|
return fetched_data
|
@@ -110,7 +119,8 @@ class ValueFetcher(StatsFetcher):
|
|
110
119
|
EV_OPI,
|
111
120
|
EV_EBIT,
|
112
121
|
EV_EBITDA,
|
113
|
-
EV_S
|
122
|
+
EV_S,
|
123
|
+
Yield
|
114
124
|
}
|
115
125
|
"""
|
116
126
|
|
@@ -142,7 +152,8 @@ class ValueFetcher(StatsFetcher):
|
|
142
152
|
"EV_OPI": "$$daily.EV_OPI",
|
143
153
|
"EV_EBIT": "$$daily.EV_EBIT",
|
144
154
|
"EV_EBITDA": "$$daily.EV_EBITDA",
|
145
|
-
"EV_S": "$$daily.EV_S"
|
155
|
+
"EV_S": "$$daily.EV_S",
|
156
|
+
"Yield": "$$daily.Yield"
|
146
157
|
}
|
147
158
|
}
|
148
159
|
}
|
@@ -153,15 +164,12 @@ class ValueFetcher(StatsFetcher):
|
|
153
164
|
fetched_data = self.collection.aggregate(pipeline).to_list()
|
154
165
|
fetched_data = fetched_data[0]
|
155
166
|
|
156
|
-
|
157
|
-
|
167
|
+
return_dict = {value_key: dict() for value_key in self.value_keys}
|
168
|
+
|
169
|
+
for data in fetched_data['daily_data']:
|
170
|
+
for value_key in self.value_keys:
|
171
|
+
return_dict[value_key].update({data['date']: data.get(value_key, None)})
|
158
172
|
|
159
|
-
for value_key in value_keys:
|
160
|
-
for data in fetched_data['daily_data']:
|
161
|
-
if (value_key not in data.keys()):
|
162
|
-
continue
|
163
|
-
else:
|
164
|
-
return_dict[value_key].update({data['date']: data[value_key]})
|
165
173
|
|
166
174
|
return_dict = {
|
167
175
|
value_key: pd.DataFrame.from_dict(value_dict, orient='index', columns=[value_key])
|
@@ -173,7 +181,7 @@ class ValueFetcher(StatsFetcher):
|
|
173
181
|
class ValueProcessor(StatsProcessor):
|
174
182
|
|
175
183
|
@staticmethod
|
176
|
-
def transform_to_df(daily_dict, yearly_dict):
|
184
|
+
def transform_to_df(daily_dict: dict, yearly_dict: dict, required_keys: list):
|
177
185
|
latest_data = {"year": f"過去4季"}
|
178
186
|
|
179
187
|
latest_data.update(daily_dict)
|
@@ -184,4 +192,16 @@ class ValueProcessor(StatsProcessor):
|
|
184
192
|
|
185
193
|
yearly_dict = pd.DataFrame.from_dict(yearly_dict)
|
186
194
|
|
195
|
+
total_keys = [
|
196
|
+
# 年度評價
|
197
|
+
'year', 'P_E', 'P_FCF', 'P_B', 'P_S',
|
198
|
+
'EV_OPI', 'EV_EBIT', 'EV_EBITDA','EV_S',
|
199
|
+
|
200
|
+
# 最新一日交易
|
201
|
+
'open', 'high', 'low', 'close', 'volume', 'dividends',
|
202
|
+
'stock splits', 'Yield', 'dividend_year', 'finance_report_time'
|
203
|
+
]
|
204
|
+
|
205
|
+
total_keys_list = list(total_keys)
|
206
|
+
yearly_dict = yearly_dict.reindex(columns=total_keys, fill_value=None)
|
187
207
|
return yearly_dict
|
@@ -9,6 +9,13 @@ TWN/AINVFQ1:
|
|
9
9
|
- annd
|
10
10
|
- fin_ind
|
11
11
|
- eps
|
12
|
+
- r307
|
13
|
+
- r305
|
14
|
+
- r306
|
15
|
+
- r316
|
16
|
+
- r609
|
17
|
+
- r614
|
18
|
+
- r611
|
12
19
|
TWN/AFESTM1:
|
13
20
|
- coid
|
14
21
|
- mdate
|
@@ -19,4 +26,8 @@ TWN/AFESTM1:
|
|
19
26
|
- curr
|
20
27
|
- annd
|
21
28
|
- fin_ind
|
22
|
-
- eps
|
29
|
+
- eps
|
30
|
+
- r307
|
31
|
+
- r305
|
32
|
+
- r306
|
33
|
+
- r316
|
@@ -1,4 +1,5 @@
|
|
1
1
|
from .data_process import StatsProcessor
|
2
|
+
import numpy as np
|
2
3
|
class YoY_Calculator:
|
3
4
|
def __init__(self):
|
4
5
|
pass
|
@@ -23,6 +24,9 @@ class YoY_Calculator:
|
|
23
24
|
|
24
25
|
if (isinstance(YoY, complex)): # 年化成長率有複數問題
|
25
26
|
return None
|
27
|
+
|
28
|
+
if YoY != YoY: # 確認是否為nan (Python 中 nan不等於自己)
|
29
|
+
return None
|
26
30
|
|
27
31
|
return YoY
|
28
32
|
@classmethod
|
@@ -91,7 +95,6 @@ class YoY_Calculator:
|
|
91
95
|
for key in list(this_data.keys()):
|
92
96
|
if key == "season":
|
93
97
|
continue
|
94
|
-
|
95
98
|
|
96
99
|
value = this_data.get(key, None)
|
97
100
|
|
@@ -102,7 +105,7 @@ class YoY_Calculator:
|
|
102
105
|
temp_dict = {"value": value}
|
103
106
|
this_value = value
|
104
107
|
|
105
|
-
this_value = StatsProcessor.process_str_to_float(
|
108
|
+
this_value = StatsProcessor.process_str_to_float(this_value)
|
106
109
|
|
107
110
|
last_value = data_dict.get(
|
108
111
|
f"{last_year}Q{last_season}",{}
|