neurostats-API 0.0.23b2__py3-none-any.whl → 0.0.24.post1__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 +64 -40
- neurostats_API/fetchers/base.py +6 -2
- neurostats_API/fetchers/cash_flow.py +91 -70
- neurostats_API/fetchers/finance_overview.py +26 -26
- neurostats_API/fetchers/institution.py +80 -7
- neurostats_API/fetchers/macro_daily_event.py +8 -0
- neurostats_API/fetchers/margin_trading.py +0 -3
- neurostats_API/fetchers/month_revenue.py +139 -105
- neurostats_API/fetchers/profit_lose.py +103 -83
- neurostats_API/fetchers/tech.py +0 -58
- neurostats_API/fetchers/tej_finance_report.py +101 -69
- neurostats_API/fetchers/value_invest.py +26 -9
- neurostats_API/utils/calculate_value.py +5 -2
- neurostats_API/utils/data_process.py +12 -6
- neurostats_API/utils/logger.py +21 -0
- {neurostats_API-0.0.23b2.dist-info → neurostats_API-0.0.24.post1.dist-info}/METADATA +2 -2
- neurostats_API-0.0.24.post1.dist-info/RECORD +36 -0
- neurostats_API-0.0.23b2.dist-info/RECORD +0 -34
- {neurostats_API-0.0.23b2.dist-info → neurostats_API-0.0.24.post1.dist-info}/WHEEL +0 -0
- {neurostats_API-0.0.23b2.dist-info → neurostats_API-0.0.24.post1.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
|
}
|
@@ -207,8 +222,8 @@ class ProfitLoseFetcher(StatsFetcher):
|
|
207
222
|
def process_data_us(self, fetched_data):
|
208
223
|
|
209
224
|
table_dict = {
|
210
|
-
f"{data['year']}Q{data['season']}": data['
|
211
|
-
for data in fetched_data
|
225
|
+
f"{data['year']}Q{data['season']}": data['data']
|
226
|
+
for data in fetched_data['seasonal_data']
|
212
227
|
}
|
213
228
|
|
214
229
|
table_dict = YoY_Calculator.cal_QoQ(table_dict)
|
@@ -218,16 +233,21 @@ 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
|
226
241
|
|
227
242
|
return_dict = {
|
228
243
|
"ticker": self.ticker,
|
229
|
-
"company_name": fetched_data[
|
244
|
+
"company_name": fetched_data['company_name'],
|
230
245
|
"profit_lose": pd.DataFrame.from_dict(table_dict)
|
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
@@ -188,10 +188,8 @@ class TechFetcher(StatsFetcher):
|
|
188
188
|
if not self.has_required_columns(df, required_cols):
|
189
189
|
missing_cols = [col for col in required_cols if col not in df.columns]
|
190
190
|
missing_cols = ",".join(missing_cols)
|
191
|
-
print(Warning(f"{missing_cols} not in columns"))
|
192
191
|
for col in missing_cols:
|
193
192
|
df[col] = pd.NA
|
194
|
-
# raise KeyError(f"Missing required columns")
|
195
193
|
|
196
194
|
return df[required_cols]
|
197
195
|
|
@@ -219,62 +217,6 @@ class TechFetcher(StatsFetcher):
|
|
219
217
|
df = df.sort_values(by = 'date').drop_duplicates(subset=['date'])
|
220
218
|
|
221
219
|
return df
|
222
|
-
|
223
|
-
def conduct_db_search_tej(self):
|
224
|
-
# 再對TEJ search
|
225
|
-
tej_required_cols = [
|
226
|
-
"mdate", "open_d", 'high_d', 'low_d', 'close_d', 'vol'
|
227
|
-
]
|
228
|
-
|
229
|
-
required_cols = ['date', 'open', 'high', 'low', 'close', 'volume']
|
230
|
-
tej_name_proj = {
|
231
|
-
tej_name: org_name
|
232
|
-
for tej_name, org_name in zip(tej_required_cols, required_cols)
|
233
|
-
}
|
234
|
-
|
235
|
-
query = {'ticker': self.ticker}
|
236
|
-
ticker_full = self.tej_collection.find_one(query)
|
237
|
-
|
238
|
-
if not ticker_full:
|
239
|
-
raise ValueError("No ticker found in database")
|
240
|
-
|
241
|
-
daily_data = ticker_full.get("data", [])
|
242
|
-
if not isinstance(daily_data, list):
|
243
|
-
raise TypeError("Expected 'daily_data' to be a list.")
|
244
|
-
|
245
|
-
df = pd.DataFrame(daily_data)
|
246
|
-
|
247
|
-
if not self.has_required_columns(df, tej_required_cols):
|
248
|
-
raise KeyError(f"Missing required columns")
|
249
|
-
df = df.rename(columns=tej_name_proj)
|
250
|
-
|
251
|
-
return df[required_cols]
|
252
|
-
|
253
|
-
def conduct_db_search_us(self):
|
254
|
-
required_cols = ['date', 'open', 'high', 'low', 'close', 'volume']
|
255
|
-
|
256
|
-
query = {'ticker': self.ticker}
|
257
|
-
filter_query = {"daily_data" : 1, "_id": 0}
|
258
|
-
ticker_full = self.collection.find_one(query, filter_query)
|
259
|
-
|
260
|
-
if not ticker_full:
|
261
|
-
raise ValueError("No ticker found in database")
|
262
|
-
|
263
|
-
daily_data = ticker_full.get("daily_data", [])
|
264
|
-
if not isinstance(daily_data, list):
|
265
|
-
raise TypeError("Expected 'daily_data' to be a list.")
|
266
|
-
|
267
|
-
df = pd.DataFrame(daily_data)
|
268
|
-
|
269
|
-
if not self.has_required_columns(df, required_cols):
|
270
|
-
missing_cols = [col for col in required_cols if col not in df.columns]
|
271
|
-
missing_cols = ",".join(missing_cols)
|
272
|
-
for col in missing_cols:
|
273
|
-
df[col] = pd.NA
|
274
|
-
# raise KeyError(f"Missing required columns")
|
275
|
-
|
276
|
-
return df[required_cols]
|
277
|
-
|
278
220
|
|
279
221
|
class TechProcessor:
|
280
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
|
@@ -127,6 +128,8 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
127
128
|
lower_bound_year, lower_bound_season, report_type, indexes
|
128
129
|
)
|
129
130
|
fetched_data = self.collection.aggregate(pipeline).to_list()
|
131
|
+
fetched_data = fetched_data[0]
|
132
|
+
fetched_data = fetched_data.get('data', []) if isinstance(fetched_data, dict) else []
|
130
133
|
|
131
134
|
data_dict = self.transform_value(
|
132
135
|
StatsProcessor.list_of_dict_to_dict(
|
@@ -174,11 +177,14 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
174
177
|
year_based=True
|
175
178
|
)
|
176
179
|
fetched_data = self.collection.aggregate(pipeline).to_list()
|
180
|
+
fetched_data = fetched_data[0]
|
181
|
+
fetched_data = fetched_data.get('data', []) if isinstance(fetched_data, dict) else []
|
182
|
+
|
177
183
|
data_dict = self.transform_value(
|
178
184
|
StatsProcessor.list_of_dict_to_dict(
|
179
|
-
data_list=fetched_data,
|
180
|
-
keys=["year", "season"],
|
181
|
-
delimeter="Q",
|
185
|
+
data_list=fetched_data,
|
186
|
+
keys=["year", "season"],
|
187
|
+
delimeter="Q",
|
182
188
|
data_key=report_type
|
183
189
|
)
|
184
190
|
)
|
@@ -198,9 +204,9 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
198
204
|
(self.percent_index_list, "%")]:
|
199
205
|
process_list = list(set(data_df.index) & set(category))
|
200
206
|
if postfix == "%":
|
201
|
-
data_df
|
202
|
-
|
203
|
-
|
207
|
+
data_df = data_df.T
|
208
|
+
data_df[process_list] = data_df[process_list].map(lambda x: f"{x}%") # if (not np.isnan(x)) else None)
|
209
|
+
data_df = data_df.T
|
204
210
|
else:
|
205
211
|
data_df.loc[process_list] = data_df.loc[process_list].map(
|
206
212
|
lambda x: StatsProcessor.
|
@@ -221,67 +227,80 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
221
227
|
indexes,
|
222
228
|
year_based=False
|
223
229
|
):
|
224
|
-
project_stage = {
|
225
|
-
"_id": 0,
|
226
|
-
"data.year": 1,
|
227
|
-
"data.season": 1,
|
228
|
-
**{
|
229
|
-
f"data.{report_type}.{idx}": 1
|
230
|
-
for idx in indexes
|
231
|
-
}
|
232
|
-
} if indexes else {
|
233
|
-
"_id": 0,
|
234
|
-
"data.year": 1,
|
235
|
-
"data.season": 1,
|
236
|
-
f"data.{report_type}": 1
|
237
|
-
}
|
238
230
|
|
239
|
-
if
|
240
|
-
|
241
|
-
"
|
242
|
-
"$in": start_year
|
243
|
-
|
244
|
-
|
245
|
-
"$lt": end_year
|
246
|
-
},
|
247
|
-
"data.season": start_season
|
231
|
+
if year_based:
|
232
|
+
filter_cond = {
|
233
|
+
"$and": [
|
234
|
+
{ "$in": ["$$item.year", start_year] },
|
235
|
+
{ "$eq": ["$$item.season", start_season] }
|
236
|
+
]
|
248
237
|
}
|
249
238
|
else:
|
250
|
-
|
239
|
+
filter_cond = {
|
251
240
|
"$or": [
|
252
241
|
{
|
253
|
-
"
|
254
|
-
"$gt": start_year,
|
255
|
-
"$lt": end_year
|
256
|
-
|
257
|
-
},
|
258
|
-
|
259
|
-
"
|
260
|
-
"$
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
242
|
+
"$and": [
|
243
|
+
{ "$gt": ["$$item.year", start_year] },
|
244
|
+
{ "$lt": ["$$item.year", end_year] }
|
245
|
+
]
|
246
|
+
},
|
247
|
+
{
|
248
|
+
"$and": [
|
249
|
+
{ "$eq": ["$$item.year", start_year] },
|
250
|
+
{ "$gte": ["$$item.season", start_season] }
|
251
|
+
]
|
252
|
+
},
|
253
|
+
{
|
254
|
+
"$and": [
|
255
|
+
{ "$eq": ["$$item.year", end_year] },
|
256
|
+
{ "$lte": ["$$item.season", end_season] }
|
257
|
+
]
|
258
|
+
},
|
259
|
+
{
|
260
|
+
"$and": [
|
261
|
+
{ "$eq": ["$$item.year", lower_bound_year] },
|
262
|
+
{ "$eq": ["$$item.season", lower_bound_season] }
|
263
|
+
]
|
270
264
|
}
|
271
265
|
]
|
272
266
|
}
|
273
267
|
|
268
|
+
# 每個 filtered item 要輸出哪些欄位
|
269
|
+
item_fields = {
|
270
|
+
"year": "$$item.year",
|
271
|
+
"season": "$$item.season"
|
272
|
+
}
|
273
|
+
|
274
|
+
if indexes:
|
275
|
+
for idx in indexes:
|
276
|
+
item_fields[idx] = f"$$item.{report_type}.{idx}"
|
277
|
+
else:
|
278
|
+
item_fields[report_type] = f"$$item.{report_type}"
|
279
|
+
|
274
280
|
return [
|
275
281
|
{
|
276
282
|
"$match": {
|
277
283
|
"ticker": ticker
|
278
284
|
}
|
279
|
-
},
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
+
},
|
286
|
+
{
|
287
|
+
"$project": {
|
288
|
+
"_id": 0,
|
289
|
+
"ticker": 1,
|
290
|
+
"data": {
|
291
|
+
"$map": {
|
292
|
+
"input": {
|
293
|
+
"$filter": {
|
294
|
+
"input": "$data",
|
295
|
+
"as": "item",
|
296
|
+
"cond": filter_cond
|
297
|
+
}
|
298
|
+
},
|
299
|
+
"as": "item",
|
300
|
+
"in": item_fields
|
301
|
+
}
|
302
|
+
}
|
303
|
+
}
|
285
304
|
}
|
286
305
|
]
|
287
306
|
|
@@ -330,7 +349,10 @@ class TEJStockPriceFetcher(BaseTEJFetcher):
|
|
330
349
|
|
331
350
|
if (period is not None):
|
332
351
|
latest_date = self.get_latest_data_time(ticker)
|
333
|
-
|
352
|
+
if (latest_date):
|
353
|
+
start_date = self.set_time_shift(date=latest_date, period=period)
|
354
|
+
else:
|
355
|
+
start_date = datetime.strptime(start_date, "%Y-%m-%d")
|
334
356
|
else:
|
335
357
|
start_date = datetime.strptime(start_date, "%Y-%m-%d")
|
336
358
|
|
@@ -339,26 +361,36 @@ class TEJStockPriceFetcher(BaseTEJFetcher):
|
|
339
361
|
"$match": {
|
340
362
|
"ticker": ticker
|
341
363
|
}
|
342
|
-
},
|
343
|
-
|
344
|
-
}, {
|
345
|
-
"$match": {
|
346
|
-
"data.mdate": {
|
347
|
-
"$gt": start_date
|
348
|
-
}
|
349
|
-
}
|
350
|
-
}, {
|
364
|
+
},
|
365
|
+
{
|
351
366
|
"$project": {
|
367
|
+
"_id": 0,
|
352
368
|
"ticker": 1,
|
353
|
-
"data":
|
354
|
-
|
369
|
+
"data": {
|
370
|
+
"$filter": {
|
371
|
+
"input": "$data",
|
372
|
+
"as": "item",
|
373
|
+
"cond": {
|
374
|
+
"$gte": ["$$item.mdate", start_date]
|
375
|
+
}
|
376
|
+
}
|
377
|
+
}
|
355
378
|
}
|
356
379
|
}
|
357
380
|
]
|
358
381
|
datas = self.collection.aggregate(pipeline).to_list()
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
382
|
+
datas = datas[0]
|
383
|
+
datas = datas.get('data', [])
|
384
|
+
elements = [element for element in datas]
|
385
|
+
try:
|
386
|
+
data_df = pd.DataFrame(elements).set_index('mdate')
|
387
|
+
except:
|
388
|
+
column_names = [
|
389
|
+
"coid", "mdate", "mkt", "open_d", "high_d", "low_d", "close_d",
|
390
|
+
"adjfac", "vol", "amt", "trn", "bid", "offer", "avgprc", "roi",
|
391
|
+
"hmlpct", "turnover", "shares", "mktcap", "mktcap_pct",
|
392
|
+
"amt_pct", "per", "pbr", "div_yid", "cdiv_yid"
|
393
|
+
]
|
394
|
+
data_df = pd.DataFrame(columns = column_names)
|
363
395
|
|
364
396
|
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()
|
@@ -90,10 +95,13 @@ class ValueFetcher(StatsFetcher):
|
|
90
95
|
fetched_data = self.collect_data(start_date, end_date)
|
91
96
|
|
92
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)
|
93
100
|
|
94
101
|
fetched_data['yearly_data'] = ValueProcessor.transform_to_df(
|
95
102
|
fetched_data['daily_data'],
|
96
103
|
fetched_data['yearly_data'],
|
104
|
+
required_keys=self.value_keys
|
97
105
|
)
|
98
106
|
|
99
107
|
return fetched_data
|
@@ -156,15 +164,12 @@ class ValueFetcher(StatsFetcher):
|
|
156
164
|
fetched_data = self.collection.aggregate(pipeline).to_list()
|
157
165
|
fetched_data = fetched_data[0]
|
158
166
|
|
159
|
-
|
160
|
-
|
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)})
|
161
172
|
|
162
|
-
for value_key in value_keys:
|
163
|
-
for data in fetched_data['daily_data']:
|
164
|
-
if (value_key not in data.keys()):
|
165
|
-
continue
|
166
|
-
else:
|
167
|
-
return_dict[value_key].update({data['date']: data[value_key]})
|
168
173
|
|
169
174
|
return_dict = {
|
170
175
|
value_key: pd.DataFrame.from_dict(value_dict, orient='index', columns=[value_key])
|
@@ -176,7 +181,7 @@ class ValueFetcher(StatsFetcher):
|
|
176
181
|
class ValueProcessor(StatsProcessor):
|
177
182
|
|
178
183
|
@staticmethod
|
179
|
-
def transform_to_df(daily_dict, yearly_dict):
|
184
|
+
def transform_to_df(daily_dict: dict, yearly_dict: dict, required_keys: list):
|
180
185
|
latest_data = {"year": f"過去4季"}
|
181
186
|
|
182
187
|
latest_data.update(daily_dict)
|
@@ -187,4 +192,16 @@ class ValueProcessor(StatsProcessor):
|
|
187
192
|
|
188
193
|
yearly_dict = pd.DataFrame.from_dict(yearly_dict)
|
189
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)
|
190
207
|
return yearly_dict
|