neurostats-API 0.0.21b0__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 +152 -102
- neurostats_API/fetchers/base.py +93 -74
- neurostats_API/fetchers/cash_flow.py +143 -113
- neurostats_API/fetchers/finance_overview.py +28 -28
- 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 +203 -108
- neurostats_API/fetchers/tech.py +117 -42
- neurostats_API/fetchers/tej_finance_report.py +248 -338
- neurostats_API/fetchers/value_invest.py +32 -12
- neurostats_API/tools/company_list/tw.json +2175 -0
- neurostats_API/tools/tej_db/tej_db_percent_index.yaml +0 -3
- neurostats_API/tools/tej_db/tej_db_skip_index.yaml +14 -1
- neurostats_API/tools/tej_db/tej_db_thousand_index.yaml +0 -5
- neurostats_API/utils/__init__.py +0 -1
- neurostats_API/utils/calculate_value.py +102 -1
- neurostats_API/utils/data_process.py +53 -19
- neurostats_API/utils/logger.py +21 -0
- {neurostats_API-0.0.21b0.dist-info → neurostats_API-0.0.23.dist-info}/METADATA +2 -2
- neurostats_API-0.0.23.dist-info/RECORD +35 -0
- neurostats_API/utils/fetcher.py +0 -1056
- neurostats_API-0.0.21b0.dist-info/RECORD +0 -34
- /neurostats_API/tools/{balance_sheet.yaml → twse/balance_sheet.yaml} +0 -0
- /neurostats_API/tools/{cash_flow_percentage.yaml → twse/cash_flow_percentage.yaml} +0 -0
- /neurostats_API/tools/{finance_overview_dict.yaml → twse/finance_overview_dict.yaml} +0 -0
- /neurostats_API/tools/{profit_lose.yaml → twse/profit_lose.yaml} +0 -0
- /neurostats_API/tools/{seasonal_data_field_dict.txt → twse/seasonal_data_field_dict.txt} +0 -0
- {neurostats_API-0.0.21b0.dist-info → neurostats_API-0.0.23.dist-info}/WHEEL +0 -0
- {neurostats_API-0.0.21b0.dist-info → neurostats_API-0.0.23.dist-info}/top_level.txt +0 -0
@@ -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
|
@@ -18,34 +19,39 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
18
19
|
QOQ_NOCAL = 4
|
19
20
|
|
20
21
|
def __init__(
|
21
|
-
|
22
|
-
|
23
|
-
db_name="company",
|
24
|
-
collection_name="TWN/AINVFQ1"
|
25
|
-
):
|
22
|
+
self, mongo_uri, db_name="company", collection_name="TWN/AINVFQ1"
|
23
|
+
):
|
26
24
|
self.client = MongoClient(mongo_uri)
|
27
25
|
self.db = self.client[db_name]
|
28
26
|
self.collection = self.db[collection_name]
|
29
27
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
self.check_index = set(index_dict[collection_name])
|
35
|
-
self.skip_index = set(skip_dict[collection_name])
|
28
|
+
index_files = [
|
29
|
+
"tej_db/tej_db_index.yaml", "tej_db/tej_db_thousand_index.yaml",
|
30
|
+
"tej_db/tej_db_percent_index.yaml"
|
31
|
+
]
|
36
32
|
|
37
|
-
self.
|
38
|
-
|
33
|
+
self.index_dict, self.thousand_dict, self.percent_dict = [
|
34
|
+
StatsProcessor.load_yaml(file) for file in index_files
|
35
|
+
]
|
39
36
|
|
37
|
+
self.check_index = set(self.index_dict.get(collection_name, []))
|
38
|
+
self.skip_index = set(self.percent_dict.get(collection_name, []))
|
39
|
+
self.thousand_index_list = list(
|
40
|
+
self.thousand_dict.get(collection_name, [])
|
41
|
+
)
|
42
|
+
self.percent_index_list = list(
|
43
|
+
self.percent_dict.get(collection_name, [])
|
44
|
+
)
|
40
45
|
|
41
46
|
def get(
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
self,
|
48
|
+
ticker,
|
49
|
+
fetch_mode: FetchMode = FetchMode.QOQ_NOCAL,
|
50
|
+
start_date: str = None,
|
51
|
+
end_date: str = None,
|
52
|
+
report_type: str = "Q",
|
53
|
+
indexes: list = []
|
54
|
+
):
|
49
55
|
"""
|
50
56
|
基礎的query function
|
51
57
|
ticker(str): 股票代碼
|
@@ -58,351 +64,242 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
58
64
|
indexes(List): 指定的index
|
59
65
|
"""
|
60
66
|
# 確認indexes中是否有錯誤的index,有的話回傳warning
|
61
|
-
if
|
62
|
-
|
63
|
-
|
64
|
-
if (difference):
|
67
|
+
if indexes and self.check_index:
|
68
|
+
invalid_indexes = set(indexes) - self.check_index
|
69
|
+
if invalid_indexes:
|
65
70
|
warnings.warn(
|
66
|
-
f"{list(
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
start_date
|
71
|
-
else
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
end_date = datetime.strptime(end_date, "%Y-%m-%d")
|
80
|
-
|
81
|
-
assert (start_date <= end_date)
|
82
|
-
|
83
|
-
start_year = start_date.year
|
84
|
-
start_season = (start_date.month - 1) // 4 + 1
|
85
|
-
end_year = end_date.year
|
86
|
-
end_season = (end_date.month - 1) // 4 + 1
|
87
|
-
|
88
|
-
if (fetch_mode == self.FetchMode.QOQ):
|
89
|
-
use_cal = True
|
90
|
-
else:
|
91
|
-
use_cal = False
|
92
|
-
|
93
|
-
data_df = self.get_QoQ_data(
|
71
|
+
f"{list(invalid_indexes)} 不存在,請確認欄位名稱", UserWarning
|
72
|
+
)
|
73
|
+
|
74
|
+
start_date = datetime.strptime(
|
75
|
+
start_date, "%Y-%m-%d"
|
76
|
+
) if start_date else datetime(2005, 1, 1)
|
77
|
+
|
78
|
+
if fetch_mode in {self.FetchMode.QOQ, self.FetchMode.QOQ_NOCAL}:
|
79
|
+
end_date = datetime.strptime(end_date, "%Y-%m-%d"
|
80
|
+
) if end_date else datetime.today()
|
81
|
+
assert start_date <= end_date
|
82
|
+
start_year, end_year = start_date.year, end_date.year
|
83
|
+
return self.get_QoQ_data(
|
94
84
|
ticker=ticker,
|
95
|
-
|
96
|
-
|
97
|
-
end_year=end_year,
|
98
|
-
end_season=end_season,
|
85
|
+
start_date=start_date,
|
86
|
+
end_date=end_date,
|
99
87
|
report_type=report_type,
|
100
88
|
indexes=indexes,
|
101
|
-
use_cal=
|
102
|
-
|
103
|
-
return data_df
|
104
|
-
|
105
|
-
elif (fetch_mode in {self.FetchMode.YOY, self.FetchMode.YOY_NOCAL}):
|
106
|
-
start_year = start_date.year
|
107
|
-
end_date = self.get_latest_data_time(ticker)
|
108
|
-
if (not end_date):
|
109
|
-
end_date = datetime.today()
|
110
|
-
|
111
|
-
end_year = end_date.year
|
112
|
-
season = (end_date.month - 1) // 4 + 1
|
113
|
-
|
114
|
-
if (fetch_mode == self.FetchMode.YOY):
|
115
|
-
use_cal = True
|
116
|
-
else:
|
117
|
-
use_cal = False
|
89
|
+
use_cal=(fetch_mode == self.FetchMode.QOQ)
|
90
|
+
)
|
118
91
|
|
119
|
-
|
92
|
+
elif fetch_mode in {self.FetchMode.YOY, self.FetchMode.YOY_NOCAL}:
|
93
|
+
end_date = self.get_latest_data_time(ticker) or datetime.today()
|
94
|
+
start_year, end_year = start_date.year, end_date.year
|
95
|
+
end_season = (end_date.month - 1) // 4 + 1
|
96
|
+
return self.get_YoY_data(
|
120
97
|
ticker=ticker,
|
121
98
|
start_year=start_year,
|
122
99
|
end_year=end_year,
|
123
|
-
season=
|
100
|
+
season=end_season,
|
124
101
|
report_type=report_type,
|
125
102
|
indexes=indexes,
|
126
|
-
use_cal=
|
127
|
-
|
128
|
-
return data_df
|
103
|
+
use_cal=(fetch_mode == self.FetchMode.YOY)
|
104
|
+
)
|
129
105
|
|
130
106
|
def get_QoQ_data(
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
use_cal=False):
|
107
|
+
self,
|
108
|
+
ticker,
|
109
|
+
start_date,
|
110
|
+
end_date,
|
111
|
+
report_type="Q",
|
112
|
+
indexes=[],
|
113
|
+
use_cal=False
|
114
|
+
):
|
140
115
|
"""
|
141
116
|
取得時間範圍內每季資料
|
142
117
|
"""
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
lower_bound_year
|
154
|
-
|
155
|
-
|
156
|
-
if (not indexes): # 沒有指定 -> 取全部
|
157
|
-
pipeline = [
|
158
|
-
{
|
159
|
-
"$match": {
|
160
|
-
"ticker": ticker
|
161
|
-
}
|
162
|
-
}, {
|
163
|
-
"$unwind": "$data"
|
164
|
-
}, {
|
165
|
-
"$match": {
|
166
|
-
"$or": [
|
167
|
-
{
|
168
|
-
"data.year": {
|
169
|
-
"$gt": start_year,
|
170
|
-
"$lt": end_year
|
171
|
-
}
|
172
|
-
}, {
|
173
|
-
"data.year": start_year,
|
174
|
-
"data.season": {
|
175
|
-
"$gte": start_season
|
176
|
-
}
|
177
|
-
}, {
|
178
|
-
"data.year": end_year,
|
179
|
-
"data.season": {
|
180
|
-
"$lte": end_season
|
181
|
-
}
|
182
|
-
}, {
|
183
|
-
"data.year": lower_bound_year,
|
184
|
-
"data.season": lower_bound_season
|
185
|
-
}
|
186
|
-
]
|
187
|
-
}
|
188
|
-
}, {
|
189
|
-
"$project": {
|
190
|
-
"data.year": 1,
|
191
|
-
"data.season": 1,
|
192
|
-
f"data.{report_type}": 1,
|
193
|
-
"_id": 0
|
194
|
-
}
|
195
|
-
}
|
196
|
-
]
|
197
|
-
|
198
|
-
else: # 取指定index
|
199
|
-
project_stage = {"data.year": 1, "data.season": 1}
|
200
|
-
for index in indexes:
|
201
|
-
project_stage[f"data.{report_type}.{index}"] = 1
|
118
|
+
start_year, start_season = start_date.year, (
|
119
|
+
start_date.month - 1
|
120
|
+
) // 4 + 1
|
121
|
+
end_year, end_season = end_date.year, (end_date.month - 1) // 4 + 1
|
122
|
+
lower_bound_year, lower_bound_season = (
|
123
|
+
start_year - 1, 4
|
124
|
+
) if start_season == 1 else (start_year, start_season - 1)
|
125
|
+
|
126
|
+
pipeline = self.build_pipeline(
|
127
|
+
ticker, start_year, start_season, end_year, end_season,
|
128
|
+
lower_bound_year, lower_bound_season, report_type, indexes
|
129
|
+
)
|
130
|
+
fetched_data = self.collection.aggregate(pipeline).to_list()
|
202
131
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
"$match": {
|
212
|
-
"$or": [
|
213
|
-
{
|
214
|
-
"data.year": {
|
215
|
-
"$gt": start_year,
|
216
|
-
"$lt": end_year
|
217
|
-
}
|
218
|
-
}, {
|
219
|
-
"data.year": start_year,
|
220
|
-
"data.season": {
|
221
|
-
"$gte": start_season
|
222
|
-
}
|
223
|
-
}, {
|
224
|
-
"data.year": end_year,
|
225
|
-
"data.season": {
|
226
|
-
"$lte": end_season
|
227
|
-
}
|
228
|
-
}, {
|
229
|
-
"data.year": lower_bound_year,
|
230
|
-
"data.season": lower_bound_season
|
231
|
-
}
|
232
|
-
]
|
233
|
-
}
|
234
|
-
}, {
|
235
|
-
"$project": project_stage
|
236
|
-
}
|
237
|
-
]
|
132
|
+
data_dict = self.transform_value(
|
133
|
+
StatsProcessor.list_of_dict_to_dict(
|
134
|
+
data_list=fetched_data,
|
135
|
+
keys=["year", "season"],
|
136
|
+
delimeter="Q",
|
137
|
+
data_key=report_type
|
138
|
+
)
|
139
|
+
)
|
238
140
|
|
239
|
-
|
240
|
-
data_dict = StatsProcessor.list_of_dict_to_dict(
|
241
|
-
fetched_data,
|
242
|
-
keys=["year", "season"],
|
243
|
-
delimeter="Q",
|
244
|
-
data_key=report_type)
|
245
|
-
|
246
|
-
data_dict = self.transform_value(data_dict)
|
247
|
-
|
248
|
-
if (use_cal):
|
249
|
-
data_with_QoQ = self.cal_QoQ(data_dict)
|
250
|
-
data_df = pd.DataFrame.from_dict(data_with_QoQ)
|
251
|
-
data_df = data_df.iloc[:, 1:]
|
252
|
-
data_df = data_df.iloc[:, ::-1].T
|
253
|
-
data_dict = data_df.to_dict()
|
254
|
-
data_dict = self.get_dict_of_df(data_dict)
|
255
|
-
return data_dict
|
256
|
-
else:
|
257
|
-
data_df = pd.DataFrame.from_dict(data_dict)
|
258
|
-
data_df = data_df.iloc[:, ::-1]
|
259
|
-
return data_df
|
141
|
+
return self.calculate_and_format(data_dict, use_cal, self.cal_QoQ)
|
260
142
|
|
261
143
|
def get_YoY_data(
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
144
|
+
self,
|
145
|
+
ticker,
|
146
|
+
start_year,
|
147
|
+
end_year,
|
148
|
+
season,
|
149
|
+
report_type="Q",
|
150
|
+
indexes=[],
|
151
|
+
use_cal=False
|
152
|
+
):
|
270
153
|
"""
|
271
154
|
取得某季歷年資料
|
272
155
|
"""
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
"$match": {
|
295
|
-
"$or": [
|
296
|
-
{
|
297
|
-
"$and": [
|
298
|
-
{
|
299
|
-
"data.year": {
|
300
|
-
"$in": select_year
|
301
|
-
}
|
302
|
-
}, {
|
303
|
-
"data.season": {
|
304
|
-
"$eq": season
|
305
|
-
}
|
306
|
-
}
|
307
|
-
]
|
308
|
-
},
|
309
|
-
]
|
310
|
-
}
|
311
|
-
}, {
|
312
|
-
"$project": {
|
313
|
-
"data.year": 1,
|
314
|
-
"data.season": 1,
|
315
|
-
f"data.{report_type}": 1,
|
316
|
-
"_id": 0
|
317
|
-
}
|
318
|
-
}
|
319
|
-
]
|
320
|
-
|
321
|
-
else: # 取指定index
|
322
|
-
project_stage = {"data.year": 1, "data.season": 1}
|
323
|
-
for index in indexes:
|
324
|
-
project_stage[f"data.{report_type}.{index}"] = 1
|
325
|
-
|
326
|
-
pipeline = [
|
327
|
-
{
|
328
|
-
"$match": {
|
329
|
-
"ticker": ticker
|
330
|
-
}
|
331
|
-
}, {
|
332
|
-
"$unwind": "$data"
|
333
|
-
}, {
|
334
|
-
"$match": {
|
335
|
-
"$and": [
|
336
|
-
{
|
337
|
-
"data.year": {
|
338
|
-
"$in": select_year
|
339
|
-
}
|
340
|
-
}, {
|
341
|
-
"data.season": {
|
342
|
-
"$eq": season
|
343
|
-
}
|
344
|
-
}
|
345
|
-
]
|
346
|
-
}
|
347
|
-
}, {
|
348
|
-
"$project": project_stage
|
349
|
-
}
|
350
|
-
]
|
351
|
-
|
156
|
+
select_year = sorted(
|
157
|
+
{year
|
158
|
+
for year in range(start_year, end_year + 1)} | {
|
159
|
+
y
|
160
|
+
for year in range(start_year, end_year + 1)
|
161
|
+
for y in {year, year - 1, year - 3, year - 5, year - 10}
|
162
|
+
}
|
163
|
+
) if use_cal else list(range(start_year, end_year + 1))
|
164
|
+
|
165
|
+
pipeline = self.build_pipeline(
|
166
|
+
ticker,
|
167
|
+
select_year,
|
168
|
+
season,
|
169
|
+
None,
|
170
|
+
None,
|
171
|
+
None,
|
172
|
+
None,
|
173
|
+
report_type,
|
174
|
+
indexes,
|
175
|
+
year_based=True
|
176
|
+
)
|
352
177
|
fetched_data = self.collection.aggregate(pipeline).to_list()
|
178
|
+
data_dict = self.transform_value(
|
179
|
+
StatsProcessor.list_of_dict_to_dict(
|
180
|
+
data_list=fetched_data,
|
181
|
+
keys=["year", "season"],
|
182
|
+
delimeter="Q",
|
183
|
+
data_key=report_type
|
184
|
+
)
|
185
|
+
)
|
186
|
+
|
187
|
+
return self.calculate_and_format(
|
188
|
+
data_dict, use_cal,
|
189
|
+
lambda x: self.cal_YoY(x, start_year, end_year, season)
|
190
|
+
)
|
353
191
|
|
354
|
-
# 處理計算YoY
|
355
|
-
data_dict = StatsProcessor.list_of_dict_to_dict(
|
356
|
-
fetched_data,
|
357
|
-
keys=['year', 'season'],
|
358
|
-
data_key=report_type,
|
359
|
-
delimeter='Q')
|
360
|
-
|
361
|
-
data_dict = self.transform_value(data_dict)
|
362
|
-
|
363
|
-
if (use_cal):
|
364
|
-
data_with_YoY = self.cal_YoY(
|
365
|
-
data_dict, start_year, end_year, season)
|
366
|
-
data_df = pd.DataFrame.from_dict(data_with_YoY)
|
367
|
-
data_df = data_df.iloc[:, ::-1].T
|
368
|
-
data_dict = data_df.to_dict()
|
369
|
-
data_dict = self.get_dict_of_df(data_dict)
|
370
|
-
return data_dict
|
371
|
-
else:
|
372
|
-
data_df = pd.DataFrame.from_dict(data_dict)
|
373
|
-
data_df = data_df.iloc[:, ::-1]
|
374
|
-
return data_df
|
375
|
-
|
376
192
|
def transform_value(self, data_dict):
|
377
193
|
"""
|
378
194
|
處理千元, %等單位
|
379
195
|
"""
|
380
196
|
|
381
197
|
data_df = pd.DataFrame.from_dict(data_dict)
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
198
|
+
for category, postfix in [(self.thousand_index_list, "千元"),
|
199
|
+
(self.percent_index_list, "%")]:
|
200
|
+
process_list = list(set(data_df.index) & set(category))
|
201
|
+
if postfix == "%":
|
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
|
205
|
+
else:
|
206
|
+
data_df.loc[process_list] = data_df.loc[process_list].map(
|
207
|
+
lambda x: StatsProcessor.
|
208
|
+
cal_non_percentage(x, postfix=postfix)
|
209
|
+
)
|
210
|
+
return data_df.to_dict()
|
211
|
+
|
212
|
+
def build_pipeline(
|
213
|
+
self,
|
214
|
+
ticker,
|
215
|
+
start_year,
|
216
|
+
start_season,
|
217
|
+
end_year,
|
218
|
+
end_season,
|
219
|
+
lower_bound_year,
|
220
|
+
lower_bound_season,
|
221
|
+
report_type,
|
222
|
+
indexes,
|
223
|
+
year_based=False
|
224
|
+
):
|
225
|
+
project_stage = {
|
226
|
+
"_id": 0,
|
227
|
+
"data.year": 1,
|
228
|
+
"data.season": 1,
|
229
|
+
**{
|
230
|
+
f"data.{report_type}.{idx}": 1
|
231
|
+
for idx in indexes
|
232
|
+
}
|
233
|
+
} if indexes else {
|
234
|
+
"_id": 0,
|
235
|
+
"data.year": 1,
|
236
|
+
"data.season": 1,
|
237
|
+
f"data.{report_type}": 1
|
238
|
+
}
|
239
|
+
|
240
|
+
if (year_based):
|
241
|
+
match_stage = {
|
242
|
+
"data.year": {
|
243
|
+
"$in": start_year
|
244
|
+
} if year_based else {
|
245
|
+
"$gt": start_year,
|
246
|
+
"$lt": end_year
|
247
|
+
},
|
248
|
+
"data.season": start_season
|
249
|
+
}
|
250
|
+
else:
|
251
|
+
match_stage = {
|
252
|
+
"$or": [
|
253
|
+
{
|
254
|
+
"data.year": {
|
255
|
+
"$gt": start_year,
|
256
|
+
"$lt": end_year
|
257
|
+
}
|
258
|
+
}, {
|
259
|
+
"data.year": start_year,
|
260
|
+
"data.season": {
|
261
|
+
"$gte": start_season
|
262
|
+
}
|
263
|
+
}, {
|
264
|
+
"data.year": end_year,
|
265
|
+
"data.season": {
|
266
|
+
"$lte": end_season
|
267
|
+
}
|
268
|
+
}, {
|
269
|
+
"data.year": lower_bound_year,
|
270
|
+
"data.season": lower_bound_season
|
271
|
+
}
|
272
|
+
]
|
273
|
+
}
|
388
274
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
275
|
+
return [
|
276
|
+
{
|
277
|
+
"$match": {
|
278
|
+
"ticker": ticker
|
279
|
+
}
|
280
|
+
}, {
|
281
|
+
"$unwind": "$data"
|
282
|
+
}, {
|
283
|
+
"$match": match_stage
|
284
|
+
}, {
|
285
|
+
"$project": project_stage
|
286
|
+
}
|
287
|
+
]
|
288
|
+
|
289
|
+
def calculate_and_format(self, data_dict, use_cal, calc_function):
|
290
|
+
data_df = pd.DataFrame.from_dict(
|
291
|
+
calc_function(data_dict) if use_cal else data_dict
|
292
|
+
).iloc[:, ::-1]
|
293
|
+
return data_df if not use_cal else self.get_dict_of_df(
|
294
|
+
data_df.T.to_dict()
|
393
295
|
)
|
394
296
|
|
395
|
-
data_dict = data_df.to_dict()
|
396
|
-
|
397
|
-
return data_dict
|
398
297
|
|
399
298
|
class TEJStockPriceFetcher(BaseTEJFetcher):
|
400
299
|
|
401
300
|
def __init__(
|
402
|
-
|
403
|
-
|
404
|
-
db_name: str = "company",
|
405
|
-
collection_name: str = None):
|
301
|
+
self, mongo_uri, db_name: str = "company", collection_name: str = None
|
302
|
+
):
|
406
303
|
self.mongo_uri = mongo_uri
|
407
304
|
self.db_name = db_name
|
408
305
|
self.collection_name = collection_name
|
@@ -411,14 +308,16 @@ class TEJStockPriceFetcher(BaseTEJFetcher):
|
|
411
308
|
self.db = self.client[self.db_name]
|
412
309
|
self.collection = self.db[self.collection_name]
|
413
310
|
|
414
|
-
self.check_period = [
|
311
|
+
self.check_period = [
|
312
|
+
'1d', '7d', '1m', '3m', '1y', '3y', '5y', '10y', 'all'
|
313
|
+
]
|
415
314
|
|
416
315
|
def get(
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
316
|
+
self,
|
317
|
+
ticker: str = "2330",
|
318
|
+
start_date: str = "2024-10-01",
|
319
|
+
period: str = None
|
320
|
+
):
|
422
321
|
"""
|
423
322
|
取得開高低收資料
|
424
323
|
start_date: str: 起始的日期
|
@@ -432,7 +331,10 @@ class TEJStockPriceFetcher(BaseTEJFetcher):
|
|
432
331
|
|
433
332
|
if (period is not None):
|
434
333
|
latest_date = self.get_latest_data_time(ticker)
|
435
|
-
|
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")
|
436
338
|
else:
|
437
339
|
start_date = datetime.strptime(start_date, "%Y-%m-%d")
|
438
340
|
|
@@ -460,7 +362,15 @@ class TEJStockPriceFetcher(BaseTEJFetcher):
|
|
460
362
|
datas = self.collection.aggregate(pipeline).to_list()
|
461
363
|
|
462
364
|
elements = [element['data'] for element in datas]
|
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)
|
463
375
|
|
464
|
-
data_df
|
465
|
-
|
466
|
-
return data_df
|
376
|
+
return data_df
|