neurostats-API 0.0.25rc1__py3-none-any.whl → 1.0.0rc2__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 +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.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 +415 -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.0rc2.dist-info/METADATA +102 -0
  92. neurostats_API-1.0.0rc2.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.0rc2.dist-info}/WHEEL +0 -0
  105. {neurostats_API-0.0.25rc1.dist-info → neurostats_API-1.0.0rc2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,152 @@
1
+ from ..base import BaseDBExtractor
2
+ from datetime import datetime
3
+ import json
4
+ import pandas as pd
5
+ from pymongo import ASCENDING, DESCENDING
6
+ from neurostats_API.async_mode.db import TWSEDBClient, USDBClient
7
+ from neurostats_API.utils import StatsDateTime, StatsProcessor, NotSupportedError
8
+ import yaml
9
+
10
+
11
+ class AsyncBaseSeasonalDBExtractor(BaseDBExtractor):
12
+
13
+ def __init__(self, ticker, client):
14
+ super().__init__(ticker, client)
15
+
16
+ self.collection_name_map = self._get_collection_map()
17
+ self.collection_name = self._get_collection_name()
18
+ if (self.collection_name is None):
19
+ raise NotSupportedError(
20
+ f"{self.__class__.__name__} only supports {list(self.collection_name_map.keys())}, got {self.zone} with ticker = \"{self.ticker}\""
21
+ )
22
+ self.collection = self.db_client.get_collection(self.collection_name)
23
+
24
+ self.column_name_map = None
25
+
26
+ async def query_data(
27
+ self,
28
+ start_date=None,
29
+ end_date=None,
30
+ get_latest=False # 決定是否只取得最新一筆資料
31
+ ):
32
+ if (start_date is None):
33
+ start_date = "1991-01-01"
34
+ if (end_date is None):
35
+ end_date = self._get_today()
36
+
37
+ start_year, start_season = self._get_year_and_season(date=start_date)
38
+ end_year, end_season = self._get_year_and_season(date=end_date)
39
+
40
+ query, projection, sort = self._prepare_query(
41
+ start_year, start_season, end_year, end_season, get_latest
42
+ )
43
+ cursor = self.collection.find(query, projection).sort(sort)
44
+
45
+ if (get_latest):
46
+ cursor = cursor.limit(1)
47
+
48
+ fetched_data = [data async for data in cursor]
49
+
50
+ return fetched_data
51
+
52
+ def _get_collection_name(self):
53
+
54
+ return self.collection_name_map.get(self.zone, None)
55
+
56
+ def _get_collection_map(self):
57
+ return {"tw": "twse_seasonal_report", "us": "us_fundamentals"}
58
+
59
+ def _prepare_query(
60
+ self,
61
+ start_year=None,
62
+ start_season=None,
63
+ end_year=None,
64
+ end_season=None,
65
+ get_latest=False
66
+ ):
67
+ chart_name = self.column_name_map.get(self.get_zone(), None)
68
+
69
+ query = {
70
+ "ticker": self.ticker,
71
+ "chart_name": {"$exists": True}
72
+ }
73
+
74
+ query = self._update_query_with_year_season(
75
+ query, start_year, start_season, end_year, end_season
76
+ )
77
+
78
+ projection = {
79
+ "_id": 0,
80
+ "ticker": 1,
81
+ "year": 1,
82
+ "season": 1,
83
+ chart_name: 1,
84
+ }
85
+
86
+ if (get_latest):
87
+ sort = [("year", DESCENDING), ("season", DESCENDING)]
88
+ else:
89
+ sort = [("year", ASCENDING), ("season", ASCENDING)]
90
+
91
+ return query, projection, sort
92
+
93
+ def _get_year_and_season(self, date):
94
+ if (isinstance(date, str)):
95
+ date = datetime.strptime(date, "%Y-%m-%d")
96
+
97
+ year = date.year
98
+ month = date.month
99
+
100
+ season = (month - 1) // 3 + 1
101
+
102
+ return year, season
103
+
104
+ def _update_query_with_year_season(
105
+ self, query, start_year, start_season, end_year, end_season
106
+ ):
107
+ if all(v is not None for v in [start_year, start_season]):
108
+ # 大於start_year條件
109
+ query.update(
110
+ {
111
+ "$or": [
112
+ {
113
+ "year": {
114
+ "$gt": start_year
115
+ }
116
+ },
117
+ {
118
+ "year": start_year,
119
+ "season": {
120
+ "$gte": start_season
121
+ }
122
+ },
123
+ ]
124
+ }
125
+ )
126
+
127
+ if all(v is not None for v in [end_year, end_season]):
128
+ # 小於end_year條件
129
+ query.update(
130
+ {
131
+ "$and":
132
+ query.get("$and", []) + [
133
+ {
134
+ "$or": [
135
+ {
136
+ "year": {
137
+ "$lt": end_year
138
+ }
139
+ },
140
+ {
141
+ "year": end_year,
142
+ "season": {
143
+ "$lte": end_season
144
+ }
145
+ },
146
+ ]
147
+ }
148
+ ]
149
+ }
150
+ )
151
+
152
+ return query
@@ -0,0 +1,10 @@
1
+ from .base import AsyncBaseSeasonalDBExtractor
2
+
3
+ class AsyncCashFlowExtractor(AsyncBaseSeasonalDBExtractor):
4
+ def __init__(self, ticker, client):
5
+ super().__init__(ticker, client)
6
+
7
+ self.column_name_map = {
8
+ 'tw': "cash_flow",
9
+ 'us': "cash_flow"
10
+ }
@@ -0,0 +1,17 @@
1
+ from .base import AsyncBaseSeasonalDBExtractor, StatsDateTime
2
+ from datetime import datetime
3
+ import json
4
+ import pandas as pd
5
+ from pymongo import ASCENDING, DESCENDING
6
+ from neurostats_API.async_mode.db import TWSEDBClient, USDBClient
7
+ from neurostats_API.utils import StatsDateTime, StatsProcessor
8
+ import yaml
9
+
10
+ class AsyncProfitLoseExtractor(AsyncBaseSeasonalDBExtractor):
11
+ def __init__(self, ticker, client):
12
+ super().__init__(ticker, client)
13
+
14
+ self.column_name_map = {
15
+ 'tw': "profit_lose",
16
+ 'us': "income_statement"
17
+ }
@@ -0,0 +1,87 @@
1
+ from .base import AsyncBaseSeasonalDBExtractor
2
+ from datetime import datetime
3
+ from neurostats_API.utils import NotSupportedError
4
+ from pymongo import ASCENDING
5
+
6
+
7
+ class AsyncTEJSeasonalDBExtractor(AsyncBaseSeasonalDBExtractor):
8
+
9
+ def __init__(self, ticker, client, fetch_type='Q'):
10
+ super().__init__(ticker, client)
11
+
12
+ self.fetch_type = fetch_type
13
+
14
+ if (self.zone != 'tw'):
15
+ raise NotSupportedError(None)
16
+
17
+ async def query_data(self, start_date=None, end_date=None):
18
+ if (start_date is None):
19
+ start_date = "1991-01-01"
20
+ if (end_date is None):
21
+ end_date = self._get_today()
22
+
23
+ query, projection, sort = self._prepare_query(start_date, end_date)
24
+
25
+ cursor = self.collection.find(query, projection).sort(sort)
26
+
27
+ fetched_data = [data async for data in cursor]
28
+
29
+ return fetched_data
30
+
31
+ def _prepare_query(self, start_date, end_date):
32
+ start_date = self._transform_date(start_date)
33
+ end_date = self._transform_date(end_date)
34
+
35
+ query = {
36
+ "ticker": self.ticker,
37
+ "date": {
38
+ "$gte": start_date,
39
+ "$lte": end_date
40
+ }
41
+ }
42
+
43
+ projection = {
44
+ "_id": 0,
45
+ "year": 1,
46
+ "season": 1,
47
+ self.fetch_type: 1,
48
+ }
49
+
50
+ sorting = [("year", ASCENDING), ("season", ASCENDING)]
51
+
52
+ return query, projection, sorting
53
+
54
+ def _transform_date(self, date):
55
+ if (isinstance(date, str)):
56
+ try:
57
+ return datetime.strptime(date, "%Y-%m-%d")
58
+ except Exception as e:
59
+ raise ValueError(
60
+ "date string format imcompatible, should be \"YYYY-MM-DD\""
61
+ )
62
+ elif (isinstance(date, datetime)):
63
+ return date
64
+ else:
65
+ raise ValueError(
66
+ "date type imcompatible, should be string(\"YYYY-MM-DD\") or datetime.datetime"
67
+ )
68
+
69
+
70
+ class AsyncTEJFinanceStatementDBExtractor(AsyncTEJSeasonalDBExtractor):
71
+
72
+ def __init__(self, ticker, mongo_uri, fetch_type='Q'):
73
+ super().__init__(ticker, mongo_uri, fetch_type=fetch_type)
74
+
75
+ def _get_collection_map(self):
76
+ return {
77
+ 'tw': "TEJ_finance_statement"
78
+ }
79
+
80
+ class AsyncTEJSelfSettlementDBExtractor(AsyncTEJSeasonalDBExtractor):
81
+ def __init__(self, ticker, mongo_uri, fetch_type='Q'):
82
+ super().__init__(ticker, mongo_uri, fetch_type=fetch_type)
83
+
84
+ def _get_collection_map(self):
85
+ return {
86
+ 'tw': "TEJ_self_settlement"
87
+ }
@@ -0,0 +1 @@
1
+ from .extractor_factory import get_extractor
@@ -0,0 +1,168 @@
1
+ from typing import Type, TypedDict, Optional
2
+ from neurostats_API.async_mode.db_extractors import (
3
+ AsyncTEJDailyTechDBExtractor, AsyncYFDailyTechDBExtractor,
4
+ AsyncDailyValueDBExtractor, AsyncTEJDailyValueDBExtractor,
5
+ AsyncTWSEChipDBExtractor, AsyncTWSEMonthlyRevenueExtractor,
6
+ AsyncBalanceSheetExtractor, AsyncProfitLoseExtractor,
7
+ AsyncCashFlowExtractor, AsyncTEJFinanceStatementDBExtractor,
8
+ AsyncTEJSelfSettlementDBExtractor
9
+ )
10
+ from neurostats_API.utils import NotSupportedError
11
+
12
+
13
+ class ExtractorConfig(TypedDict):
14
+ class_: Type
15
+ description: Optional[str]
16
+ default_kwargs: dict
17
+
18
+
19
+ EXTRACTOR_MAP: dict[str, ExtractorConfig] = {
20
+ # TEJ 相關
21
+ "TEJ_finance_statement": {
22
+ "class_":
23
+ AsyncTEJFinanceStatementDBExtractor,
24
+ "default_kwargs": {
25
+ "fetch_type": "Q"
26
+ },
27
+ "description":
28
+ """
29
+ DB_collection: TEJ_finance_statement
30
+ 對應TEJ的\"會計師簽證財務資料\"
31
+ """,
32
+ },
33
+ "TEJ_self_settlement": {
34
+ "class_":
35
+ AsyncTEJSelfSettlementDBExtractor,
36
+ "default_kwargs": {
37
+ "fetch_type": "Q"
38
+ },
39
+ "description":
40
+ """
41
+ DB_collection: TEJ_self_settlement
42
+ 對應TEJ的\"公司自結數\"
43
+ """
44
+ },
45
+
46
+ # TWSE 本地DB
47
+ "DB_balance_sheet": {
48
+ "class_":
49
+ AsyncBalanceSheetExtractor,
50
+ "default_kwargs": {},
51
+ "description":
52
+ """
53
+ DB_collection: twse_seasonal_report(tw), us_fundamental(us)
54
+ 對應TWSE的\"資產負債表\"與美股的\"Balance_Sheet\"
55
+ tw: 選取balance_sheet欄
56
+ us: 選取balance_sheet欄
57
+ """
58
+ },
59
+ "DB_profit_lose": {
60
+ "class_":
61
+ AsyncProfitLoseExtractor,
62
+ "default_kwargs": {},
63
+ "description":
64
+ """
65
+ DB_collection: twse_seasonal_report(tw), us_fundamental(us)
66
+ 對應TWSE的\"損益表\"與美股的\"Income Statement\"
67
+ tw: 選取profit_lose欄
68
+ us: 選取income_statement欄
69
+ """
70
+ },
71
+ "DB_cash_flow": {
72
+ "class_":
73
+ AsyncCashFlowExtractor,
74
+ "default_kwargs": {},
75
+ "description":
76
+ """
77
+ DB_collection: twse_seasonal_report(tw), us_fundamental(us)
78
+ 對應TWSE的\"金流表\"與美股的\"Cash Flow\"
79
+ tw: 選取cash_flow欄
80
+ us: 選取cash_flow欄
81
+ """
82
+ },
83
+
84
+ # tech
85
+ "YF_tech": {
86
+ "class_":
87
+ AsyncYFDailyTechDBExtractor,
88
+ "default_kwargs": {},
89
+ "description":
90
+ """
91
+ DB_collection: twse_daily_share_price(tw), us_tech(us)
92
+ 開高低收
93
+ """
94
+ },
95
+ "TEJ_tech": {
96
+ "class_":
97
+ AsyncTEJDailyTechDBExtractor,
98
+ "default_kwargs": {},
99
+ "description":
100
+ """
101
+ DB_collection: TEJ_share_price
102
+ TEJ的開高低收
103
+ """
104
+ },
105
+
106
+ # Month Revenue
107
+ "TWSE_month_revenue": {
108
+ "class_":
109
+ AsyncTWSEMonthlyRevenueExtractor,
110
+ "default_kwargs": {},
111
+ "description":
112
+ """
113
+ DB_collection: twse_month_revenue
114
+ TWSE的月營收
115
+ """
116
+ },
117
+
118
+ # Chip
119
+ "TWSE_chip": {
120
+ "class_":
121
+ AsyncTWSEChipDBExtractor,
122
+ "default_kwargs": {
123
+ "fetch_type": "I"
124
+ },
125
+ "description":
126
+ """
127
+ DB_collection: twse_chip
128
+ TWSE的籌碼面
129
+ """
130
+ },
131
+
132
+ # Value
133
+ "TWSE_value": {
134
+ "class_":
135
+ AsyncDailyValueDBExtractor,
136
+ "default_kwargs": {},
137
+ "description":
138
+ """
139
+ DB_collection: twse_daily_share_price
140
+ TWSE的評價面
141
+ """
142
+ }
143
+ }
144
+
145
+
146
+ def get_extractor(collection: str, ticker: str, client, **override_kwargs):
147
+ """
148
+ collection: extractor對應的KEY_NAME,
149
+ ticker: 初始化extractor用的參數, 指定哪家公司
150
+ client: 初始化extractor用的參數, 連接的db_client
151
+ overide_kwargs: 除ticker與client外部分extractor會需要的參數,例如fetch_type
152
+ """
153
+
154
+ config = EXTRACTOR_MAP.get(collection, None)
155
+
156
+ if (config is None):
157
+ # 不存在的extractor name處理
158
+ valid = ", ".join(EXTRACTOR_MAP.keys())
159
+ raise NotSupportedError(
160
+ f"{collection} not a regular argument for collection, only {[valid]} available"
161
+ )
162
+
163
+ extractor_cls = config["class_"]
164
+ default_kwargs: dict = config["default_kwargs"]
165
+
166
+ init_kwargs = {**default_kwargs, **override_kwargs} # 用後續的參數覆蓋
167
+
168
+ return extractor_cls(ticker, client, **init_kwargs)
@@ -0,0 +1,164 @@
1
+ # 本模組提供各種財報、評價、籌碼等資料轉換器的工廠函式,根據 zone(如 "tw"、"us")自動選擇對應的 Transformer 類別。
2
+ # 若 zone 不支援則會拋出 NotSupportedError,方便統一管理與擴充。
3
+
4
+ from typing import Type, TypedDict, Optional
5
+ from neurostats_API.transformers import (
6
+ AgentOverviewTransformer,
7
+ TEJFinanceStatementTransformer,
8
+ TWSEAnnualValueTransformer,
9
+ TWSEBalanceSheetTransformer,
10
+ TWSECashFlowTransformer,
11
+ TWSEChipTransformer,
12
+ TWSEHistoryValueTransformer,
13
+ TWSEMonthlyRevenueTransformer,
14
+ TWSEProfitLoseTransformer,
15
+ USBalanceSheetTransformer,
16
+ USCashFlowTransformer,
17
+ USProfitLoseTransformer
18
+ )
19
+ from neurostats_API.utils import NotSupportedError
20
+
21
+
22
+ def get_balance_sheet_transformer(zone):
23
+ """
24
+ 取得資產負債表(Balance Sheet)對應地區的 Transformer 類別。
25
+ 參數:
26
+ zone: "tw"(台灣)或 "us"(美國)
27
+ 回傳:
28
+ 對應的 Transformer 類別
29
+ """
30
+ transformer_map = {
31
+ "tw": TWSEBalanceSheetTransformer,
32
+ "us": USBalanceSheetTransformer
33
+ }
34
+ return _get_transformer_by_zone("Balance Sheet", zone, transformer_map)
35
+
36
+ def get_cash_flow_transformer(zone):
37
+ """
38
+ 取得現金流量表(Cash Flow)對應地區的 Transformer 類別。
39
+ 參數:
40
+ zone: "tw" 或 "us"
41
+ 回傳:
42
+ 對應的 Transformer 類別
43
+ """
44
+ transformer_map = {
45
+ "tw": TWSECashFlowTransformer,
46
+ "us": USCashFlowTransformer
47
+ }
48
+ return _get_transformer_by_zone("Cash Flow", zone, transformer_map)
49
+
50
+ def get_profit_lose_transformer(zone):
51
+ """
52
+ 取得損益表(Profit Lose, 又稱 Income Statement)對應地區的 Transformer 類別。
53
+ 參數:
54
+ zone: "tw" 或 "us"
55
+ 回傳:
56
+ 對應的 Transformer 類別
57
+ """
58
+
59
+ transformer_map = {
60
+ "tw": TWSEProfitLoseTransformer,
61
+ "us": USProfitLoseTransformer
62
+ }
63
+ return _get_transformer_by_zone("Profit Lose (a.k.a. Income Statement)", zone, transformer_map)
64
+
65
+ def get_chip_transformer(zone):
66
+ """
67
+ 取得籌碼面(Chip)對應地區的 Transformer 類別。
68
+ 參數:
69
+ zone: "tw"
70
+ 回傳:
71
+ 對應的 Transformer 類別
72
+ """
73
+ transformer_map = {
74
+ "tw": TWSEChipTransformer
75
+ }
76
+ return _get_transformer_by_zone("Chip", zone, transformer_map)
77
+
78
+
79
+ def get_monthly_revenue_transformer(zone):
80
+ """
81
+ 取得月營收(Monthly Revenue)對應地區的 Transformer 類別。
82
+ 參數:
83
+ zone: "tw"
84
+ 回傳:
85
+ 對應的 Transformer 類別
86
+ """
87
+ transformer_map = {
88
+ "tw": TWSEMonthlyRevenueTransformer
89
+ }
90
+ return _get_transformer_by_zone("MonthlyRevenue", zone, transformer_map)
91
+
92
+
93
+ def get_annual_value_transformer(zone):
94
+ """
95
+ 取得歷年評價面數值(Annual Value)對應地區的 Transformer 類別。
96
+ 參數:
97
+ zone: "tw"
98
+ 回傳:
99
+ 對應的 Transformer 類別
100
+ """
101
+ transformer_map = {
102
+ "tw": TWSEAnnualValueTransformer
103
+ }
104
+ return _get_transformer_by_zone("AnnualValue", zone, transformer_map)
105
+
106
+
107
+ def get_history_value_transformer(zone):
108
+ """
109
+ 取得歷來評價面數值(History Value)對應地區的 Transformer 類別。
110
+ 參數:
111
+ zone: "tw"
112
+ 回傳:
113
+ 對應的 Transformer 類別
114
+ """
115
+ transformer_map = {
116
+ "tw": TWSEHistoryValueTransformer
117
+ }
118
+ return _get_transformer_by_zone("HistoryValue", zone, transformer_map)
119
+
120
+
121
+ def get_agent_overview_transformer(zone):
122
+ """
123
+ 取得美股券商總覽(Agent Overview)對應地區的 Transformer 類別。
124
+ 參數:
125
+ zone: "us"
126
+ 回傳:
127
+ 對應的 Transformer 類別
128
+ """
129
+ transformer_map = {
130
+ "us": AgentOverviewTransformer
131
+ }
132
+ return _get_transformer_by_zone("AgentOverview", zone, transformer_map)
133
+
134
+
135
+ def get_tej_finance_statement_transformer(zone):
136
+ """
137
+ 取得 TEJ 財報(TEJ Finance Statement)對應地區的 Transformer 類別。
138
+ 參數:
139
+ zone: "tw"
140
+ 回傳:
141
+ 對應的 Transformer 類別
142
+ """
143
+ transformer_map = {
144
+ "tw": TEJFinanceStatementTransformer
145
+ }
146
+ return _get_transformer_by_zone("TEJFinanceStatement", zone, transformer_map)
147
+
148
+ # === 共用內部工具 ===
149
+ def _get_transformer_by_zone(name: str, zone: str, transformer_map: dict):
150
+ """
151
+ 根據 zone 回傳對應的 Transformer 類別,若 zone 不支援則拋出 NotSupportedError。
152
+ 參數:
153
+ name: 字串,Transformer 名稱(用於錯誤訊息)
154
+ zone: 地區代碼
155
+ transformer_map: dict,zone 對應的 Transformer 類別
156
+ 回傳:
157
+ 對應的 Transformer 類別
158
+ """
159
+ transformer = transformer_map.get(zone)
160
+ if not transformer:
161
+ raise NotSupportedError(
162
+ f"[transformer_factory] {name} Transformer only supports {list(transformer_map.keys())}, got '{zone}'"
163
+ )
164
+ return transformer
@@ -0,0 +1,10 @@
1
+ from .balance_sheet import AsyncBalanceSheetFetcher
2
+ from .cash_flow import AsyncCashFlowFetcher
3
+ from .finance_overview import AsyncFinanceOverviewFetcher, AsyncAgentOverviewFetcher
4
+ from .tech import AsyncTechFetcher
5
+ from .twse_institution import AsyncTWSEInstitutionFetcher
6
+ from .twse_margin import AsyncTWSEMarginFetcher
7
+ from .month_revenue import AsyncMonthlyRevenueFetcher
8
+ from .profit_lose import AsyncProfitLoseFetcher
9
+ from .value import AsyncTWSEStatsValueFetcher
10
+ from .tej import AsyncTEJSeasonalFetcher
@@ -0,0 +1,31 @@
1
+ from .base import AsyncBaseFetcher
2
+ from ..db_extractors import AsyncBalanceSheetExtractor
3
+ from neurostats_API.transformers import (
4
+ TWSEBalanceSheetTransformer, USBalanceSheetTransformer
5
+ )
6
+ from neurostats_API.async_mode.factory import get_extractor
7
+ from neurostats_API.utils import NotSupportedError
8
+
9
+
10
+ class AsyncBalanceSheetFetcher(AsyncBaseFetcher):
11
+
12
+ def __init__(self, ticker, client):
13
+ super().__init__()
14
+ self.ticker = ticker
15
+ self.transformer_map = {
16
+ "tw": TWSEBalanceSheetTransformer,
17
+ "us": USBalanceSheetTransformer
18
+ }
19
+ self.extractor = get_extractor("DB_balance_sheet", ticker, client)
20
+ company_name = self.extractor.get_company_name()
21
+ zone = self.extractor.get_zone()
22
+
23
+ transformer = self.get_transformer(zone)
24
+ self.transformer = transformer(ticker, company_name, zone)
25
+
26
+ async def query_data(self, start_date=None, end_date=None):
27
+ fetched_data = await self.extractor.query_data(start_date, end_date)
28
+
29
+ transformed_data = self.transformer.process_transform(fetched_data)
30
+
31
+ return transformed_data
@@ -0,0 +1,48 @@
1
+ import abc
2
+ from datetime import datetime, timedelta, date
3
+ import json
4
+ import pandas as pd
5
+ from pymongo import MongoClient
6
+ import pytz
7
+ from typing import Union
8
+ from neurostats_API.utils import StatsDateTime, StatsProcessor, YoY_Calculator, NotSupportedError
9
+
10
+ class AsyncBaseFetcher(abc.ABC):
11
+ """
12
+ Fetcher目的: 真正給其他使用者的接口
13
+ """
14
+ def __init__(self):
15
+ self.timezone = pytz.timezone("Asia/Taipei")
16
+ self.target_metric_dict = {
17
+ 'value': ['value'],
18
+ 'value_and_percentage': ['value', 'percentage'],
19
+ 'percentage': ['percentage'],
20
+ 'grand_total': ['grand_total'],
21
+ 'grand_total_values': ['grand_total', 'grand_total_percentage'],
22
+ 'grand_total_percentage': ['grand_total_percentage'],
23
+ 'growth': [f'YoY_{i}' for i in [1, 3, 5, 10]],
24
+ 'grand_total_growth': [f"YoY_{i}" for i in [1, 3, 5, 10]]
25
+ }
26
+ self.transformer_map = None
27
+ @ abc.abstractmethod
28
+ async def query_data(self):
29
+ """
30
+ user使用的接口
31
+ """
32
+ return NotImplementedError()
33
+
34
+ @staticmethod
35
+ def str_to_datetime(self, date_str: str) -> StatsDateTime:
36
+ date = self.timezone.localize(datetime.strptime(date_str, "%Y-%m-%d"))
37
+ year, month, day = date.year, date.month, date.day
38
+ season = (month - 1) // 3 + 1
39
+ return StatsDateTime(date, year, month, day, season)
40
+
41
+ def get_transformer(self, zone):
42
+ transformer = self.transformer_map.get(zone)
43
+ if (transformer is None):
44
+ raise NotSupportedError(
45
+ f"{self.__class__.__name__} get transformer failed: only supports {list(self.transformer_map.keys())}, got {zone}"
46
+ )
47
+
48
+ return transformer