neurostats-API 0.0.25rc1__py3-none-any.whl → 1.0.0rc2__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/async_mode/__init__.py +13 -0
- neurostats_API/async_mode/db/__init__.py +3 -0
- neurostats_API/async_mode/db/base.py +24 -0
- neurostats_API/async_mode/db/tej.py +10 -0
- neurostats_API/async_mode/db/twse.py +8 -0
- neurostats_API/async_mode/db/us.py +9 -0
- neurostats_API/async_mode/db_extractors/__init__.py +20 -0
- neurostats_API/async_mode/db_extractors/base.py +66 -0
- neurostats_API/async_mode/db_extractors/daily/__init__.py +7 -0
- neurostats_API/async_mode/db_extractors/daily/base.py +89 -0
- neurostats_API/async_mode/db_extractors/daily/tej_chip.py +14 -0
- neurostats_API/async_mode/db_extractors/daily/tej_tech.py +12 -0
- neurostats_API/async_mode/db_extractors/daily/twse_chip.py +49 -0
- neurostats_API/async_mode/db_extractors/daily/value.py +93 -0
- neurostats_API/async_mode/db_extractors/daily/yf.py +12 -0
- neurostats_API/async_mode/db_extractors/month_revenue/__init__.py +1 -0
- neurostats_API/async_mode/db_extractors/month_revenue/base.py +140 -0
- neurostats_API/async_mode/db_extractors/month_revenue/twse.py +5 -0
- neurostats_API/async_mode/db_extractors/seasonal/__init__.py +4 -0
- neurostats_API/async_mode/db_extractors/seasonal/balance_sheet.py +19 -0
- neurostats_API/async_mode/db_extractors/seasonal/base.py +152 -0
- neurostats_API/async_mode/db_extractors/seasonal/cashflow.py +10 -0
- neurostats_API/async_mode/db_extractors/seasonal/profit_lose.py +17 -0
- neurostats_API/async_mode/db_extractors/seasonal/tej.py +87 -0
- neurostats_API/async_mode/factory/__init__.py +1 -0
- neurostats_API/async_mode/factory/extractor_factory.py +168 -0
- neurostats_API/async_mode/factory/transformer_factory.py +164 -0
- neurostats_API/async_mode/fetchers/__init__.py +10 -0
- neurostats_API/async_mode/fetchers/balance_sheet.py +31 -0
- neurostats_API/async_mode/fetchers/base.py +48 -0
- neurostats_API/async_mode/fetchers/cash_flow.py +56 -0
- neurostats_API/async_mode/fetchers/finance_overview.py +134 -0
- neurostats_API/async_mode/fetchers/month_revenue.py +35 -0
- neurostats_API/async_mode/fetchers/profit_lose.py +46 -0
- neurostats_API/async_mode/fetchers/tech.py +205 -0
- neurostats_API/async_mode/fetchers/tej.py +88 -0
- neurostats_API/async_mode/fetchers/twse_institution.py +62 -0
- neurostats_API/async_mode/fetchers/twse_margin.py +100 -0
- neurostats_API/async_mode/fetchers/value.py +76 -0
- neurostats_API/config/company_list/ticker_index_industry_map.json +7946 -0
- neurostats_API/config/company_list/us.json +9986 -0
- neurostats_API/{tools → config}/tej_db/tej_db_skip_index.yaml +0 -2
- neurostats_API/{tools → config}/twse/profit_lose.yaml +0 -6
- neurostats_API/fetchers/finance_overview.py +27 -5
- neurostats_API/transformers/__init__.py +40 -0
- neurostats_API/transformers/balance_sheet/__init__.py +2 -0
- neurostats_API/transformers/balance_sheet/base.py +51 -0
- neurostats_API/transformers/balance_sheet/twse.py +76 -0
- neurostats_API/transformers/balance_sheet/us.py +30 -0
- neurostats_API/transformers/base.py +110 -0
- neurostats_API/transformers/cash_flow/__init__.py +2 -0
- neurostats_API/transformers/cash_flow/base.py +114 -0
- neurostats_API/transformers/cash_flow/twse.py +68 -0
- neurostats_API/transformers/cash_flow/us.py +38 -0
- neurostats_API/transformers/daily_chip/__init__.py +1 -0
- neurostats_API/transformers/daily_chip/base.py +5 -0
- neurostats_API/transformers/daily_chip/tej.py +0 -0
- neurostats_API/transformers/daily_chip/twse_chip.py +415 -0
- neurostats_API/transformers/daily_chip/utils/__init__.py +0 -0
- neurostats_API/transformers/daily_chip/utils/institution.py +90 -0
- neurostats_API/transformers/daily_chip/utils/margin_trading.py +2 -0
- neurostats_API/transformers/daily_chip/utils/security_lending.py +0 -0
- neurostats_API/transformers/daily_tech/__init__.py +1 -0
- neurostats_API/transformers/daily_tech/base.py +5 -0
- neurostats_API/transformers/daily_tech/tech.py +84 -0
- neurostats_API/transformers/daily_tech/utils/__init__.py +1 -0
- neurostats_API/transformers/daily_tech/utils/processor.py +251 -0
- neurostats_API/transformers/finance_overview/__init__.py +2 -0
- neurostats_API/transformers/finance_overview/agent_overview.py +55 -0
- neurostats_API/transformers/finance_overview/base.py +824 -0
- neurostats_API/transformers/finance_overview/stats_overview.py +64 -0
- neurostats_API/transformers/month_revenue/__init__.py +1 -0
- neurostats_API/transformers/month_revenue/base.py +60 -0
- neurostats_API/transformers/month_revenue/twse.py +129 -0
- neurostats_API/transformers/profit_lose/__init__.py +2 -0
- neurostats_API/transformers/profit_lose/base.py +82 -0
- neurostats_API/transformers/profit_lose/twse.py +133 -0
- neurostats_API/transformers/profit_lose/us.py +25 -0
- neurostats_API/transformers/tej/__init__.py +1 -0
- neurostats_API/transformers/tej/base.py +149 -0
- neurostats_API/transformers/tej/finance_statement.py +80 -0
- neurostats_API/transformers/value/__init__.py +1 -0
- neurostats_API/transformers/value/base.py +5 -0
- neurostats_API/transformers/value/tej.py +8 -0
- neurostats_API/transformers/value/twse.py +48 -0
- neurostats_API/utils/__init__.py +1 -1
- neurostats_API/utils/data_process.py +10 -6
- neurostats_API/utils/exception.py +8 -0
- neurostats_API/utils/logger.py +21 -0
- neurostats_API-1.0.0rc2.dist-info/METADATA +102 -0
- neurostats_API-1.0.0rc2.dist-info/RECORD +119 -0
- neurostats_API-0.0.25rc1.dist-info/METADATA +0 -858
- neurostats_API-0.0.25rc1.dist-info/RECORD +0 -36
- /neurostats_API/{tools → config}/company_list/tw.json +0 -0
- /neurostats_API/{tools → config}/company_list/us_TradingView_list.json +0 -0
- /neurostats_API/{tools → config}/tej_db/tej_db_index.yaml +0 -0
- /neurostats_API/{tools → config}/tej_db/tej_db_percent_index.yaml +0 -0
- /neurostats_API/{tools → config}/tej_db/tej_db_thousand_index.yaml +0 -0
- /neurostats_API/{tools → config}/twse/balance_sheet.yaml +0 -0
- /neurostats_API/{tools → config}/twse/cash_flow_percentage.yaml +0 -0
- /neurostats_API/{tools → config}/twse/finance_overview_dict.yaml +0 -0
- /neurostats_API/{tools → config}/twse/seasonal_data_field_dict.txt +0 -0
- {neurostats_API-0.0.25rc1.dist-info → neurostats_API-1.0.0rc2.dist-info}/WHEEL +0 -0
- {neurostats_API-0.0.25rc1.dist-info → neurostats_API-1.0.0rc2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,824 @@
|
|
1
|
+
from ..base import BaseTransformer
|
2
|
+
from datetime import datetime
|
3
|
+
import holidays
|
4
|
+
from neurostats_API.utils import StatsProcessor
|
5
|
+
import numpy as np
|
6
|
+
import pandas as pd
|
7
|
+
import pytz
|
8
|
+
|
9
|
+
|
10
|
+
class BaseFinanceOverviewTransformer(BaseTransformer):
|
11
|
+
|
12
|
+
def __init__(self, ticker, company_name, zone):
|
13
|
+
super().__init__(ticker, company_name, zone)
|
14
|
+
self.tw_company_list = StatsProcessor.load_json(
|
15
|
+
"company_list/ticker_index_industry_map.json"
|
16
|
+
)
|
17
|
+
self.us_company_list = StatsProcessor.load_json(
|
18
|
+
"company_list/us_TradingView_list.json"
|
19
|
+
)
|
20
|
+
|
21
|
+
def _get_latest_date(self, tech_data):
|
22
|
+
latest_date = tech_data.iloc[-1]['date']
|
23
|
+
if (latest_date is None):
|
24
|
+
str_date = datetime.today().strftime("%Y-%m-%d")
|
25
|
+
if (isinstance(latest_date, datetime)):
|
26
|
+
str_date = latest_date.strftime("%Y-%m-%d")
|
27
|
+
elif (isinstance(latest_date, str)):
|
28
|
+
str_date = latest_date
|
29
|
+
|
30
|
+
return f"{str_date} [GMT+8]"
|
31
|
+
|
32
|
+
def _get_latest_close(self, tech_data):
|
33
|
+
latest_close = tech_data.iloc[-1]['close']
|
34
|
+
latest_close = str(round(latest_close, 2))
|
35
|
+
return latest_close
|
36
|
+
|
37
|
+
def _get_latest_offset(self, tech_data):
|
38
|
+
latest_close = tech_data.iloc[-1]['close']
|
39
|
+
previous = tech_data.iloc[-2]['close']
|
40
|
+
|
41
|
+
offset = latest_close - previous
|
42
|
+
percentage = offset / previous
|
43
|
+
|
44
|
+
offset = str(np.round(offset, 2))
|
45
|
+
|
46
|
+
return {'value': offset, "percentage": f"{percentage:.2f}%"}
|
47
|
+
|
48
|
+
def _get_category(self):
|
49
|
+
|
50
|
+
def tw_category(ticker):
|
51
|
+
return self.tw_company_list.get(ticker, {}).get('industry')
|
52
|
+
|
53
|
+
def us_category(ticker):
|
54
|
+
return self.us_company_list.get(ticker, {}).get('en_industry')
|
55
|
+
|
56
|
+
fn_map = {'tw': tw_category, 'us': us_category}
|
57
|
+
|
58
|
+
get_fn = fn_map.get(self.zone)
|
59
|
+
return get_fn(self.ticker)
|
60
|
+
|
61
|
+
def _get_latest_volume(self, tech_data):
|
62
|
+
try:
|
63
|
+
volume = round(tech_data[-1]['volume'], 2)
|
64
|
+
return str(volume)
|
65
|
+
except Exception as e:
|
66
|
+
return "Error"
|
67
|
+
|
68
|
+
def _get_TTM_data(self, datas):
|
69
|
+
dfs = pd.DataFrame(datas)
|
70
|
+
TTM_data = dfs.mean()
|
71
|
+
|
72
|
+
return TTM_data
|
73
|
+
|
74
|
+
def _process_TTM_tw(self, datas):
|
75
|
+
return datas
|
76
|
+
|
77
|
+
def _get_latest_average_volume(self, tech_data, length=30):
|
78
|
+
try:
|
79
|
+
tech_data = pd.DataFrame(tech_data).tail(length)
|
80
|
+
|
81
|
+
value = tech_data['volume'].mean().item()
|
82
|
+
value = round(value, 2)
|
83
|
+
|
84
|
+
return str(value)
|
85
|
+
|
86
|
+
except Exception as e:
|
87
|
+
return "Error"
|
88
|
+
|
89
|
+
def _get_market_capitalization(self, daily_data, seasonal_data):
|
90
|
+
try:
|
91
|
+
latest_close = float(self._get_latest_close(daily_data))
|
92
|
+
latest_common_share = seasonal_data[-1]['Common Stock']
|
93
|
+
|
94
|
+
percentage = round((latest_close * latest_common_share), 2)
|
95
|
+
|
96
|
+
return str(percentage)
|
97
|
+
|
98
|
+
except Exception as e:
|
99
|
+
return "Error"
|
100
|
+
|
101
|
+
def _get_PE(self, daily_data, TTM_data):
|
102
|
+
try:
|
103
|
+
latest_close = float(self._get_latest_close(daily_data))
|
104
|
+
TTM_EPS = TTM_data['Diluted EPS']
|
105
|
+
|
106
|
+
value = round((latest_close / TTM_EPS), 2)
|
107
|
+
|
108
|
+
return str(value)
|
109
|
+
|
110
|
+
except Exception as e:
|
111
|
+
return "Error"
|
112
|
+
|
113
|
+
def _get_PS(self, daily_data, TTM_data, seasonal_data):
|
114
|
+
try:
|
115
|
+
market_capitalzation = self._get_market_capitalization(
|
116
|
+
daily_data, seasonal_data
|
117
|
+
)
|
118
|
+
market_capitalzation = float(market_capitalzation)
|
119
|
+
TTM_revenue = TTM_data['Total Revenue']
|
120
|
+
|
121
|
+
value = round((market_capitalzation / TTM_revenue), 2)
|
122
|
+
|
123
|
+
return str(value)
|
124
|
+
except Exception as e:
|
125
|
+
return "Error"
|
126
|
+
|
127
|
+
def _is_us_market_open(self):
|
128
|
+
|
129
|
+
taiwan_dt = datetime.now(pytz.timezone('Asia/Taipei'))
|
130
|
+
|
131
|
+
# 轉成美東時間(會自動處理夏令時間)
|
132
|
+
eastern = pytz.timezone('US/Eastern')
|
133
|
+
us_dt = taiwan_dt.astimezone(eastern)
|
134
|
+
|
135
|
+
# 假日判斷
|
136
|
+
us_holidays = holidays.NYSE(years=us_dt.year)
|
137
|
+
if us_dt.date() in us_holidays:
|
138
|
+
return False
|
139
|
+
|
140
|
+
# 週末
|
141
|
+
if us_dt.weekday() >= 5: # 5 = Saturday, 6 = Sunday
|
142
|
+
return False
|
143
|
+
|
144
|
+
# 判斷是否在開盤時間
|
145
|
+
market_open = us_dt.replace(hour=9, minute=30, second=0, microsecond=0)
|
146
|
+
market_close = us_dt.replace(hour=16, minute=0, second=0, microsecond=0)
|
147
|
+
|
148
|
+
return market_open <= us_dt <= market_close
|
149
|
+
|
150
|
+
def _process_us_format(self, fetched_data):
|
151
|
+
"""
|
152
|
+
主要用於report generator
|
153
|
+
"""
|
154
|
+
table_dict = {}
|
155
|
+
for data in fetched_data:
|
156
|
+
year, season, balance_sheet = data['year'], data['season'], data[
|
157
|
+
'balance_sheet']
|
158
|
+
time_index = f"{year}Q{season}"
|
159
|
+
|
160
|
+
table_dict[time_index] = balance_sheet
|
161
|
+
|
162
|
+
stats_df = pd.DataFrame.from_dict(table_dict)
|
163
|
+
return stats_df.T
|
164
|
+
|
165
|
+
|
166
|
+
class FinanceOverviewProcessor(StatsProcessor):
|
167
|
+
|
168
|
+
@classmethod
|
169
|
+
def process_rate(cls, finance_dict):
|
170
|
+
for key in finance_dict:
|
171
|
+
if ('YoY' in key):
|
172
|
+
finance_dict[key] = StatsProcessor.cal_percentage(
|
173
|
+
finance_dict[key]
|
174
|
+
)
|
175
|
+
elif ("rate" in key or 'ratio' in key):
|
176
|
+
finance_dict[key] = StatsProcessor.cal_non_percentage(
|
177
|
+
finance_dict[key], to_str=True, postfix='%'
|
178
|
+
)
|
179
|
+
else:
|
180
|
+
finance_dict[key] = StatsProcessor.cal_non_percentage(
|
181
|
+
finance_dict[key]
|
182
|
+
)
|
183
|
+
|
184
|
+
@classmethod
|
185
|
+
def process_thousand_dollar(cls, finance_dict):
|
186
|
+
process_index = [
|
187
|
+
"revenue", "gross_profit", "operating_income", "net_income",
|
188
|
+
"operating_cash_flow", "invest_cash_flow", "financing_cash_flow",
|
189
|
+
"fcf", 'current_assets', 'current_liabilities',
|
190
|
+
'non_current_assets', 'non_current_liabilities', 'total_assets',
|
191
|
+
"total_liabilities", "equity"
|
192
|
+
]
|
193
|
+
|
194
|
+
for index in process_index:
|
195
|
+
try:
|
196
|
+
finance_dict[index] = StatsProcessor.cal_non_percentage(
|
197
|
+
finance_dict[index], postfix="千元"
|
198
|
+
)
|
199
|
+
except Exception as e:
|
200
|
+
finance_dict[index] = None
|
201
|
+
|
202
|
+
@classmethod
|
203
|
+
def process_all(cls, finance_dict):
|
204
|
+
methods = [
|
205
|
+
cls.cal_EBIT,
|
206
|
+
cls.cal_share_outstanding,
|
207
|
+
cls.cal_fcf,
|
208
|
+
cls.cal_interest_bearing_debt,
|
209
|
+
cls.cal_revenue_per_share,
|
210
|
+
cls.cal_gross_per_share,
|
211
|
+
cls.cal_operating_income_per_share,
|
212
|
+
cls.cal_operating_cash_flow_per_share,
|
213
|
+
cls.fcf_per_share,
|
214
|
+
cls.cal_roa,
|
215
|
+
cls.cal_roe,
|
216
|
+
cls.cal_gross_over_asset,
|
217
|
+
cls.cal_roce,
|
218
|
+
cls.cal_gross_profit_marginal,
|
219
|
+
cls.cal_operation_profit_rate,
|
220
|
+
cls.cal_operating_cash_flow_profit_rate,
|
221
|
+
cls.cal_dso,
|
222
|
+
cls.cal_account_receive_over_revenue,
|
223
|
+
cls.cal_dpo,
|
224
|
+
cls.cal_inventories_cycle_ratio,
|
225
|
+
cls.cal_dio,
|
226
|
+
cls.cal_inventories_revenue_ratio,
|
227
|
+
cls.cal_cash_of_conversion_cycle,
|
228
|
+
cls.cal_asset_turnover,
|
229
|
+
cls.cal_application_turnover,
|
230
|
+
cls.cal_current_ratio,
|
231
|
+
cls.cal_quick_ratio,
|
232
|
+
cls.cal_debt_to_equity_ratio,
|
233
|
+
cls.cal_net_debt_to_equity_ratio,
|
234
|
+
cls.cal_interest_coverage_ratio,
|
235
|
+
cls.cal_debt_to_operating_cash_flow,
|
236
|
+
cls.cal_debt_to_free_cash_flow,
|
237
|
+
cls.cal_cash_flow_ratio,
|
238
|
+
]
|
239
|
+
|
240
|
+
for method in methods:
|
241
|
+
method(finance_dict)
|
242
|
+
|
243
|
+
@classmethod
|
244
|
+
def cal_EBIT(cls, finance_dict):
|
245
|
+
"""
|
246
|
+
計算EBIT
|
247
|
+
EBIT = 營業收入 - 營業成本 - 營業費用 - 所得稅費用
|
248
|
+
"""
|
249
|
+
try:
|
250
|
+
EBIT = (
|
251
|
+
finance_dict['revenue'] - finance_dict['operating_cost'] -
|
252
|
+
finance_dict['operating_expenses'] - finance_dict['tax_fee']
|
253
|
+
)
|
254
|
+
finance_dict['EBIT'] = StatsProcessor.cal_non_percentage(EBIT)
|
255
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
256
|
+
finance_dict['EBIT'] = None
|
257
|
+
# print(f"Error calculating EBIT: {e}")
|
258
|
+
|
259
|
+
@classmethod
|
260
|
+
def cal_fcf(cls, finance_dict):
|
261
|
+
"""
|
262
|
+
計算自由現金流(FCF):
|
263
|
+
自由現金流 = 營業現金流 + 投資現金流
|
264
|
+
"""
|
265
|
+
try:
|
266
|
+
fcf = (
|
267
|
+
finance_dict["operating_cash_flow"] +
|
268
|
+
finance_dict["financing_cash_flow"]
|
269
|
+
)
|
270
|
+
finance_dict["fcf"] = StatsProcessor.cal_non_percentage(fcf)
|
271
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
272
|
+
finance_dict['fcf'] = None
|
273
|
+
# print(f"Error calculating FCF: {e}")
|
274
|
+
|
275
|
+
@classmethod
|
276
|
+
def cal_interest_bearing_debt(cls, finance_dict):
|
277
|
+
"""
|
278
|
+
計算有息負債
|
279
|
+
短期借款+長期借款
|
280
|
+
"""
|
281
|
+
finance_dict['interest_bearing_debt'] = 0.0
|
282
|
+
|
283
|
+
try:
|
284
|
+
finance_dict['interest_bearing_debt'] += finance_dict[
|
285
|
+
'short_term_liabilities']
|
286
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
287
|
+
finance_dict['interest_bearing_debt'] += 0.0
|
288
|
+
try:
|
289
|
+
finance_dict['interest_bearing_debt'] += finance_dict[
|
290
|
+
'long_term_liabilities']
|
291
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
292
|
+
finance_dict['interest_bearing_debt'] += 0.0
|
293
|
+
|
294
|
+
@classmethod
|
295
|
+
def cal_share_outstanding(cls, finance_dict):
|
296
|
+
"""
|
297
|
+
計算流通股數
|
298
|
+
流通股數 = 本期淨利 ÷ 基本每股盈餘
|
299
|
+
"""
|
300
|
+
try:
|
301
|
+
finance_dict["share_outstanding"] = (
|
302
|
+
finance_dict['net_income'] / finance_dict['eps']
|
303
|
+
)
|
304
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
305
|
+
finance_dict['share_outstanding'] = None
|
306
|
+
# print(f"share_outstanding failed because of {str(e)}")
|
307
|
+
|
308
|
+
@classmethod
|
309
|
+
def cal_revenue_per_share(cls, finance_dict):
|
310
|
+
"""
|
311
|
+
計算每股營收
|
312
|
+
每股營收 = 營業收入 / 在外流通股數
|
313
|
+
"""
|
314
|
+
try:
|
315
|
+
revenue_per_share = (
|
316
|
+
finance_dict['revenue'] / finance_dict['share_outstanding']
|
317
|
+
)
|
318
|
+
finance_dict['revenue_per_share'
|
319
|
+
] = StatsProcessor.cal_non_percentage(
|
320
|
+
revenue_per_share, False
|
321
|
+
)
|
322
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
323
|
+
finance_dict['revenue_per_share'] = None
|
324
|
+
# print(f"revenue_per_share failed because of {str(e)}")
|
325
|
+
|
326
|
+
@classmethod
|
327
|
+
def cal_gross_per_share(cls, finance_dict):
|
328
|
+
"""
|
329
|
+
計算每股毛利
|
330
|
+
= (當期營業毛利)÷(當期在外流通股數)
|
331
|
+
"""
|
332
|
+
if ('gross_profit' not in finance_dict.keys()):
|
333
|
+
try:
|
334
|
+
finance_dict['gross_profit'] = (
|
335
|
+
finance_dict['revenue'] - finance_dict['operating_cost']
|
336
|
+
)
|
337
|
+
except:
|
338
|
+
finance_dict['gross_profit'] = None
|
339
|
+
try:
|
340
|
+
gross_per_share = (
|
341
|
+
finance_dict['gross_profit'] / finance_dict['share_outstanding']
|
342
|
+
)
|
343
|
+
finance_dict['gross_per_share'] = StatsProcessor.cal_non_percentage(
|
344
|
+
gross_per_share, False
|
345
|
+
)
|
346
|
+
|
347
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
348
|
+
finance_dict['gross_per_share'] = None
|
349
|
+
# print(f"gross_per_share failed because of {str(e)}")
|
350
|
+
|
351
|
+
@classmethod
|
352
|
+
def cal_operating_income_per_share(cls, finance_dict):
|
353
|
+
"""
|
354
|
+
計算每股營業利益
|
355
|
+
每股營業利益= (當期營業利益)÷(當期在外流通股數)
|
356
|
+
"""
|
357
|
+
try:
|
358
|
+
operating_income_per_share = (
|
359
|
+
finance_dict['operating_income'] /
|
360
|
+
finance_dict['share_outstanding']
|
361
|
+
)
|
362
|
+
finance_dict['operating_income_per_share'
|
363
|
+
] = StatsProcessor.cal_non_percentage(
|
364
|
+
operating_income_per_share
|
365
|
+
)
|
366
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
367
|
+
finance_dict['operating_income_per_share'] = None
|
368
|
+
# print(f"operating_income_per_share failed because of {str(e)}")
|
369
|
+
|
370
|
+
@classmethod
|
371
|
+
def cal_operating_cash_flow_per_share(cls, finance_dict):
|
372
|
+
"""
|
373
|
+
計算每股營業現金流
|
374
|
+
= (當期營業現金流) ÷(當期在外流通股數)
|
375
|
+
"""
|
376
|
+
try:
|
377
|
+
operating_cash_flow_per_share = (
|
378
|
+
finance_dict["operating_cash_flow"] /
|
379
|
+
finance_dict['share_outstanding']
|
380
|
+
)
|
381
|
+
finance_dict["operating_cash_flow_per_share"
|
382
|
+
] = StatsProcessor.cal_non_percentage(
|
383
|
+
operating_cash_flow_per_share
|
384
|
+
)
|
385
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
386
|
+
finance_dict['operating_cash_flow_per_share'] = None
|
387
|
+
# print(f'operating_cash_flow_per_share because of {str(e)}')
|
388
|
+
|
389
|
+
@classmethod
|
390
|
+
def fcf_per_share(cls, finance_dict):
|
391
|
+
"""
|
392
|
+
計算每股自由現金流
|
393
|
+
每股自由現金流 = (當期自由現金流) ÷(當期在外流通股數)
|
394
|
+
"""
|
395
|
+
try:
|
396
|
+
fcf_per_share = (
|
397
|
+
finance_dict['fcf'] / finance_dict['share_outstanding']
|
398
|
+
)
|
399
|
+
finance_dict['fcf_per_share'] = StatsProcessor.cal_non_percentage(
|
400
|
+
fcf_per_share
|
401
|
+
)
|
402
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
403
|
+
finance_dict['fcf_per_share'] = None
|
404
|
+
# print(f"fcf_per_share failed because of {str(e)}")
|
405
|
+
|
406
|
+
# 盈利能力
|
407
|
+
|
408
|
+
@classmethod
|
409
|
+
def cal_roa(cls, finance_dict):
|
410
|
+
"""
|
411
|
+
計算資產報酬率(ROA)
|
412
|
+
ROA = [ 本期淨利 + 利息費用 × (1-有效稅率) ] ÷(資產總額)
|
413
|
+
"""
|
414
|
+
try:
|
415
|
+
roa = (
|
416
|
+
finance_dict['net_income'] + finance_dict['interest'] +
|
417
|
+
(1 * 0.1) # 有效稅率需要改,這裡先設0.1
|
418
|
+
) / finance_dict['inventories']
|
419
|
+
|
420
|
+
finance_dict["roa"] = StatsProcessor.cal_percentage(roa)
|
421
|
+
except Exception as e:
|
422
|
+
finance_dict["roa"] = None
|
423
|
+
|
424
|
+
@classmethod
|
425
|
+
def cal_roe(cls, finance_dict):
|
426
|
+
"""
|
427
|
+
計算股東權益報酬率(ROE)
|
428
|
+
ROE = (本期淨利) ÷(權益總額)
|
429
|
+
"""
|
430
|
+
try:
|
431
|
+
roe = (finance_dict['net_income'] / finance_dict['equity'])
|
432
|
+
finance_dict['roe'] = StatsProcessor.cal_percentage(roe)
|
433
|
+
except Exception as e:
|
434
|
+
finance_dict['roe'] = None
|
435
|
+
|
436
|
+
@classmethod
|
437
|
+
def cal_gross_over_asset(cls, finance_dict):
|
438
|
+
"""
|
439
|
+
計算營業毛利/總資產
|
440
|
+
"""
|
441
|
+
try:
|
442
|
+
gross_over_asset = (
|
443
|
+
finance_dict['gross_profit'] / finance_dict['total_assets']
|
444
|
+
)
|
445
|
+
finance_dict['gross_over_asset'] = StatsProcessor.cal_percentage(
|
446
|
+
gross_over_asset
|
447
|
+
)
|
448
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
449
|
+
finance_dict['gross_over_asset'] = None
|
450
|
+
# print(f"營業毛利/總資產 failed because of {str(e)}")
|
451
|
+
|
452
|
+
@classmethod
|
453
|
+
def cal_roce(cls, finance_dict):
|
454
|
+
"""
|
455
|
+
計算資本運用報酬率(ROCE):
|
456
|
+
ROCE = (稅前淨利+利息費用) / (資產總額-流動負債)
|
457
|
+
"""
|
458
|
+
try:
|
459
|
+
roce = (
|
460
|
+
(
|
461
|
+
finance_dict['net_income_before_tax'] +
|
462
|
+
finance_dict['interest']
|
463
|
+
) / (
|
464
|
+
finance_dict['total_assets'] -
|
465
|
+
finance_dict['current_liabilities']
|
466
|
+
)
|
467
|
+
)
|
468
|
+
finance_dict['roce'] = StatsProcessor.cal_percentage(roce)
|
469
|
+
|
470
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
471
|
+
finance_dict['roce'] = None
|
472
|
+
# print(f"ROCE failed because of {str(e)}")
|
473
|
+
|
474
|
+
@classmethod
|
475
|
+
def cal_gross_profit_marginal(cls, finance_dict):
|
476
|
+
"""
|
477
|
+
計算營業毛利率(gross profit margin)
|
478
|
+
營業毛利率 = 營業毛利 ÷ 營業收入
|
479
|
+
"""
|
480
|
+
try:
|
481
|
+
gross_profit_margin = (
|
482
|
+
finance_dict['gross_profit'] / finance_dict['revenue']
|
483
|
+
)
|
484
|
+
finance_dict['gross_profit_margin'] = StatsProcessor.cal_percentage(
|
485
|
+
gross_profit_margin
|
486
|
+
)
|
487
|
+
except Exception as e:
|
488
|
+
finance_dict['gross_profit_margin'] = None
|
489
|
+
# print(f"gross_profit_margin failed because of {str(e)}")
|
490
|
+
|
491
|
+
@classmethod
|
492
|
+
def cal_operation_profit_rate(cls, finance_dict):
|
493
|
+
"""
|
494
|
+
計算營業利益率
|
495
|
+
營業利益率 = ( 營業收入-營業成本-營業費用)÷ 營業收入
|
496
|
+
"""
|
497
|
+
try:
|
498
|
+
operation_profit_rate = (
|
499
|
+
finance_dict['revenue'] - finance_dict['operating_cost'] -
|
500
|
+
finance_dict['operating_expenses']
|
501
|
+
) / finance_dict['revenue']
|
502
|
+
finance_dict["operation_profit_rate"
|
503
|
+
] = StatsProcessor.cal_percentage(
|
504
|
+
operation_profit_rate
|
505
|
+
)
|
506
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
507
|
+
finance_dict["operation_profit_rate"] = None
|
508
|
+
# print(f"operation_profit failed because of {str(e)}")
|
509
|
+
|
510
|
+
@classmethod
|
511
|
+
def cal_operating_cash_flow_profit_rate(cls, finance_dict):
|
512
|
+
"""
|
513
|
+
計算營業現金流利潤率
|
514
|
+
營業現金流利潤率 = 營業活動現金流 ÷ 營業收入
|
515
|
+
"""
|
516
|
+
try:
|
517
|
+
operating_cash_flow_profit_rate = (
|
518
|
+
finance_dict["operating_cash_flow"] / finance_dict["revenue"]
|
519
|
+
)
|
520
|
+
finance_dict["operating_cash_flow_profit_rate"
|
521
|
+
] = StatsProcessor.cal_percentage(
|
522
|
+
operating_cash_flow_profit_rate
|
523
|
+
)
|
524
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
525
|
+
finance_dict["operating_cash_flow_profit_rate"] = None
|
526
|
+
# print(
|
527
|
+
# f"operating_cash_flow_profit_rate failed because of {str(e)}")
|
528
|
+
|
529
|
+
|
530
|
+
# 成長動能
|
531
|
+
|
532
|
+
"""
|
533
|
+
前四個已經有了 revenue_YoY, gross_prof_YoY, operating_income_YoY, net_income_YoY:
|
534
|
+
後四個在金流,還需要處理
|
535
|
+
"""
|
536
|
+
# 營運指標
|
537
|
+
|
538
|
+
@classmethod
|
539
|
+
def cal_dso(cls, finance_dict):
|
540
|
+
"""
|
541
|
+
計算應收帳款收現天數(DSO)
|
542
|
+
DSO = 365 × (應收帳款平均餘額 ÷ 營業收入)
|
543
|
+
"""
|
544
|
+
try:
|
545
|
+
dso = (
|
546
|
+
365 * (finance_dict['account_pay'] / finance_dict['revenue'])
|
547
|
+
)
|
548
|
+
finance_dict['dso'] = StatsProcessor.cal_non_percentage(
|
549
|
+
dso, to_str=True, postfix="日"
|
550
|
+
)
|
551
|
+
except Exception as e:
|
552
|
+
finance_dict['dso'] = None
|
553
|
+
# print(f"Error calculating 應收帳款收現天數 because of {str(e)}")
|
554
|
+
|
555
|
+
@classmethod
|
556
|
+
def cal_account_receive_over_revenue(cls, finance_dict):
|
557
|
+
"""
|
558
|
+
計算應收帳款佔營收比率
|
559
|
+
= 應收帳款平均餘額 ÷ 營業收入
|
560
|
+
"""
|
561
|
+
try:
|
562
|
+
account_receive_over_revenue = (
|
563
|
+
finance_dict['account_receive'] / finance_dict['revenue']
|
564
|
+
)
|
565
|
+
finance_dict["account_receive_over_revenue"
|
566
|
+
] = StatsProcessor.cal_percentage(
|
567
|
+
account_receive_over_revenue
|
568
|
+
)
|
569
|
+
except Exception as e:
|
570
|
+
finance_dict["account_receive_over_revenue"] = None
|
571
|
+
|
572
|
+
@classmethod
|
573
|
+
def cal_dpo(cls, finance_dict):
|
574
|
+
"""
|
575
|
+
計算應付帳款週轉天數
|
576
|
+
DPO = 365天 ÷ (銷貨成本÷平均應付帳款)
|
577
|
+
"""
|
578
|
+
try:
|
579
|
+
dpo = (
|
580
|
+
365 *
|
581
|
+
(finance_dict['account_pay'] / finance_dict['operating_cost'])
|
582
|
+
)
|
583
|
+
finance_dict["dpo"] = StatsProcessor.cal_non_percentage(
|
584
|
+
dpo, to_str=True, postfix="日"
|
585
|
+
)
|
586
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
587
|
+
finance_dict["dpo"] = None
|
588
|
+
# print(f"應付帳款週轉天數 failed because of {str(e)}")
|
589
|
+
|
590
|
+
@classmethod
|
591
|
+
def cal_inventories_cycle_ratio(cls, finance_dict):
|
592
|
+
"""
|
593
|
+
計算存貨周轉率
|
594
|
+
= 銷貨成本 ÷ 存貨
|
595
|
+
"""
|
596
|
+
try:
|
597
|
+
inventories_cycle_ratio = (
|
598
|
+
finance_dict['operating_cost'] / finance_dict['inventories']
|
599
|
+
)
|
600
|
+
|
601
|
+
finance_dict["inventories_cycle_ratio"
|
602
|
+
] = StatsProcessor.cal_percentage(
|
603
|
+
inventories_cycle_ratio
|
604
|
+
)
|
605
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
606
|
+
finance_dict["inventories_cycle_ratio"] = None
|
607
|
+
# print(f"Error calculating 存貨周轉率 because of {str(e)}")
|
608
|
+
|
609
|
+
@classmethod
|
610
|
+
def cal_dio(cls, finance_dict):
|
611
|
+
"""
|
612
|
+
計算 存貨週轉天數 or 平均售貨天數
|
613
|
+
DIO = 365天 * (存貨 ÷ 銷貨成本)
|
614
|
+
MUDA MUDA MUDA !!!
|
615
|
+
"""
|
616
|
+
try:
|
617
|
+
dio = 365 * (
|
618
|
+
finance_dict["inventories"] / finance_dict["operating_cost"]
|
619
|
+
)
|
620
|
+
finance_dict["dio"] = StatsProcessor.cal_non_percentage(
|
621
|
+
dio, to_str=True, postfix="日"
|
622
|
+
)
|
623
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
624
|
+
finance_dict["dio"] = None
|
625
|
+
# print(f"Error calculating 存貨週轉天數 because of {str(e)}")
|
626
|
+
|
627
|
+
@classmethod
|
628
|
+
def cal_inventories_revenue_ratio(cls, finance_dict):
|
629
|
+
"""
|
630
|
+
計算存貨佔營收比率
|
631
|
+
存貨佔營收比= 存貨 ÷ 營業收入
|
632
|
+
"""
|
633
|
+
try:
|
634
|
+
inventories_revenue_ratio = (
|
635
|
+
finance_dict['inventories'] / finance_dict['revenue']
|
636
|
+
)
|
637
|
+
|
638
|
+
finance_dict["inventories_revenue_ratio"
|
639
|
+
] = StatsProcessor.cal_percentage(
|
640
|
+
inventories_revenue_ratio
|
641
|
+
)
|
642
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
643
|
+
finance_dict["inventories_revenue_ratio"] = None
|
644
|
+
# print(f"Error calculating 存貨佔營收比率 because of {str(e)}")
|
645
|
+
|
646
|
+
@classmethod
|
647
|
+
def cal_cash_of_conversion_cycle(cls, finance_dict):
|
648
|
+
"""
|
649
|
+
計算現金循環週期
|
650
|
+
存貨週轉天數 + 應收帳款週轉天數 - 應付帳款週轉天數
|
651
|
+
"""
|
652
|
+
try:
|
653
|
+
cash_of_conversion_cycle = (
|
654
|
+
finance_dict["dio"] + finance_dict["dso"] - finance_dict['dpo']
|
655
|
+
)
|
656
|
+
finance_dict["cash_of_conversion_cycle"
|
657
|
+
] = StatsProcessor.cal_non_percentage(
|
658
|
+
cash_of_conversion_cycle, to_str=True, postfix="日"
|
659
|
+
)
|
660
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
661
|
+
finance_dict["cash_of_conversion_cycle"] = None
|
662
|
+
|
663
|
+
@classmethod
|
664
|
+
def cal_asset_turnover(cls, finance_dict):
|
665
|
+
"""
|
666
|
+
計算資產周轉率
|
667
|
+
營業收入 ÷ 資產總額
|
668
|
+
"""
|
669
|
+
try:
|
670
|
+
asset_turnover = (
|
671
|
+
finance_dict["revenue"] / finance_dict["inventories"]
|
672
|
+
)
|
673
|
+
finance_dict["asset_turnover"] = StatsProcessor.cal_percentage(
|
674
|
+
asset_turnover
|
675
|
+
)
|
676
|
+
except Exception as e:
|
677
|
+
finance_dict["asset_turnover"] = None
|
678
|
+
|
679
|
+
@classmethod
|
680
|
+
def cal_application_turnover(cls, finance_dict):
|
681
|
+
"""
|
682
|
+
不動產、廠房及設備週轉率
|
683
|
+
營業收入 ÷ 不動產、廠房與設備平均餘額
|
684
|
+
"""
|
685
|
+
try:
|
686
|
+
applcation_turnover = (
|
687
|
+
finance_dict['revenue'] / finance_dict["application"]
|
688
|
+
)
|
689
|
+
finance_dict['application_turnover'
|
690
|
+
] = StatsProcessor.cal_percentage(applcation_turnover)
|
691
|
+
|
692
|
+
except Exception as e:
|
693
|
+
finance_dict['application_turnover'] = None
|
694
|
+
|
695
|
+
@classmethod
|
696
|
+
def cal_current_ratio(cls, finance_dict):
|
697
|
+
"""
|
698
|
+
計算流動比率 = 流動資產 / 流動負債
|
699
|
+
"""
|
700
|
+
try:
|
701
|
+
current_ratio = (
|
702
|
+
finance_dict['current_assets'] /
|
703
|
+
finance_dict['current_liabilities']
|
704
|
+
)
|
705
|
+
finance_dict['current_ratio'] = StatsProcessor.cal_percentage(
|
706
|
+
current_ratio
|
707
|
+
)
|
708
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
709
|
+
finance_dict['current_ratio'] = None
|
710
|
+
# print(f"Error calculating current ratio: {e}")
|
711
|
+
|
712
|
+
@classmethod
|
713
|
+
def cal_quick_ratio(cls, finance_dict):
|
714
|
+
"""
|
715
|
+
速動比率
|
716
|
+
(流動資產 - 存貨) / 流動負債
|
717
|
+
"""
|
718
|
+
try:
|
719
|
+
quick_ratio = (
|
720
|
+
finance_dict['current_assets'] - finance_dict['inventories']
|
721
|
+
) / finance_dict['current_liabilities']
|
722
|
+
finance_dict['quick_ratio'] = StatsProcessor.cal_percentage(
|
723
|
+
quick_ratio
|
724
|
+
)
|
725
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
726
|
+
finance_dict['quick_ratio'] = None
|
727
|
+
# print(f"Error calculating quick ratio: {e}")
|
728
|
+
|
729
|
+
@classmethod
|
730
|
+
def cal_debt_to_equity_ratio(cls, finance_dict):
|
731
|
+
"""
|
732
|
+
# 負債權益比率 = 總負債 / 股東權益
|
733
|
+
"""
|
734
|
+
try:
|
735
|
+
debt_to_equity_ratio = finance_dict['total_liabilities'
|
736
|
+
] / finance_dict['equity']
|
737
|
+
finance_dict['debt_to_equity_ratio'
|
738
|
+
] = StatsProcessor.cal_percentage(
|
739
|
+
debt_to_equity_ratio
|
740
|
+
)
|
741
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
742
|
+
finance_dict['debt_to_equity_ratio'] = None
|
743
|
+
# print(f"Error calculating debt to equity ratio: {e}")
|
744
|
+
|
745
|
+
@classmethod
|
746
|
+
def cal_net_debt_to_equity_ratio(cls, finance_dict):
|
747
|
+
"""
|
748
|
+
# 淨負債權益比率 = (總負債 - 現金及約當現金) / 股東權益
|
749
|
+
"""
|
750
|
+
try:
|
751
|
+
net_debt_to_equity_ratio = (
|
752
|
+
finance_dict['total_liabilities'] -
|
753
|
+
finance_dict['cash_and_cash_equivalents']
|
754
|
+
) / finance_dict['equity']
|
755
|
+
finance_dict['net_debt_to_equity_ratio'
|
756
|
+
] = StatsProcessor.cal_percentage(
|
757
|
+
net_debt_to_equity_ratio
|
758
|
+
)
|
759
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
760
|
+
finance_dict['net_debt_to_equity_ratio'] = None
|
761
|
+
# print(f"Error calculating net debt to equity ratio: {e}")
|
762
|
+
|
763
|
+
@classmethod
|
764
|
+
def cal_interest_coverage_ratio(cls, finance_dict):
|
765
|
+
"""
|
766
|
+
# 利息保障倍數 = EBIT / 利息費用
|
767
|
+
"""
|
768
|
+
try:
|
769
|
+
interest_coverage_ratio = finance_dict['EBIT'] / finance_dict[
|
770
|
+
'interest_expense']
|
771
|
+
finance_dict['interest_coverage_ratio'
|
772
|
+
] = StatsProcessor.cal_non_percentage(
|
773
|
+
interest_coverage_ratio, to_str=True, postfix="倍"
|
774
|
+
)
|
775
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
776
|
+
finance_dict['interest_coverage_ratio'] = None
|
777
|
+
# print(f"Error calculating interest coverage ratio: {e}")
|
778
|
+
|
779
|
+
@classmethod
|
780
|
+
def cal_debt_to_operating_cash_flow(cls, finance_dict):
|
781
|
+
"""
|
782
|
+
有息負債 / 營業活動現金流
|
783
|
+
"""
|
784
|
+
try:
|
785
|
+
debt_to_operating_cash_flow = finance_dict[
|
786
|
+
'interest_bearing_debt'] / finance_dict['operating_cash_flow']
|
787
|
+
finance_dict['debt_to_operating_cash_flow'
|
788
|
+
] = StatsProcessor.cal_percentage(
|
789
|
+
debt_to_operating_cash_flow
|
790
|
+
)
|
791
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
792
|
+
finance_dict['debt_to_operating_cash_flow'] = None
|
793
|
+
# print(f"Error calculating debt to operating cash flow: {e}")
|
794
|
+
|
795
|
+
@classmethod
|
796
|
+
def cal_debt_to_free_cash_flow(cls, finance_dict):
|
797
|
+
"""
|
798
|
+
# 有息負債 / 自由現金流
|
799
|
+
"""
|
800
|
+
try:
|
801
|
+
debt_to_free_cash_flow = finance_dict['interest_bearing_debt'
|
802
|
+
] / finance_dict['fcf']
|
803
|
+
finance_dict['debt_to_free_cash_flow'
|
804
|
+
] = StatsProcessor.cal_percentage(
|
805
|
+
debt_to_free_cash_flow
|
806
|
+
)
|
807
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
808
|
+
finance_dict['debt_to_free_cash_flow'] = None
|
809
|
+
# print(f"Error calculating debt to free cash flow: {e}")
|
810
|
+
|
811
|
+
@classmethod
|
812
|
+
def cal_cash_flow_ratio(cls, finance_dict):
|
813
|
+
"""
|
814
|
+
# 現金流量比率 = 營業活動現金流 / 流動負債
|
815
|
+
"""
|
816
|
+
try:
|
817
|
+
cash_flow_ratio = finance_dict[
|
818
|
+
'operating_cash_flow'] / finance_dict['current_liabilities']
|
819
|
+
finance_dict['cash_flow_ratio'] = StatsProcessor.cal_percentage(
|
820
|
+
cash_flow_ratio
|
821
|
+
)
|
822
|
+
except (KeyError, ZeroDivisionError, TypeError) as e:
|
823
|
+
finance_dict['cash_flow_ratio'] = None
|
824
|
+
# print(f"Error calculating cash flow ratio: {e}")
|