neurostats-API 0.0.25rc1__py3-none-any.whl → 1.0.0rc1__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 (105) hide show
  1. neurostats_API/__init__.py +1 -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 +44 -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 +159 -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.py +88 -0
  38. neurostats_API/async_mode/fetchers/twse_institution.py +62 -0
  39. neurostats_API/async_mode/fetchers/twse_margin.py +100 -0
  40. neurostats_API/async_mode/fetchers/value.py +76 -0
  41. neurostats_API/config/company_list/ticker_index_industry_map.json +7946 -0
  42. neurostats_API/config/company_list/us.json +9986 -0
  43. neurostats_API/{tools → config}/tej_db/tej_db_skip_index.yaml +0 -2
  44. neurostats_API/{tools → config}/twse/profit_lose.yaml +0 -6
  45. neurostats_API/fetchers/finance_overview.py +27 -5
  46. neurostats_API/transformers/__init__.py +40 -0
  47. neurostats_API/transformers/balance_sheet/__init__.py +2 -0
  48. neurostats_API/transformers/balance_sheet/base.py +51 -0
  49. neurostats_API/transformers/balance_sheet/twse.py +76 -0
  50. neurostats_API/transformers/balance_sheet/us.py +30 -0
  51. neurostats_API/transformers/base.py +110 -0
  52. neurostats_API/transformers/cash_flow/__init__.py +2 -0
  53. neurostats_API/transformers/cash_flow/base.py +114 -0
  54. neurostats_API/transformers/cash_flow/twse.py +68 -0
  55. neurostats_API/transformers/cash_flow/us.py +38 -0
  56. neurostats_API/transformers/daily_chip/__init__.py +1 -0
  57. neurostats_API/transformers/daily_chip/base.py +5 -0
  58. neurostats_API/transformers/daily_chip/tej.py +0 -0
  59. neurostats_API/transformers/daily_chip/twse_chip.py +412 -0
  60. neurostats_API/transformers/daily_chip/utils/__init__.py +0 -0
  61. neurostats_API/transformers/daily_chip/utils/institution.py +90 -0
  62. neurostats_API/transformers/daily_chip/utils/margin_trading.py +2 -0
  63. neurostats_API/transformers/daily_chip/utils/security_lending.py +0 -0
  64. neurostats_API/transformers/daily_tech/__init__.py +1 -0
  65. neurostats_API/transformers/daily_tech/base.py +5 -0
  66. neurostats_API/transformers/daily_tech/tech.py +84 -0
  67. neurostats_API/transformers/daily_tech/utils/__init__.py +1 -0
  68. neurostats_API/transformers/daily_tech/utils/processor.py +251 -0
  69. neurostats_API/transformers/finance_overview/__init__.py +2 -0
  70. neurostats_API/transformers/finance_overview/agent_overview.py +55 -0
  71. neurostats_API/transformers/finance_overview/base.py +824 -0
  72. neurostats_API/transformers/finance_overview/stats_overview.py +64 -0
  73. neurostats_API/transformers/month_revenue/__init__.py +1 -0
  74. neurostats_API/transformers/month_revenue/base.py +60 -0
  75. neurostats_API/transformers/month_revenue/twse.py +129 -0
  76. neurostats_API/transformers/profit_lose/__init__.py +2 -0
  77. neurostats_API/transformers/profit_lose/base.py +82 -0
  78. neurostats_API/transformers/profit_lose/twse.py +133 -0
  79. neurostats_API/transformers/profit_lose/us.py +25 -0
  80. neurostats_API/transformers/tej/__init__.py +1 -0
  81. neurostats_API/transformers/tej/base.py +149 -0
  82. neurostats_API/transformers/tej/finance_statement.py +80 -0
  83. neurostats_API/transformers/value/__init__.py +1 -0
  84. neurostats_API/transformers/value/base.py +5 -0
  85. neurostats_API/transformers/value/tej.py +8 -0
  86. neurostats_API/transformers/value/twse.py +48 -0
  87. neurostats_API/utils/__init__.py +1 -1
  88. neurostats_API/utils/data_process.py +10 -6
  89. neurostats_API/utils/exception.py +8 -0
  90. neurostats_API/utils/logger.py +21 -0
  91. neurostats_API-1.0.0rc1.dist-info/METADATA +102 -0
  92. neurostats_API-1.0.0rc1.dist-info/RECORD +119 -0
  93. neurostats_API-0.0.25rc1.dist-info/METADATA +0 -858
  94. neurostats_API-0.0.25rc1.dist-info/RECORD +0 -36
  95. /neurostats_API/{tools → config}/company_list/tw.json +0 -0
  96. /neurostats_API/{tools → config}/company_list/us_TradingView_list.json +0 -0
  97. /neurostats_API/{tools → config}/tej_db/tej_db_index.yaml +0 -0
  98. /neurostats_API/{tools → config}/tej_db/tej_db_percent_index.yaml +0 -0
  99. /neurostats_API/{tools → config}/tej_db/tej_db_thousand_index.yaml +0 -0
  100. /neurostats_API/{tools → config}/twse/balance_sheet.yaml +0 -0
  101. /neurostats_API/{tools → config}/twse/cash_flow_percentage.yaml +0 -0
  102. /neurostats_API/{tools → config}/twse/finance_overview_dict.yaml +0 -0
  103. /neurostats_API/{tools → config}/twse/seasonal_data_field_dict.txt +0 -0
  104. {neurostats_API-0.0.25rc1.dist-info → neurostats_API-1.0.0rc1.dist-info}/WHEEL +0 -0
  105. {neurostats_API-0.0.25rc1.dist-info → neurostats_API-1.0.0rc1.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,18 +808,17 @@ 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"
814
815
  self.db = db_client[db_name]
815
816
 
816
817
  daily_collection_name_map = {
817
- "company": "twse_daily_share_price",
818
818
  'company_us': "us_technical"
819
819
  }
820
820
 
821
821
  seasonal_collection_name_map = {
822
- 'company': "twse_seasonal_report",
823
822
  "company_us": "us_fundamentals"
824
823
  }
825
824
 
@@ -835,7 +834,9 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
835
834
  self.seasonal_collection_name]
836
835
 
837
836
  def query_data(self, date=None):
838
-
837
+ """
838
+ 抓取當天技術面以及當天之前的季報
839
+ """
839
840
  query_set = self._prepare_query(date)
840
841
  if (date is None):
841
842
  str_date = datetime.today().strftime("%Y-%m-%d")
@@ -863,8 +864,9 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
863
864
  } for data in seasonal_datas
864
865
  ]
865
866
 
867
+ # 計算TTM: 財報近四期數值平均
866
868
  seasonal_df = pd.DataFrame(seasonal_data)
867
- TTM_data = seasonal_df.mean()
869
+ TTM_data = seasonal_df.mean()
868
870
 
869
871
  return_dict = {
870
872
  "date": str_date,
@@ -948,6 +950,9 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
948
950
  return "Error"
949
951
 
950
952
  def _get_latest_average_volume(self, daily_data, length=30):
953
+ """
954
+ 計算平均成交量
955
+ """
951
956
  try:
952
957
  daily_data = pd.DataFrame(daily_data).tail(length)
953
958
 
@@ -960,6 +965,10 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
960
965
  return "Error"
961
966
 
962
967
  def _get_market_capitalization(self, daily_data, seasonal_data):
968
+ """
969
+ 計算市值
970
+ 市值 = 收盤價 * 流通股數
971
+ """
963
972
  try:
964
973
  latest_close = float(self._get_latest_close(daily_data))
965
974
  latest_common_share = seasonal_data[-1][
@@ -973,6 +982,10 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
973
982
  return "Error"
974
983
 
975
984
  def _get_PE(self, daily_data, TTM_data):
985
+ """
986
+ 本益比
987
+ 本益比 = 收盤價 / 稀釋EPS(TTM)
988
+ """
976
989
  try:
977
990
  latest_close = float(self._get_latest_close(daily_data))
978
991
  TTM_EPS = TTM_data['Diluted EPS']
@@ -985,6 +998,10 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
985
998
  return "Error"
986
999
 
987
1000
  def _get_PS(self, daily_data, TTM_data, seasonal_data):
1001
+ """
1002
+ 股價營收比:
1003
+ 本益比 = 市值 / 總營收(TTM)
1004
+ """
988
1005
  try:
989
1006
  market_capitalzation = self._get_market_capitalization(daily_data, seasonal_data)
990
1007
  market_capitalzation = float(market_capitalzation)
@@ -997,7 +1014,9 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
997
1014
  return "Error"
998
1015
 
999
1016
  def _is_us_market_open(self):
1000
-
1017
+ """
1018
+ 判斷目前美股是否開市
1019
+ """
1001
1020
  taiwan_dt = datetime.now(pytz.timezone('Asia/Taipei'))
1002
1021
 
1003
1022
  # 轉成美東時間(會自動處理夏令時間)
@@ -1020,6 +1039,9 @@ class AgentFinanceOverviewFetcher(FinanceOverviewFetcher):
1020
1039
  return market_open <= us_dt <= market_close
1021
1040
 
1022
1041
  def _get_category(self):
1042
+ """
1043
+ 判斷公司類別,透過檔案
1044
+ """
1023
1045
  category = self.us_company_list.get(self.ticker,{}).get('en_industry')
1024
1046
 
1025
1047
  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,76 @@
1
+ from .base import BaseBalanceSheetTransformer
2
+ from neurostats_API.utils import StatsProcessor
3
+
4
+ class TWSEBalanceSheetTransformer(BaseBalanceSheetTransformer):
5
+ def __init__(self, ticker, company_name, zone):
6
+ super().__init__(ticker, company_name, zone)
7
+
8
+ self.table_settings = StatsProcessor.load_yaml("twse/balance_sheet.yaml")
9
+ self.return_keys = [
10
+ 'balance_sheet', 'total_asset', 'current_asset', 'non_current_asset',
11
+ 'current_debt', 'non_current_debt', 'equity', 'balance_sheet_all', 'balance_sheet_YoY'
12
+ ]
13
+
14
+ self.stats_df = None
15
+ self.new_df = None
16
+
17
+ def process_transform(self, fetched_data):
18
+ if (not fetched_data):
19
+ return self._get_empty_structure()
20
+
21
+ processed_data = self._process_fn(fetched_data)
22
+
23
+ return processed_data
24
+ def _process_fn(self, fetched_data):
25
+
26
+ return_dict = {
27
+ "ticker": self.ticker,
28
+ "company_name": self.company_name
29
+ }
30
+
31
+ target_season = fetched_data[0]['season']
32
+
33
+ self.stats_df = self._process_twse_to_stats_format(fetched_data)
34
+ self.stats_df = self._slice_target_season(self.stats_df, target_season)
35
+
36
+ # 轉換格式 (元 / 千元 / %)
37
+ self.stats_df = StatsProcessor.expand_value_percentage(self.stats_df)
38
+ self.stats_df = self._apply_process_unit_pipeline(
39
+ self.stats_df,
40
+ postfix_list=['_value', "_percentage"]
41
+ )
42
+
43
+ self.new_df = self._process_twse_to_tej_format(fetched_data)
44
+ # 轉換格式 (元 / 千元 / %)
45
+ self.new_df = self._apply_process_unit_pipeline(
46
+ self.new_df,
47
+ postfix_list=['_value', "_percentage"]
48
+ )
49
+
50
+ total_table_YoY = self._slice_target_season(self.new_df.T, target_season)
51
+
52
+ return_dict.update(
53
+ {
54
+ "balance_sheet": self.stats_df,
55
+ "balance_sheet_all": self.new_df,
56
+ "balance_sheet_YoY": total_table_YoY
57
+ }
58
+ )
59
+ # 抽取出小欄位整合
60
+ self._process_target_columns(return_dict, self.stats_df)
61
+
62
+ return return_dict
63
+
64
+ def _process_target_columns(self, return_dict, stats_df):
65
+ for name, setting in self.table_settings.items():
66
+ target_indexes = setting.get('target_index', [None])
67
+ for target_index in target_indexes:
68
+ try:
69
+ return_dict[name] = StatsProcessor.slice_old_table(
70
+ total_table=stats_df,
71
+ target_index=target_index
72
+ )
73
+ break
74
+ except Exception as e:
75
+ continue
76
+
@@ -0,0 +1,30 @@
1
+ from .base import BaseBalanceSheetTransformer
2
+ from neurostats_API.utils import StatsProcessor
3
+
4
+ class USBalanceSheetTransformer(BaseBalanceSheetTransformer):
5
+ def __init__(self, ticker, company_name, zone):
6
+ super().__init__(ticker, company_name, zone)
7
+
8
+ self.data_df = None
9
+
10
+ def process_transform(self, fetched_data):
11
+ return_dict = {
12
+ "ticker": self.ticker,
13
+ "company_name": self.company_name
14
+ }
15
+
16
+ # QoQ表格
17
+ self.data_df = self._process_us_format(fetched_data)
18
+
19
+ # YoY表格
20
+ target_season = fetched_data[0]['season']
21
+ total_table_YoY = self._slice_target_season(self.data_df.T, target_season)
22
+
23
+ return_dict.update(
24
+ {
25
+ 'balance_sheet': self.data_df,
26
+ "balance_sheet_YoY": total_table_YoY
27
+ }
28
+ )
29
+
30
+ return return_dict
@@ -0,0 +1,110 @@
1
+ import abc
2
+ from neurostats_API.utils import StatsProcessor, YoY_Calculator
3
+ import pandas as pd
4
+
5
+ class BaseTransformer(abc.ABC):
6
+ """
7
+ Transformer用途: 轉換資料為fetcher可使用的格式
8
+ """
9
+ def __init__(self, ticker, company_name, zone):
10
+ self.ticker = ticker
11
+ self.company_name = company_name
12
+
13
+ self.zone = zone
14
+ self.return_keys = []
15
+
16
+
17
+ @abc.abstractmethod
18
+ def process_transform(self):
19
+ pass
20
+
21
+ @classmethod
22
+ def flatten_dict(cls, value_dict, target_keys):
23
+ indexes = value_dict.keys()
24
+ new_dict = {}
25
+
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
+ return new_dict
35
+
36
+ @staticmethod
37
+ def _process_unit(data_df, postfix):
38
+
39
+ lambda_map = {
40
+ "_value": lambda x: StatsProcessor.cal_non_percentage(x, postfix="千元"),
41
+ "_percentage": lambda x : StatsProcessor.cal_non_percentage(x, to_str=True, postfix="%"),
42
+ '_growth': lambda x : StatsProcessor.cal_non_percentage(x, to_str=True, postfix="%"),
43
+ "_YoY_1": StatsProcessor.cal_percentage,
44
+ "_YoY_3": StatsProcessor.cal_percentage,
45
+ "_YoY_5": StatsProcessor.cal_percentage,
46
+ "_YoY_10": StatsProcessor.cal_percentage
47
+ }
48
+
49
+ process_fn = lambda_map.get(postfix)
50
+ postfix_cols = data_df.columns.str.endswith(postfix)
51
+ postfix_cols = data_df.loc[:, postfix_cols].columns
52
+
53
+ if (postfix == "_value"):
54
+ postfix_cols = [col for col in postfix_cols if not ("eps" in col or "每股盈餘" in col)]
55
+
56
+ if (postfix == '_growth'):
57
+ data_df[postfix_cols] =data_df[postfix_cols].map(
58
+ lambda x: x * 100.0 if isinstance(x, float) else x
59
+ )
60
+
61
+ data_df[postfix_cols] = (
62
+ data_df[postfix_cols].map(
63
+ process_fn
64
+ )
65
+ )
66
+
67
+ return data_df
68
+
69
+ def _apply_process_unit_pipeline(self, data_df, postfix_list = ["_value", "percentage"]):
70
+ for postfix in postfix_list:
71
+ data_df = self._process_unit(data_df, postfix)
72
+ return data_df
73
+
74
+ @staticmethod
75
+ def _slice_target_season(stats_df, target_season):
76
+ target_season_columns = stats_df.columns.str.endswith(f"Q{target_season}")
77
+ stats_df = stats_df.loc[:, target_season_columns]
78
+
79
+ return stats_df
80
+
81
+ def _get_empty_structure(self):
82
+ return_dict = {
83
+ "warning": "No data fetched",
84
+ "ticker": self.ticker,
85
+ "company_name": self.company_name
86
+ }
87
+
88
+ return_dict.update(
89
+ {
90
+ key: pd.DataFrame(columns= pd.Index([], name = 'date')) for key in self.return_keys
91
+ }
92
+ )
93
+ return return_dict
94
+
95
+ def _process_value(self, value):
96
+ if isinstance(value, str) and "%" in value:
97
+ value = value.replace("%", "")
98
+ try:
99
+ return float(value)
100
+ except (ValueError, TypeError):
101
+ return None
102
+
103
+ def _calculate_growth(self, this_value, last_value, delta):
104
+ try:
105
+ return YoY_Calculator.cal_growth(
106
+ this_value, last_value, delta
107
+ ) * 100
108
+ except Exception:
109
+ return None
110
+
@@ -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,68 @@
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
+ target_season = fetched_data[0]['season']
56
+
57
+ data_df_YoY = self._slice_target_season(data_df, target_season)
58
+
59
+ return {
60
+ "cash_flow_all": data_df,
61
+ "cash_flow_YoY": data_df_YoY
62
+ }
63
+
64
+ def _slice_and_expand(self, data_df, target_season):
65
+ data_df = self._slice_target_season(data_df, target_season)
66
+ data_df = StatsProcessor.expand_value_percentage(data_df)
67
+
68
+ return data_df
@@ -0,0 +1,38 @@
1
+ from .base import BaseCashFlowTransformer
2
+ from neurostats_API.utils import StatsProcessor, YoY_Calculator
3
+ import pandas as pd
4
+
5
+ class USCashFlowTransformer(BaseCashFlowTransformer):
6
+ def __init__(self, ticker, company_name, zone):
7
+ super().__init__(ticker, company_name, zone)
8
+ self.data_df = None
9
+ self.return_keys = ['cash_flow', 'cash_flow_YoY']
10
+ def process_transform(self, fetched_data):
11
+ if (not fetched_data):
12
+ return self._get_empty_structure()
13
+
14
+ self.data_df = self._process_us_format(fetched_data)
15
+ target_season = fetched_data[0]['season']
16
+
17
+ return_dict = {
18
+ "ticker": self.ticker,
19
+ "company_name": self.company_name,
20
+ "cash_flow": self.data_df,
21
+ "cash_flow_YoY": self._slice_target_season(self.data_df, target_season)
22
+ }
23
+
24
+ return return_dict
25
+
26
+ def process_QoQ(self, fetched_data):
27
+ if (not fetched_data):
28
+ return self._get_empty_structure()
29
+
30
+ data_df = self._process_twse_to_tej_format(fetched_data)
31
+ target_season = fetched_data[0]['season']
32
+
33
+ data_df_YoY = self._slice_target_season(data_df, target_season)
34
+
35
+ return {
36
+ "cash_flow_all": data_df,
37
+ "cash_flow_YoY": data_df_YoY
38
+ }
@@ -0,0 +1 @@
1
+ from .twse_chip import TWSEChipTransformer
@@ -0,0 +1,5 @@
1
+ from ..base import BaseTransformer
2
+
3
+ class BaseChipTransformer(BaseTransformer):
4
+ def __init__(self, ticker, company_name, zone):
5
+ super().__init__(ticker, company_name, zone)
File without changes