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.
- neurostats_API/__init__.py +15 -1
- neurostats_API/async_mode/__init__.py +13 -0
- neurostats_API/async_mode/db/__init__.py +3 -0
- neurostats_API/async_mode/db/base.py +24 -0
- neurostats_API/async_mode/db/tej.py +10 -0
- neurostats_API/async_mode/db/twse.py +8 -0
- neurostats_API/async_mode/db/us.py +9 -0
- neurostats_API/async_mode/db_extractors/__init__.py +20 -0
- neurostats_API/async_mode/db_extractors/base.py +66 -0
- neurostats_API/async_mode/db_extractors/daily/__init__.py +7 -0
- neurostats_API/async_mode/db_extractors/daily/base.py +89 -0
- neurostats_API/async_mode/db_extractors/daily/tej_chip.py +14 -0
- neurostats_API/async_mode/db_extractors/daily/tej_tech.py +12 -0
- neurostats_API/async_mode/db_extractors/daily/twse_chip.py +49 -0
- neurostats_API/async_mode/db_extractors/daily/value.py +93 -0
- neurostats_API/async_mode/db_extractors/daily/yf.py +12 -0
- neurostats_API/async_mode/db_extractors/month_revenue/__init__.py +1 -0
- neurostats_API/async_mode/db_extractors/month_revenue/base.py +140 -0
- neurostats_API/async_mode/db_extractors/month_revenue/twse.py +5 -0
- neurostats_API/async_mode/db_extractors/seasonal/__init__.py +4 -0
- neurostats_API/async_mode/db_extractors/seasonal/balance_sheet.py +19 -0
- neurostats_API/async_mode/db_extractors/seasonal/base.py +152 -0
- neurostats_API/async_mode/db_extractors/seasonal/cashflow.py +10 -0
- neurostats_API/async_mode/db_extractors/seasonal/profit_lose.py +17 -0
- neurostats_API/async_mode/db_extractors/seasonal/tej.py +87 -0
- neurostats_API/async_mode/factory/__init__.py +1 -0
- neurostats_API/async_mode/factory/extractor_factory.py +168 -0
- neurostats_API/async_mode/factory/transformer_factory.py +164 -0
- neurostats_API/async_mode/fetchers/__init__.py +10 -0
- neurostats_API/async_mode/fetchers/balance_sheet.py +31 -0
- neurostats_API/async_mode/fetchers/base.py +48 -0
- neurostats_API/async_mode/fetchers/cash_flow.py +56 -0
- neurostats_API/async_mode/fetchers/finance_overview.py +134 -0
- neurostats_API/async_mode/fetchers/month_revenue.py +35 -0
- neurostats_API/async_mode/fetchers/profit_lose.py +46 -0
- neurostats_API/async_mode/fetchers/tech.py +205 -0
- neurostats_API/async_mode/fetchers/tej/__init__.py +2 -0
- neurostats_API/async_mode/fetchers/tej/seasonal_data.py +88 -0
- neurostats_API/async_mode/fetchers/tej/tech.py +34 -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 +22 -2
- 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 +81 -0
- neurostats_API/transformers/balance_sheet/us.py +38 -0
- neurostats_API/transformers/base.py +158 -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 +70 -0
- neurostats_API/transformers/cash_flow/us.py +38 -0
- neurostats_API/transformers/daily_chip/__init__.py +1 -0
- neurostats_API/transformers/daily_chip/base.py +5 -0
- neurostats_API/transformers/daily_chip/tej.py +0 -0
- neurostats_API/transformers/daily_chip/twse_chip.py +415 -0
- neurostats_API/transformers/daily_chip/utils/__init__.py +0 -0
- neurostats_API/transformers/daily_chip/utils/institution.py +90 -0
- neurostats_API/transformers/daily_chip/utils/margin_trading.py +2 -0
- neurostats_API/transformers/daily_chip/utils/security_lending.py +0 -0
- neurostats_API/transformers/daily_tech/__init__.py +1 -0
- neurostats_API/transformers/daily_tech/base.py +5 -0
- neurostats_API/transformers/daily_tech/tech.py +84 -0
- neurostats_API/transformers/daily_tech/utils/__init__.py +1 -0
- neurostats_API/transformers/daily_tech/utils/processor.py +251 -0
- neurostats_API/transformers/finance_overview/__init__.py +2 -0
- neurostats_API/transformers/finance_overview/agent_overview.py +55 -0
- neurostats_API/transformers/finance_overview/base.py +824 -0
- neurostats_API/transformers/finance_overview/stats_overview.py +79 -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 +134 -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 +26 -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.0.dist-info/METADATA +102 -0
- neurostats_API-1.0.0.dist-info/RECORD +121 -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.0.dist-info}/WHEEL +0 -0
- {neurostats_API-0.0.25rc1.dist-info → neurostats_API-1.0.0.dist-info}/top_level.txt +0 -0
@@ -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,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,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
|