neurostats-API 0.0.18__tar.gz → 0.0.20__tar.gz
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-0.0.18/neurostats_API.egg-info → neurostats_api-0.0.20}/PKG-INFO +39 -12
- neurostats_api-0.0.18/PKG-INFO → neurostats_api-0.0.20/README.md +37 -26
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/__init__.py +3 -2
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/fetchers/__init__.py +1 -1
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/fetchers/base.py +41 -13
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/fetchers/tech.py +107 -80
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/fetchers/tej_finance_report.py +180 -78
- neurostats_api-0.0.18/README.md → neurostats_api-0.0.20/neurostats_API.egg-info/PKG-INFO +54 -11
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API.egg-info/requires.txt +1 -1
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/setup.py +2 -2
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/test/test_fetchers.py +9 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/test/test_tej.py +12 -8
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/MANIFEST.in +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/cli.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/fetchers/balance_sheet.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/fetchers/cash_flow.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/fetchers/finance_overview.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/fetchers/institution.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/fetchers/margin_trading.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/fetchers/month_revenue.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/fetchers/profit_lose.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/fetchers/value_invest.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/main.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/tools/balance_sheet.yaml +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/tools/cash_flow_percentage.yaml +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/tools/finance_overview_dict.yaml +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/tools/profit_lose.yaml +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/tools/seasonal_data_field_dict.txt +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/tools/tej_db_index.yaml +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/utils/__init__.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/utils/calculate_value.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/utils/data_process.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/utils/datetime.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/utils/db_client.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/utils/fetcher.py +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API.egg-info/SOURCES.txt +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API.egg-info/dependency_links.txt +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API.egg-info/top_level.txt +0 -0
- {neurostats_api-0.0.18 → neurostats_api-0.0.20}/setup.cfg +0 -0
@@ -1,13 +1,13 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: neurostats_API
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.20
|
4
4
|
Summary: The service of NeuroStats website
|
5
5
|
Home-page: https://github.com/NeurowattStats/NeuroStats_API.git
|
6
6
|
Author: JasonWang@Neurowatt
|
7
7
|
Author-email: jason@neurowatt.ai
|
8
8
|
Requires-Python: >=3.6
|
9
9
|
Description-Content-Type: text/markdown
|
10
|
-
Requires-Dist: numpy
|
10
|
+
Requires-Dist: numpy
|
11
11
|
Requires-Dist: pandas>=2.2.0
|
12
12
|
Requires-Dist: pymongo
|
13
13
|
Requires-Dist: pytz
|
@@ -728,6 +728,7 @@ data = fetcher.get(
|
|
728
728
|
#### 回傳資料
|
729
729
|
##### `YOY_NOCAL` 與 `QOQ_NOCAL`
|
730
730
|
為回傳`pd.DataFrame`,column名稱為<年份>Q<季>, row名稱為指定財報項目
|
731
|
+
|
731
732
|
```Python
|
732
733
|
# fetch_mode = fetcher.FetchMode.QOQ_NOCAL
|
733
734
|
2024Q3 2024Q2 2024Q1
|
@@ -742,17 +743,18 @@ bp51 3.111298e+09 3.173919e+09 2.453840e+09
|
|
742
743
|
|
743
744
|
##### `YOY` 與 `QOQ`
|
744
745
|
回傳為`Dict[pd.DataFrame]`, key 為指定的index, DataFrame中則是該index歷年的數值與成長率
|
746
|
+
成長率單位為`%`
|
745
747
|
```Python
|
746
748
|
# fetch_mode = fetcher.FetchMode.QOQ
|
747
749
|
{
|
748
750
|
'bp41':
|
749
751
|
2024Q3 2024Q2 2024Q1
|
750
752
|
value 7.082005e+07 6.394707e+07 5.761001e+07
|
751
|
-
growth 1.074791e
|
753
|
+
growth 1.074791e+01 1.099994e+01 5.532101e-01,
|
752
754
|
'bp51':
|
753
755
|
2024Q3 2024Q2 2024Q1
|
754
756
|
value 3.111298e+09 3.145373e+09 3.091985e+09
|
755
|
-
growth -1.083335e
|
757
|
+
growth -1.083335e+00 1.726663e+00 -4.159542e-01
|
756
758
|
}
|
757
759
|
|
758
760
|
# fetch_mode = fetcher.FetchMode.YOY
|
@@ -760,17 +762,17 @@ growth -1.083335e-02 1.726663e-02 -4.159542e-03
|
|
760
762
|
'bp41':
|
761
763
|
2024Q3 2023Q3 2022Q3
|
762
764
|
value 7.082005e+07 5.377231e+07 6.201822e+07
|
763
|
-
YoY_1
|
764
|
-
YoY_3 1.729171e
|
765
|
-
YoY_5 1.389090e
|
766
|
-
YoY_10 1.255138e
|
765
|
+
YoY_1 3.170357e+01 -1.329596e+01 4.130744e+01
|
766
|
+
YoY_3 1.729171e+01 9.556684e+00 1.883274e+01
|
767
|
+
YoY_5 1.389090e+01 1.215242e+01 1.642914e+01
|
768
|
+
YoY_10 1.255138e+01 1.356297e+01 1.559702e+01
|
767
769
|
'bp51':
|
768
770
|
2024Q3 2023Q3 2022Q3
|
769
771
|
value 3.111298e+09 3.173919e+09 2.453840e+09
|
770
|
-
YoY_1
|
771
|
-
YoY_3 1.866752e
|
772
|
-
YoY_5 2.068132e
|
773
|
-
YoY_10 1.420500e
|
772
|
+
YoY_1 -1.972987e+00 2.934499e+01 3.179539e+01
|
773
|
+
YoY_3 1.866752e+01 2.766851e+01 2.638677e+01
|
774
|
+
YoY_5 2.068132e+01 2.479698e+01 1.815106e+01
|
775
|
+
YoY_10 1.420500e+01 1.586797e+01 1.551364e+01
|
774
776
|
}
|
775
777
|
```
|
776
778
|
|
@@ -819,3 +821,28 @@ data = fetcher.get(
|
|
819
821
|
|
820
822
|
[TEJ資料集連結](https://tquant.tejwin.com/%E8%B3%87%E6%96%99%E9%9B%86/)
|
821
823
|
請看 `公司自結數`
|
824
|
+
|
825
|
+
### 開高低收
|
826
|
+
```Python
|
827
|
+
mongo_uri = <MongoDB 的 URI>
|
828
|
+
db_name = 'company' # 連接的DB名稱
|
829
|
+
collection_name = "TWN/APIPRCD" # 連接的collection對象
|
830
|
+
from neurostats_API import TEJStockPriceFetcher
|
831
|
+
|
832
|
+
fetcher = TEJStockPriceFetcher(
|
833
|
+
mongo_uri = mongo_uri,
|
834
|
+
db_name = db_name,
|
835
|
+
collection_name = collection_name
|
836
|
+
)
|
837
|
+
|
838
|
+
data = fetcher.get(
|
839
|
+
ticker = "2330" # 任意的股票代碼
|
840
|
+
start_date = "2005-01-01",
|
841
|
+
period = "3m"
|
842
|
+
) # -> pd.DataFrame
|
843
|
+
```
|
844
|
+
- `ticker`: 股票代碼
|
845
|
+
- `start_date`: 搜尋範圍的開始日期
|
846
|
+
- `period`: 搜尋的時間範圍長度
|
847
|
+
|
848
|
+
`period`與`start_date`同時存在時以period優先
|
@@ -1,19 +1,3 @@
|
|
1
|
-
Metadata-Version: 2.1
|
2
|
-
Name: neurostats_API
|
3
|
-
Version: 0.0.18
|
4
|
-
Summary: The service of NeuroStats website
|
5
|
-
Home-page: https://github.com/NeurowattStats/NeuroStats_API.git
|
6
|
-
Author: JasonWang@Neurowatt
|
7
|
-
Author-email: jason@neurowatt.ai
|
8
|
-
Requires-Python: >=3.6
|
9
|
-
Description-Content-Type: text/markdown
|
10
|
-
Requires-Dist: numpy>=2.1.0
|
11
|
-
Requires-Dist: pandas>=2.2.0
|
12
|
-
Requires-Dist: pymongo
|
13
|
-
Requires-Dist: pytz
|
14
|
-
Requires-Dist: python-dotenv
|
15
|
-
Requires-Dist: yfinance
|
16
|
-
|
17
1
|
# neurostats_API
|
18
2
|
|
19
3
|
- [檔案架構](#檔案架構)
|
@@ -728,6 +712,7 @@ data = fetcher.get(
|
|
728
712
|
#### 回傳資料
|
729
713
|
##### `YOY_NOCAL` 與 `QOQ_NOCAL`
|
730
714
|
為回傳`pd.DataFrame`,column名稱為<年份>Q<季>, row名稱為指定財報項目
|
715
|
+
|
731
716
|
```Python
|
732
717
|
# fetch_mode = fetcher.FetchMode.QOQ_NOCAL
|
733
718
|
2024Q3 2024Q2 2024Q1
|
@@ -742,17 +727,18 @@ bp51 3.111298e+09 3.173919e+09 2.453840e+09
|
|
742
727
|
|
743
728
|
##### `YOY` 與 `QOQ`
|
744
729
|
回傳為`Dict[pd.DataFrame]`, key 為指定的index, DataFrame中則是該index歷年的數值與成長率
|
730
|
+
成長率單位為`%`
|
745
731
|
```Python
|
746
732
|
# fetch_mode = fetcher.FetchMode.QOQ
|
747
733
|
{
|
748
734
|
'bp41':
|
749
735
|
2024Q3 2024Q2 2024Q1
|
750
736
|
value 7.082005e+07 6.394707e+07 5.761001e+07
|
751
|
-
growth 1.074791e
|
737
|
+
growth 1.074791e+01 1.099994e+01 5.532101e-01,
|
752
738
|
'bp51':
|
753
739
|
2024Q3 2024Q2 2024Q1
|
754
740
|
value 3.111298e+09 3.145373e+09 3.091985e+09
|
755
|
-
growth -1.083335e
|
741
|
+
growth -1.083335e+00 1.726663e+00 -4.159542e-01
|
756
742
|
}
|
757
743
|
|
758
744
|
# fetch_mode = fetcher.FetchMode.YOY
|
@@ -760,17 +746,17 @@ growth -1.083335e-02 1.726663e-02 -4.159542e-03
|
|
760
746
|
'bp41':
|
761
747
|
2024Q3 2023Q3 2022Q3
|
762
748
|
value 7.082005e+07 5.377231e+07 6.201822e+07
|
763
|
-
YoY_1
|
764
|
-
YoY_3 1.729171e
|
765
|
-
YoY_5 1.389090e
|
766
|
-
YoY_10 1.255138e
|
749
|
+
YoY_1 3.170357e+01 -1.329596e+01 4.130744e+01
|
750
|
+
YoY_3 1.729171e+01 9.556684e+00 1.883274e+01
|
751
|
+
YoY_5 1.389090e+01 1.215242e+01 1.642914e+01
|
752
|
+
YoY_10 1.255138e+01 1.356297e+01 1.559702e+01
|
767
753
|
'bp51':
|
768
754
|
2024Q3 2023Q3 2022Q3
|
769
755
|
value 3.111298e+09 3.173919e+09 2.453840e+09
|
770
|
-
YoY_1
|
771
|
-
YoY_3 1.866752e
|
772
|
-
YoY_5 2.068132e
|
773
|
-
YoY_10 1.420500e
|
756
|
+
YoY_1 -1.972987e+00 2.934499e+01 3.179539e+01
|
757
|
+
YoY_3 1.866752e+01 2.766851e+01 2.638677e+01
|
758
|
+
YoY_5 2.068132e+01 2.479698e+01 1.815106e+01
|
759
|
+
YoY_10 1.420500e+01 1.586797e+01 1.551364e+01
|
774
760
|
}
|
775
761
|
```
|
776
762
|
|
@@ -819,3 +805,28 @@ data = fetcher.get(
|
|
819
805
|
|
820
806
|
[TEJ資料集連結](https://tquant.tejwin.com/%E8%B3%87%E6%96%99%E9%9B%86/)
|
821
807
|
請看 `公司自結數`
|
808
|
+
|
809
|
+
### 開高低收
|
810
|
+
```Python
|
811
|
+
mongo_uri = <MongoDB 的 URI>
|
812
|
+
db_name = 'company' # 連接的DB名稱
|
813
|
+
collection_name = "TWN/APIPRCD" # 連接的collection對象
|
814
|
+
from neurostats_API import TEJStockPriceFetcher
|
815
|
+
|
816
|
+
fetcher = TEJStockPriceFetcher(
|
817
|
+
mongo_uri = mongo_uri,
|
818
|
+
db_name = db_name,
|
819
|
+
collection_name = collection_name
|
820
|
+
)
|
821
|
+
|
822
|
+
data = fetcher.get(
|
823
|
+
ticker = "2330" # 任意的股票代碼
|
824
|
+
start_date = "2005-01-01",
|
825
|
+
period = "3m"
|
826
|
+
) # -> pd.DataFrame
|
827
|
+
```
|
828
|
+
- `ticker`: 股票代碼
|
829
|
+
- `start_date`: 搜尋範圍的開始日期
|
830
|
+
- `period`: 搜尋的時間範圍長度
|
831
|
+
|
832
|
+
`period`與`start_date`同時存在時以period優先
|
@@ -1,4 +1,4 @@
|
|
1
|
-
__version__='0.0.
|
1
|
+
__version__='0.0.20'
|
2
2
|
|
3
3
|
from .fetchers import (
|
4
4
|
BalanceSheetFetcher,
|
@@ -9,5 +9,6 @@ from .fetchers import (
|
|
9
9
|
MarginTradingFetcher,
|
10
10
|
MonthRevenueFetcher,
|
11
11
|
TechFetcher,
|
12
|
-
|
12
|
+
TEJStockPriceFetcher,
|
13
|
+
ProfitLoseFetcher,
|
13
14
|
)
|
@@ -2,7 +2,7 @@ from .base import StatsDateTime, StatsFetcher
|
|
2
2
|
from .balance_sheet import BalanceSheetFetcher
|
3
3
|
from .cash_flow import CashFlowFetcher
|
4
4
|
from .finance_overview import FinanceOverviewFetcher
|
5
|
-
from .tej_finance_report import FinanceReportFetcher
|
5
|
+
from .tej_finance_report import FinanceReportFetcher, TEJStockPriceFetcher
|
6
6
|
from .tech import TechFetcher
|
7
7
|
from .institution import InstitutionFetcher
|
8
8
|
from .margin_trading import MarginTradingFetcher
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import abc
|
2
|
+
from typing import Union
|
2
3
|
from pymongo import MongoClient
|
3
4
|
import pandas as pd
|
4
5
|
import json
|
@@ -53,8 +54,8 @@ class StatsFetcher:
|
|
53
54
|
season = (month - 1) // 3 + 1
|
54
55
|
|
55
56
|
return StatsDateTime(date, year, month, day, season)
|
56
|
-
|
57
|
-
def has_required_columns(self, df:pd.DataFrame, required_cols=None):
|
57
|
+
|
58
|
+
def has_required_columns(self, df: pd.DataFrame, required_cols=None):
|
58
59
|
"""
|
59
60
|
Check if the required columns are present in the DataFrame.
|
60
61
|
|
@@ -68,23 +69,26 @@ class StatsFetcher:
|
|
68
69
|
"""
|
69
70
|
if required_cols is None:
|
70
71
|
required_cols = ['date', 'open', 'high', 'low', 'close', 'volume']
|
71
|
-
|
72
|
+
|
72
73
|
return all(col in df.columns for col in required_cols)
|
73
74
|
|
74
75
|
|
75
76
|
class BaseTEJFetcher(abc.ABC):
|
76
77
|
|
77
|
-
def __init__(self):
|
78
|
-
self.client = None
|
79
|
-
self.db = None
|
80
|
-
self.collection = None
|
81
|
-
|
82
78
|
@abc.abstractmethod
|
83
79
|
def get(self):
|
84
80
|
pass
|
85
81
|
|
86
82
|
def get_latest_data_time(self, ticker):
|
87
|
-
latest_data = self.collection.find_one(
|
83
|
+
latest_data = self.collection.find_one(
|
84
|
+
{
|
85
|
+
"ticker": ticker
|
86
|
+
},
|
87
|
+
{
|
88
|
+
"last_update": 1,
|
89
|
+
"_id": 0
|
90
|
+
}
|
91
|
+
)
|
88
92
|
|
89
93
|
try:
|
90
94
|
latest_date = latest_data['last_update']["latest_data_date"]
|
@@ -93,7 +97,8 @@ class BaseTEJFetcher(abc.ABC):
|
|
93
97
|
|
94
98
|
return latest_date
|
95
99
|
|
96
|
-
def cal_YoY(
|
100
|
+
def cal_YoY(
|
101
|
+
self, data_dict: dict, start_year: int, end_year: int, season: int):
|
97
102
|
year_shifts = [1, 3, 5, 10]
|
98
103
|
return_dict = {}
|
99
104
|
for year in range(start_year, end_year + 1):
|
@@ -115,7 +120,8 @@ class BaseTEJFetcher(abc.ABC):
|
|
115
120
|
try:
|
116
121
|
past_year = year - shift
|
117
122
|
last_value = data_dict[f"{past_year}Q{season}"][key]
|
118
|
-
temp_dict[f"YoY_{shift}"] = YoY_Calculator.cal_growth(
|
123
|
+
temp_dict[f"YoY_{shift}"] = YoY_Calculator.cal_growth(
|
124
|
+
this_value, last_value, delta=shift) * 100
|
119
125
|
except Exception as e:
|
120
126
|
temp_dict[f"YoY_{shift}"] = None
|
121
127
|
|
@@ -153,9 +159,11 @@ class BaseTEJFetcher(abc.ABC):
|
|
153
159
|
temp_dict = {"value": this_value}
|
154
160
|
|
155
161
|
try:
|
156
|
-
last_value = data_dict[f"{last_year}Q{last_season}"][
|
162
|
+
last_value = data_dict[f"{last_year}Q{last_season}"][
|
163
|
+
key]['value']
|
157
164
|
|
158
|
-
temp_dict['growth'] = YoY_Calculator.cal_growth(
|
165
|
+
temp_dict['growth'] = YoY_Calculator.cal_growth(
|
166
|
+
this_value, last_value, delta=1) * 100
|
159
167
|
except Exception as e:
|
160
168
|
temp_dict['growth'] = None
|
161
169
|
|
@@ -173,3 +181,23 @@ class BaseTEJFetcher(abc.ABC):
|
|
173
181
|
for key in data_dict.keys():
|
174
182
|
data_dict[key] = pd.DataFrame.from_dict(data_dict[key])
|
175
183
|
return data_dict
|
184
|
+
|
185
|
+
def set_time_shift(self, date: Union[str, datetime], period: str):
|
186
|
+
if isinstance(date, str):
|
187
|
+
date = datetime.strptime(date, "%Y-%m-%d")
|
188
|
+
|
189
|
+
period_mapping = {
|
190
|
+
"1d": timedelta(days=1),
|
191
|
+
"7d": timedelta(days=7),
|
192
|
+
"1m": timedelta(days=30),
|
193
|
+
"3m": timedelta(days=90),
|
194
|
+
"1y": timedelta(days=365),
|
195
|
+
"3y": timedelta(days=365 * 3),
|
196
|
+
"5y": timedelta(days=365 * 5),
|
197
|
+
"10y": timedelta(days=365 * 10),
|
198
|
+
}
|
199
|
+
|
200
|
+
if period == "all":
|
201
|
+
return datetime.strptime("1991-01-01", "%Y-%m-%d")
|
202
|
+
|
203
|
+
return date - period_mapping.get(period, timedelta(days=0)) # 預設為不變
|
@@ -2,48 +2,49 @@ from .base import StatsFetcher
|
|
2
2
|
import pandas as pd
|
3
3
|
import yfinance as yf
|
4
4
|
|
5
|
+
|
5
6
|
class TechFetcher(StatsFetcher):
|
6
|
-
|
7
|
-
def __init__(self, ticker:str, db_client):
|
8
7
|
|
8
|
+
def __init__(self, ticker: str, db_client):
|
9
9
|
"""
|
10
10
|
The Capitalization-Weighted Index includes the following tickers:
|
11
11
|
['GSPC', 'IXIC', 'DJI', 'TWII']
|
12
12
|
"""
|
13
|
-
|
13
|
+
|
14
14
|
super().__init__(ticker, db_client)
|
15
|
+
self.collection = self.db["TWN/APIPRCD"]
|
15
16
|
self.full_ohlcv = self._get_ohlcv()
|
16
|
-
self.basic_indexes = [
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
self.basic_indexes = [
|
18
|
+
'SMA5', 'SMA20', 'SMA60', 'EMA5', 'EMA20', 'EMA40', 'EMA12',
|
19
|
+
'EMA26', 'RSI7', 'RSI14', 'RSI21', 'MACD', 'Signal Line',
|
20
|
+
'Middle Band', 'Upper Band', 'Lower Band', '%b', 'ATR', 'BBW',
|
21
|
+
'EMA Cycle', 'EMA Cycle Instructions', 'Day Trading Signal'
|
22
|
+
]
|
23
|
+
|
23
24
|
self.daily_index = TechProcessor.cal_basic_index(self.full_ohlcv)
|
24
25
|
|
25
26
|
self.weekly_index = TechProcessor.resample(
|
26
|
-
self.daily_index,
|
27
|
-
period=
|
28
|
-
technical_indicators
|
27
|
+
self.daily_index,
|
28
|
+
period='W',
|
29
|
+
technical_indicators=self.basic_indexes
|
29
30
|
)
|
30
31
|
|
31
32
|
self.monthly_index = TechProcessor.resample(
|
32
|
-
self.daily_index,
|
33
|
-
period=
|
34
|
-
technical_indicators
|
33
|
+
self.daily_index,
|
34
|
+
period='ME',
|
35
|
+
technical_indicators=self.basic_indexes
|
35
36
|
)
|
36
37
|
|
37
38
|
self.quarterly_index = TechProcessor.resample(
|
38
|
-
self.daily_index,
|
39
|
-
period=
|
40
|
-
technical_indicators
|
39
|
+
self.daily_index,
|
40
|
+
period='QE',
|
41
|
+
technical_indicators=self.basic_indexes
|
41
42
|
)
|
42
43
|
|
43
44
|
self.yearly_index = TechProcessor.resample(
|
44
|
-
self.daily_index,
|
45
|
-
period=
|
46
|
-
technical_indicators
|
45
|
+
self.daily_index,
|
46
|
+
period='YE',
|
47
|
+
technical_indicators=self.basic_indexes
|
47
48
|
)
|
48
49
|
|
49
50
|
def _get_ohlcv(self):
|
@@ -51,25 +52,7 @@ class TechFetcher(StatsFetcher):
|
|
51
52
|
required_cols = ['date', 'open', 'high', 'low', 'close', 'volume']
|
52
53
|
|
53
54
|
try:
|
54
|
-
|
55
|
-
ticker_full = self.collection.find_one(query)
|
56
|
-
|
57
|
-
if not ticker_full:
|
58
|
-
raise ValueError(f"No data found for ticker: {self.ticker}")
|
59
|
-
|
60
|
-
daily_data = ticker_full.get("daily_data", [])
|
61
|
-
if not isinstance(daily_data, list):
|
62
|
-
raise TypeError("Expected 'daily_data' to be a list.")
|
63
|
-
|
64
|
-
df = pd.DataFrame(daily_data)
|
65
|
-
|
66
|
-
if not self.has_required_columns(df, required_cols):
|
67
|
-
raise KeyError(f"Missing required columns")
|
68
|
-
|
69
|
-
except (KeyError, ValueError, TypeError) as e:
|
70
|
-
|
71
|
-
print(f"Conduct yf searching")
|
72
|
-
|
55
|
+
# 先對yf search
|
73
56
|
if self.ticker in ['GSPC', 'IXIC', 'DJI', 'TWII']:
|
74
57
|
full_tick = f'^{self.ticker}'
|
75
58
|
else:
|
@@ -80,13 +63,41 @@ class TechFetcher(StatsFetcher):
|
|
80
63
|
if not self.has_required_columns(df, required_cols):
|
81
64
|
|
82
65
|
print(f".tw failed, try .two")
|
83
|
-
|
66
|
+
|
84
67
|
full_tick = f'{self.ticker}.two'
|
85
68
|
|
86
69
|
df = self.conduct_yf_search(full_tick)
|
87
|
-
|
88
|
-
return df[required_cols]
|
89
70
|
|
71
|
+
if (df.empty):
|
72
|
+
raise ValueError(f"No data found for ticker: {self.ticker}")
|
73
|
+
|
74
|
+
except (KeyError, ValueError, TypeError) as e:
|
75
|
+
# 再對TEJ search
|
76
|
+
tej_required_cols = [
|
77
|
+
"mdate", "open_d", 'high_d', 'low_d', 'close_d', 'vol'
|
78
|
+
]
|
79
|
+
tej_name_proj = {
|
80
|
+
tej_name: org_name
|
81
|
+
for tej_name, org_name in zip(tej_required_cols, required_cols)
|
82
|
+
}
|
83
|
+
|
84
|
+
query = {'ticker': self.ticker}
|
85
|
+
ticker_full = self.collection.find_one(query)
|
86
|
+
|
87
|
+
if not ticker_full:
|
88
|
+
raise ValueError("No ticker found in database")
|
89
|
+
|
90
|
+
daily_data = ticker_full.get("data", [])
|
91
|
+
if not isinstance(daily_data, list):
|
92
|
+
raise TypeError("Expected 'daily_data' to be a list.")
|
93
|
+
|
94
|
+
df = pd.DataFrame(daily_data)
|
95
|
+
|
96
|
+
if not self.has_required_columns(df, tej_required_cols):
|
97
|
+
raise KeyError(f"Missing required columns")
|
98
|
+
df = df.rename(columns=tej_name_proj)
|
99
|
+
|
100
|
+
return df[required_cols]
|
90
101
|
|
91
102
|
def get_daily(self):
|
92
103
|
|
@@ -95,7 +106,7 @@ class TechFetcher(StatsFetcher):
|
|
95
106
|
def get_weekly(self):
|
96
107
|
|
97
108
|
return self.weekly_index
|
98
|
-
|
109
|
+
|
99
110
|
def get_monthly(self):
|
100
111
|
|
101
112
|
return self.monthly_index
|
@@ -103,19 +114,19 @@ class TechFetcher(StatsFetcher):
|
|
103
114
|
def get_quarterly(self):
|
104
115
|
|
105
116
|
return self.quarterly_index
|
106
|
-
|
117
|
+
|
107
118
|
def get_yearly(self):
|
108
119
|
|
109
120
|
return self.yearly_index
|
110
|
-
|
111
|
-
def conduct_yf_search(self, ticker:str):
|
121
|
+
|
122
|
+
def conduct_yf_search(self, ticker: str):
|
112
123
|
|
113
124
|
yf_ticker = yf.Ticker(ticker)
|
114
125
|
origin_df = yf_ticker.history(period="10y")
|
115
|
-
|
126
|
+
|
116
127
|
if origin_df.empty:
|
117
128
|
return origin_df
|
118
|
-
|
129
|
+
|
119
130
|
origin_df = origin_df.reset_index()
|
120
131
|
origin_df["Date"] = pd.to_datetime(origin_df["Date"])
|
121
132
|
df = origin_df.rename(
|
@@ -131,8 +142,9 @@ class TechFetcher(StatsFetcher):
|
|
131
142
|
|
132
143
|
return df
|
133
144
|
|
145
|
+
|
134
146
|
class TechProcessor:
|
135
|
-
|
147
|
+
|
136
148
|
@staticmethod
|
137
149
|
def cal_sma(closes: pd.Series, n_days: int) -> pd.Series:
|
138
150
|
return closes.rolling(window=n_days).mean()
|
@@ -158,7 +170,9 @@ class TechProcessor:
|
|
158
170
|
return macds.ewm(span=n_days, adjust=False).mean()
|
159
171
|
|
160
172
|
@staticmethod
|
161
|
-
def cal_bollinger_bands(
|
173
|
+
def cal_bollinger_bands(
|
174
|
+
closes: pd.Series, n_days: int = 20
|
175
|
+
) -> pd.DataFrame:
|
162
176
|
middle = closes.rolling(window=n_days).mean()
|
163
177
|
upper = middle + 2 * closes.rolling(window=n_days).std()
|
164
178
|
lower = middle - 2 * closes.rolling(window=n_days).std()
|
@@ -174,12 +188,15 @@ class TechProcessor:
|
|
174
188
|
)
|
175
189
|
|
176
190
|
@staticmethod
|
177
|
-
def cal_atr(
|
191
|
+
def cal_atr(
|
192
|
+
highes: pd.Series, lows: pd.Series, closes: pd.Series, n_days: int
|
193
|
+
) -> pd.Series:
|
178
194
|
high_low = highes - lows
|
179
195
|
high_close = (highes - closes.shift(1)).abs()
|
180
196
|
low_close = (lows - closes.shift(1)).abs()
|
181
197
|
|
182
|
-
true_range = pd.concat([high_low, high_close, low_close],
|
198
|
+
true_range = pd.concat([high_low, high_close, low_close],
|
199
|
+
axis=1).max(axis=1)
|
183
200
|
atr = true_range.rolling(window=n_days, min_periods=1).mean()
|
184
201
|
|
185
202
|
return atr
|
@@ -233,15 +250,15 @@ class TechProcessor:
|
|
233
250
|
return '今日此股票為好的當沖標的'
|
234
251
|
else:
|
235
252
|
return f'今日此股票並非好的當沖標的, 原因: {", ".join(reasons)}'
|
236
|
-
|
253
|
+
|
237
254
|
@staticmethod
|
238
|
-
def cal_basic_index(ohlcvs:pd.DataFrame):
|
239
|
-
|
255
|
+
def cal_basic_index(ohlcvs: pd.DataFrame):
|
256
|
+
|
240
257
|
# SMA
|
241
|
-
ohlcvs['SMA5'] = TechProcessor.cal_sma(ohlcvs['close'], 5)
|
258
|
+
ohlcvs['SMA5'] = TechProcessor.cal_sma(ohlcvs['close'], 5)
|
242
259
|
ohlcvs['SMA20'] = TechProcessor.cal_sma(ohlcvs['close'], 20)
|
243
260
|
ohlcvs['SMA60'] = TechProcessor.cal_sma(ohlcvs['close'], 40)
|
244
|
-
|
261
|
+
|
245
262
|
# EMA
|
246
263
|
ohlcvs['EMA5'] = TechProcessor.cal_ema(ohlcvs['close'], 5)
|
247
264
|
ohlcvs['EMA20'] = TechProcessor.cal_ema(ohlcvs['close'], 20)
|
@@ -256,34 +273,42 @@ class TechProcessor:
|
|
256
273
|
ohlcvs['RSI21'] = TechProcessor.cal_rsi(ohlcvs['close'], 21)
|
257
274
|
|
258
275
|
# MACD
|
259
|
-
ohlcvs['MACD'] = TechProcessor.cal_macd(
|
276
|
+
ohlcvs['MACD'] = TechProcessor.cal_macd(
|
277
|
+
ohlcvs['EMA12'], ohlcvs['EMA26']
|
278
|
+
)
|
260
279
|
ohlcvs['Signal Line'] = TechProcessor.cal_single_line(ohlcvs['MACD'], 9)
|
261
|
-
|
280
|
+
|
262
281
|
# BANDS
|
263
282
|
bands = TechProcessor.cal_bollinger_bands(ohlcvs['close'], 20)
|
264
283
|
ohlcvs['Middle Band'] = bands['middle']
|
265
284
|
ohlcvs['Upper Band'] = bands['upper']
|
266
285
|
ohlcvs['Lower Band'] = bands['lower']
|
267
286
|
ohlcvs['%b'] = bands['%b']
|
268
|
-
ohlcvs['BBW'] = (ohlcvs["Upper Band"] -
|
287
|
+
ohlcvs['BBW'] = (ohlcvs["Upper Band"] -
|
288
|
+
ohlcvs["Lower Band"]) / ohlcvs["Middle Band"]
|
269
289
|
|
270
290
|
# ATR
|
271
|
-
ohlcvs['ATR'] = TechProcessor.cal_atr(
|
291
|
+
ohlcvs['ATR'] = TechProcessor.cal_atr(
|
292
|
+
ohlcvs['high'], ohlcvs['low'], ohlcvs['close'], 14
|
293
|
+
)
|
272
294
|
|
273
295
|
# EMA CYCLE
|
274
296
|
ohlcvs['EMA Cycle'] = ohlcvs.apply(
|
275
|
-
lambda row: TechProcessor.
|
297
|
+
lambda row: TechProcessor.
|
298
|
+
check_tech_trend(row['EMA5'], row['EMA20'], row['EMA40']),
|
276
299
|
axis=1
|
277
300
|
)
|
278
301
|
guidance_map = {
|
279
302
|
'穩定上升期': "三條移動平均線都左下右上, 買方優勢, 三線間隔越來越遠時, 進一步強攻",
|
280
|
-
'牛市結束期':
|
303
|
+
'牛市結束期':
|
304
|
+
"ema20 & 40 左下右上, ema5 緩慢下滑, 行情仍強, 賣出條件為 ema5 持續下跌, ema20 停止上漲",
|
281
305
|
'熊市入口期': "全數出清穩定上升期布局的多頭部位, 考慮提早佈局建立空頭部位",
|
282
306
|
'穩定下跌期': "三條移動平均線都是左上右下, 賣方優勢, 三線間隔越來越遠時, 進一步強攻",
|
283
|
-
'熊市結束期':
|
307
|
+
'熊市結束期':
|
308
|
+
"ema20 & 40 左上右下, ema5 緩慢上升, 行情仍走弱, 布局買進的條件是 ema 持續上漲, ema20 停止下降, 幾乎持平",
|
284
309
|
'牛市入口期': "全數出清穩定下跌期布局的空頭部位, 考慮提早佈局多頭部位",
|
285
310
|
'未定義': "無對應指導"
|
286
|
-
}
|
311
|
+
}
|
287
312
|
|
288
313
|
ohlcvs['EMA Cycle Instructions'] = ohlcvs['EMA Cycle'].map(guidance_map)
|
289
314
|
|
@@ -292,7 +317,7 @@ class TechProcessor:
|
|
292
317
|
ohlcvs['Day Trading Signal'] = ohlcvs.apply(
|
293
318
|
lambda row: TechProcessor.check_day_trading(
|
294
319
|
close_today=row['close'],
|
295
|
-
close_yesterday=row['close_yesterday'],
|
320
|
+
close_yesterday=row['close_yesterday'], # 使用前一天的收盤價
|
296
321
|
today_atr=row['ATR'],
|
297
322
|
today_rsi7=row['RSI7']
|
298
323
|
),
|
@@ -302,7 +327,12 @@ class TechProcessor:
|
|
302
327
|
return ohlcvs
|
303
328
|
|
304
329
|
@staticmethod
|
305
|
-
def resample(
|
330
|
+
def resample(
|
331
|
+
df: pd.DataFrame,
|
332
|
+
period='W',
|
333
|
+
technical_indicators=None,
|
334
|
+
date_col='date'
|
335
|
+
):
|
306
336
|
"""
|
307
337
|
將 DataFrame 中的技術指標數據重新取樣為指定的時間週期。
|
308
338
|
參數:
|
@@ -344,11 +374,16 @@ class TechProcessor:
|
|
344
374
|
agg_dict[indicator] = 'mean'
|
345
375
|
|
346
376
|
# 過濾出存在於 DataFrame 中的列
|
347
|
-
existing_cols = {
|
348
|
-
|
377
|
+
existing_cols = {
|
378
|
+
col: agg_dict[col]
|
379
|
+
for col in agg_dict if col in numeric_df.columns
|
380
|
+
}
|
381
|
+
|
349
382
|
# 確保索引為 DatetimeIndex,進行重新取樣
|
350
383
|
if not isinstance(df.index, pd.DatetimeIndex):
|
351
|
-
raise TypeError(
|
384
|
+
raise TypeError(
|
385
|
+
"The DataFrame index must be a DatetimeIndex for resampling."
|
386
|
+
)
|
352
387
|
|
353
388
|
resampled_df = numeric_df.resample(period).agg(existing_cols)
|
354
389
|
|
@@ -357,11 +392,3 @@ class TechProcessor:
|
|
357
392
|
resampled_df.reset_index(inplace=True)
|
358
393
|
|
359
394
|
return resampled_df
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
{neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/fetchers/tej_finance_report.py
RENAMED
@@ -3,6 +3,7 @@ from datetime import datetime
|
|
3
3
|
from enum import Enum
|
4
4
|
import pandas as pd
|
5
5
|
from pymongo import MongoClient
|
6
|
+
from .tech import TechProcessor
|
6
7
|
from ..utils import StatsProcessor, YoY_Calculator
|
7
8
|
import warnings
|
8
9
|
import yaml
|
@@ -16,7 +17,12 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
16
17
|
YOY_NOCAL = 3
|
17
18
|
QOQ_NOCAL = 4
|
18
19
|
|
19
|
-
def __init__(
|
20
|
+
def __init__(
|
21
|
+
self,
|
22
|
+
mongo_uri,
|
23
|
+
db_name="company",
|
24
|
+
collection_name="TWN/AINVFQ1"
|
25
|
+
):
|
20
26
|
self.client = MongoClient(mongo_uri)
|
21
27
|
self.db = self.client[db_name]
|
22
28
|
self.collection = self.db[collection_name]
|
@@ -48,7 +54,9 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
48
54
|
indexes = set(indexes)
|
49
55
|
difference = indexes - self.check_index
|
50
56
|
if (difference):
|
51
|
-
warnings.warn(
|
57
|
+
warnings.warn(
|
58
|
+
f"{list(difference)} 沒有出現在資料表中,請確認column名稱是否正確",
|
59
|
+
UserWarning)
|
52
60
|
|
53
61
|
if (not start_date):
|
54
62
|
start_date = datetime.strptime("2005-01-01", "%Y-%m-%d")
|
@@ -112,7 +120,15 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
112
120
|
return data_df
|
113
121
|
|
114
122
|
def get_QoQ_data(
|
115
|
-
self,
|
123
|
+
self,
|
124
|
+
ticker,
|
125
|
+
start_year,
|
126
|
+
start_season,
|
127
|
+
end_year,
|
128
|
+
end_season,
|
129
|
+
report_type="Q",
|
130
|
+
indexes=[],
|
131
|
+
use_cal=False):
|
116
132
|
"""
|
117
133
|
取得時間範圍內每季資料
|
118
134
|
"""
|
@@ -138,31 +154,29 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
138
154
|
}, {
|
139
155
|
"$unwind": "$data"
|
140
156
|
}, {
|
141
|
-
"$match":
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
]
|
165
|
-
}
|
157
|
+
"$match": {
|
158
|
+
"$or": [
|
159
|
+
{
|
160
|
+
"data.year": {
|
161
|
+
"$gt": start_year,
|
162
|
+
"$lt": end_year
|
163
|
+
}
|
164
|
+
}, {
|
165
|
+
"data.year": start_year,
|
166
|
+
"data.season": {
|
167
|
+
"$gte": start_season
|
168
|
+
}
|
169
|
+
}, {
|
170
|
+
"data.year": end_year,
|
171
|
+
"data.season": {
|
172
|
+
"$lte": end_season
|
173
|
+
}
|
174
|
+
}, {
|
175
|
+
"data.year": lower_bound_year,
|
176
|
+
"data.season": lower_bound_season
|
177
|
+
}
|
178
|
+
]
|
179
|
+
}
|
166
180
|
}, {
|
167
181
|
"$project": {
|
168
182
|
"data.year": 1,
|
@@ -186,31 +200,29 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
186
200
|
}, {
|
187
201
|
"$unwind": "$data"
|
188
202
|
}, {
|
189
|
-
"$match":
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
]
|
213
|
-
}
|
203
|
+
"$match": {
|
204
|
+
"$or": [
|
205
|
+
{
|
206
|
+
"data.year": {
|
207
|
+
"$gt": start_year,
|
208
|
+
"$lt": end_year
|
209
|
+
}
|
210
|
+
}, {
|
211
|
+
"data.year": start_year,
|
212
|
+
"data.season": {
|
213
|
+
"$gte": start_season
|
214
|
+
}
|
215
|
+
}, {
|
216
|
+
"data.year": end_year,
|
217
|
+
"data.season": {
|
218
|
+
"$lte": end_season
|
219
|
+
}
|
220
|
+
}, {
|
221
|
+
"data.year": lower_bound_year,
|
222
|
+
"data.season": lower_bound_season
|
223
|
+
}
|
224
|
+
]
|
225
|
+
}
|
214
226
|
}, {
|
215
227
|
"$project": project_stage
|
216
228
|
}
|
@@ -218,7 +230,10 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
218
230
|
|
219
231
|
fetched_data = self.collection.aggregate(pipeline).to_list()
|
220
232
|
data_dict = StatsProcessor.list_of_dict_to_dict(
|
221
|
-
fetched_data,
|
233
|
+
fetched_data,
|
234
|
+
keys=["year", "season"],
|
235
|
+
delimeter="Q",
|
236
|
+
data_key=report_type)
|
222
237
|
|
223
238
|
if (use_cal):
|
224
239
|
data_with_QoQ = self.cal_QoQ(data_dict)
|
@@ -233,7 +248,15 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
233
248
|
data_df = data_df.iloc[:, ::-1]
|
234
249
|
return data_df
|
235
250
|
|
236
|
-
def get_YoY_data(
|
251
|
+
def get_YoY_data(
|
252
|
+
self,
|
253
|
+
ticker,
|
254
|
+
start_year,
|
255
|
+
end_year,
|
256
|
+
season,
|
257
|
+
report_type="Q",
|
258
|
+
indexes=[],
|
259
|
+
use_cal=False):
|
237
260
|
"""
|
238
261
|
取得某季歷年資料
|
239
262
|
"""
|
@@ -258,20 +281,23 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
258
281
|
}, {
|
259
282
|
"$unwind": "$data"
|
260
283
|
}, {
|
261
|
-
"$match":
|
262
|
-
|
263
|
-
|
264
|
-
"$and": [
|
265
|
-
|
266
|
-
"
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
"
|
284
|
+
"$match": {
|
285
|
+
"$or": [
|
286
|
+
{
|
287
|
+
"$and": [
|
288
|
+
{
|
289
|
+
"data.year": {
|
290
|
+
"$in": select_year
|
291
|
+
}
|
292
|
+
}, {
|
293
|
+
"data.season": {
|
294
|
+
"$eq": season
|
295
|
+
}
|
271
296
|
}
|
272
|
-
|
273
|
-
},
|
274
|
-
|
297
|
+
]
|
298
|
+
},
|
299
|
+
]
|
300
|
+
}
|
275
301
|
}, {
|
276
302
|
"$project": {
|
277
303
|
"data.year": 1,
|
@@ -296,15 +322,17 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
296
322
|
"$unwind": "$data"
|
297
323
|
}, {
|
298
324
|
"$match": {
|
299
|
-
"$and": [
|
300
|
-
|
301
|
-
"
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
"
|
325
|
+
"$and": [
|
326
|
+
{
|
327
|
+
"data.year": {
|
328
|
+
"$in": select_year
|
329
|
+
}
|
330
|
+
}, {
|
331
|
+
"data.season": {
|
332
|
+
"$eq": season
|
333
|
+
}
|
306
334
|
}
|
307
|
-
|
335
|
+
]
|
308
336
|
}
|
309
337
|
}, {
|
310
338
|
"$project": project_stage
|
@@ -315,10 +343,14 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
315
343
|
|
316
344
|
# 處理計算YoY
|
317
345
|
data_dict = StatsProcessor.list_of_dict_to_dict(
|
318
|
-
fetched_data,
|
346
|
+
fetched_data,
|
347
|
+
keys=['year', 'season'],
|
348
|
+
data_key=report_type,
|
349
|
+
delimeter='Q')
|
319
350
|
|
320
351
|
if (use_cal):
|
321
|
-
data_with_YoY = self.cal_YoY(
|
352
|
+
data_with_YoY = self.cal_YoY(
|
353
|
+
data_dict, start_year, end_year, season)
|
322
354
|
data_df = pd.DataFrame.from_dict(data_with_YoY)
|
323
355
|
data_df = data_df.iloc[:, ::-1].T
|
324
356
|
data_dict = data_df.to_dict()
|
@@ -328,3 +360,73 @@ class FinanceReportFetcher(BaseTEJFetcher):
|
|
328
360
|
data_df = pd.DataFrame.from_dict(data_dict)
|
329
361
|
data_df = data_df.iloc[:, ::-1]
|
330
362
|
return data_df
|
363
|
+
|
364
|
+
|
365
|
+
class TEJStockPriceFetcher(BaseTEJFetcher):
|
366
|
+
|
367
|
+
def __init__(
|
368
|
+
self,
|
369
|
+
mongo_uri,
|
370
|
+
db_name: str = "company",
|
371
|
+
collection_name: str = None):
|
372
|
+
self.mongo_uri = mongo_uri
|
373
|
+
self.db_name = db_name
|
374
|
+
self.collection_name = collection_name
|
375
|
+
|
376
|
+
self.client = MongoClient(self.mongo_uri)
|
377
|
+
self.db = self.client[self.db_name]
|
378
|
+
self.collection = self.db[self.collection_name]
|
379
|
+
|
380
|
+
self.check_period = ['1d', '7d', '1m', '3m', '1y', '3y', '5y', '10y', 'all']
|
381
|
+
|
382
|
+
def get(
|
383
|
+
self,
|
384
|
+
ticker: str = "2330",
|
385
|
+
start_date: str = "2024-10-01",
|
386
|
+
period: str = None
|
387
|
+
):
|
388
|
+
"""
|
389
|
+
取得開高低收資料
|
390
|
+
start_date: str: 起始的日期
|
391
|
+
period: 指定日期範圍(E.g. 1天, 7天...etc)
|
392
|
+
如果宣告period, 以period為優先
|
393
|
+
"""
|
394
|
+
|
395
|
+
assert (
|
396
|
+
period is None or period in self.check_period
|
397
|
+
), f"period should be None or {','.join(self.check_period)}"
|
398
|
+
|
399
|
+
if (period is not None):
|
400
|
+
latest_date = self.get_latest_data_time(ticker)
|
401
|
+
start_date = self.set_time_shift(date=latest_date, period=period)
|
402
|
+
else:
|
403
|
+
start_date = datetime.strptime(start_date, "%Y-%m-%d")
|
404
|
+
|
405
|
+
pipeline = [
|
406
|
+
{
|
407
|
+
"$match": {
|
408
|
+
"ticker": ticker
|
409
|
+
}
|
410
|
+
}, {
|
411
|
+
"$unwind": "$data"
|
412
|
+
}, {
|
413
|
+
"$match": {
|
414
|
+
"data.mdate": {
|
415
|
+
"$gt": start_date
|
416
|
+
}
|
417
|
+
}
|
418
|
+
}, {
|
419
|
+
"$project": {
|
420
|
+
"ticker": 1,
|
421
|
+
"data": 1,
|
422
|
+
"_id": 0
|
423
|
+
}
|
424
|
+
}
|
425
|
+
]
|
426
|
+
datas = self.collection.aggregate(pipeline).to_list()
|
427
|
+
|
428
|
+
elements = [element['data'] for element in datas]
|
429
|
+
|
430
|
+
data_df = pd.DataFrame(elements).set_index('mdate')
|
431
|
+
|
432
|
+
return data_df
|
@@ -1,3 +1,19 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: neurostats_API
|
3
|
+
Version: 0.0.20
|
4
|
+
Summary: The service of NeuroStats website
|
5
|
+
Home-page: https://github.com/NeurowattStats/NeuroStats_API.git
|
6
|
+
Author: JasonWang@Neurowatt
|
7
|
+
Author-email: jason@neurowatt.ai
|
8
|
+
Requires-Python: >=3.6
|
9
|
+
Description-Content-Type: text/markdown
|
10
|
+
Requires-Dist: numpy
|
11
|
+
Requires-Dist: pandas>=2.2.0
|
12
|
+
Requires-Dist: pymongo
|
13
|
+
Requires-Dist: pytz
|
14
|
+
Requires-Dist: python-dotenv
|
15
|
+
Requires-Dist: yfinance
|
16
|
+
|
1
17
|
# neurostats_API
|
2
18
|
|
3
19
|
- [檔案架構](#檔案架構)
|
@@ -712,6 +728,7 @@ data = fetcher.get(
|
|
712
728
|
#### 回傳資料
|
713
729
|
##### `YOY_NOCAL` 與 `QOQ_NOCAL`
|
714
730
|
為回傳`pd.DataFrame`,column名稱為<年份>Q<季>, row名稱為指定財報項目
|
731
|
+
|
715
732
|
```Python
|
716
733
|
# fetch_mode = fetcher.FetchMode.QOQ_NOCAL
|
717
734
|
2024Q3 2024Q2 2024Q1
|
@@ -726,17 +743,18 @@ bp51 3.111298e+09 3.173919e+09 2.453840e+09
|
|
726
743
|
|
727
744
|
##### `YOY` 與 `QOQ`
|
728
745
|
回傳為`Dict[pd.DataFrame]`, key 為指定的index, DataFrame中則是該index歷年的數值與成長率
|
746
|
+
成長率單位為`%`
|
729
747
|
```Python
|
730
748
|
# fetch_mode = fetcher.FetchMode.QOQ
|
731
749
|
{
|
732
750
|
'bp41':
|
733
751
|
2024Q3 2024Q2 2024Q1
|
734
752
|
value 7.082005e+07 6.394707e+07 5.761001e+07
|
735
|
-
growth 1.074791e
|
753
|
+
growth 1.074791e+01 1.099994e+01 5.532101e-01,
|
736
754
|
'bp51':
|
737
755
|
2024Q3 2024Q2 2024Q1
|
738
756
|
value 3.111298e+09 3.145373e+09 3.091985e+09
|
739
|
-
growth -1.083335e
|
757
|
+
growth -1.083335e+00 1.726663e+00 -4.159542e-01
|
740
758
|
}
|
741
759
|
|
742
760
|
# fetch_mode = fetcher.FetchMode.YOY
|
@@ -744,17 +762,17 @@ growth -1.083335e-02 1.726663e-02 -4.159542e-03
|
|
744
762
|
'bp41':
|
745
763
|
2024Q3 2023Q3 2022Q3
|
746
764
|
value 7.082005e+07 5.377231e+07 6.201822e+07
|
747
|
-
YoY_1
|
748
|
-
YoY_3 1.729171e
|
749
|
-
YoY_5 1.389090e
|
750
|
-
YoY_10 1.255138e
|
765
|
+
YoY_1 3.170357e+01 -1.329596e+01 4.130744e+01
|
766
|
+
YoY_3 1.729171e+01 9.556684e+00 1.883274e+01
|
767
|
+
YoY_5 1.389090e+01 1.215242e+01 1.642914e+01
|
768
|
+
YoY_10 1.255138e+01 1.356297e+01 1.559702e+01
|
751
769
|
'bp51':
|
752
770
|
2024Q3 2023Q3 2022Q3
|
753
771
|
value 3.111298e+09 3.173919e+09 2.453840e+09
|
754
|
-
YoY_1
|
755
|
-
YoY_3 1.866752e
|
756
|
-
YoY_5 2.068132e
|
757
|
-
YoY_10 1.420500e
|
772
|
+
YoY_1 -1.972987e+00 2.934499e+01 3.179539e+01
|
773
|
+
YoY_3 1.866752e+01 2.766851e+01 2.638677e+01
|
774
|
+
YoY_5 2.068132e+01 2.479698e+01 1.815106e+01
|
775
|
+
YoY_10 1.420500e+01 1.586797e+01 1.551364e+01
|
758
776
|
}
|
759
777
|
```
|
760
778
|
|
@@ -802,4 +820,29 @@ data = fetcher.get(
|
|
802
820
|
- 範例輸入: `['bp41', 'bp51']`
|
803
821
|
|
804
822
|
[TEJ資料集連結](https://tquant.tejwin.com/%E8%B3%87%E6%96%99%E9%9B%86/)
|
805
|
-
請看 `公司自結數`
|
823
|
+
請看 `公司自結數`
|
824
|
+
|
825
|
+
### 開高低收
|
826
|
+
```Python
|
827
|
+
mongo_uri = <MongoDB 的 URI>
|
828
|
+
db_name = 'company' # 連接的DB名稱
|
829
|
+
collection_name = "TWN/APIPRCD" # 連接的collection對象
|
830
|
+
from neurostats_API import TEJStockPriceFetcher
|
831
|
+
|
832
|
+
fetcher = TEJStockPriceFetcher(
|
833
|
+
mongo_uri = mongo_uri,
|
834
|
+
db_name = db_name,
|
835
|
+
collection_name = collection_name
|
836
|
+
)
|
837
|
+
|
838
|
+
data = fetcher.get(
|
839
|
+
ticker = "2330" # 任意的股票代碼
|
840
|
+
start_date = "2005-01-01",
|
841
|
+
period = "3m"
|
842
|
+
) # -> pd.DataFrame
|
843
|
+
```
|
844
|
+
- `ticker`: 股票代碼
|
845
|
+
- `start_date`: 搜尋範圍的開始日期
|
846
|
+
- `period`: 搜尋的時間範圍長度
|
847
|
+
|
848
|
+
`period`與`start_date`同時存在時以period優先
|
@@ -2,11 +2,11 @@ from setuptools import setup, find_packages
|
|
2
2
|
|
3
3
|
setup(
|
4
4
|
name='neurostats_API',
|
5
|
-
version='0.0.
|
5
|
+
version='0.0.20',
|
6
6
|
long_description=open('README.md', 'r', encoding='utf-8').read(),
|
7
7
|
long_description_content_type='text/markdown',
|
8
8
|
install_requires=[
|
9
|
-
"numpy
|
9
|
+
"numpy",
|
10
10
|
"pandas>=2.2.0",
|
11
11
|
"pymongo",
|
12
12
|
"pytz",
|
@@ -212,4 +212,13 @@ def test_margin_trading():
|
|
212
212
|
|
213
213
|
fetched_data = fetcher.query_data()
|
214
214
|
|
215
|
+
pp.pprint(fetched_data)
|
216
|
+
|
217
|
+
def test_tech():
|
218
|
+
from neurostats_API.fetchers import TechFetcher
|
219
|
+
|
220
|
+
fetcher = TechFetcher(ticker = '1260', db_client=db_client)
|
221
|
+
|
222
|
+
fetched_data = fetcher.get_quarterly()
|
223
|
+
|
215
224
|
pp.pprint(fetched_data)
|
@@ -23,8 +23,8 @@ def test_QoQ():
|
|
23
23
|
data = fetcher.get(
|
24
24
|
ticker,
|
25
25
|
fetch_mode = mode,
|
26
|
-
|
27
|
-
|
26
|
+
start_date="2024-01-01",
|
27
|
+
end_date="2024-12-31",
|
28
28
|
indexes = []
|
29
29
|
)
|
30
30
|
|
@@ -40,10 +40,14 @@ def test_YoY():
|
|
40
40
|
]
|
41
41
|
|
42
42
|
for mode in modes:
|
43
|
-
data = fetcher.get(
|
44
|
-
ticker,
|
45
|
-
mode,
|
46
|
-
start_date="2024-01-01",
|
47
|
-
end_date="2024-12-31",
|
48
|
-
indexes = ["ip12"])
|
43
|
+
data = fetcher.get(ticker, mode, indexes = ["r405"])
|
49
44
|
print (data)
|
45
|
+
|
46
|
+
def test_stock_price():
|
47
|
+
from neurostats_API import TEJStockPriceFetcher
|
48
|
+
|
49
|
+
fetcher = TEJStockPriceFetcher(mongo_uri, collection_name="TWN/APIPRCD")
|
50
|
+
|
51
|
+
data = fetcher.get(ticker, period='3y')
|
52
|
+
|
53
|
+
print(data)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/tools/cash_flow_percentage.yaml
RENAMED
File without changes
|
{neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/tools/finance_overview_dict.yaml
RENAMED
File without changes
|
File without changes
|
{neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API/tools/seasonal_data_field_dict.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{neurostats_api-0.0.18 → neurostats_api-0.0.20}/neurostats_API.egg-info/dependency_links.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|