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.
@@ -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
- "$unwind": "$seasonal_data" # 展開 seasonal_data 陣列
35
- },
36
- {
37
- "$project": {
38
- "_id": 0,
39
- "ticker": 1,
40
- "company_name": 1,
41
- "year": "$seasonal_data.year",
42
- "season": "$seasonal_data.season",
43
- "profit_lose": {
44
- "$ifNull": [f"$seasonal_data.{chart_name}", []]
45
- } # 避免 null
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
- 'seasonal_data',{}
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[-1]['company_name'],
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['profit_lose']
93
- for data in fetched_data
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
- profit_lose_df = StatsProcessor.expand_value_percentage(
116
+
117
+ old_profit_lose_df = StatsProcessor.expand_value_percentage(
102
118
  profit_lose_df
103
119
  )
104
-
105
- value_col = profit_lose_df.columns.str.endswith(f"_value")
106
- percentage_col = profit_lose_df.columns.str.endswith(f"_percentage")
107
-
108
- grand_total_value_col = profit_lose_df.columns.str.endswith(
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 = profit_lose_df.columns.str.endswith(
127
+ grand_total_percentage_col = old_profit_lose_df.columns.str.endswith(
112
128
  f"grand_total_percentage"
113
129
  )
114
130
 
115
- profit_lose_stats_df = profit_lose_df.loc[:, (
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, profit_lose in profit_lose_dict.items():
121
- # 蒐集整體的keys
122
- index_names = list(profit_lose.keys())
123
- target_keys = [
124
- "value",
125
- "percentage",
126
- "grand_total",
127
- "grand_total_percentage",
128
- "YoY_1",
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 = profit_lose_df.index.str.endswith(
147
- "_value"
148
- ) & profit_lose_df.index.str.contains("每股盈餘")
149
- profit_lose_df.loc[eps_index] = profit_lose_df.loc[
150
- eps_index].apply(
151
- lambda x: StatsProcessor.cal_non_percentage(x, postfix="元")
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.index.str.endswith("percentage")
156
- profit_lose_df.loc[percentage_index] = profit_lose_df.loc[
157
- percentage_index].apply(
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.index.str.contains("YoY")
164
- profit_lose_df.loc[YoY_index] = profit_lose_df.loc[
165
- YoY_index].apply(lambda x: StatsProcessor.cal_percentage(x))
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 | profit_lose_df.index.isin(eps_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
- total_table = profit_lose_df.replace("N/A", None)
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": profit_lose_stats_df,
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['profit_lose']
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
- [f"YoY_{i}" for i in [1, 3, 5, 10]]
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[-1]['company_name'],
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
+ }
@@ -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.loc[process_list] = data_df.loc[process_list].astype(
202
- str
203
- ).map(lambda x: f"{x}%")
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 (year_based):
240
- match_stage = {
241
- "data.year": {
242
- "$in": start_year
243
- } if year_based else {
244
- "$gt": start_year,
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
- match_stage = {
239
+ filter_cond = {
251
240
  "$or": [
252
241
  {
253
- "data.year": {
254
- "$gt": start_year,
255
- "$lt": end_year
256
- }
257
- }, {
258
- "data.year": start_year,
259
- "data.season": {
260
- "$gte": start_season
261
- }
262
- }, {
263
- "data.year": end_year,
264
- "data.season": {
265
- "$lte": end_season
266
- }
267
- }, {
268
- "data.year": lower_bound_year,
269
- "data.season": lower_bound_season
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
- "$unwind": "$data"
281
- }, {
282
- "$match": match_stage
283
- }, {
284
- "$project": project_stage
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
- start_date = self.set_time_shift(date=latest_date, period=period)
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
- "$unwind": "$data"
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": 1,
354
- "_id": 0
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
- elements = [element['data'] for element in datas]
361
-
362
- data_df = pd.DataFrame(elements).set_index('mdate')
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
- value_keys = ["P_E", "P_FCF", "P_B", "P_S", "EV_OPI", "EV_EBIT", "EV_EBITDA", "EV_S", "Yield"]
160
- return_dict = {value_key: dict() for value_key in value_keys}
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