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.
- 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 +44 -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 +159 -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 +412 -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.0rc1.dist-info/METADATA +102 -0
- neurostats_API-1.0.0rc1.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.0rc1.dist-info}/WHEEL +0 -0
- {neurostats_API-0.0.25rc1.dist-info → neurostats_API-1.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -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,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,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
|
File without changes
|