neurostats-API 0.0.7__py3-none-any.whl → 0.0.9__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/balance_sheet.py +53 -34
- neurostats_API/fetchers/cash_flow.py +15 -7
- neurostats_API/fetchers/finance_overview.py +276 -121
- neurostats_API/fetchers/month_revenue.py +9 -3
- neurostats_API/fetchers/profit_lose.py +19 -4
- neurostats_API/fetchers/value_invest.py +14 -5
- neurostats_API/tools/finance_overview_dict.yaml +15 -8
- neurostats_API/tools/seasonal_data_field_dict.txt +1 -0
- neurostats_API/utils/data_process.py +28 -2
- neurostats_API/utils/datetime.py +8 -0
- {neurostats_API-0.0.7.dist-info → neurostats_API-0.0.9.dist-info}/METADATA +6 -5
- neurostats_API-0.0.9.dist-info/RECORD +26 -0
- neurostats_API-0.0.7.dist-info/RECORD +0 -26
- {neurostats_API-0.0.7.dist-info → neurostats_API-0.0.9.dist-info}/WHEEL +0 -0
- {neurostats_API-0.0.7.dist-info → neurostats_API-0.0.9.dist-info}/top_level.txt +0 -0
neurostats_API/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__='0.0.
|
1
|
+
__version__='0.0.8'
|
@@ -1,10 +1,12 @@
|
|
1
1
|
from .base import StatsFetcher, StatsDateTime
|
2
2
|
import json
|
3
|
+
import numpy as np
|
3
4
|
import pandas as pd
|
4
5
|
from ..utils import StatsDateTime, StatsProcessor
|
5
6
|
import importlib.resources as pkg_resources
|
6
7
|
import yaml
|
7
8
|
|
9
|
+
|
8
10
|
class BalanceSheetFetcher(StatsFetcher):
|
9
11
|
"""
|
10
12
|
對應iFa.ai -> 財務分析 -> 資產負債表
|
@@ -13,7 +15,7 @@ class BalanceSheetFetcher(StatsFetcher):
|
|
13
15
|
def __init__(self, ticker, db_client):
|
14
16
|
super().__init__(ticker, db_client)
|
15
17
|
self.table_settings = StatsProcessor.load_yaml("balance_sheet.yaml")
|
16
|
-
|
18
|
+
|
17
19
|
def prepare_query(self, target_year, target_season):
|
18
20
|
pipeline = super().prepare_query()
|
19
21
|
|
@@ -24,36 +26,42 @@ class BalanceSheetFetcher(StatsFetcher):
|
|
24
26
|
}
|
25
27
|
|
26
28
|
pipeline.append({
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
29
|
+
"$project": {
|
30
|
+
"_id": 0,
|
31
|
+
"ticker": 1,
|
32
|
+
"company_name": 1,
|
33
|
+
"balance_sheets": {
|
34
|
+
"$sortArray": {
|
35
|
+
"input": {
|
36
|
+
"$map": {
|
37
|
+
"input": {
|
38
|
+
"$filter": {
|
39
|
+
"input": "$seasonal_data",
|
40
|
+
"as": "season",
|
41
|
+
"cond": {
|
42
|
+
"$eq":
|
43
|
+
["$$season.season", target_season]
|
44
|
+
}
|
41
45
|
}
|
46
|
+
},
|
47
|
+
"as": "target_season_data",
|
48
|
+
"in": {
|
49
|
+
"year":
|
50
|
+
"$$target_season_data.year",
|
51
|
+
"season":
|
52
|
+
"$$target_season_data.season",
|
53
|
+
"balance_sheet":
|
54
|
+
"$$target_season_data.balance_sheet"
|
42
55
|
}
|
43
|
-
},
|
44
|
-
"as": "target_season_data",
|
45
|
-
"in": {
|
46
|
-
"year": "$$target_season_data.year",
|
47
|
-
"season": "$$target_season_data.season",
|
48
|
-
"balance_sheet": "$$target_season_data.balance_sheet"
|
49
56
|
}
|
50
|
-
}
|
51
|
-
|
52
|
-
|
57
|
+
},
|
58
|
+
"sortBy": {
|
59
|
+
"year": -1
|
60
|
+
} # 按 year 降序排序
|
61
|
+
}
|
53
62
|
}
|
54
63
|
}
|
55
|
-
}
|
56
|
-
})
|
64
|
+
})
|
57
65
|
|
58
66
|
return pipeline
|
59
67
|
|
@@ -67,10 +75,16 @@ class BalanceSheetFetcher(StatsFetcher):
|
|
67
75
|
return fetched_data[-1]
|
68
76
|
|
69
77
|
def query_data(self):
|
70
|
-
|
78
|
+
try:
|
79
|
+
latest_time = StatsDateTime.get_latest_time(
|
80
|
+
self.ticker, self.collection)['last_update_time']
|
81
|
+
year = latest_time['seasonal_data']['latest_year']
|
82
|
+
season = latest_time['seasonal_data']['latest_season']
|
83
|
+
except Exception as e:
|
84
|
+
today = StatsDateTime.get_today()
|
85
|
+
year = today.year - 1 if (today.season == 1) else today.year
|
86
|
+
season = 4 if (today.season == 1) else today.season - 1
|
71
87
|
|
72
|
-
year = today.year - 1 if (today.season == 1) else today.year
|
73
|
-
season = 4 if (today.season == 1) else today.season - 2
|
74
88
|
fetched_data = self.collect_data(year, season)
|
75
89
|
|
76
90
|
return self.process_data(season, fetched_data)
|
@@ -93,20 +107,26 @@ class BalanceSheetFetcher(StatsFetcher):
|
|
93
107
|
|
94
108
|
time_index = f"{year}Q{target_season}"
|
95
109
|
|
96
|
-
|
110
|
+
# 蒐集整體的keys
|
97
111
|
index_names += list(data['balance_sheet'].keys())
|
98
112
|
balance_sheet = data['balance_sheet']
|
99
113
|
|
100
114
|
for index_name, value_dict in balance_sheet.items():
|
101
115
|
for item_name, item in value_dict.items():
|
102
|
-
try:
|
103
|
-
|
116
|
+
try: # table_dict[項目][(2020Q1, '%')]
|
117
|
+
if (item_name == 'percentage'):
|
118
|
+
if (isinstance(item, (float, int))):
|
119
|
+
item = np.round(item, 2)
|
120
|
+
if ("YoY" in item_name):
|
121
|
+
if (isinstance(item, (float, int))):
|
122
|
+
item = np.round(item * 100, 2)
|
123
|
+
table_dict[index_name][(time_index, item_name)] = item
|
104
124
|
|
105
125
|
except KeyError:
|
106
126
|
if (index_name not in table_dict.keys()):
|
107
127
|
table_dict[index_name] = dict()
|
108
128
|
|
109
|
-
table_dict[index_name][(time_index,item_name)] = item
|
129
|
+
table_dict[index_name][(time_index, item_name)] = item
|
110
130
|
|
111
131
|
total_table = pd.DataFrame.from_dict(table_dict, orient='index')
|
112
132
|
total_table.columns = pd.MultiIndex.from_tuples(total_table.columns)
|
@@ -118,5 +138,4 @@ class BalanceSheetFetcher(StatsFetcher):
|
|
118
138
|
target_index=setting['target_index']
|
119
139
|
if "target_index" in setting.keys() else None)
|
120
140
|
|
121
|
-
print(f"{name}: {return_dict[name].columns}")
|
122
141
|
return return_dict
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from .base import StatsFetcher, StatsDateTime
|
2
2
|
import json
|
3
|
+
import numpy as np
|
3
4
|
import pandas as pd
|
4
5
|
from ..utils import StatsDateTime, StatsProcessor
|
5
6
|
import importlib.resources as pkg_resources
|
@@ -64,9 +65,14 @@ class CashFlowFetcher(StatsFetcher):
|
|
64
65
|
return list(fetched_data)[0]
|
65
66
|
|
66
67
|
def query_data(self):
|
67
|
-
|
68
|
-
|
69
|
-
|
68
|
+
|
69
|
+
try:
|
70
|
+
latest_time = StatsDateTime.get_latest_time(self.ticker, self.collection)['last_update_time']
|
71
|
+
target_season = latest_time['seasonal_data']['latest_season']
|
72
|
+
|
73
|
+
except:
|
74
|
+
today = StatsDateTime.get_today()
|
75
|
+
target_season = today.season - 1 if (today.season > 1) else 4
|
70
76
|
|
71
77
|
fetched_data = self.collect_data(target_season)
|
72
78
|
|
@@ -127,8 +133,9 @@ class CashFlowFetcher(StatsFetcher):
|
|
127
133
|
'value']
|
128
134
|
if (value['value']):
|
129
135
|
table_dict[time_index][index_name][
|
130
|
-
'percentage'] =
|
131
|
-
|
136
|
+
'percentage'] = np.round(
|
137
|
+
(value['value'] / cash_flow[
|
138
|
+
main_cash_flow_name]['value']) * 100, 2)
|
132
139
|
else:
|
133
140
|
table_dict[time_index][index_name][
|
134
141
|
'percentage'] = None
|
@@ -141,8 +148,9 @@ class CashFlowFetcher(StatsFetcher):
|
|
141
148
|
'value']
|
142
149
|
if (value['value']):
|
143
150
|
table_dict[time_index][index_name][
|
144
|
-
'percentage'] =
|
145
|
-
|
151
|
+
'percentage'] = np.round(
|
152
|
+
(value['value'] / cash_flow[
|
153
|
+
main_cash_flow_name]['value']) * 100, 2)
|
146
154
|
else:
|
147
155
|
table_dict[time_index][index_name][
|
148
156
|
'percentage'] = None
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from .base import StatsFetcher, StatsDateTime
|
2
2
|
import json
|
3
|
+
import numpy as np
|
3
4
|
import pandas as pd
|
4
5
|
from ..utils import StatsDateTime, StatsProcessor
|
5
6
|
import importlib.resources as pkg_resources
|
@@ -14,8 +15,10 @@ class FinanceOverviewFetcher(StatsFetcher):
|
|
14
15
|
def __init__(self, ticker, db_client):
|
15
16
|
super().__init__(ticker, db_client)
|
16
17
|
|
17
|
-
self.target_fields = StatsProcessor.load_yaml(
|
18
|
-
|
18
|
+
self.target_fields = StatsProcessor.load_yaml(
|
19
|
+
"finance_overview_dict.yaml")
|
20
|
+
self.inverse_dict = StatsProcessor.load_txt(
|
21
|
+
"seasonal_data_field_dict.txt", json_load=True)
|
19
22
|
|
20
23
|
def prepare_query(self, target_year, target_season):
|
21
24
|
|
@@ -80,12 +83,20 @@ class FinanceOverviewFetcher(StatsFetcher):
|
|
80
83
|
return fetched_data[0]
|
81
84
|
|
82
85
|
def query_data(self):
|
83
|
-
today = StatsDateTime.get_today()
|
84
86
|
|
85
|
-
|
86
|
-
|
87
|
+
try:
|
88
|
+
latest_time = StatsDateTime.get_latest_time(
|
89
|
+
self.ticker, self.collection)['last_update_time']
|
90
|
+
year = latest_time['seasonal_data']['latest_year']
|
91
|
+
season = latest_time['seasonal_data']['latest_season']
|
92
|
+
except Exception as e:
|
93
|
+
today = StatsDateTime.get_today()
|
94
|
+
year = today.year - 1 if (today.season == 1) else today.year
|
95
|
+
season = 4 if (today.season == 1) else today.season - 1
|
96
|
+
|
87
97
|
fetched_data = self.collect_data(year, season)
|
88
98
|
finance_dict = fetched_data['seasonal_data'][0]
|
99
|
+
FinanceOverviewProcessor.process_rate(finance_dict)
|
89
100
|
FinanceOverviewProcessor.process_all(finance_dict)
|
90
101
|
fetched_data['seasonal_data'] = finance_dict
|
91
102
|
return fetched_data
|
@@ -93,41 +104,38 @@ class FinanceOverviewFetcher(StatsFetcher):
|
|
93
104
|
|
94
105
|
class FinanceOverviewProcessor(StatsProcessor):
|
95
106
|
|
107
|
+
@classmethod
|
108
|
+
def process_rate(cls, finance_dict):
|
109
|
+
for key in finance_dict:
|
110
|
+
if ('YoY' in key):
|
111
|
+
finance_dict[key] = StatsProcessor.cal_percentage(
|
112
|
+
finance_dict[key])
|
113
|
+
elif ("rate" in key or 'ratio' in key):
|
114
|
+
finance_dict[key] = StatsProcessor.cal_non_percentage(
|
115
|
+
finance_dict[key], to_str=True, postfix='%')
|
116
|
+
else:
|
117
|
+
finance_dict[key] = StatsProcessor.cal_non_percentage(
|
118
|
+
finance_dict[key])
|
119
|
+
|
96
120
|
@classmethod
|
97
121
|
def process_all(cls, finance_dict):
|
98
122
|
methods = [
|
99
|
-
cls.cal_EBIT,
|
100
|
-
cls.
|
101
|
-
cls.
|
102
|
-
cls.
|
103
|
-
cls.
|
104
|
-
cls.
|
105
|
-
cls.
|
106
|
-
cls.
|
107
|
-
cls.
|
108
|
-
cls.cal_roe,
|
109
|
-
cls.cal_gross_over_asset,
|
110
|
-
cls.cal_roce,
|
111
|
-
cls.cal_gross_profit_marginal,
|
112
|
-
cls.cal_operation_profit_rate,
|
113
|
-
cls.cal_operating_cash_flow_profit_rate,
|
114
|
-
cls.cal_dso,
|
115
|
-
cls.cal_account_receive_over_revenue,
|
116
|
-
cls.cal_dpo,
|
117
|
-
cls.cal_inventories_cycle_ratio,
|
118
|
-
cls.cal_dio,
|
123
|
+
cls.cal_EBIT, cls.cal_share_outstanding, cls.cal_fcf,
|
124
|
+
cls.cal_interest_bearing_debt, cls.cal_revenue_per_share,
|
125
|
+
cls.cal_gross_per_share, cls.cal_operating_income_per_share,
|
126
|
+
cls.cal_operating_cash_flow_per_share, cls.fcf_per_share,
|
127
|
+
cls.cal_roa, cls.cal_roe, cls.cal_gross_over_asset, cls.cal_roce,
|
128
|
+
cls.cal_gross_profit_marginal, cls.cal_operation_profit_rate,
|
129
|
+
cls.cal_operating_cash_flow_profit_rate, cls.cal_dso,
|
130
|
+
cls.cal_account_receive_over_revenue, cls.cal_dpo,
|
131
|
+
cls.cal_inventories_cycle_ratio, cls.cal_dio,
|
119
132
|
cls.cal_inventories_revenue_ratio,
|
120
|
-
cls.cal_cash_of_conversion_cycle,
|
121
|
-
cls.
|
122
|
-
cls.
|
123
|
-
cls.
|
124
|
-
cls.cal_quick_ratio,
|
125
|
-
cls.cal_debt_to_equity_ratio,
|
126
|
-
cls.cal_net_debt_to_equity_ratio,
|
127
|
-
cls.cal_interest_coverage_ratio,
|
133
|
+
cls.cal_cash_of_conversion_cycle, cls.cal_asset_turnover,
|
134
|
+
cls.cal_application_turnover, cls.cal_current_ratio,
|
135
|
+
cls.cal_quick_ratio, cls.cal_debt_to_equity_ratio,
|
136
|
+
cls.cal_net_debt_to_equity_ratio, cls.cal_interest_coverage_ratio,
|
128
137
|
cls.cal_debt_to_operating_cash_flow,
|
129
|
-
cls.cal_debt_to_free_cash_flow,
|
130
|
-
cls.cal_cash_flow_ratio
|
138
|
+
cls.cal_debt_to_free_cash_flow, cls.cal_cash_flow_ratio
|
131
139
|
]
|
132
140
|
|
133
141
|
for method in methods:
|
@@ -137,13 +145,14 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
137
145
|
def cal_EBIT(cls, finance_dict):
|
138
146
|
"""
|
139
147
|
計算EBIT
|
140
|
-
EBIT = 營業收入 - 營業成本 - 營業費用
|
148
|
+
EBIT = 營業收入 - 營業成本 - 營業費用 - 所得稅費用
|
141
149
|
"""
|
142
150
|
try:
|
143
|
-
finance_dict['
|
144
|
-
|
145
|
-
|
146
|
-
|
151
|
+
EBIT = (finance_dict['revenue'] - finance_dict['operating_cost'] -
|
152
|
+
finance_dict['operating_expenses'] -
|
153
|
+
finance_dict['tax_fee'])
|
154
|
+
finance_dict['EBIT'] = StatsProcessor.cal_non_percentage(EBIT)
|
155
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
147
156
|
finance_dict['EBIT'] = None
|
148
157
|
print(f"Error calculating EBIT: {e}")
|
149
158
|
|
@@ -154,12 +163,32 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
154
163
|
自由現金流 = 營業現金流 + 投資現金流
|
155
164
|
"""
|
156
165
|
try:
|
157
|
-
|
158
|
-
|
159
|
-
|
166
|
+
fcf = (finance_dict["operating_cash_flow"] +
|
167
|
+
finance_dict["financing_cash_flow"])
|
168
|
+
finance_dict["fcf"] = StatsProcessor.cal_non_percentage(fcf)
|
169
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
160
170
|
finance_dict['fcf'] = None
|
161
171
|
print(f"Error calculating FCF: {e}")
|
162
172
|
|
173
|
+
@classmethod
|
174
|
+
def cal_interest_bearing_debt(cls, finance_dict):
|
175
|
+
"""
|
176
|
+
計算有息負債
|
177
|
+
短期借款+長期借款
|
178
|
+
"""
|
179
|
+
finance_dict['interest_bearing_debt'] = 0.0
|
180
|
+
|
181
|
+
try:
|
182
|
+
finance_dict['interest_bearing_debt'] += finance_dict[
|
183
|
+
'short_term_liabilities']
|
184
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
185
|
+
finance_dict['interest_bearing_debt'] += 0.0
|
186
|
+
try:
|
187
|
+
finance_dict['interest_bearing_debt'] += finance_dict[
|
188
|
+
'long_term_liabilities']
|
189
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
190
|
+
finance_dict['interest_bearing_debt'] += 0.0
|
191
|
+
|
163
192
|
@classmethod
|
164
193
|
def cal_share_outstanding(cls, finance_dict):
|
165
194
|
"""
|
@@ -169,7 +198,7 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
169
198
|
try:
|
170
199
|
finance_dict["share_outstanding"] = (finance_dict['net_income'] /
|
171
200
|
finance_dict['eps'])
|
172
|
-
except KeyError as e:
|
201
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
173
202
|
finance_dict['share_outstanding'] = None
|
174
203
|
print(f"share_outstanding failed because of {str(e)}")
|
175
204
|
|
@@ -180,9 +209,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
180
209
|
每股營收 = 營業收入 / 在外流通股數
|
181
210
|
"""
|
182
211
|
try:
|
183
|
-
finance_dict['
|
184
|
-
|
185
|
-
|
212
|
+
revenue_per_share = (finance_dict['revenue'] /
|
213
|
+
finance_dict['share_outstanding'])
|
214
|
+
finance_dict[
|
215
|
+
'revenue_per_share'] = StatsProcessor.cal_non_percentage(
|
216
|
+
revenue_per_share, False)
|
217
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
186
218
|
finance_dict['revenue_per_share'] = None
|
187
219
|
print(f"revenue_per_share failed because of {str(e)}")
|
188
220
|
|
@@ -194,10 +226,13 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
194
226
|
"""
|
195
227
|
|
196
228
|
try:
|
197
|
-
finance_dict['
|
198
|
-
|
199
|
-
|
200
|
-
|
229
|
+
gross_per_share = (finance_dict['gross_profit'] /
|
230
|
+
finance_dict['share_outstanding'])
|
231
|
+
finance_dict[
|
232
|
+
'gross_per_share'] = StatsProcessor.cal_non_percentage(
|
233
|
+
gross_per_share, False)
|
234
|
+
|
235
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
201
236
|
finance_dict['gross_per_share'] = None
|
202
237
|
print(f"gross_per_share failed because of {str(e)}")
|
203
238
|
|
@@ -208,10 +243,12 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
208
243
|
每股營業利益= (當期營業利益)÷(當期在外流通股數)
|
209
244
|
"""
|
210
245
|
try:
|
211
|
-
finance_dict['
|
212
|
-
|
213
|
-
|
214
|
-
|
246
|
+
operating_income_per_share = (finance_dict['operating_income'] /
|
247
|
+
finance_dict['share_outstanding'])
|
248
|
+
finance_dict[
|
249
|
+
'operating_income_per_share'] = StatsProcessor.cal_non_percentage(
|
250
|
+
operating_income_per_share)
|
251
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
215
252
|
finance_dict['operating_income_per_share'] = None
|
216
253
|
print(f"operating_income_per_share failed because of {str(e)}")
|
217
254
|
|
@@ -222,10 +259,13 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
222
259
|
= (當期營業現金流) ÷(當期在外流通股數)
|
223
260
|
"""
|
224
261
|
try:
|
225
|
-
|
262
|
+
operating_cash_flow_per_share = (
|
226
263
|
finance_dict["operating_cash_flow"] /
|
227
264
|
finance_dict['share_outstanding'])
|
228
|
-
|
265
|
+
finance_dict[
|
266
|
+
"operating_cash_flow_per_share"] = StatsProcessor.cal_non_percentage(
|
267
|
+
operating_cash_flow_per_share)
|
268
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
229
269
|
finance_dict['operating_cash_flow_per_share'] = None
|
230
270
|
print(f'operating_cash_flow_per_share because of {str(e)}')
|
231
271
|
|
@@ -236,9 +276,11 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
236
276
|
每股自由現金流 = (當期自由現金流) ÷(當期在外流通股數)
|
237
277
|
"""
|
238
278
|
try:
|
239
|
-
|
240
|
-
|
241
|
-
|
279
|
+
fcf_per_share = (finance_dict['fcf'] /
|
280
|
+
finance_dict['share_outstanding'])
|
281
|
+
finance_dict['fcf_per_share'] = StatsProcessor.cal_non_percentage(
|
282
|
+
fcf_per_share)
|
283
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
242
284
|
finance_dict['fcf_per_share'] = None
|
243
285
|
print(f"fcf_per_share failed because of {str(e)}")
|
244
286
|
|
@@ -250,27 +292,35 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
250
292
|
計算資產報酬率(ROA)
|
251
293
|
ROA = [ 本期淨利 + 利息費用 × (1-有效稅率) ] ÷(資產總額)
|
252
294
|
"""
|
253
|
-
|
295
|
+
roa = (
|
254
296
|
finance_dict['net_income'] + finance_dict['interest'] +
|
255
297
|
(1 * 0.1) # 有效稅率需要改,這裡先設0.1
|
256
298
|
) / finance_dict['inventories']
|
257
299
|
|
300
|
+
finance_dict["roa"] = StatsProcessor.cal_percentage(roa)
|
301
|
+
|
258
302
|
@classmethod
|
259
303
|
def cal_roe(cls, finance_dict):
|
260
304
|
"""
|
261
305
|
計算股東權益報酬率(ROE)
|
262
306
|
ROE = (本期淨利) ÷(權益總額)
|
263
307
|
"""
|
264
|
-
|
265
|
-
|
308
|
+
roe = (finance_dict['net_income'] / finance_dict['equity'])
|
309
|
+
finance_dict['roe'] = StatsProcessor.cal_percentage(roe)
|
266
310
|
|
267
311
|
@classmethod
|
268
312
|
def cal_gross_over_asset(cls, finance_dict):
|
269
313
|
"""
|
270
314
|
計算營業毛利/總資產
|
271
315
|
"""
|
272
|
-
|
273
|
-
|
316
|
+
try:
|
317
|
+
gross_over_asset = (finance_dict['gross_profit'] /
|
318
|
+
finance_dict['total_asset'])
|
319
|
+
finance_dict['gross_over_asset'] = StatsProcessor.cal_percentage(
|
320
|
+
gross_over_asset)
|
321
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
322
|
+
finance_dict['gross_over_asset'] = None
|
323
|
+
print(f"營業毛利/總資產 failed because of {str(e)}")
|
274
324
|
|
275
325
|
@classmethod
|
276
326
|
def cal_roce(cls, finance_dict):
|
@@ -279,11 +329,13 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
279
329
|
ROCE = (稅前淨利+利息費用) / (資產總額-流動負債)
|
280
330
|
"""
|
281
331
|
try:
|
282
|
-
finance_dict['
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
332
|
+
roce = ((finance_dict['net_income_before_tax'] +
|
333
|
+
finance_dict['interest']) /
|
334
|
+
(finance_dict['total_asset'] -
|
335
|
+
finance_dict['current_liabilities']))
|
336
|
+
finance_dict['roce'] = StatsProcessor.cal_percentage(roce)
|
337
|
+
|
338
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
287
339
|
finance_dict['roce'] = None
|
288
340
|
print(f"ROCE failed because of {str(e)}")
|
289
341
|
|
@@ -294,8 +346,11 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
294
346
|
營業毛利率 = 營業毛利 ÷ 營業收入
|
295
347
|
"""
|
296
348
|
try:
|
297
|
-
finance_dict['
|
298
|
-
|
349
|
+
gross_profit_margin = (finance_dict['gross_profit'] /
|
350
|
+
finance_dict['revenue'])
|
351
|
+
finance_dict[
|
352
|
+
'gross_profit_margin'] = StatsProcessor.cal_percentage(
|
353
|
+
gross_profit_margin)
|
299
354
|
except:
|
300
355
|
finance_dict['gross_profit_margin'] = None
|
301
356
|
print(f"gross_profit_margin failed because of {str(e)}")
|
@@ -307,10 +362,13 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
307
362
|
營業利益率 = ( 營業收入-營業成本-營業費用)÷ 營業收入
|
308
363
|
"""
|
309
364
|
try:
|
310
|
-
|
365
|
+
operation_profit_rate = (
|
311
366
|
finance_dict['revenue'] - finance_dict['operating_cost'] -
|
312
|
-
finance_dict['
|
313
|
-
|
367
|
+
finance_dict['operating_expenses']) / finance_dict['revenue']
|
368
|
+
finance_dict[
|
369
|
+
"operation_profit_rate"] = StatsProcessor.cal_percentage(
|
370
|
+
operation_profit_rate)
|
371
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
314
372
|
finance_dict["operation_profit_rate"] = None
|
315
373
|
print(f"operation_profit failed because of {str(e)}")
|
316
374
|
|
@@ -321,11 +379,13 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
321
379
|
營業現金流利潤率 = 營業活動現金流 ÷ 營業收入
|
322
380
|
"""
|
323
381
|
try:
|
324
|
-
|
382
|
+
operating_cash_flow_profit_rate = (
|
325
383
|
finance_dict["operating_cash_flow"] / finance_dict["revenue"])
|
326
|
-
|
384
|
+
finance_dict[
|
385
|
+
"operating_cash_flow_profit_rate"] = StatsProcessor.cal_percentage(
|
386
|
+
operating_cash_flow_profit_rate)
|
387
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
327
388
|
finance_dict["operating_cash_flow_profit_rate"] = None
|
328
|
-
|
329
389
|
print(
|
330
390
|
f"operating_cash_flow_profit_rate failed because of {str(e)}")
|
331
391
|
|
@@ -342,10 +402,16 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
342
402
|
def cal_dso(cls, finance_dict):
|
343
403
|
"""
|
344
404
|
計算應收帳款收現天數(DSO)
|
345
|
-
DSO = 365 × (
|
405
|
+
DSO = 365 × (應收帳款平均餘額 ÷ 營業收入)
|
346
406
|
"""
|
347
|
-
|
348
|
-
|
407
|
+
try:
|
408
|
+
dso = (365 *
|
409
|
+
(finance_dict['account_pay'] / finance_dict['revenue']))
|
410
|
+
finance_dict['dso'] = StatsProcessor.cal_non_percentage(
|
411
|
+
dso, to_str=True, postfix="日")
|
412
|
+
except:
|
413
|
+
finance_dict['dso'] = None
|
414
|
+
print(f"Error calculating 應收帳款收現天數 because of {str(e)}")
|
349
415
|
|
350
416
|
@classmethod
|
351
417
|
def cal_account_receive_over_revenue(cls, finance_dict):
|
@@ -353,8 +419,11 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
353
419
|
計算應收帳款佔營收比率
|
354
420
|
= 應收帳款平均餘額 ÷ 營業收入
|
355
421
|
"""
|
356
|
-
|
357
|
-
|
422
|
+
account_receive_over_revenue = (finance_dict['account_receive'] /
|
423
|
+
finance_dict['revenue'])
|
424
|
+
finance_dict[
|
425
|
+
"account_receive_over_revenue"] = StatsProcessor.cal_percentage(
|
426
|
+
account_receive_over_revenue)
|
358
427
|
|
359
428
|
@classmethod
|
360
429
|
def cal_dpo(cls, finance_dict):
|
@@ -362,9 +431,15 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
362
431
|
計算應付帳款週轉天數
|
363
432
|
DPO = 365天 ÷ (銷貨成本÷平均應付帳款)
|
364
433
|
"""
|
365
|
-
|
366
|
-
|
367
|
-
|
434
|
+
try:
|
435
|
+
dpo = (
|
436
|
+
365 *
|
437
|
+
(finance_dict['account_pay'] / finance_dict['operating_cost']))
|
438
|
+
finance_dict["dpo"] = StatsProcessor.cal_non_percentage(
|
439
|
+
dpo, to_str=True, postfix="日")
|
440
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
441
|
+
finance_dict["dpo"] = None
|
442
|
+
print(f"應付帳款週轉天數 failed because of {str(e)}")
|
368
443
|
|
369
444
|
@classmethod
|
370
445
|
def cal_inventories_cycle_ratio(cls, finance_dict):
|
@@ -372,19 +447,32 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
372
447
|
計算存貨周轉率
|
373
448
|
= 銷貨成本 ÷ 存貨
|
374
449
|
"""
|
450
|
+
try:
|
451
|
+
inventories_cycle_ratio = (finance_dict['operating_cost'] /
|
452
|
+
finance_dict['inventories'])
|
375
453
|
|
376
|
-
|
377
|
-
|
454
|
+
finance_dict[
|
455
|
+
"inventories_cycle_ratio"] = StatsProcessor.cal_percentage(
|
456
|
+
inventories_cycle_ratio)
|
457
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
458
|
+
finance_dict["inventories_cycle_ratio"] = None
|
459
|
+
print(f"Error calculating 存貨周轉率 because of {str(e)}")
|
378
460
|
|
379
461
|
@classmethod
|
380
462
|
def cal_dio(cls, finance_dict):
|
381
463
|
"""
|
382
|
-
計算 存貨週轉天數
|
383
|
-
DIO = 365天
|
384
|
-
MUDA MUDA MUDA
|
464
|
+
計算 存貨週轉天數 or 平均售貨天數
|
465
|
+
DIO = 365天 * (存貨 ÷ 銷貨成本)
|
466
|
+
MUDA MUDA MUDA !!!
|
385
467
|
"""
|
386
|
-
|
387
|
-
|
468
|
+
try:
|
469
|
+
dio = 365 * (finance_dict["inventories"] /
|
470
|
+
finance_dict["operating_cost"])
|
471
|
+
finance_dict["dio"] = StatsProcessor.cal_non_percentage(
|
472
|
+
dio, to_str=True, postfix="日")
|
473
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
474
|
+
finance_dict["dio"] = None
|
475
|
+
print(f"Error calculating 存貨週轉天數 because of {str(e)}")
|
388
476
|
|
389
477
|
@classmethod
|
390
478
|
def cal_inventories_revenue_ratio(cls, finance_dict):
|
@@ -392,8 +480,16 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
392
480
|
計算存貨佔營收比率
|
393
481
|
存貨佔營收比= 存貨 ÷ 營業收入
|
394
482
|
"""
|
395
|
-
|
396
|
-
finance_dict['inventories'] /
|
483
|
+
try:
|
484
|
+
inventories_revenue_ratio = (finance_dict['inventories'] /
|
485
|
+
finance_dict['revenue'])
|
486
|
+
|
487
|
+
finance_dict[
|
488
|
+
"inventories_revenue_ratio"] = StatsProcessor.cal_percentage(
|
489
|
+
inventories_revenue_ratio)
|
490
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
491
|
+
finance_dict["inventories_revenue_ratio"] = None
|
492
|
+
print(f"Error calculating 存貨佔營收比率 because of {str(e)}")
|
397
493
|
|
398
494
|
@classmethod
|
399
495
|
def cal_cash_of_conversion_cycle(cls, finance_dict):
|
@@ -401,19 +497,42 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
401
497
|
計算現金循環週期
|
402
498
|
存貨週轉天數 + 應收帳款週轉天數 - 應付帳款週轉天數
|
403
499
|
"""
|
404
|
-
|
405
|
-
|
406
|
-
|
500
|
+
try:
|
501
|
+
cash_of_conversion_cycle = (finance_dict["dio"] +
|
502
|
+
finance_dict["dso"] -
|
503
|
+
finance_dict['dpo'])
|
504
|
+
finance_dict[
|
505
|
+
"cash_of_conversion_cycle"] = StatsProcessor.cal_non_percentage(
|
506
|
+
cash_of_conversion_cycle, to_str=True, postfix="日")
|
507
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
508
|
+
finance_dict["cash_of_conversion_cycle"] = None
|
407
509
|
|
408
510
|
@classmethod
|
409
511
|
def cal_asset_turnover(cls, finance_dict):
|
410
|
-
|
411
|
-
|
512
|
+
"""
|
513
|
+
計算資產周轉率
|
514
|
+
營業收入 ÷ 資產總額
|
515
|
+
"""
|
516
|
+
asset_turnover = (finance_dict["revenue"] /
|
517
|
+
finance_dict["inventories"])
|
518
|
+
finance_dict["asset_turnover"] = StatsProcessor.cal_percentage(
|
519
|
+
asset_turnover)
|
412
520
|
|
413
521
|
@classmethod
|
414
522
|
def cal_application_turnover(cls, finance_dict):
|
415
|
-
|
416
|
-
|
523
|
+
"""
|
524
|
+
不動產、廠房及設備週轉率
|
525
|
+
營業收入 ÷ 不動產、廠房與設備平均餘額
|
526
|
+
"""
|
527
|
+
try:
|
528
|
+
applcation_turnover = (finance_dict['revenue'] /
|
529
|
+
finance_dict["application"])
|
530
|
+
finance_dict[
|
531
|
+
'applcation_turnover'] = StatsProcessor.cal_percentage(
|
532
|
+
applcation_turnover)
|
533
|
+
|
534
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
535
|
+
finance_dict['application_turnover'] = None
|
417
536
|
|
418
537
|
@classmethod
|
419
538
|
def cal_current_ratio(cls, finance_dict):
|
@@ -421,81 +540,117 @@ class FinanceOverviewProcessor(StatsProcessor):
|
|
421
540
|
計算流動比率 = 流動資產 / 流動負債
|
422
541
|
"""
|
423
542
|
try:
|
424
|
-
finance_dict['
|
425
|
-
|
543
|
+
current_ratio = (finance_dict['current_assets'] /
|
544
|
+
finance_dict['current_liabilities'])
|
545
|
+
finance_dict['current_ratio'] = StatsProcessor.cal_percentage(
|
546
|
+
current_ratio)
|
426
547
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
427
548
|
finance_dict['current_ratio'] = None
|
428
549
|
print(f"Error calculating current ratio: {e}")
|
429
550
|
|
430
551
|
@classmethod
|
431
552
|
def cal_quick_ratio(cls, finance_dict):
|
553
|
+
"""
|
554
|
+
速動比率
|
555
|
+
(流動資產 - 存貨) / 流動負債
|
556
|
+
"""
|
432
557
|
try:
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
558
|
+
quick_ratio = (finance_dict['current_assets'] -
|
559
|
+
finance_dict['inventories']
|
560
|
+
) / finance_dict['current_liabilities']
|
561
|
+
finance_dict['quick_ratio'] = StatsProcessor.cal_percentage(
|
562
|
+
quick_ratio)
|
437
563
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
438
564
|
finance_dict['quick_ratio'] = None
|
439
565
|
print(f"Error calculating quick ratio: {e}")
|
440
566
|
|
441
567
|
@classmethod
|
442
568
|
def cal_debt_to_equity_ratio(cls, finance_dict):
|
569
|
+
"""
|
570
|
+
# 負債權益比率 = 總負債 / 股東權益
|
571
|
+
"""
|
443
572
|
try:
|
444
|
-
|
445
|
-
finance_dict['debt_to_equity_ratio'] = finance_dict[
|
573
|
+
debt_to_equity_ratio = finance_dict[
|
446
574
|
'total_liabilities'] / finance_dict['equity']
|
575
|
+
finance_dict[
|
576
|
+
'debt_to_equity_ratio'] = StatsProcessor.cal_percentage(
|
577
|
+
debt_to_equity_ratio)
|
447
578
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
448
579
|
finance_dict['debt_to_equity_ratio'] = None
|
449
580
|
print(f"Error calculating debt to equity ratio: {e}")
|
450
581
|
|
451
582
|
@classmethod
|
452
583
|
def cal_net_debt_to_equity_ratio(cls, finance_dict):
|
584
|
+
"""
|
585
|
+
# 淨負債權益比率 = (總負債 - 現金及約當現金) / 股東權益
|
586
|
+
"""
|
453
587
|
try:
|
454
|
-
|
455
|
-
finance_dict['net_debt_to_equity_ratio'] = (
|
588
|
+
net_debt_to_equity_ratio = (
|
456
589
|
finance_dict['total_liabilities'] -
|
457
590
|
finance_dict['cash_and_cash_equivalents']
|
458
591
|
) / finance_dict['equity']
|
592
|
+
finance_dict[
|
593
|
+
'net_debt_to_equity_ratio'] = StatsProcessor.cal_percentage(
|
594
|
+
net_debt_to_equity_ratio)
|
459
595
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
460
596
|
finance_dict['net_debt_to_equity_ratio'] = None
|
461
597
|
print(f"Error calculating net debt to equity ratio: {e}")
|
462
598
|
|
463
599
|
@classmethod
|
464
600
|
def cal_interest_coverage_ratio(cls, finance_dict):
|
601
|
+
"""
|
602
|
+
# 利息保障倍數 = EBIT / 利息費用
|
603
|
+
"""
|
465
604
|
try:
|
466
|
-
|
467
|
-
|
468
|
-
|
605
|
+
interest_coverage_ratio = finance_dict['EBIT'] / finance_dict[
|
606
|
+
'interest_expense']
|
607
|
+
finance_dict[
|
608
|
+
'interest_coverage_ratio'] = StatsProcessor.cal_non_percentage(
|
609
|
+
interest_coverage_ratio, to_str=True, postfix="倍")
|
469
610
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
470
611
|
finance_dict['interest_coverage_ratio'] = None
|
471
612
|
print(f"Error calculating interest coverage ratio: {e}")
|
472
613
|
|
473
614
|
@classmethod
|
474
615
|
def cal_debt_to_operating_cash_flow(cls, finance_dict):
|
616
|
+
"""
|
617
|
+
有息負債 / 營業活動現金流
|
618
|
+
"""
|
475
619
|
try:
|
476
|
-
|
477
|
-
finance_dict['debt_to_operating_cash_flow'] = finance_dict[
|
620
|
+
debt_to_operating_cash_flow = finance_dict[
|
478
621
|
'interest_bearing_debt'] / finance_dict['operating_cash_flow']
|
622
|
+
finance_dict[
|
623
|
+
'debt_to_operating_cash_flow'] = StatsProcessor.cal_percentage(
|
624
|
+
debt_to_operating_cash_flow)
|
479
625
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
480
626
|
finance_dict['debt_to_operating_cash_flow'] = None
|
481
627
|
print(f"Error calculating debt to operating cash flow: {e}")
|
482
628
|
|
483
629
|
@classmethod
|
484
630
|
def cal_debt_to_free_cash_flow(cls, finance_dict):
|
631
|
+
"""
|
632
|
+
# 有息負債 / 自由現金流
|
633
|
+
"""
|
485
634
|
try:
|
486
|
-
|
487
|
-
finance_dict['debt_to_free_cash_flow'] = finance_dict[
|
635
|
+
debt_to_free_cash_flow = finance_dict[
|
488
636
|
'interest_bearing_debt'] / finance_dict['fcf']
|
637
|
+
finance_dict[
|
638
|
+
'debt_to_free_cash_flow'] = StatsProcessor.cal_percentage(
|
639
|
+
debt_to_free_cash_flow)
|
489
640
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
490
641
|
finance_dict['debt_to_free_cash_flow'] = None
|
491
642
|
print(f"Error calculating debt to free cash flow: {e}")
|
492
643
|
|
493
644
|
@classmethod
|
494
645
|
def cal_cash_flow_ratio(cls, finance_dict):
|
646
|
+
"""
|
647
|
+
# 現金流量比率 = 營業活動現金流 / 流動負債
|
648
|
+
"""
|
495
649
|
try:
|
496
|
-
|
497
|
-
finance_dict['cash_flow_ratio'] = finance_dict[
|
650
|
+
cash_flow_ratio = finance_dict[
|
498
651
|
'operating_cash_flow'] / finance_dict['current_liabilities']
|
652
|
+
finance_dict['cash_flow_ratio'] = StatsProcessor.cal_percentage(
|
653
|
+
cash_flow_ratio)
|
499
654
|
except (KeyError, ZeroDivisionError, TypeError) as e:
|
500
655
|
finance_dict['cash_flow_ratio'] = None
|
501
656
|
print(f"Error calculating cash flow ratio: {e}")
|
@@ -46,9 +46,15 @@ class MonthRevenueFetcher(StatsFetcher):
|
|
46
46
|
return fetched_data[-1]
|
47
47
|
|
48
48
|
def query_data(self):
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
try:
|
50
|
+
latest_time = StatsDateTime.get_latest_time(
|
51
|
+
self.ticker, self.collection)['last_update_time']
|
52
|
+
target_year = latest_time['monthly_data']['latest_year']
|
53
|
+
target_month = latest_time['monthly_data']['latest_month']
|
54
|
+
except Exception as e:
|
55
|
+
today = StatsDateTime.get_today()
|
56
|
+
target_month = today.month
|
57
|
+
target_year = today.year
|
52
58
|
|
53
59
|
# Query data
|
54
60
|
fetched_data = self.collect_data(target_year, target_month)
|
@@ -1,11 +1,13 @@
|
|
1
1
|
from .base import StatsFetcher, StatsDateTime
|
2
|
+
import importlib.resources as pkg_resources
|
2
3
|
import json
|
4
|
+
import numpy as np
|
3
5
|
import pandas as pd
|
4
6
|
from ..utils import StatsDateTime, StatsProcessor
|
5
|
-
import importlib.resources as pkg_resources
|
6
7
|
import yaml
|
7
8
|
|
8
9
|
|
10
|
+
|
9
11
|
class ProfitLoseFetcher(StatsFetcher):
|
10
12
|
"""
|
11
13
|
iFa.ai: 財務分析 -> 損益表
|
@@ -73,10 +75,15 @@ class ProfitLoseFetcher(StatsFetcher):
|
|
73
75
|
return list(fetched_data)[-1]
|
74
76
|
|
75
77
|
def query_data(self):
|
76
|
-
|
78
|
+
try:
|
79
|
+
latest_time = StatsDateTime.get_latest_time(
|
80
|
+
self.ticker, self.collection)['last_update_time']
|
81
|
+
target_season = latest_time['seasonal_data']['latest_season']
|
82
|
+
except Exception as e:
|
83
|
+
today = StatsDateTime.get_today()
|
77
84
|
|
78
|
-
|
79
|
-
|
85
|
+
target_season = today.season
|
86
|
+
target_season = target_season - 1 if target_season > 1 else 4
|
80
87
|
|
81
88
|
fetched_data = self.collect_data(target_season)
|
82
89
|
|
@@ -108,6 +115,12 @@ class ProfitLoseFetcher(StatsFetcher):
|
|
108
115
|
for index_name, value_dict in profit_lose.items():
|
109
116
|
# (2020Q1, 項目, 金額或%)
|
110
117
|
for item_name, item in value_dict.items():
|
118
|
+
if (item_name == 'percentage'):
|
119
|
+
if (isinstance(item, (float, int))):
|
120
|
+
item = np.round(item, 2)
|
121
|
+
if ('YoY' in item_name):
|
122
|
+
if (isinstance(item, (float, int))):
|
123
|
+
item = np.round(item * 100, 2)
|
111
124
|
try:
|
112
125
|
table_dict[index_name][(time_index, item_name)] = item
|
113
126
|
|
@@ -121,6 +134,8 @@ class ProfitLoseFetcher(StatsFetcher):
|
|
121
134
|
total_table = pd.DataFrame.from_dict(table_dict, orient='index')
|
122
135
|
total_table.columns = pd.MultiIndex.from_tuples(total_table.columns)
|
123
136
|
|
137
|
+
total_table = total_table.replace("N/A", None)
|
138
|
+
|
124
139
|
for name, setting in self.table_settings.items():
|
125
140
|
return_dict[name] = StatsProcessor.slice_multi_col_table(
|
126
141
|
total_table=total_table,
|
@@ -54,11 +54,20 @@ class ValueFetcher(StatsFetcher):
|
|
54
54
|
return pipeline
|
55
55
|
|
56
56
|
def query_data(self):
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
57
|
+
try:
|
58
|
+
latest_time = StatsDateTime.get_latest_time(
|
59
|
+
self.ticker, self.collection)['last_update_time']
|
60
|
+
target_year = latest_time['daily_data']['last_update'].year
|
61
|
+
start_date = latest_time['daily_data']['last_update'] - timedelta(days=31)
|
62
|
+
end_date = latest_time['daily_data']['last_update']
|
63
|
+
|
64
|
+
except Exception as e:
|
65
|
+
today = StatsDateTime.get_today()
|
66
|
+
target_year = today.year
|
67
|
+
start_date = (today.date - timedelta(days=31))
|
68
|
+
end_date = today.date
|
69
|
+
|
70
|
+
this_year = target_year - 1911
|
62
71
|
|
63
72
|
fetched_data = self.collect_data(start_date, end_date)
|
64
73
|
|
@@ -15,22 +15,22 @@ net_income:
|
|
15
15
|
field: 本期淨利(淨損)
|
16
16
|
value: value
|
17
17
|
|
18
|
+
tax_fee:
|
19
|
+
field: 所得稅費用(利益)合計
|
20
|
+
value: value
|
21
|
+
|
18
22
|
# TODO: 以下所爬到的資料都是累計的,Ifa有額外計算當季的變化量
|
19
23
|
operating_cash_flow:
|
20
24
|
field: 營業活動之淨現金流入(流出)
|
21
|
-
value:
|
25
|
+
value: single_season_value
|
22
26
|
|
23
27
|
invest_cash_flow:
|
24
28
|
field: 投資活動之淨現金流入(流出)
|
25
|
-
value:
|
29
|
+
value: single_season_value
|
26
30
|
|
27
31
|
financing_cash_flow:
|
28
32
|
field: 籌資活動之淨現金流入(流出)
|
29
|
-
value:
|
30
|
-
|
31
|
-
fcf:
|
32
|
-
field: 本期現金及約當現金增加(減少)數
|
33
|
-
value: value
|
33
|
+
value: single_season_value
|
34
34
|
# ^^^ 以上皆需要額外在DataBase處理
|
35
35
|
|
36
36
|
# 每股財務狀況
|
@@ -84,7 +84,7 @@ net_income_YoY:
|
|
84
84
|
|
85
85
|
operating_cash_flow_YoY:
|
86
86
|
field: 營業活動之淨現金流入(流出)
|
87
|
-
value:
|
87
|
+
value: single_season_YoY
|
88
88
|
|
89
89
|
# operating_cash_flow_per_share_YoY:
|
90
90
|
# field: 每股營業現金流年成長率
|
@@ -123,6 +123,13 @@ total_liabilities:
|
|
123
123
|
field: 負債總額
|
124
124
|
value: value
|
125
125
|
|
126
|
+
short_term_liabilities:
|
127
|
+
field: 短期借款
|
128
|
+
value: value
|
129
|
+
|
130
|
+
long_term_liabilities:
|
131
|
+
field: 長期借款
|
132
|
+
value: value
|
126
133
|
#
|
127
134
|
cash_and_cash_equivalents:
|
128
135
|
field: 現金及約當現金
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from importlib.resources import files
|
2
2
|
import json
|
3
|
+
import numpy as np
|
3
4
|
import pandas as pd
|
4
5
|
import yaml
|
5
6
|
|
@@ -117,8 +118,9 @@ class StatsProcessor:
|
|
117
118
|
sliced_table = sliced_table.pivot(index='level_1',
|
118
119
|
columns='level_0',
|
119
120
|
values=target_index).sort_index(
|
120
|
-
|
121
|
-
|
121
|
+
axis=1,
|
122
|
+
level=1,
|
123
|
+
ascending=False)
|
122
124
|
|
123
125
|
sliced_table.columns = sliced_table.columns.get_level_values(1)
|
124
126
|
sliced_table.columns.name = None
|
@@ -136,3 +138,27 @@ class StatsProcessor:
|
|
136
138
|
]
|
137
139
|
return return_table
|
138
140
|
|
141
|
+
@classmethod
|
142
|
+
def cal_percentage(cls, value, postfix="%"):
|
143
|
+
if (isinstance(value, (float, int))):
|
144
|
+
value = np.round(value * 100 , 2).item()
|
145
|
+
value = f"{value:.2f}{postfix}"
|
146
|
+
|
147
|
+
return value
|
148
|
+
|
149
|
+
else:
|
150
|
+
return value
|
151
|
+
|
152
|
+
@classmethod
|
153
|
+
def cal_non_percentage(cls, value, to_str=False, postfix="元"):
|
154
|
+
if (isinstance(value, (float, int))):
|
155
|
+
value = np.round(value, 2).item()
|
156
|
+
if (to_str):
|
157
|
+
value = f"{value:.2f}{postfix}"
|
158
|
+
return value
|
159
|
+
|
160
|
+
else:
|
161
|
+
return value
|
162
|
+
|
163
|
+
else:
|
164
|
+
return value
|
neurostats_API/utils/datetime.py
CHANGED
@@ -19,3 +19,11 @@ class StatsDateTime():
|
|
19
19
|
|
20
20
|
return StatsDateTime(today, this_year, this_month, this_day,
|
21
21
|
this_season)
|
22
|
+
|
23
|
+
@classmethod
|
24
|
+
def get_latest_time(cls, ticker, collection):
|
25
|
+
return collection.find_one(
|
26
|
+
{ "ticker" : ticker },
|
27
|
+
{ "_id": 0, "last_update_time": 1 }
|
28
|
+
)
|
29
|
+
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: neurostats-API
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.9
|
4
4
|
Summary: The service of NeuroStats website
|
5
5
|
Home-page: https://github.com/NeurowattStats/NeuroStats_API.git
|
6
6
|
Author: JasonWang@Neurowatt
|
@@ -78,7 +78,7 @@ pip install neurostats-API
|
|
78
78
|
```Python
|
79
79
|
>>> import neurostats_API
|
80
80
|
>>> print(neurostats_API.__version__)
|
81
|
-
0.0.
|
81
|
+
0.0.9
|
82
82
|
```
|
83
83
|
|
84
84
|
### 得到最新一期的評價資料與歷年評價
|
@@ -379,11 +379,12 @@ stats_fetcher.query_data()
|
|
379
379
|
|
380
380
|
### 現金流量表
|
381
381
|
``` Python
|
382
|
-
from neurostats_API.
|
382
|
+
from neurostats_API.fetchers import CashFlowFetcher
|
383
383
|
db_client = DBClient("<連接的DB位置>").get_client()
|
384
|
-
fetcher = StatsFetcher(db_client)
|
385
384
|
ticker = 2330 # 換成tw50內任意ticker
|
386
|
-
|
385
|
+
fetcher = StatsFetcher(ticker, db_client)
|
386
|
+
|
387
|
+
stats_fetcher.query()
|
387
388
|
```
|
388
389
|
#### 回傳
|
389
390
|
```Python
|
@@ -0,0 +1,26 @@
|
|
1
|
+
neurostats_API/__init__.py,sha256=9_jSwg7P5SlFv0Ci2ZSYBcAbygp9XV2C8sryRO8tvko,19
|
2
|
+
neurostats_API/cli.py,sha256=UJSWLIw03P24p-gkBb6JSEI5dW5U12UvLf1L8HjQD-o,873
|
3
|
+
neurostats_API/main.py,sha256=QcsfmWivg2Dnqw3MTJWiI0QvEiRs0VuH-BjwQHFCv00,677
|
4
|
+
neurostats_API/fetchers/__init__.py,sha256=U_OMG-mLpsVKYnCBrW2OjFuCzvPeVQ__7A676vGzztY,313
|
5
|
+
neurostats_API/fetchers/balance_sheet.py,sha256=VeWhd8Z2XZnL5RzxmenLzAd4eyK2sWTuiRGkqWsEmzk,5219
|
6
|
+
neurostats_API/fetchers/base.py,sha256=NW2SFzrimyAIrdJx1LVmTazelyZOAtcj54kJKHc4Vaw,1662
|
7
|
+
neurostats_API/fetchers/cash_flow.py,sha256=4G4SIUoBSwT-BePmz-SprQ0IJRL2QNWqWdQtlgaRKd4,7371
|
8
|
+
neurostats_API/fetchers/finance_overview.py,sha256=EVP7k0JkQq3ydXy0f3t2kzy12iIQEwDniTLn98qZ460,25637
|
9
|
+
neurostats_API/fetchers/month_revenue.py,sha256=QmhMAO8jbkjg2R1LR0TAPE3bmDnyuLNjnD24ZsFkTBU,3501
|
10
|
+
neurostats_API/fetchers/profit_lose.py,sha256=C0y42RBA-s20XcG6CJ10Rt6Gm_rB6lcvBmIzbTpn64o,5123
|
11
|
+
neurostats_API/fetchers/tech.py,sha256=wH1kkqiETQhF0HAhk-UIiucnZ3EiL85Q-yMWCcVOiFM,11395
|
12
|
+
neurostats_API/fetchers/value_invest.py,sha256=O5IKC8Nl7p5-E-1zoyAyWtiDznaxNemeabanmaHDdJs,3327
|
13
|
+
neurostats_API/tools/balance_sheet.yaml,sha256=dKTMbsYR9EFp48WAzmm_ISHMiJQLyE0V-XWS_gkxmr0,541
|
14
|
+
neurostats_API/tools/cash_flow_percentage.yaml,sha256=fk2Z4eb1JjGFvP134eJatHacB7BgTkBenhDJr83w8RE,1345
|
15
|
+
neurostats_API/tools/finance_overview_dict.yaml,sha256=URL1IFqO0j5uOwN3xETHriy_u9lYbLvdwghuznenP2Q,2500
|
16
|
+
neurostats_API/tools/profit_lose.yaml,sha256=qHBnqG7fR4Pxc_c3n4raL-3l7o5RnABLz9YGOXoaGiA,2086
|
17
|
+
neurostats_API/tools/seasonal_data_field_dict.txt,sha256=Za1fJR1yERbqrX8TgsS2kmMYMbaye43Gu_5ukUNBCNM,7904
|
18
|
+
neurostats_API/utils/__init__.py,sha256=FTYKRFzW2XVXdnSHXnS3mQQaHlKF9xGqrMsgZZ2kroc,142
|
19
|
+
neurostats_API/utils/data_process.py,sha256=m1B4EhCNSzOMfTBDtYCjkQSjbDTAEFC6TNf3NNxV36k,5657
|
20
|
+
neurostats_API/utils/datetime.py,sha256=XJya4G8b_-ZOaBbMXgQjWh2MC4wc-o6goQ7EQJQMWrQ,773
|
21
|
+
neurostats_API/utils/db_client.py,sha256=OYe6yazcR4Aa6jYmy47JrryUeh2NnKGqY2K_lSZe6i8,455
|
22
|
+
neurostats_API/utils/fetcher.py,sha256=VbrUhjA-GG5AyjPX2SHtFIbZM4dm3jo0RgZzuCbb_Io,40927
|
23
|
+
neurostats_API-0.0.9.dist-info/METADATA,sha256=z--BAc0e6HHBuXSQgc5ikwjPFzTfe-2o02gFLmgY_do,18232
|
24
|
+
neurostats_API-0.0.9.dist-info/WHEEL,sha256=bFJAMchF8aTQGUgMZzHJyDDMPTO3ToJ7x23SLJa1SVo,92
|
25
|
+
neurostats_API-0.0.9.dist-info/top_level.txt,sha256=nSlQPMG0VtXivJyedp4Bkf86EOy2TpW10VGxolXrqnU,15
|
26
|
+
neurostats_API-0.0.9.dist-info/RECORD,,
|
@@ -1,26 +0,0 @@
|
|
1
|
-
neurostats_API/__init__.py,sha256=3Kn8sHWnxjlagph2LmftcF8JLlcMlmQIbU5t_jzgK3w,19
|
2
|
-
neurostats_API/cli.py,sha256=UJSWLIw03P24p-gkBb6JSEI5dW5U12UvLf1L8HjQD-o,873
|
3
|
-
neurostats_API/main.py,sha256=QcsfmWivg2Dnqw3MTJWiI0QvEiRs0VuH-BjwQHFCv00,677
|
4
|
-
neurostats_API/fetchers/__init__.py,sha256=U_OMG-mLpsVKYnCBrW2OjFuCzvPeVQ__7A676vGzztY,313
|
5
|
-
neurostats_API/fetchers/balance_sheet.py,sha256=FPsVobaNzFOxpXCo1Ui_rPmlG1crTRj9ukqQ4J8aiJg,4268
|
6
|
-
neurostats_API/fetchers/base.py,sha256=NW2SFzrimyAIrdJx1LVmTazelyZOAtcj54kJKHc4Vaw,1662
|
7
|
-
neurostats_API/fetchers/cash_flow.py,sha256=muVnteEEyeFPapGqgBOoHa8PAieEI796rHPNi5otOMY,7009
|
8
|
-
neurostats_API/fetchers/finance_overview.py,sha256=t5QlTM0bL3fkrqlyMn8-8GB6YgMLsofH9NRI8PfPRxE,18447
|
9
|
-
neurostats_API/fetchers/month_revenue.py,sha256=RNA7ROl2vm8Xbib3k50p_1shsHDVSKIbHkyNiRa8yMw,3182
|
10
|
-
neurostats_API/fetchers/profit_lose.py,sha256=ffEVNo7-fvtnAq2_gj-Ga55TGW7pPd6WhruOZH8NGYM,4463
|
11
|
-
neurostats_API/fetchers/tech.py,sha256=wH1kkqiETQhF0HAhk-UIiucnZ3EiL85Q-yMWCcVOiFM,11395
|
12
|
-
neurostats_API/fetchers/value_invest.py,sha256=tg8yELbVnTFTEclrwgXnCRW377KkcoLiP-Gk2pyM-9Y,2886
|
13
|
-
neurostats_API/tools/balance_sheet.yaml,sha256=dKTMbsYR9EFp48WAzmm_ISHMiJQLyE0V-XWS_gkxmr0,541
|
14
|
-
neurostats_API/tools/cash_flow_percentage.yaml,sha256=fk2Z4eb1JjGFvP134eJatHacB7BgTkBenhDJr83w8RE,1345
|
15
|
-
neurostats_API/tools/finance_overview_dict.yaml,sha256=Vvf8bv23NwJP8Yyw8DPS8c0_jjT_Wctnnz51SHS4AeI,2335
|
16
|
-
neurostats_API/tools/profit_lose.yaml,sha256=qHBnqG7fR4Pxc_c3n4raL-3l7o5RnABLz9YGOXoaGiA,2086
|
17
|
-
neurostats_API/tools/seasonal_data_field_dict.txt,sha256=KlIIdTTdbvUd9TSDE9-gpzk2jt2ck_LdisX8cnrWMD4,7869
|
18
|
-
neurostats_API/utils/__init__.py,sha256=FTYKRFzW2XVXdnSHXnS3mQQaHlKF9xGqrMsgZZ2kroc,142
|
19
|
-
neurostats_API/utils/data_process.py,sha256=rRKf2H0X2J-tDXDreErcz3Y3TGb8_0Q6GKe0izRjnmA,4942
|
20
|
-
neurostats_API/utils/datetime.py,sha256=I9CIgZdE5OMzUciOS5wvapOVEIrXG_0Qb6iDKfIod6c,574
|
21
|
-
neurostats_API/utils/db_client.py,sha256=OYe6yazcR4Aa6jYmy47JrryUeh2NnKGqY2K_lSZe6i8,455
|
22
|
-
neurostats_API/utils/fetcher.py,sha256=VbrUhjA-GG5AyjPX2SHtFIbZM4dm3jo0RgZzuCbb_Io,40927
|
23
|
-
neurostats_API-0.0.7.dist-info/METADATA,sha256=Ajugxp0vnHoxdzriFfmLhTt32ckq2VnIiIO_eRU2QDo,18241
|
24
|
-
neurostats_API-0.0.7.dist-info/WHEEL,sha256=bFJAMchF8aTQGUgMZzHJyDDMPTO3ToJ7x23SLJa1SVo,92
|
25
|
-
neurostats_API-0.0.7.dist-info/top_level.txt,sha256=nSlQPMG0VtXivJyedp4Bkf86EOy2TpW10VGxolXrqnU,15
|
26
|
-
neurostats_API-0.0.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|