neurostats-API 0.0.7__py3-none-any.whl → 0.0.9__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 +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
|