neurostats-API 0.0.25rc1__py3-none-any.whl → 1.0.0__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.
Files changed (107) hide show
  1. neurostats_API/__init__.py +15 -1
  2. neurostats_API/async_mode/__init__.py +13 -0
  3. neurostats_API/async_mode/db/__init__.py +3 -0
  4. neurostats_API/async_mode/db/base.py +24 -0
  5. neurostats_API/async_mode/db/tej.py +10 -0
  6. neurostats_API/async_mode/db/twse.py +8 -0
  7. neurostats_API/async_mode/db/us.py +9 -0
  8. neurostats_API/async_mode/db_extractors/__init__.py +20 -0
  9. neurostats_API/async_mode/db_extractors/base.py +66 -0
  10. neurostats_API/async_mode/db_extractors/daily/__init__.py +7 -0
  11. neurostats_API/async_mode/db_extractors/daily/base.py +89 -0
  12. neurostats_API/async_mode/db_extractors/daily/tej_chip.py +14 -0
  13. neurostats_API/async_mode/db_extractors/daily/tej_tech.py +12 -0
  14. neurostats_API/async_mode/db_extractors/daily/twse_chip.py +49 -0
  15. neurostats_API/async_mode/db_extractors/daily/value.py +93 -0
  16. neurostats_API/async_mode/db_extractors/daily/yf.py +12 -0
  17. neurostats_API/async_mode/db_extractors/month_revenue/__init__.py +1 -0
  18. neurostats_API/async_mode/db_extractors/month_revenue/base.py +140 -0
  19. neurostats_API/async_mode/db_extractors/month_revenue/twse.py +5 -0
  20. neurostats_API/async_mode/db_extractors/seasonal/__init__.py +4 -0
  21. neurostats_API/async_mode/db_extractors/seasonal/balance_sheet.py +19 -0
  22. neurostats_API/async_mode/db_extractors/seasonal/base.py +152 -0
  23. neurostats_API/async_mode/db_extractors/seasonal/cashflow.py +10 -0
  24. neurostats_API/async_mode/db_extractors/seasonal/profit_lose.py +17 -0
  25. neurostats_API/async_mode/db_extractors/seasonal/tej.py +87 -0
  26. neurostats_API/async_mode/factory/__init__.py +1 -0
  27. neurostats_API/async_mode/factory/extractor_factory.py +168 -0
  28. neurostats_API/async_mode/factory/transformer_factory.py +164 -0
  29. neurostats_API/async_mode/fetchers/__init__.py +10 -0
  30. neurostats_API/async_mode/fetchers/balance_sheet.py +31 -0
  31. neurostats_API/async_mode/fetchers/base.py +48 -0
  32. neurostats_API/async_mode/fetchers/cash_flow.py +56 -0
  33. neurostats_API/async_mode/fetchers/finance_overview.py +134 -0
  34. neurostats_API/async_mode/fetchers/month_revenue.py +35 -0
  35. neurostats_API/async_mode/fetchers/profit_lose.py +46 -0
  36. neurostats_API/async_mode/fetchers/tech.py +205 -0
  37. neurostats_API/async_mode/fetchers/tej/__init__.py +2 -0
  38. neurostats_API/async_mode/fetchers/tej/seasonal_data.py +88 -0
  39. neurostats_API/async_mode/fetchers/tej/tech.py +34 -0
  40. neurostats_API/async_mode/fetchers/twse_institution.py +62 -0
  41. neurostats_API/async_mode/fetchers/twse_margin.py +100 -0
  42. neurostats_API/async_mode/fetchers/value.py +76 -0
  43. neurostats_API/config/company_list/ticker_index_industry_map.json +7946 -0
  44. neurostats_API/config/company_list/us.json +9986 -0
  45. neurostats_API/{tools → config}/tej_db/tej_db_skip_index.yaml +0 -2
  46. neurostats_API/{tools → config}/twse/profit_lose.yaml +0 -6
  47. neurostats_API/fetchers/finance_overview.py +22 -2
  48. neurostats_API/transformers/__init__.py +40 -0
  49. neurostats_API/transformers/balance_sheet/__init__.py +2 -0
  50. neurostats_API/transformers/balance_sheet/base.py +51 -0
  51. neurostats_API/transformers/balance_sheet/twse.py +81 -0
  52. neurostats_API/transformers/balance_sheet/us.py +38 -0
  53. neurostats_API/transformers/base.py +158 -0
  54. neurostats_API/transformers/cash_flow/__init__.py +2 -0
  55. neurostats_API/transformers/cash_flow/base.py +114 -0
  56. neurostats_API/transformers/cash_flow/twse.py +70 -0
  57. neurostats_API/transformers/cash_flow/us.py +38 -0
  58. neurostats_API/transformers/daily_chip/__init__.py +1 -0
  59. neurostats_API/transformers/daily_chip/base.py +5 -0
  60. neurostats_API/transformers/daily_chip/tej.py +0 -0
  61. neurostats_API/transformers/daily_chip/twse_chip.py +415 -0
  62. neurostats_API/transformers/daily_chip/utils/__init__.py +0 -0
  63. neurostats_API/transformers/daily_chip/utils/institution.py +90 -0
  64. neurostats_API/transformers/daily_chip/utils/margin_trading.py +2 -0
  65. neurostats_API/transformers/daily_chip/utils/security_lending.py +0 -0
  66. neurostats_API/transformers/daily_tech/__init__.py +1 -0
  67. neurostats_API/transformers/daily_tech/base.py +5 -0
  68. neurostats_API/transformers/daily_tech/tech.py +84 -0
  69. neurostats_API/transformers/daily_tech/utils/__init__.py +1 -0
  70. neurostats_API/transformers/daily_tech/utils/processor.py +251 -0
  71. neurostats_API/transformers/finance_overview/__init__.py +2 -0
  72. neurostats_API/transformers/finance_overview/agent_overview.py +55 -0
  73. neurostats_API/transformers/finance_overview/base.py +824 -0
  74. neurostats_API/transformers/finance_overview/stats_overview.py +79 -0
  75. neurostats_API/transformers/month_revenue/__init__.py +1 -0
  76. neurostats_API/transformers/month_revenue/base.py +60 -0
  77. neurostats_API/transformers/month_revenue/twse.py +134 -0
  78. neurostats_API/transformers/profit_lose/__init__.py +2 -0
  79. neurostats_API/transformers/profit_lose/base.py +82 -0
  80. neurostats_API/transformers/profit_lose/twse.py +133 -0
  81. neurostats_API/transformers/profit_lose/us.py +26 -0
  82. neurostats_API/transformers/tej/__init__.py +1 -0
  83. neurostats_API/transformers/tej/base.py +149 -0
  84. neurostats_API/transformers/tej/finance_statement.py +80 -0
  85. neurostats_API/transformers/value/__init__.py +1 -0
  86. neurostats_API/transformers/value/base.py +5 -0
  87. neurostats_API/transformers/value/tej.py +8 -0
  88. neurostats_API/transformers/value/twse.py +48 -0
  89. neurostats_API/utils/__init__.py +1 -1
  90. neurostats_API/utils/data_process.py +10 -6
  91. neurostats_API/utils/exception.py +8 -0
  92. neurostats_API/utils/logger.py +21 -0
  93. neurostats_API-1.0.0.dist-info/METADATA +102 -0
  94. neurostats_API-1.0.0.dist-info/RECORD +121 -0
  95. neurostats_API-0.0.25rc1.dist-info/METADATA +0 -858
  96. neurostats_API-0.0.25rc1.dist-info/RECORD +0 -36
  97. /neurostats_API/{tools → config}/company_list/tw.json +0 -0
  98. /neurostats_API/{tools → config}/company_list/us_TradingView_list.json +0 -0
  99. /neurostats_API/{tools → config}/tej_db/tej_db_index.yaml +0 -0
  100. /neurostats_API/{tools → config}/tej_db/tej_db_percent_index.yaml +0 -0
  101. /neurostats_API/{tools → config}/tej_db/tej_db_thousand_index.yaml +0 -0
  102. /neurostats_API/{tools → config}/twse/balance_sheet.yaml +0 -0
  103. /neurostats_API/{tools → config}/twse/cash_flow_percentage.yaml +0 -0
  104. /neurostats_API/{tools → config}/twse/finance_overview_dict.yaml +0 -0
  105. /neurostats_API/{tools → config}/twse/seasonal_data_field_dict.txt +0 -0
  106. {neurostats_API-0.0.25rc1.dist-info → neurostats_API-1.0.0.dist-info}/WHEEL +0 -0
  107. {neurostats_API-0.0.25rc1.dist-info → neurostats_API-1.0.0.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}")