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
@@ -0,0 +1,159 @@
|
|
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
|
+
class AsyncBaseSeasonalDBExtractor(BaseDBExtractor):
|
11
|
+
|
12
|
+
def __init__(self, ticker, client):
|
13
|
+
super().__init__(ticker, client)
|
14
|
+
|
15
|
+
self.collection_name_map = self._get_collection_map()
|
16
|
+
self.collection_name = self._get_collection_name()
|
17
|
+
if (self.collection_name is None):
|
18
|
+
raise NotSupportedError(
|
19
|
+
f"{self.__class__.__name__} only supports {list(self.collection_name_map.keys())}, got {self.zone} with ticker = \"{self.ticker}\""
|
20
|
+
)
|
21
|
+
self.collection = self.db_client.get_collection(
|
22
|
+
self.collection_name
|
23
|
+
)
|
24
|
+
|
25
|
+
self.column_name_map = None
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
async def query_data(
|
31
|
+
self,
|
32
|
+
start_date = None,
|
33
|
+
end_date = None,
|
34
|
+
get_latest = False # 決定是否只取得最新一筆資料
|
35
|
+
):
|
36
|
+
if (start_date is None):
|
37
|
+
start_date = "1991-01-01"
|
38
|
+
if (end_date is None):
|
39
|
+
end_date = self._get_today()
|
40
|
+
|
41
|
+
start_year, start_season = self._get_year_and_season(
|
42
|
+
date = start_date
|
43
|
+
)
|
44
|
+
end_year, end_season = self._get_year_and_season(
|
45
|
+
date = end_date
|
46
|
+
)
|
47
|
+
|
48
|
+
query, projection, sort = self._prepare_query(
|
49
|
+
start_year,
|
50
|
+
start_season,
|
51
|
+
end_year,
|
52
|
+
end_season,
|
53
|
+
get_latest
|
54
|
+
)
|
55
|
+
cursor = self.collection.find(query, projection).sort(sort)
|
56
|
+
|
57
|
+
if (get_latest):
|
58
|
+
cursor = cursor.limit(1)
|
59
|
+
|
60
|
+
fetched_data = [data async for data in cursor]
|
61
|
+
|
62
|
+
return fetched_data
|
63
|
+
|
64
|
+
def _get_collection_name(self):
|
65
|
+
|
66
|
+
return self.collection_name_map.get(self.zone, None)
|
67
|
+
|
68
|
+
def _get_collection_map(self):
|
69
|
+
return {
|
70
|
+
"tw": "twse_seasonal_report",
|
71
|
+
"us": "us_fundamentals"
|
72
|
+
}
|
73
|
+
|
74
|
+
def _prepare_query(
|
75
|
+
self,
|
76
|
+
start_year = None,
|
77
|
+
start_season = None,
|
78
|
+
end_year = None,
|
79
|
+
end_season = None,
|
80
|
+
get_latest = False
|
81
|
+
):
|
82
|
+
chart_name = self.column_name_map.get(self.get_zone(), None)
|
83
|
+
|
84
|
+
query = {
|
85
|
+
"ticker": self.ticker
|
86
|
+
}
|
87
|
+
|
88
|
+
query = self._update_query_with_year_season(
|
89
|
+
query,
|
90
|
+
start_year,
|
91
|
+
start_season,
|
92
|
+
end_year,
|
93
|
+
end_season
|
94
|
+
)
|
95
|
+
|
96
|
+
projection = {
|
97
|
+
"_id": 0,
|
98
|
+
"ticker": 1,
|
99
|
+
"year": 1,
|
100
|
+
"season": 1,
|
101
|
+
chart_name: 1,
|
102
|
+
}
|
103
|
+
|
104
|
+
if (get_latest):
|
105
|
+
sort = [
|
106
|
+
("year", DESCENDING),
|
107
|
+
("season", DESCENDING)
|
108
|
+
]
|
109
|
+
else:
|
110
|
+
sort = [
|
111
|
+
("year", ASCENDING),
|
112
|
+
("season", ASCENDING)
|
113
|
+
]
|
114
|
+
|
115
|
+
return query, projection, sort
|
116
|
+
|
117
|
+
|
118
|
+
def _get_year_and_season(self, date):
|
119
|
+
if (isinstance(date, str)):
|
120
|
+
date = datetime.strptime(date, "%Y-%m-%d")
|
121
|
+
|
122
|
+
year = date.year
|
123
|
+
month = date.month
|
124
|
+
|
125
|
+
season = (month - 1) // 3 + 1
|
126
|
+
|
127
|
+
return year, season
|
128
|
+
|
129
|
+
def _update_query_with_year_season(
|
130
|
+
self,
|
131
|
+
query,
|
132
|
+
start_year,
|
133
|
+
start_season,
|
134
|
+
end_year,
|
135
|
+
end_season
|
136
|
+
):
|
137
|
+
if all(v is not None for v in [start_year, start_season]):
|
138
|
+
# 大於start_year條件
|
139
|
+
query.update({
|
140
|
+
"$or": [
|
141
|
+
{"year": {"$gt": start_year}},
|
142
|
+
{"year": start_year, "season": {"$gte": start_season}},
|
143
|
+
]
|
144
|
+
})
|
145
|
+
|
146
|
+
if all(v is not None for v in [end_year, end_season]):
|
147
|
+
# 小於end_year條件
|
148
|
+
query.update({
|
149
|
+
"$and": query.get("$and", []) + [
|
150
|
+
{
|
151
|
+
"$or": [
|
152
|
+
{"year": {"$lt": end_year}},
|
153
|
+
{"year": end_year, "season": {"$lte": end_season}},
|
154
|
+
]
|
155
|
+
}
|
156
|
+
]
|
157
|
+
})
|
158
|
+
|
159
|
+
return query
|
@@ -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
|