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
@@ -8,7 +8,6 @@ TWN/AINVFQ1:
8
8
  - curr
9
9
  - annd
10
10
  - fin_ind
11
- - eps
12
11
  - r307
13
12
  - r305
14
13
  - r306
@@ -26,7 +25,6 @@ TWN/AFESTM1:
26
25
  - curr
27
26
  - annd
28
27
  - fin_ind
29
- - eps
30
28
  - r307
31
29
  - r305
32
30
  - r306
@@ -1,9 +1,3 @@
1
- profit_lose: # 總營收表
2
- mode: value_and_percentage
3
-
4
- grand_total_profit_lose:
5
- mode: grand_total_values
6
-
7
1
  revenue:
8
2
  mode: growth
9
3
  target_index:
@@ -808,6 +808,7 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
808
808
  self.us_company_list = StatsProcessor.load_json("company_list/us_TradingView_list.json")
809
809
 
810
810
  if (ticker not in self.us_company_list.keys()):
811
+ # 目前只支援美股
811
812
  raise NoCompanyError("class \"AgentFinanceOverviewFetcher\" only supports US company now")
812
813
 
813
814
  db_name = "company" if self.ticker in self.tw_company_list else "company_us"
@@ -835,7 +836,6 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
835
836
  self.seasonal_collection_name]
836
837
 
837
838
  def query_data(self, date=None):
838
-
839
839
  query_set = self._prepare_query(date)
840
840
  if (date is None):
841
841
  str_date = datetime.today().strftime("%Y-%m-%d")
@@ -948,6 +948,9 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
948
948
  return "Error"
949
949
 
950
950
  def _get_latest_average_volume(self, daily_data, length=30):
951
+ """
952
+ 計算平均成交量
953
+ """
951
954
  try:
952
955
  daily_data = pd.DataFrame(daily_data).tail(length)
953
956
 
@@ -960,6 +963,10 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
960
963
  return "Error"
961
964
 
962
965
  def _get_market_capitalization(self, daily_data, seasonal_data):
966
+ """
967
+ 計算市值
968
+ 市值 = 收盤價 * 流通股數
969
+ """
963
970
  try:
964
971
  latest_close = float(self._get_latest_close(daily_data))
965
972
  latest_common_share = seasonal_data[-1][
@@ -973,6 +980,10 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
973
980
  return "Error"
974
981
 
975
982
  def _get_PE(self, daily_data, TTM_data):
983
+ """
984
+ 本益比
985
+ 本益比 = 收盤價 / 稀釋EPS(TTM)
986
+ """
976
987
  try:
977
988
  latest_close = float(self._get_latest_close(daily_data))
978
989
  TTM_EPS = TTM_data['Diluted EPS']
@@ -985,6 +996,10 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
985
996
  return "Error"
986
997
 
987
998
  def _get_PS(self, daily_data, TTM_data, seasonal_data):
999
+ """
1000
+ 股價營收比:
1001
+ 本益比 = 市值 / 總營收(TTM)
1002
+ """
988
1003
  try:
989
1004
  market_capitalzation = self._get_market_capitalization(daily_data, seasonal_data)
990
1005
  market_capitalzation = float(market_capitalzation)
@@ -997,7 +1012,9 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
997
1012
  return "Error"
998
1013
 
999
1014
  def _is_us_market_open(self):
1000
-
1015
+ """
1016
+ 判斷目前美股是否開市
1017
+ """
1001
1018
  taiwan_dt = datetime.now(pytz.timezone('Asia/Taipei'))
1002
1019
 
1003
1020
  # 轉成美東時間(會自動處理夏令時間)
@@ -1020,6 +1037,9 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
1020
1037
  return market_open <= us_dt <= market_close
1021
1038
 
1022
1039
  def _get_category(self):
1040
+ """
1041
+ 判斷公司類別,透過檔案
1042
+ """
1023
1043
  category = self.us_company_list.get(self.ticker,{}).get('en_industry')
1024
1044
 
1025
1045
  if (category):
@@ -0,0 +1,40 @@
1
+ from .balance_sheet import (
2
+ TWSEBalanceSheetTransformer,
3
+ USBalanceSheetTransformer
4
+ )
5
+
6
+ from .cash_flow import (
7
+ TWSECashFlowTransformer,
8
+ USCashFlowTransformer
9
+ )
10
+
11
+ from .daily_tech import(
12
+ DailyTechTransformer
13
+ )
14
+
15
+ from .daily_chip import(
16
+ TWSEChipTransformer
17
+ )
18
+
19
+ from .month_revenue import(
20
+ TWSEMonthlyRevenueTransformer
21
+ )
22
+
23
+ from .profit_lose import (
24
+ TWSEProfitLoseTransformer,
25
+ USProfitLoseTransformer
26
+ )
27
+
28
+ from .tej import (
29
+ TEJFinanceStatementTransformer
30
+ )
31
+
32
+ from .value import (
33
+ TWSEAnnualValueTransformer,
34
+ TWSEHistoryValueTransformer
35
+ )
36
+
37
+ from .finance_overview import (
38
+ AgentOverviewTransformer,
39
+ FinanceOverviewTransformer
40
+ )
@@ -0,0 +1,2 @@
1
+ from .twse import TWSEBalanceSheetTransformer
2
+ from .us import USBalanceSheetTransformer
@@ -0,0 +1,51 @@
1
+ from ..base import BaseTransformer
2
+ import pandas as pd
3
+
4
+ class BaseBalanceSheetTransformer(BaseTransformer):
5
+ def __init__(self, ticker, company_name, zone):
6
+ super().__init__(ticker, company_name, zone)
7
+
8
+ def _process_twse_to_stats_format(self, fetched_data):
9
+ table_dict = {}
10
+ for data in fetched_data:
11
+ year, season, balance_sheet = data['year'], data['season'], data['balance_sheet']
12
+
13
+ time_index = f"{year}Q{season}"
14
+ table_dict[time_index] = balance_sheet
15
+
16
+ stats_df = pd.DataFrame(table_dict)
17
+
18
+ return stats_df
19
+
20
+ def _process_twse_to_tej_format(self, fetched_data):
21
+ """
22
+ new_df 迎合 TEJ格式, 用於 report_generator
23
+ """
24
+ table_dict = {}
25
+ for data in fetched_data:
26
+ year, season, balance_sheet = data['year'], data['season'], data['balance_sheet']
27
+ time_index = f"{year}Q{season}"
28
+
29
+ new_balance_sheet = self.flatten_dict(
30
+ balance_sheet,
31
+ target_keys=["value", "percentage"]
32
+ )
33
+
34
+ table_dict[time_index] = new_balance_sheet
35
+
36
+ stats_df = pd.DataFrame.from_dict(table_dict)
37
+ return stats_df.T
38
+
39
+ def _process_us_format(self, fetched_data):
40
+ """
41
+ 主要用於report generator
42
+ """
43
+ table_dict = {}
44
+ for data in fetched_data:
45
+ year, season, balance_sheet = data['year'], data['season'], data['balance_sheet']
46
+ time_index = f"{year}Q{season}"
47
+
48
+ table_dict[time_index] = balance_sheet
49
+
50
+ stats_df = pd.DataFrame.from_dict(table_dict)
51
+ return stats_df.T
@@ -0,0 +1,81 @@
1
+ from .base import BaseBalanceSheetTransformer
2
+ from neurostats_API.utils import StatsProcessor
3
+
4
+
5
+ class TWSEBalanceSheetTransformer(BaseBalanceSheetTransformer):
6
+
7
+ def __init__(self, ticker, company_name, zone):
8
+ super().__init__(ticker, company_name, zone)
9
+
10
+ self.table_settings = StatsProcessor.load_yaml(
11
+ "twse/balance_sheet.yaml"
12
+ )
13
+ self.return_keys = [
14
+ 'balance_sheet', 'total_asset', 'current_asset',
15
+ 'non_current_asset', 'current_debt', 'non_current_debt', 'equity',
16
+ 'balance_sheet_all', 'balance_sheet_YoY'
17
+ ]
18
+
19
+ self.stats_df = None
20
+ self.new_df = None
21
+
22
+ def process_transform(self, fetched_data):
23
+ if (not fetched_data):
24
+ return self._get_empty_structure()
25
+
26
+ processed_data = self._process_fn(fetched_data)
27
+
28
+ return processed_data
29
+
30
+ def _process_fn(self, fetched_data):
31
+
32
+ return_dict = {"ticker": self.ticker, "company_name": self.company_name}
33
+
34
+ target_season = fetched_data[0]['season']
35
+
36
+ self.stats_df = self._process_twse_to_stats_format(fetched_data)
37
+ self.stats_df = self._slice_target_season(self.stats_df, target_season)
38
+
39
+ # 轉換格式 (元 / 千元 / %)
40
+ self.stats_df = StatsProcessor.expand_value_percentage(self.stats_df)
41
+ self.stats_df = self._apply_process_unit_pipeline(
42
+ self.stats_df, postfix_list=['_value', "_percentage"]
43
+ )
44
+
45
+ self.new_df = self._process_twse_to_tej_format(fetched_data)
46
+ # 轉換格式 (元 / 千元 / %)
47
+ self.new_df = self._apply_process_unit_pipeline(
48
+ self.new_df, postfix_list=['_value', "_percentage"]
49
+ )
50
+
51
+ self.new_dict = self.new_df.to_dict()
52
+ self.new_df = self._cal_QoQ(self.new_dict)
53
+ self.new_df = self.new_df.T
54
+
55
+ total_table_YoY = self._slice_target_season(
56
+ self.new_df, target_season
57
+ )
58
+
59
+ return_dict.update(
60
+ {
61
+ "balance_sheet": self.stats_df,
62
+ "balance_sheet_all": self.new_df,
63
+ "balance_sheet_YoY": total_table_YoY
64
+ }
65
+ )
66
+ # 抽取出小欄位整合
67
+ self._process_target_columns(return_dict, self.stats_df)
68
+
69
+ return return_dict
70
+
71
+ def _process_target_columns(self, return_dict, stats_df):
72
+ for name, setting in self.table_settings.items():
73
+ target_indexes = setting.get('target_index', [None])
74
+ for target_index in target_indexes:
75
+ try:
76
+ return_dict[name] = StatsProcessor.slice_old_table(
77
+ total_table=stats_df, target_index=target_index
78
+ )
79
+ break
80
+ except Exception as e:
81
+ continue
@@ -0,0 +1,38 @@
1
+ from .base import BaseBalanceSheetTransformer
2
+ from neurostats_API.utils import StatsProcessor
3
+
4
+
5
+ class USBalanceSheetTransformer(BaseBalanceSheetTransformer):
6
+
7
+ def __init__(self, ticker, company_name, zone):
8
+ super().__init__(ticker, company_name, zone)
9
+
10
+ self.data_df = None
11
+ self.return_keys = [
12
+ 'balance_sheet',
13
+ 'balance_sheet_YoY'
14
+ ]
15
+
16
+ def process_transform(self, fetched_data):
17
+ if (not fetched_data):
18
+ return self._get_empty_structure()
19
+
20
+ return_dict = {"ticker": self.ticker, "company_name": self.company_name}
21
+
22
+ # QoQ表格
23
+ self.data_df = self._process_us_format(fetched_data)
24
+
25
+ # YoY表格
26
+ target_season = fetched_data[0]['season']
27
+ total_table_YoY = self._slice_target_season(
28
+ self.data_df.T, target_season
29
+ )
30
+
31
+ return_dict.update(
32
+ {
33
+ 'balance_sheet': self.data_df,
34
+ "balance_sheet_YoY": total_table_YoY
35
+ }
36
+ )
37
+
38
+ return return_dict
@@ -0,0 +1,158 @@
1
+ import abc
2
+ from neurostats_API.utils import StatsProcessor, YoY_Calculator
3
+ import pandas as pd
4
+
5
+
6
+ class BaseTransformer(abc.ABC):
7
+ """
8
+ Transformer用途: 轉換資料為fetcher可使用的格式
9
+ """
10
+
11
+ def __init__(self, ticker, company_name, zone):
12
+ self.ticker = ticker
13
+ self.company_name = company_name
14
+
15
+ self.zone = zone
16
+ self.return_keys = []
17
+
18
+ @abc.abstractmethod
19
+ def process_transform(self):
20
+ pass
21
+
22
+ @classmethod
23
+ def flatten_dict(cls, value_dict, target_keys):
24
+ indexes = value_dict.keys()
25
+ new_dict = {}
26
+
27
+ for key in indexes:
28
+ new_dict.update(
29
+ {
30
+ f"{key}_{sub_key}": value_dict[key].get(sub_key, None)
31
+ for sub_key in target_keys
32
+ }
33
+ )
34
+
35
+ return new_dict
36
+
37
+ @staticmethod
38
+ def _process_unit(data_df, postfix):
39
+
40
+ lambda_map = {
41
+ "_value":
42
+ lambda x: StatsProcessor.cal_non_percentage(x, postfix="千元"),
43
+ "_percentage":
44
+ lambda x: StatsProcessor.
45
+ cal_non_percentage(x, to_str=True, postfix="%"),
46
+ '_growth':
47
+ lambda x: StatsProcessor.
48
+ cal_non_percentage(x, to_str=True, postfix="%"),
49
+ "_YoY_1":
50
+ StatsProcessor.cal_percentage,
51
+ "_YoY_3":
52
+ StatsProcessor.cal_percentage,
53
+ "_YoY_5":
54
+ StatsProcessor.cal_percentage,
55
+ "_YoY_10":
56
+ StatsProcessor.cal_percentage
57
+ }
58
+
59
+ process_fn = lambda_map.get(postfix)
60
+ postfix_cols = data_df.columns.str.endswith(postfix)
61
+ postfix_cols = data_df.loc[:, postfix_cols].columns
62
+
63
+ if (postfix == "_value"):
64
+ postfix_cols = [
65
+ col for col in postfix_cols
66
+ if not ("eps" in col or "每股盈餘" in col)
67
+ ]
68
+
69
+ if (postfix == '_growth'):
70
+ data_df[postfix_cols] = data_df[postfix_cols].map(
71
+ lambda x: x * 100.0 if isinstance(x, float) else x
72
+ )
73
+
74
+ data_df[postfix_cols] = (data_df[postfix_cols].map(process_fn))
75
+
76
+ return data_df
77
+
78
+ def _apply_process_unit_pipeline(
79
+ self, data_df, postfix_list=["_value", "percentage"]
80
+ ):
81
+ for postfix in postfix_list:
82
+ data_df = self._process_unit(data_df, postfix)
83
+ return data_df
84
+
85
+ @staticmethod
86
+ def _slice_target_season(stats_df, target_season):
87
+ target_season_columns = stats_df.columns.str.endswith(
88
+ f"Q{target_season}"
89
+ )
90
+ stats_df = stats_df.loc[:, target_season_columns]
91
+
92
+ return stats_df
93
+
94
+ def _get_empty_structure(self):
95
+ return_dict = {
96
+ "warning": "No data fetched",
97
+ "ticker": self.ticker,
98
+ "company_name": self.company_name
99
+ }
100
+
101
+ return_dict.update(
102
+ {
103
+ key: pd.DataFrame(columns=pd.Index([], name='index'))
104
+ for key in self.return_keys
105
+ }
106
+ )
107
+ return return_dict
108
+
109
+ def _process_value(self, value):
110
+ if isinstance(value, str) and "%" in value:
111
+ value = value.replace("%", "")
112
+ try:
113
+ return float(value)
114
+ except (ValueError, TypeError):
115
+ return None
116
+
117
+ def _calculate_growth(self, this_value, last_value, delta):
118
+ try:
119
+ return YoY_Calculator.cal_growth(
120
+ this_value, last_value, delta
121
+ ) * 100
122
+ except Exception:
123
+ return None
124
+
125
+ def _cal_QoQ(self, data_dict):
126
+ """
127
+ data_dict: {"<key>_value": {"2020Q1": <value>, ....}, "<key>_percentage": {"2020Q1": <value>, ....}}
128
+ """
129
+
130
+ return_dict = data_dict.copy()
131
+
132
+ for key, datas in data_dict.items():
133
+ if (key.endswith("_value")):
134
+ main_key = key.split("_")[0]
135
+ temp_growth_dict = dict()
136
+ for year_season, value in datas.items():
137
+
138
+ year, season = year_season.split('Q')
139
+ year, season = int(year), int(season)
140
+
141
+ last_year, last_season = (
142
+ year, season - 1
143
+ ) if season != 1 else (year - 1, 4)
144
+
145
+ last_value = datas.get(f"{last_year}Q{last_season}", None)
146
+
147
+ growth = YoY_Calculator.cal_growth(
148
+ value, last_value, delta=1
149
+ )
150
+ if (growth):
151
+ growth = growth * 100
152
+ growth = f"{growth:.2f}%"
153
+
154
+ temp_growth_dict[year_season] = growth
155
+
156
+ return_dict[f"{main_key}_growth"] = growth
157
+ return_df = pd.DataFrame.from_dict(return_dict)
158
+ return return_df
@@ -0,0 +1,2 @@
1
+ from .twse import TWSECashFlowTransformer
2
+ from .us import USCashFlowTransformer
@@ -0,0 +1,114 @@
1
+ from ..base import BaseTransformer
2
+ from neurostats_API.utils import StatsProcessor
3
+ import numpy as np
4
+ import pandas as pd
5
+
6
+ class BaseCashFlowTransformer(BaseTransformer):
7
+ def __init__(self, ticker, company_name, zone):
8
+ super().__init__(ticker, company_name, zone)
9
+
10
+ def _process_twse_to_stats_format(self, fetched_data):
11
+ table_dict = {}
12
+ CASHO_dict = {}
13
+ CASHI_dict = {}
14
+ CASHF_dict = {}
15
+
16
+ checkpoints = ["營業活動之現金流量-間接法", "投資活動之現金流量", "籌資活動之現金流量", "匯率變動對現金及約當現金之影響"]
17
+ main_cash_flow_names = [
18
+ "營業活動之淨現金流入(流出)", "投資活動之淨現金流入(流出)", "籌資活動之淨現金流入(流出)", "其他"
19
+ ] # 主要的比例對象
20
+ partial_cash_flow_dicts = [CASHO_dict, CASHI_dict, CASHF_dict, dict()]
21
+
22
+ boundary = len(main_cash_flow_names) - 1
23
+
24
+ for data in fetched_data:
25
+ year, season, cash_flow = data['year'], data['season'], data['cash_flow']
26
+
27
+ time_index = f"{year}Q{season}"
28
+ table_dict[time_index] = cash_flow
29
+
30
+ # 處理sub part
31
+ main_cash_flow_name = ""
32
+ partial_cash_flow = {}
33
+ next_checkpoint = 0
34
+
35
+ temp_dict = {}
36
+
37
+ for index_name, cash_flow_value in cash_flow.items():
38
+ if (next_checkpoint < boundary
39
+ and index_name == checkpoints[next_checkpoint]): # 找到了主要的變動點
40
+ main_cash_flow_name = main_cash_flow_names[next_checkpoint]
41
+ partial_cash_flow = partial_cash_flow_dicts[next_checkpoint]
42
+ partial_cash_flow[time_index] = {}
43
+ next_checkpoint += 1
44
+
45
+ if main_cash_flow_name: # 有取得cash flow name再進行接下來的計算percentage
46
+ if (isinstance(cash_flow_value, dict)):
47
+ value = cash_flow_value.get('value', None)
48
+ else:
49
+ value = cash_flow_value
50
+
51
+ # 處理cashflow percentage部分(取2位 + 接%)
52
+ main_value = cash_flow.get(main_cash_flow_name, None)
53
+ if (isinstance(main_value, dict)):
54
+ main_value = main_value.get('value', None)
55
+ else:
56
+ pass
57
+
58
+ try:
59
+ ratio = np.round(
60
+ (value / main_value) * 100, 2
61
+ )
62
+ ratio = f"{ratio}%"
63
+ except:
64
+ ratio = None
65
+
66
+ value = StatsProcessor.cal_non_percentage(value, postfix="千元")
67
+ temp_dict[index_name] = {
68
+ "value" : value,
69
+ "percentage": ratio
70
+ }
71
+ partial_cash_flow[time_index][index_name] = temp_dict[index_name]
72
+
73
+ table_dict[time_index] = temp_dict
74
+
75
+ stats_df = pd.DataFrame(table_dict)
76
+
77
+ CASHO_df = pd.DataFrame(CASHO_dict)
78
+ CASHI_df = pd.DataFrame(CASHI_dict)
79
+ CASHF_df = pd.DataFrame(CASHF_dict)
80
+
81
+ return stats_df, CASHO_df, CASHI_df, CASHF_df
82
+
83
+ def _process_twse_to_tej_format(self, fetched_data):
84
+ """
85
+ new_df 迎合 TEJ格式, 用於 report_generator
86
+ """
87
+ table_dict = {}
88
+ for data in fetched_data:
89
+ year, season, cash_flow = data['year'], data['season'], data['cash_flow']
90
+ time_index = f"{year}Q{season}"
91
+
92
+ new_cash_flow = self.flatten_dict(
93
+ cash_flow,
94
+ target_keys=["value", "percentage"]
95
+ )
96
+
97
+ table_dict[time_index] = new_cash_flow
98
+
99
+ stats_df = pd.DataFrame.from_dict(table_dict)
100
+ return stats_df
101
+
102
+ def _process_us_format(self, fetched_data):
103
+ """
104
+ 主要用於report generator
105
+ """
106
+ table_dict = {}
107
+ for data in fetched_data:
108
+ year, season, cash_flow = data['year'], data['season'], data['cash_flow']
109
+ time_index = f"{year}Q{season}"
110
+
111
+ table_dict[time_index] = cash_flow
112
+
113
+ stats_df = pd.DataFrame.from_dict(table_dict)
114
+ return stats_df
@@ -0,0 +1,70 @@
1
+ from .base import BaseCashFlowTransformer
2
+ from neurostats_API.utils import StatsProcessor
3
+
4
+ class TWSECashFlowTransformer(BaseCashFlowTransformer):
5
+ def __init__(self, ticker, company_name, zone):
6
+ super().__init__(ticker, company_name, zone)
7
+
8
+ self.cash_flow_dict = StatsProcessor.load_yaml(
9
+ "twse/cash_flow_percentage.yaml"
10
+ ) # 計算子表格用
11
+
12
+ self.return_keys = ['cash_flow', 'CASHO', 'CASHI', 'CASHF', 'cash_flow_all', 'cash_flow_YoY']
13
+
14
+ def process_transform(self, fetched_data):
15
+ """
16
+ 轉換整體
17
+ """
18
+ if (not fetched_data):
19
+ return self._get_empty_structure()
20
+
21
+ return_dict = {
22
+ "ticker": self.ticker,
23
+ "company_name": self.company_name
24
+ }
25
+
26
+ return_dict.update(self.process_stats_page(fetched_data))
27
+ return_dict.update(self.process_QoQ(fetched_data))
28
+
29
+ return return_dict
30
+ def process_stats_page(self, fetched_data):
31
+ """
32
+ 這裡主要回傳stats頁面相關的部分
33
+ """
34
+ if (not fetched_data):
35
+ return self._get_empty_structure()
36
+
37
+ stats_page_df, CASHO_df, CASHI_df, CASHF_df = self._process_twse_to_stats_format(fetched_data)
38
+ target_season = fetched_data[0]['season']
39
+
40
+ return {
41
+ 'cash_flow': self._slice_and_expand(stats_page_df, target_season),
42
+ "CASHO": self._slice_and_expand(CASHO_df, target_season),
43
+ "CASHI": self._slice_and_expand(CASHI_df, target_season),
44
+ "CASHF": self._slice_and_expand(CASHF_df, target_season)
45
+ }
46
+
47
+ def process_QoQ(self, fetched_data):
48
+ """
49
+ 這裡主要只會回傳其他地方需要的部分,此符合TEJ格式
50
+ """
51
+ if (not fetched_data):
52
+ return self._get_empty_structure()
53
+
54
+ data_df = self._process_twse_to_tej_format(fetched_data)
55
+ data_df = self._cal_QoQ(data_df.T.to_dict())
56
+ data_df = data_df.T
57
+ target_season = fetched_data[0]['season']
58
+
59
+ data_df_YoY = self._slice_target_season(data_df, target_season)
60
+
61
+ return {
62
+ "cash_flow_all": data_df,
63
+ "cash_flow_YoY": data_df_YoY
64
+ }
65
+
66
+ def _slice_and_expand(self, data_df, target_season):
67
+ data_df = self._slice_target_season(data_df, target_season)
68
+ data_df = StatsProcessor.expand_value_percentage(data_df)
69
+
70
+ return data_df