neurostats-API 0.0.15__py3-none-any.whl → 0.0.16__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- neurostats_API/__init__.py +1 -1
- neurostats_API/fetchers/base.py +30 -28
- neurostats_API/fetchers/tej_finance_report.py +203 -177
- neurostats_API/fetchers/value_invest.py +84 -67
- {neurostats_API-0.0.15.dist-info → neurostats_API-0.0.16.dist-info}/METADATA +63 -8
- {neurostats_API-0.0.15.dist-info → neurostats_API-0.0.16.dist-info}/RECORD +8 -8
- {neurostats_API-0.0.15.dist-info → neurostats_API-0.0.16.dist-info}/WHEEL +1 -1
- {neurostats_API-0.0.15.dist-info → neurostats_API-0.0.16.dist-info}/top_level.txt +0 -0
neurostats_API/__init__.py
CHANGED
neurostats_API/fetchers/base.py
CHANGED
@@ -7,10 +7,12 @@ from datetime import datetime, timedelta, date
|
|
7
7
|
from ..utils import StatsDateTime, StatsProcessor, YoY_Calculator
|
8
8
|
import yaml
|
9
9
|
|
10
|
+
|
10
11
|
class StatsFetcher:
|
12
|
+
|
11
13
|
def __init__(self, ticker, db_client):
|
12
14
|
self.ticker = ticker
|
13
|
-
self.db = db_client["company"]
|
15
|
+
self.db = db_client["company"] # Replace with your database name
|
14
16
|
self.collection = self.db["twse_stats"]
|
15
17
|
|
16
18
|
self.timezone = pytz.timezone("Asia/Taipei")
|
@@ -26,7 +28,6 @@ class StatsFetcher:
|
|
26
28
|
'grand_total_growth': [f"YoY_{i}" for i in [1, 3, 5, 10]]
|
27
29
|
}
|
28
30
|
|
29
|
-
|
30
31
|
def prepare_query(self):
|
31
32
|
return [
|
32
33
|
{
|
@@ -53,7 +54,9 @@ class StatsFetcher:
|
|
53
54
|
|
54
55
|
return StatsDateTime(date, year, month, day, season)
|
55
56
|
|
57
|
+
|
56
58
|
class BaseTEJFetcher(abc.ABC):
|
59
|
+
|
57
60
|
def __init__(self):
|
58
61
|
self.client = None
|
59
62
|
self.db = None
|
@@ -62,25 +65,22 @@ class BaseTEJFetcher(abc.ABC):
|
|
62
65
|
@abc.abstractmethod
|
63
66
|
def get(self):
|
64
67
|
pass
|
65
|
-
|
68
|
+
|
66
69
|
def get_latest_data_time(self, ticker):
|
67
|
-
latest_data = self.collection.find_one(
|
68
|
-
{"ticker": ticker},
|
69
|
-
{"last_update": 1, "_id" : 0}
|
70
|
-
)
|
70
|
+
latest_data = self.collection.find_one({"ticker": ticker}, {"last_update": 1, "_id": 0})
|
71
71
|
|
72
72
|
try:
|
73
73
|
latest_date = latest_data['last_update']["latest_data_date"]
|
74
74
|
except Exception as e:
|
75
75
|
latest_date = None
|
76
|
-
|
76
|
+
|
77
77
|
return latest_date
|
78
78
|
|
79
|
-
def cal_YoY(self, data_dict: dict, start_year: int, end_year: int):
|
80
|
-
year_shifts = [1,3,5,10]
|
79
|
+
def cal_YoY(self, data_dict: dict, start_year: int, end_year: int, season: int):
|
80
|
+
year_shifts = [1, 3, 5, 10]
|
81
81
|
return_dict = {}
|
82
|
-
for year in range(start_year, end_year+1):
|
83
|
-
year_data = data_dict[
|
82
|
+
for year in range(start_year, end_year + 1):
|
83
|
+
year_data = data_dict[f"{year}Q{season}"]
|
84
84
|
year_keys = list(year_data.keys())
|
85
85
|
for key in year_keys:
|
86
86
|
if (key in 'season'):
|
@@ -93,23 +93,20 @@ class BaseTEJFetcher(abc.ABC):
|
|
93
93
|
this_value = year_data[key]
|
94
94
|
try:
|
95
95
|
past_year = str(year - shift)
|
96
|
-
last_value = data_dict[past_year][key]
|
97
|
-
temp_dict[f"YoY_{shift}"] = YoY_Calculator.cal_growth(
|
98
|
-
this_value, last_value, delta = shift
|
99
|
-
)
|
96
|
+
last_value = data_dict[f"{past_year}Q{season}"][key]
|
97
|
+
temp_dict[f"YoY_{shift}"] = YoY_Calculator.cal_growth(this_value, last_value, delta=shift)
|
100
98
|
except Exception as e:
|
101
99
|
temp_dict[f"YoY_{shift}"] = None
|
102
|
-
|
100
|
+
|
103
101
|
year_data[key] = temp_dict
|
104
102
|
|
105
103
|
else:
|
106
104
|
year_data.pop(key)
|
107
|
-
|
108
|
-
return_dict[year] = year_data
|
109
|
-
|
110
|
-
|
105
|
+
|
106
|
+
return_dict[f"{year}Q{season}"] = year_data
|
107
|
+
|
111
108
|
return return_dict
|
112
|
-
|
109
|
+
|
113
110
|
def cal_QoQ(self, data_dict):
|
114
111
|
return_dict = {}
|
115
112
|
for i, time_index in enumerate(data_dict.keys()):
|
@@ -122,7 +119,7 @@ class BaseTEJFetcher(abc.ABC):
|
|
122
119
|
else:
|
123
120
|
last_year = year
|
124
121
|
last_season = season - 1
|
125
|
-
|
122
|
+
|
126
123
|
this_data = data_dict[time_index]
|
127
124
|
this_keys = list(this_data.keys())
|
128
125
|
for key in this_keys:
|
@@ -137,16 +134,21 @@ class BaseTEJFetcher(abc.ABC):
|
|
137
134
|
try:
|
138
135
|
last_value = data_dict[f"{last_year}Q{last_season}"][key]['value']
|
139
136
|
|
140
|
-
temp_dict['growth'] = YoY_Calculator.cal_growth(
|
141
|
-
this_value, last_value, delta=1
|
142
|
-
)
|
137
|
+
temp_dict['growth'] = YoY_Calculator.cal_growth(this_value, last_value, delta=1)
|
143
138
|
except Exception as e:
|
144
139
|
temp_dict['growth'] = None
|
145
|
-
|
140
|
+
|
146
141
|
this_data[key] = temp_dict
|
147
142
|
|
148
143
|
else:
|
149
144
|
this_data.pop(key)
|
150
145
|
return_dict[time_index] = this_data
|
151
146
|
return return_dict
|
152
|
-
|
147
|
+
|
148
|
+
def get_dict_of_df(self, data_dict):
|
149
|
+
"""
|
150
|
+
dict[dict] -> dict[df]
|
151
|
+
"""
|
152
|
+
for key in data_dict.keys():
|
153
|
+
data_dict[key] = pd.DataFrame.from_dict(data_dict[key])
|
154
|
+
return data_dict
|
@@ -8,56 +8,39 @@ import warnings
|
|
8
8
|
|
9
9
|
|
10
10
|
class FinanceReportFetcher(BaseTEJFetcher):
|
11
|
+
|
11
12
|
class FetchMode(Enum):
|
12
13
|
YOY = 1
|
13
14
|
QOQ = 2
|
14
15
|
YOY_NOCAL = 3
|
15
16
|
QOQ_NOCAL = 4
|
16
17
|
|
17
|
-
def __init__(self, mongo_uri, db_name
|
18
|
+
def __init__(self, mongo_uri, db_name="company", collection_name="TWN/AINVFQ1"):
|
18
19
|
self.client = MongoClient(mongo_uri)
|
19
20
|
self.db = self.client[db_name]
|
20
21
|
self.collection = self.db[collection_name]
|
21
22
|
|
23
|
+
# yapf: disabled
|
22
24
|
self.check_index = {
|
23
|
-
'coid', 'mdate', 'key3', 'no',
|
24
|
-
'
|
25
|
-
'
|
26
|
-
'
|
27
|
-
'
|
28
|
-
'
|
29
|
-
'
|
30
|
-
'
|
31
|
-
'
|
32
|
-
|
33
|
-
'quick','ppe','ar','ip12',
|
34
|
-
'ip22','ip31','ip51','iv41',
|
35
|
-
'if11','isibt','isni','isnip',
|
36
|
-
'eps','ispsd','gm','opi',
|
37
|
-
'nri','ri','nopi','ebit',
|
38
|
-
'cip31','cscfo','cscfi','cscff',
|
39
|
-
'person','shares','wavg','taxrate',
|
40
|
-
'r104','r115','r105','r106',
|
41
|
-
'r107','r108','r201','r112',
|
42
|
-
'r401','r402','r403','r404',
|
43
|
-
'r405','r408','r409','r410',
|
44
|
-
'r502','r501','r205','r505',
|
45
|
-
'r517','r512','r509','r608',
|
46
|
-
'r616','r610','r607','r613',
|
47
|
-
'r612','r609','r614','r611',
|
48
|
-
'r307','r304','r305','r306',
|
49
|
-
'r316','r834'
|
50
|
-
}
|
25
|
+
'coid', 'mdate', 'key3', 'no', 'sem', 'merg', 'curr', 'annd', 'fin_ind', 'bp11', 'bp21', 'bp22', 'bp31',
|
26
|
+
'bp41', 'bp51', 'bp53', 'bp61', 'bp62', 'bp63', 'bp64', 'bp65', 'bf11', 'bf12', 'bf21', 'bf22', 'bf41',
|
27
|
+
'bf42', 'bf43', 'bf44', 'bf45', 'bf99', 'bsca', 'bsnca', 'bsta', 'bscl', 'bsncl', 'bstl', 'bsse', 'bslse',
|
28
|
+
'debt', 'quick', 'ppe', 'ar', 'ip12', 'ip22', 'ip31', 'ip51', 'iv41', 'if11', 'isibt', 'isni', 'isnip',
|
29
|
+
'eps', 'ispsd', 'gm', 'opi', 'nri', 'ri', 'nopi', 'ebit', 'cip31', 'cscfo', 'cscfi', 'cscff', 'person',
|
30
|
+
'shares', 'wavg', 'taxrate', 'r104', 'r115', 'r105', 'r106', 'r107', 'r108', 'r201', 'r112', 'r401', 'r402',
|
31
|
+
'r403', 'r404', 'r405', 'r408', 'r409', 'r410', 'r502', 'r501', 'r205', 'r505', 'r517', 'r512', 'r509',
|
32
|
+
'r608', 'r616', 'r610', 'r607', 'r613', 'r612', 'r609', 'r614', 'r611', 'r307', 'r304', 'r305', 'r306',
|
33
|
+
'r316', 'r834'
|
34
|
+
} # yapf: enabled
|
51
35
|
|
52
36
|
def get(
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
):
|
37
|
+
self,
|
38
|
+
ticker,
|
39
|
+
fetch_mode: FetchMode = FetchMode.QOQ_NOCAL,
|
40
|
+
start_date: str = None,
|
41
|
+
end_date: str = None,
|
42
|
+
report_type: str = "Q",
|
43
|
+
indexes: list = []):
|
61
44
|
"""
|
62
45
|
基礎的query function
|
63
46
|
ticker(str): 股票代碼
|
@@ -72,22 +55,14 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
72
55
|
# 確認indexes中是否有錯誤的index,有的話回傳warning
|
73
56
|
if (indexes and self.check_index):
|
74
57
|
indexes = set(indexes)
|
75
|
-
difference = indexes-self.check_index
|
58
|
+
difference = indexes - self.check_index
|
76
59
|
if (difference):
|
77
|
-
warnings.warn(
|
78
|
-
f"{list(difference)} 沒有出現在資料表中,請確認column名稱是否正確",
|
79
|
-
UserWarning
|
80
|
-
)
|
81
|
-
|
60
|
+
warnings.warn(f"{list(difference)} 沒有出現在資料表中,請確認column名稱是否正確", UserWarning)
|
82
61
|
|
83
|
-
if (fetch_mode in {
|
84
|
-
self.FetchMode.QOQ,
|
85
|
-
self.FetchMode.QOQ_NOCAL
|
86
|
-
}
|
87
|
-
):
|
62
|
+
if (fetch_mode in {self.FetchMode.QOQ, self.FetchMode.QOQ_NOCAL}):
|
88
63
|
if (not start_date):
|
89
64
|
warnings.warn("No start_date specified, use default date = \"2005-01-01\"", UserWarning)
|
90
|
-
start_date = datetime.strptime("2005-01-01", "%Y-%m-%d")
|
65
|
+
start_date = datetime.strptime("2005-01-01", "%Y-%m-%d")
|
91
66
|
if (not end_date):
|
92
67
|
warnings.warn("No end_date specified, use default date = today", UserWarning)
|
93
68
|
end_date = datetime.today()
|
@@ -114,17 +89,13 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
114
89
|
end_season=end_season,
|
115
90
|
report_type=report_type,
|
116
91
|
indexes=indexes,
|
117
|
-
use_cal=use_cal
|
118
|
-
)
|
92
|
+
use_cal=use_cal)
|
119
93
|
|
120
94
|
return data_df
|
121
95
|
|
122
|
-
elif (fetch_mode in {
|
123
|
-
|
124
|
-
|
125
|
-
}
|
126
|
-
):
|
127
|
-
start_year = 2005
|
96
|
+
elif (fetch_mode in {self.FetchMode.YOY, self.FetchMode.YOY_NOCAL}):
|
97
|
+
start_date = datetime.strptime(start_date, "%Y-%m-%d")
|
98
|
+
start_year = start_date.year
|
128
99
|
end_date = self.get_latest_data_time(ticker)
|
129
100
|
if (not end_date):
|
130
101
|
end_date = datetime.today()
|
@@ -138,98 +109,140 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
138
109
|
use_cal = False
|
139
110
|
|
140
111
|
data_df = self.get_YoY_data(
|
141
|
-
ticker
|
142
|
-
start_year
|
143
|
-
end_year
|
144
|
-
season
|
145
|
-
report_type
|
146
|
-
indexes
|
147
|
-
|
148
|
-
|
112
|
+
ticker=ticker,
|
113
|
+
start_year=start_year,
|
114
|
+
end_year=end_year,
|
115
|
+
season=season,
|
116
|
+
report_type=report_type,
|
117
|
+
indexes=indexes,
|
118
|
+
use_cal=use_cal)
|
119
|
+
|
149
120
|
return data_df
|
150
121
|
|
151
122
|
def get_QoQ_data(
|
152
|
-
|
153
|
-
ticker,
|
154
|
-
start_year,
|
155
|
-
start_season,
|
156
|
-
end_year,
|
157
|
-
end_season,
|
158
|
-
report_type = "Q",
|
159
|
-
indexes = [],
|
160
|
-
use_cal = False
|
161
|
-
):
|
123
|
+
self, ticker, start_year, start_season, end_year, end_season, report_type="Q", indexes=[], use_cal=False):
|
162
124
|
"""
|
163
125
|
取得時間範圍內每季資料
|
164
126
|
"""
|
165
|
-
if (
|
127
|
+
if (use_cal):
|
128
|
+
if (start_season == 1):
|
129
|
+
lower_bound_year = start_year - 1
|
130
|
+
lower_bound_season = 4
|
131
|
+
|
132
|
+
else:
|
133
|
+
lower_bound_year = start_year
|
134
|
+
lower_bound_season = start_season - 1
|
135
|
+
|
136
|
+
else:
|
137
|
+
lower_bound_year = start_year,
|
138
|
+
lower_bound_season = start_season
|
139
|
+
|
140
|
+
if (not indexes): # 沒有指定 -> 取全部
|
166
141
|
pipeline = [
|
167
|
-
{
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
142
|
+
{
|
143
|
+
"$match": {
|
144
|
+
"ticker": ticker
|
145
|
+
}
|
146
|
+
}, {
|
147
|
+
"$unwind": "$data"
|
148
|
+
}, {
|
149
|
+
"$match":
|
150
|
+
{
|
151
|
+
"$or":
|
152
|
+
[
|
153
|
+
{
|
154
|
+
"data.year": {
|
155
|
+
"$gt": start_year,
|
156
|
+
"$lt": end_year
|
157
|
+
}
|
158
|
+
}, {
|
159
|
+
"data.year": start_year,
|
160
|
+
"data.season": {
|
161
|
+
"$gte": start_season
|
162
|
+
}
|
163
|
+
}, {
|
164
|
+
"data.year": end_year,
|
165
|
+
"data.season": {
|
166
|
+
"$lte": end_season
|
167
|
+
}
|
168
|
+
}, {
|
169
|
+
"data.year": lower_bound_year,
|
170
|
+
"data.season": lower_bound_season
|
171
|
+
}
|
172
|
+
]
|
173
|
+
}
|
174
|
+
}, {
|
175
|
+
"$project": {
|
176
|
+
"data.year": 1,
|
177
|
+
"data.season": 1,
|
178
|
+
f"data.{report_type}": 1,
|
179
|
+
"_id": 0
|
180
|
+
}
|
182
181
|
}
|
183
182
|
]
|
184
183
|
|
185
|
-
|
186
|
-
|
187
|
-
project_stage = {
|
188
|
-
"data.year": 1,
|
189
|
-
"data.season": 1
|
190
|
-
}
|
184
|
+
else: # 取指定index
|
185
|
+
project_stage = {"data.year": 1, "data.season": 1}
|
191
186
|
for index in indexes:
|
192
187
|
project_stage[f"data.{report_type}.{index}"] = 1
|
193
188
|
|
194
189
|
pipeline = [
|
195
|
-
{
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
190
|
+
{
|
191
|
+
"$match": {
|
192
|
+
"ticker": ticker
|
193
|
+
}
|
194
|
+
}, {
|
195
|
+
"$unwind": "$data"
|
196
|
+
}, {
|
197
|
+
"$match":
|
198
|
+
{
|
199
|
+
"$or":
|
200
|
+
[
|
201
|
+
{
|
202
|
+
"data.year": {
|
203
|
+
"$gt": start_year,
|
204
|
+
"$lt": end_year
|
205
|
+
}
|
206
|
+
}, {
|
207
|
+
"data.year": start_year,
|
208
|
+
"data.season": {
|
209
|
+
"$gte": start_season
|
210
|
+
}
|
211
|
+
}, {
|
212
|
+
"data.year": end_year,
|
213
|
+
"data.season": {
|
214
|
+
"$lte": end_season
|
215
|
+
}
|
216
|
+
}, {
|
217
|
+
"data.year": lower_bound_year,
|
218
|
+
"data.season": lower_bound_season
|
219
|
+
}
|
220
|
+
]
|
221
|
+
}
|
222
|
+
}, {
|
223
|
+
"$project": project_stage
|
224
|
+
}
|
205
225
|
]
|
206
226
|
|
207
|
-
|
208
227
|
fetched_data = self.collection.aggregate(pipeline).to_list()
|
209
|
-
|
228
|
+
|
210
229
|
data_dict = StatsProcessor.list_of_dict_to_dict(
|
211
|
-
fetched_data,
|
212
|
-
|
213
|
-
delimeter = "Q",
|
214
|
-
data_key=report_type
|
215
|
-
)
|
230
|
+
fetched_data, keys=["year", "season"], delimeter="Q", data_key=report_type)
|
231
|
+
|
216
232
|
if (use_cal):
|
217
233
|
data_with_QoQ = self.cal_QoQ(data_dict)
|
218
234
|
data_df = pd.DataFrame.from_dict(data_with_QoQ)
|
235
|
+
data_df = data_df.iloc[:, 1:]
|
236
|
+
data_df = data_df.iloc[:, ::-1].T
|
237
|
+
data_dict = data_df.to_dict()
|
238
|
+
data_dict = self.get_dict_of_df(data_dict)
|
239
|
+
return data_dict
|
219
240
|
else:
|
220
241
|
data_df = pd.DataFrame.from_dict(data_dict)
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
ticker,
|
226
|
-
start_year,
|
227
|
-
end_year,
|
228
|
-
season,
|
229
|
-
report_type = "Q",
|
230
|
-
indexes = [],
|
231
|
-
use_cal = False
|
232
|
-
):
|
242
|
+
data_df = data_df.iloc[:, ::-1]
|
243
|
+
return data_df
|
244
|
+
|
245
|
+
def get_YoY_data(self, ticker, start_year, end_year, season, report_type="Q", indexes=[], use_cal=False):
|
233
246
|
"""
|
234
247
|
取得某季歷年資料
|
235
248
|
"""
|
@@ -237,77 +250,90 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
237
250
|
select_year = set()
|
238
251
|
|
239
252
|
for year in range(start_year, end_year + 1):
|
240
|
-
year_shifts = {
|
241
|
-
year,
|
242
|
-
year - 1,
|
243
|
-
year - 3,
|
244
|
-
year - 5,
|
245
|
-
year - 10
|
246
|
-
}
|
253
|
+
year_shifts = {year, year - 1, year - 3, year - 5, year - 10}
|
247
254
|
|
248
255
|
select_year = select_year.union(year_shifts)
|
249
|
-
|
256
|
+
|
250
257
|
select_year = sorted(list(select_year), reverse=True)
|
251
258
|
else:
|
252
259
|
select_year = [year for year in range(start_year, end_year + 1)]
|
253
260
|
|
254
|
-
if (not indexes):
|
261
|
+
if (not indexes): # 沒有指定 -> 取全部
|
255
262
|
pipeline = [
|
256
|
-
{
|
257
|
-
|
258
|
-
|
259
|
-
|
263
|
+
{
|
264
|
+
"$match": {
|
265
|
+
"ticker": ticker
|
266
|
+
}
|
267
|
+
}, {
|
268
|
+
"$unwind": "$data"
|
269
|
+
}, {
|
270
|
+
"$match":
|
260
271
|
{
|
261
|
-
"$
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
272
|
+
"$or": [{
|
273
|
+
"$and": [{
|
274
|
+
"data.year": {
|
275
|
+
"$in": select_year
|
276
|
+
}
|
277
|
+
}, {
|
278
|
+
"data.season": {
|
279
|
+
"$eq": season
|
280
|
+
}
|
281
|
+
}]
|
282
|
+
},]
|
283
|
+
}
|
284
|
+
}, {
|
285
|
+
"$project": {
|
286
|
+
"data.year": 1,
|
287
|
+
"data.season": 1,
|
288
|
+
f"data.{report_type}": 1,
|
289
|
+
"_id": 0
|
290
|
+
}
|
274
291
|
}
|
275
292
|
]
|
276
293
|
|
277
|
-
|
278
|
-
|
279
|
-
project_stage = {
|
280
|
-
"data.year": 1,
|
281
|
-
"data.season": 1
|
282
|
-
}
|
294
|
+
else: # 取指定index
|
295
|
+
project_stage = {"data.year": 1, "data.season": 1}
|
283
296
|
for index in indexes:
|
284
297
|
project_stage[f"data.{report_type}.{index}"] = 1
|
285
298
|
|
286
299
|
pipeline = [
|
287
|
-
{
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
300
|
+
{
|
301
|
+
"$match": {
|
302
|
+
"ticker": ticker
|
303
|
+
}
|
304
|
+
}, {
|
305
|
+
"$unwind": "$data"
|
306
|
+
}, {
|
307
|
+
"$match": {
|
308
|
+
"$and": [{
|
309
|
+
"data.year": {
|
310
|
+
"$in": select_year
|
311
|
+
}
|
312
|
+
}, {
|
313
|
+
"data.season": {
|
314
|
+
"$eq": season
|
315
|
+
}
|
316
|
+
}]
|
317
|
+
}
|
318
|
+
}, {
|
319
|
+
"$project": project_stage
|
320
|
+
}
|
296
321
|
]
|
297
322
|
|
298
323
|
fetched_data = self.collection.aggregate(pipeline).to_list()
|
299
324
|
|
300
325
|
# 處理計算YoY
|
301
326
|
data_dict = StatsProcessor.list_of_dict_to_dict(
|
302
|
-
fetched_data,
|
303
|
-
|
304
|
-
data_key=report_type,
|
305
|
-
delimeter='Q'
|
306
|
-
)
|
327
|
+
fetched_data, keys=['year', 'season'], data_key=report_type, delimeter='Q')
|
328
|
+
|
307
329
|
if (use_cal):
|
308
|
-
data_with_YoY = self.cal_YoY(data_dict, start_year, end_year)
|
309
|
-
|
330
|
+
data_with_YoY = self.cal_YoY(data_dict, start_year, end_year, season)
|
331
|
+
data_df = pd.DataFrame.from_dict(data_with_YoY)
|
332
|
+
data_df = data_df.iloc[:, ::-1].T
|
333
|
+
data_dict = data_df.to_dict()
|
334
|
+
data_dict = self.get_dict_of_df(data_dict)
|
335
|
+
return data_dict
|
310
336
|
else:
|
311
|
-
|
312
|
-
|
313
|
-
|
337
|
+
data_df = pd.DataFrame.from_dict(data_dict)
|
338
|
+
data_df = data_df.iloc[:, ::-1]
|
339
|
+
return data_df
|
@@ -12,51 +12,68 @@ class ValueFetcher(StatsFetcher):
|
|
12
12
|
def prepare_query(self, start_date, end_date):
|
13
13
|
pipeline = super().prepare_query()
|
14
14
|
|
15
|
-
pipeline.append(
|
16
|
-
|
17
|
-
"
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
"
|
23
|
-
|
24
|
-
"
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
15
|
+
pipeline.append(
|
16
|
+
{
|
17
|
+
"$project":
|
18
|
+
{
|
19
|
+
"_id": 0,
|
20
|
+
"ticker": 1,
|
21
|
+
"company_name": 1,
|
22
|
+
"daily_data":
|
23
|
+
{
|
24
|
+
"$map":
|
25
|
+
{
|
26
|
+
"input":
|
27
|
+
{
|
28
|
+
"$filter":
|
29
|
+
{
|
30
|
+
"input": "$daily_data",
|
31
|
+
"as": "daily",
|
32
|
+
"cond":
|
33
|
+
{
|
34
|
+
"$and":
|
35
|
+
[
|
36
|
+
{
|
37
|
+
"$gte": ["$$daily.date", start_date]
|
38
|
+
}, {
|
39
|
+
"$lte": ["$$daily.date", end_date]
|
40
|
+
}
|
41
|
+
]
|
42
|
+
}
|
43
|
+
}
|
44
|
+
},
|
45
|
+
"as": "daily_item",
|
46
|
+
"in":
|
47
|
+
{
|
48
|
+
"date": "$$daily_item.date",
|
49
|
+
"close": "$$daily_item.close",
|
50
|
+
"P_B": "$$daily_item.P_B",
|
51
|
+
"P_E": "$$daily_item.P_E",
|
52
|
+
"P_FCF": "$$daily_item.P_FCF",
|
53
|
+
"P_S": "$$daily_item.P_S",
|
54
|
+
"EV_OPI": "$$daily_item.EV_OPI",
|
55
|
+
"EV_EBIT": "$$daily_item.EV_EBIT",
|
56
|
+
"EV_EBITDA": "$$daily_item.EV_EBITDA",
|
57
|
+
"EV_S": "$$daily_item.EV_S"
|
58
|
+
}
|
59
|
+
}
|
60
|
+
},
|
61
|
+
"yearly_data": 1
|
48
62
|
}
|
49
|
-
|
50
|
-
"yearly_data": 1
|
51
|
-
}
|
52
|
-
})
|
63
|
+
})
|
53
64
|
|
54
65
|
return pipeline
|
55
66
|
|
67
|
+
def collect_data(self, start_date, end_date):
|
68
|
+
pipeline = self.prepare_query(start_date, end_date)
|
69
|
+
|
70
|
+
fetched_data = list(self.collection.aggregate(pipeline))
|
71
|
+
|
72
|
+
return fetched_data[0]
|
73
|
+
|
56
74
|
def query_data(self):
|
57
75
|
try:
|
58
|
-
latest_time = StatsDateTime.get_latest_time(
|
59
|
-
self.ticker, self.collection)['last_update_time']
|
76
|
+
latest_time = StatsDateTime.get_latest_time(self.ticker, self.collection)['last_update_time']
|
60
77
|
target_year = latest_time['daily_data']['last_update'].year
|
61
78
|
start_date = latest_time['daily_data']['last_update'] - timedelta(days=31)
|
62
79
|
end_date = latest_time['daily_data']['last_update']
|
@@ -79,7 +96,7 @@ class ValueFetcher(StatsFetcher):
|
|
79
96
|
)
|
80
97
|
|
81
98
|
return fetched_data
|
82
|
-
|
99
|
+
|
83
100
|
def query_value_serie(self):
|
84
101
|
"""
|
85
102
|
回傳指定公司的歷來評價
|
@@ -104,28 +121,32 @@ class ValueFetcher(StatsFetcher):
|
|
104
121
|
}
|
105
122
|
},
|
106
123
|
{
|
107
|
-
"$project":
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
"
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
124
|
+
"$project":
|
125
|
+
{
|
126
|
+
"_id": 0,
|
127
|
+
"ticker": 1,
|
128
|
+
"company_name": 1,
|
129
|
+
"daily_data":
|
130
|
+
{
|
131
|
+
"$map":
|
132
|
+
{
|
133
|
+
"input": "$daily_data", # 正確地指定要處理的陣列
|
134
|
+
"as": "daily", # 每個元素的名稱
|
135
|
+
"in":
|
136
|
+
{
|
137
|
+
"date": "$$daily.date",
|
138
|
+
"P_E": "$$daily.P_E",
|
139
|
+
"P_FCF": "$$daily.P_FCF",
|
140
|
+
"P_B": "$$daily.P_B",
|
141
|
+
"P_S": "$$daily.P_S",
|
142
|
+
"EV_OPI": "$$daily.EV_OPI",
|
143
|
+
"EV_EBIT": "$$daily.EV_EBIT",
|
144
|
+
"EV_EBITDA": "$$daily.EV_EBITDA",
|
145
|
+
"EV_S": "$$daily.EV_S"
|
146
|
+
}
|
147
|
+
}
|
125
148
|
}
|
126
|
-
}
|
127
149
|
}
|
128
|
-
}
|
129
150
|
}
|
130
151
|
]
|
131
152
|
|
@@ -133,21 +154,17 @@ class ValueFetcher(StatsFetcher):
|
|
133
154
|
fetched_data = fetched_data[0]
|
134
155
|
|
135
156
|
value_keys = ["P_E", "P_FCF", "P_B", "P_S", "EV_OPI", "EV_EBIT", "EV_EVITDA", "EV_S"]
|
136
|
-
return_dict = {
|
137
|
-
value_key: dict() for value_key in value_keys
|
138
|
-
}
|
157
|
+
return_dict = {value_key: dict() for value_key in value_keys}
|
139
158
|
|
140
159
|
for value_key in value_keys:
|
141
160
|
for data in fetched_data['daily_data']:
|
142
161
|
if (value_key not in data.keys()):
|
143
162
|
continue
|
144
163
|
else:
|
145
|
-
return_dict[value_key].update({
|
146
|
-
data['date']: data[value_key]
|
147
|
-
})
|
164
|
+
return_dict[value_key].update({data['date']: data[value_key]})
|
148
165
|
|
149
166
|
return_dict = {
|
150
|
-
value_key: pd.DataFrame.from_dict(value_dict, orient
|
167
|
+
value_key: pd.DataFrame.from_dict(value_dict, orient='index', columns=[value_key])
|
151
168
|
for value_key, value_dict in return_dict.items()
|
152
169
|
}
|
153
170
|
return return_dict
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
|
-
Name:
|
3
|
-
Version: 0.0.
|
2
|
+
Name: neurostats_API
|
3
|
+
Version: 0.0.16
|
4
4
|
Summary: The service of NeuroStats website
|
5
5
|
Home-page: https://github.com/NeurowattStats/NeuroStats_API.git
|
6
6
|
Author: JasonWang@Neurowatt
|
@@ -89,7 +89,7 @@ pip install neurostats-API
|
|
89
89
|
```Python
|
90
90
|
>>> import neurostats_API
|
91
91
|
>>> print(neurostats_API.__version__)
|
92
|
-
0.0.
|
92
|
+
0.0.16
|
93
93
|
```
|
94
94
|
|
95
95
|
### 得到最新一期的評價資料與歷年評價
|
@@ -691,21 +691,25 @@ fetcher = FinanceReportFetcher(
|
|
691
691
|
|
692
692
|
data = fetcher.get(
|
693
693
|
ticker = "2330" # 任意的股票代碼
|
694
|
-
fetch_mode = fetcher.
|
694
|
+
fetch_mode = fetcher.FetchMode.QOQ_NOCAL # 取得模式
|
695
695
|
start_date = "2005-01-01",
|
696
696
|
end_date = "2024-12-31",
|
697
697
|
report_type = "Q",
|
698
698
|
indexes = []
|
699
|
-
)
|
699
|
+
) # -> pd.DataFrame or Dict[pd.DataFrame]
|
700
700
|
```
|
701
701
|
- `ticker`: 股票代碼
|
702
702
|
|
703
703
|
- `fetch_mode` : 取得模式,為`fetcher.YOY_NOCAL` 或 `fetcher.QOQ_NOCAL`
|
704
|
-
- `YOY_NOCAL`: 以end_date為準,取得與end_date
|
704
|
+
- `YOY_NOCAL`: 以end_date為準,取得與end_date同季的歷年資料,時間範圍以start_date為起始
|
705
705
|
> 例如`start_date = "2020-07-01"`, `end_date = "2024-01-01"`,會回傳2020~2024的第一季資料
|
706
706
|
|
707
707
|
- `QOQ_NOCAL`: 時間範圍內的每季資料
|
708
708
|
|
709
|
+
- `QOQ`: 時間範圍內每季的每個index的數值以及QoQ
|
710
|
+
|
711
|
+
- `YoY`: 以end_date為準,取得與end_date同季的歷年資料以及成長率,時間範圍以start_date為起始
|
712
|
+
|
709
713
|
- `start_date`: 開始日期,不設定時預設為`2005-01-01`
|
710
714
|
|
711
715
|
- `end_date`: 結束日期,不設定時預設為資料庫最新資料的日期
|
@@ -722,10 +726,61 @@ data = fetcher.get(
|
|
722
726
|
請看 `會計師簽證財務資料`
|
723
727
|
|
724
728
|
#### 回傳資料
|
725
|
-
|
726
|
-
|
729
|
+
##### `YOY_NOCAL` 與 `QOQ_NOCAL`
|
730
|
+
為回傳`pd.DataFrame`,column名稱為<年份>Q<季>, row名稱為指定財報項目
|
731
|
+
```Python
|
732
|
+
# fetch_mode = fetcher.FetchMode.QOQ_NOCAL
|
733
|
+
2024Q3 2024Q2 2024Q1
|
734
|
+
bp41 7.082005e+07 6.394707e+07 5.761001e+07
|
735
|
+
bp51 3.111298e+09 3.145373e+09 3.091985e+09
|
736
|
+
|
737
|
+
# fetch_mode = fetcher.FetchMode.YOY_NOCAL
|
738
|
+
2024Q3 2023Q3 2022Q3
|
739
|
+
bp41 7.082005e+07 5.377231e+07 6.201822e+07
|
740
|
+
bp51 3.111298e+09 3.173919e+09 2.453840e+09
|
741
|
+
```
|
742
|
+
|
743
|
+
##### `YOY` 與 `QOQ`
|
744
|
+
回傳為`Dict[pd.DataFrame]`, key 為指定的index, DataFrame中則是該index歷年的數值與成長率
|
745
|
+
```Python
|
746
|
+
# fetch_mode = fetcher.FetchMode.QOQ
|
747
|
+
{
|
748
|
+
'bp41':
|
749
|
+
2024Q3 2024Q2 2024Q1
|
750
|
+
value 7.082005e+07 6.394707e+07 5.761001e+07
|
751
|
+
growth 1.074791e-01 1.099994e-01 5.532101e-03,
|
752
|
+
'bp51':
|
753
|
+
2024Q3 2024Q2 2024Q1
|
754
|
+
value 3.111298e+09 3.145373e+09 3.091985e+09
|
755
|
+
growth -1.083335e-02 1.726663e-02 -4.159542e-03
|
756
|
+
}
|
757
|
+
|
758
|
+
# fetch_mode = fetcher.FetchMode.YOY
|
759
|
+
{
|
760
|
+
'bp41':
|
761
|
+
2024Q3 2023Q3 2022Q3
|
762
|
+
value 7.082005e+07 5.377231e+07 6.201822e+07
|
763
|
+
YoY_1 NaN NaN 4.130744e-01
|
764
|
+
YoY_3 1.729171e-01 9.556684e-02 1.883274e-01
|
765
|
+
YoY_5 1.389090e-01 1.215242e-01 1.642914e-01
|
766
|
+
YoY_10 1.255138e-01 1.356297e-01 1.559702e-01,
|
767
|
+
'bp51':
|
768
|
+
2024Q3 2023Q3 2022Q3
|
769
|
+
value 3.111298e+09 3.173919e+09 2.453840e+09
|
770
|
+
YoY_1 NaN NaN 3.179539e-01
|
771
|
+
YoY_3 1.866752e-01 2.766851e-01 2.638677e-01
|
772
|
+
YoY_5 2.068132e-01 2.479698e-01 1.815106e-01
|
773
|
+
YoY_10 1.420500e-01 1.586797e-01 1.551364e-01
|
774
|
+
}
|
775
|
+
```
|
776
|
+
|
727
777
|
|
728
778
|
## 版本紀錄
|
779
|
+
## 0.0.16
|
780
|
+
- 處理ValueFetcher的error #issue76
|
781
|
+
|
782
|
+
- tej_fetcher新增 QOQ, YOY功能
|
783
|
+
|
729
784
|
## 0.0.15
|
730
785
|
- TechFetcher中新增指數條件
|
731
786
|
|
@@ -1,9 +1,9 @@
|
|
1
|
-
neurostats_API/__init__.py,sha256=
|
1
|
+
neurostats_API/__init__.py,sha256=5ToELVqNOIdVJrMj5G8JvbyRIjvo1FxcP6e-a-iMe1Y,261
|
2
2
|
neurostats_API/cli.py,sha256=UJSWLIw03P24p-gkBb6JSEI5dW5U12UvLf1L8HjQD-o,873
|
3
3
|
neurostats_API/main.py,sha256=QcsfmWivg2Dnqw3MTJWiI0QvEiRs0VuH-BjwQHFCv00,677
|
4
4
|
neurostats_API/fetchers/__init__.py,sha256=B4aBwVzf_X-YieEf3fZteU0qmBPVIB9VjrmkyWhLK18,489
|
5
5
|
neurostats_API/fetchers/balance_sheet.py,sha256=sQv4Gk5uoKURLEdh57YknOQWiyVwaXJ2Mw75jxNqUS0,5804
|
6
|
-
neurostats_API/fetchers/base.py,sha256=
|
6
|
+
neurostats_API/fetchers/base.py,sha256=rcrKW2PTJYfBsxsiGpCYiVTK9pQW4aALYLixIjvNMUk,4890
|
7
7
|
neurostats_API/fetchers/cash_flow.py,sha256=TY7VAWVXkj5-mzH5Iu0sIE-oV8MvGmmDy0URNotNV1E,7614
|
8
8
|
neurostats_API/fetchers/finance_overview.py,sha256=PxUdWY0x030olYMLcCHDBn068JLmCE2RTOce1dxs5vM,27753
|
9
9
|
neurostats_API/fetchers/institution.py,sha256=UrcBc6t7u7CnEwUsf6YmLbbJ8VncdWpq8bCz17q2dgs,11168
|
@@ -11,8 +11,8 @@ neurostats_API/fetchers/margin_trading.py,sha256=lQImtNdvaBoSlKhJvQ3DkH3HjSSgKRJ
|
|
11
11
|
neurostats_API/fetchers/month_revenue.py,sha256=nixX2llzjCFr2m2YVjxrSfkBusnZPrPb2dRDq1XLGhw,4251
|
12
12
|
neurostats_API/fetchers/profit_lose.py,sha256=EN9Y0iamcAaHMZdjHXO6b_2buLnORssf8ZS7A0hi74s,5896
|
13
13
|
neurostats_API/fetchers/tech.py,sha256=Hol1bcwJ_ERcnoTXNWlqqaWOuzdl7MeiAjCvzQMZDTg,12269
|
14
|
-
neurostats_API/fetchers/tej_finance_report.py,sha256=
|
15
|
-
neurostats_API/fetchers/value_invest.py,sha256=
|
14
|
+
neurostats_API/fetchers/tej_finance_report.py,sha256=laXph2ca1LCFocZjjdvtzmm5fcUecHk2Gs5h6-XMSWY,12967
|
15
|
+
neurostats_API/fetchers/value_invest.py,sha256=b_x2Dpgs8VBU5HdG8ocKtfIEkqhU-Q0S5n6RxuFuM2g,7467
|
16
16
|
neurostats_API/tools/balance_sheet.yaml,sha256=6XygNG_Ybb1Xkk1e39LMLKr7ATvaCP3xxuwFbgNl6dA,673
|
17
17
|
neurostats_API/tools/cash_flow_percentage.yaml,sha256=fk2Z4eb1JjGFvP134eJatHacB7BgTkBenhDJr83w8RE,1345
|
18
18
|
neurostats_API/tools/finance_overview_dict.yaml,sha256=B9nV75StXkrF3yv2-eezzitlJ38eEK86RD_VY6588gQ,2884
|
@@ -24,7 +24,7 @@ neurostats_API/utils/data_process.py,sha256=A--dzOsu42jRxqqCD41gTtjE5rhEBYmhB6y-
|
|
24
24
|
neurostats_API/utils/datetime.py,sha256=XJya4G8b_-ZOaBbMXgQjWh2MC4wc-o6goQ7EQJQMWrQ,773
|
25
25
|
neurostats_API/utils/db_client.py,sha256=OYe6yazcR4Aa6jYmy47JrryUeh2NnKGqY2K_lSZe6i8,455
|
26
26
|
neurostats_API/utils/fetcher.py,sha256=VbrUhjA-GG5AyjPX2SHtFIbZM4dm3jo0RgZzuCbb_Io,40927
|
27
|
-
neurostats_API-0.0.
|
28
|
-
neurostats_API-0.0.
|
29
|
-
neurostats_API-0.0.
|
30
|
-
neurostats_API-0.0.
|
27
|
+
neurostats_API-0.0.16.dist-info/METADATA,sha256=9US1mdwWnOCAnwfsOj-ZLRCfo07p3yd0UfwMKS6989g,29848
|
28
|
+
neurostats_API-0.0.16.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
29
|
+
neurostats_API-0.0.16.dist-info/top_level.txt,sha256=nSlQPMG0VtXivJyedp4Bkf86EOy2TpW10VGxolXrqnU,15
|
30
|
+
neurostats_API-0.0.16.dist-info/RECORD,,
|
File without changes
|