neurostats-API 0.0.18__py3-none-any.whl → 0.0.19__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 +3 -2
- neurostats_API/fetchers/__init__.py +1 -1
- neurostats_API/fetchers/base.py +48 -13
- neurostats_API/fetchers/tech.py +107 -80
- neurostats_API/fetchers/tej_finance_report.py +180 -78
- {neurostats_API-0.0.18.dist-info → neurostats_API-0.0.19.dist-info}/METADATA +26 -1
- {neurostats_API-0.0.18.dist-info → neurostats_API-0.0.19.dist-info}/RECORD +9 -9
- {neurostats_API-0.0.18.dist-info → neurostats_API-0.0.19.dist-info}/WHEEL +0 -0
- {neurostats_API-0.0.18.dist-info → neurostats_API-0.0.19.dist-info}/top_level.txt +0 -0
neurostats_API/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
__version__='0.0.
|
1
|
+
__version__='0.0.19'
|
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
|
neurostats_API/fetchers/base.py
CHANGED
@@ -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,22 @@ 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
|
+
{"ticker": ticker}, {
|
85
|
+
"last_update": 1,
|
86
|
+
"_id": 0
|
87
|
+
})
|
88
88
|
|
89
89
|
try:
|
90
90
|
latest_date = latest_data['last_update']["latest_data_date"]
|
@@ -93,7 +93,8 @@ class BaseTEJFetcher(abc.ABC):
|
|
93
93
|
|
94
94
|
return latest_date
|
95
95
|
|
96
|
-
def cal_YoY(
|
96
|
+
def cal_YoY(
|
97
|
+
self, data_dict: dict, start_year: int, end_year: int, season: int):
|
97
98
|
year_shifts = [1, 3, 5, 10]
|
98
99
|
return_dict = {}
|
99
100
|
for year in range(start_year, end_year + 1):
|
@@ -115,7 +116,9 @@ class BaseTEJFetcher(abc.ABC):
|
|
115
116
|
try:
|
116
117
|
past_year = year - shift
|
117
118
|
last_value = data_dict[f"{past_year}Q{season}"][key]
|
118
|
-
temp_dict[
|
119
|
+
temp_dict[
|
120
|
+
f"YoY_{shift}"] = YoY_Calculator.cal_growth(
|
121
|
+
this_value, last_value, delta=shift)
|
119
122
|
except Exception as e:
|
120
123
|
temp_dict[f"YoY_{shift}"] = None
|
121
124
|
|
@@ -153,9 +156,11 @@ class BaseTEJFetcher(abc.ABC):
|
|
153
156
|
temp_dict = {"value": this_value}
|
154
157
|
|
155
158
|
try:
|
156
|
-
last_value = data_dict[f"{last_year}Q{last_season}"][
|
159
|
+
last_value = data_dict[f"{last_year}Q{last_season}"][
|
160
|
+
key]['value']
|
157
161
|
|
158
|
-
temp_dict['growth'] = YoY_Calculator.cal_growth(
|
162
|
+
temp_dict['growth'] = YoY_Calculator.cal_growth(
|
163
|
+
this_value, last_value, delta=1)
|
159
164
|
except Exception as e:
|
160
165
|
temp_dict['growth'] = None
|
161
166
|
|
@@ -173,3 +178,33 @@ class BaseTEJFetcher(abc.ABC):
|
|
173
178
|
for key in data_dict.keys():
|
174
179
|
data_dict[key] = pd.DataFrame.from_dict(data_dict[key])
|
175
180
|
return data_dict
|
181
|
+
|
182
|
+
def set_time_shift(self, date: Union[str, datetime], period: str):
|
183
|
+
if (isinstance(date, str)):
|
184
|
+
date = datetime.strptime(date, "%Y-%m-%d")
|
185
|
+
if (period == '1d'):
|
186
|
+
return date - timedelta(days=1)
|
187
|
+
|
188
|
+
elif (period == '7d'):
|
189
|
+
return date - timedelta(days=7)
|
190
|
+
|
191
|
+
elif (period == '1m'):
|
192
|
+
return date - timedelta(days=30)
|
193
|
+
|
194
|
+
elif (period == '3m'):
|
195
|
+
return date - timedelta(days=90)
|
196
|
+
|
197
|
+
elif (period == '1y'):
|
198
|
+
return date - timedelta(days=365)
|
199
|
+
|
200
|
+
elif (period == '3y'):
|
201
|
+
return date - timedelta(days=365 * 3)
|
202
|
+
|
203
|
+
elif (period == '5y'):
|
204
|
+
return date - timedelta(days=365 * 5)
|
205
|
+
|
206
|
+
elif (period == '10y'):
|
207
|
+
return date - timedelta(days=365 * 10)
|
208
|
+
|
209
|
+
elif (period == 'all'):
|
210
|
+
return datetime.strptime("1991-01-01", "%Y-%m-%d")
|
neurostats_API/fetchers/tech.py
CHANGED
@@ -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
|
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
|
-
|
@@ -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,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: neurostats_API
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.19
|
4
4
|
Summary: The service of NeuroStats website
|
5
5
|
Home-page: https://github.com/NeurowattStats/NeuroStats_API.git
|
6
6
|
Author: JasonWang@Neurowatt
|
@@ -819,3 +819,28 @@ data = fetcher.get(
|
|
819
819
|
|
820
820
|
[TEJ資料集連結](https://tquant.tejwin.com/%E8%B3%87%E6%96%99%E9%9B%86/)
|
821
821
|
請看 `公司自結數`
|
822
|
+
|
823
|
+
### 開高低收
|
824
|
+
```Python
|
825
|
+
mongo_uri = <MongoDB 的 URI>
|
826
|
+
db_name = 'company' # 連接的DB名稱
|
827
|
+
collection_name = "TWN/APIPRCD" # 連接的collection對象
|
828
|
+
from neurostats_API import TEJStockPriceFetcher
|
829
|
+
|
830
|
+
fetcher = TEJStockPriceFetcher(
|
831
|
+
mongo_uri = mongo_uri,
|
832
|
+
db_name = db_name,
|
833
|
+
collection_name = collection_name
|
834
|
+
)
|
835
|
+
|
836
|
+
data = fetcher.get(
|
837
|
+
ticker = "2330" # 任意的股票代碼
|
838
|
+
start_date = "2005-01-01",
|
839
|
+
period = "3m"
|
840
|
+
) # -> pd.DataFrame
|
841
|
+
```
|
842
|
+
- `ticker`: 股票代碼
|
843
|
+
- `start_date`: 搜尋範圍的開始日期
|
844
|
+
- `period`: 搜尋的時間範圍長度
|
845
|
+
|
846
|
+
`period`與`start_date`同時存在時以period優先
|
@@ -1,17 +1,17 @@
|
|
1
|
-
neurostats_API/__init__.py,sha256=
|
1
|
+
neurostats_API/__init__.py,sha256=U-0Tn9McUXzABJINU15pYkGchFGI5R3njBMgn-F4CzM,288
|
2
2
|
neurostats_API/cli.py,sha256=UJSWLIw03P24p-gkBb6JSEI5dW5U12UvLf1L8HjQD-o,873
|
3
3
|
neurostats_API/main.py,sha256=QcsfmWivg2Dnqw3MTJWiI0QvEiRs0VuH-BjwQHFCv00,677
|
4
|
-
neurostats_API/fetchers/__init__.py,sha256=
|
4
|
+
neurostats_API/fetchers/__init__.py,sha256=KCw-yRSDFa3fw83u73LJ9OVop7gRl_YQYlQq-cITxuo,511
|
5
5
|
neurostats_API/fetchers/balance_sheet.py,sha256=sQv4Gk5uoKURLEdh57YknOQWiyVwaXJ2Mw75jxNqUS0,5804
|
6
|
-
neurostats_API/fetchers/base.py,sha256=
|
6
|
+
neurostats_API/fetchers/base.py,sha256=hlHtCDKpVFFs2n0uJ0yjz26dTzgc2ZtOKleiguT_HxY,6673
|
7
7
|
neurostats_API/fetchers/cash_flow.py,sha256=TY7VAWVXkj5-mzH5Iu0sIE-oV8MvGmmDy0URNotNV1E,7614
|
8
8
|
neurostats_API/fetchers/finance_overview.py,sha256=PxUdWY0x030olYMLcCHDBn068JLmCE2RTOce1dxs5vM,27753
|
9
9
|
neurostats_API/fetchers/institution.py,sha256=UrcBc6t7u7CnEwUsf6YmLbbJ8VncdWpq8bCz17q2dgs,11168
|
10
10
|
neurostats_API/fetchers/margin_trading.py,sha256=lQImtNdvaBoSlKhJvQ3DkH3HjSSgKRJz4ZZpyR5-Z4I,10433
|
11
11
|
neurostats_API/fetchers/month_revenue.py,sha256=nixX2llzjCFr2m2YVjxrSfkBusnZPrPb2dRDq1XLGhw,4251
|
12
12
|
neurostats_API/fetchers/profit_lose.py,sha256=EN9Y0iamcAaHMZdjHXO6b_2buLnORssf8ZS7A0hi74s,5896
|
13
|
-
neurostats_API/fetchers/tech.py,sha256=
|
14
|
-
neurostats_API/fetchers/tej_finance_report.py,sha256=
|
13
|
+
neurostats_API/fetchers/tech.py,sha256=116UQP00FyZUGwOttHsauJLxAMNE99rPL6nl4U3ETK4,13386
|
14
|
+
neurostats_API/fetchers/tej_finance_report.py,sha256=1q_c2B_Dd7qIagdlXt9eNT7O53JmsquCrgdi2OOeQ6w,14008
|
15
15
|
neurostats_API/fetchers/value_invest.py,sha256=b_x2Dpgs8VBU5HdG8ocKtfIEkqhU-Q0S5n6RxuFuM2g,7467
|
16
16
|
neurostats_API/tools/balance_sheet.yaml,sha256=6XygNG_Ybb1Xkk1e39LMLKr7ATvaCP3xxuwFbgNl6dA,673
|
17
17
|
neurostats_API/tools/cash_flow_percentage.yaml,sha256=fk2Z4eb1JjGFvP134eJatHacB7BgTkBenhDJr83w8RE,1345
|
@@ -25,7 +25,7 @@ neurostats_API/utils/data_process.py,sha256=A--dzOsu42jRxqqCD41gTtjE5rhEBYmhB6y-
|
|
25
25
|
neurostats_API/utils/datetime.py,sha256=XJya4G8b_-ZOaBbMXgQjWh2MC4wc-o6goQ7EQJQMWrQ,773
|
26
26
|
neurostats_API/utils/db_client.py,sha256=OYe6yazcR4Aa6jYmy47JrryUeh2NnKGqY2K_lSZe6i8,455
|
27
27
|
neurostats_API/utils/fetcher.py,sha256=VbrUhjA-GG5AyjPX2SHtFIbZM4dm3jo0RgZzuCbb_Io,40927
|
28
|
-
neurostats_API-0.0.
|
29
|
-
neurostats_API-0.0.
|
30
|
-
neurostats_API-0.0.
|
31
|
-
neurostats_API-0.0.
|
28
|
+
neurostats_API-0.0.19.dist-info/METADATA,sha256=2YG4AS_jlIEZ8gPBxd7rZ9CEOyuAqTiBStFDH4k9ntM,30981
|
29
|
+
neurostats_API-0.0.19.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
30
|
+
neurostats_API-0.0.19.dist-info/top_level.txt,sha256=nSlQPMG0VtXivJyedp4Bkf86EOy2TpW10VGxolXrqnU,15
|
31
|
+
neurostats_API-0.0.19.dist-info/RECORD,,
|
File without changes
|
File without changes
|