neurostats-API 0.0.3__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,312 @@
1
+ from .base import StatsFetcher
2
+ import pandas as pd
3
+
4
+ class TechFetcher(StatsFetcher):
5
+
6
+ def __init__(self, ticker:str):
7
+ super().__init__()
8
+ self.ticker = ticker
9
+ self.full_ohlcv = self._get_ohlcv()
10
+ self.basic_indexes = ['SMA5', 'SMA20', 'SMA60', 'EMA5', 'EMA20',
11
+ 'EMA40', 'EMA12', 'EMA26', 'RSI7', 'RSI14',
12
+ 'RSI21', 'MACD', 'Signal Line', 'Middle Band',
13
+ 'Upper Band', 'Lower Band', '%b', 'ATR',
14
+ 'BBW','EMA Cycle','EMA Cycle Instructions',
15
+ 'Day Trading Signal']
16
+
17
+ self.daily_index = TechProcessor.cal_basic_index(self.full_ohlcv)
18
+
19
+ self.weekly_index = TechProcessor.resample(
20
+ self.daily_index,
21
+ period= 'W',
22
+ technical_indicators = self.basic_indexes
23
+ )
24
+
25
+ self.monthly_index = TechProcessor.resample(
26
+ self.daily_index,
27
+ period= 'ME',
28
+ technical_indicators = self.basic_indexes
29
+ )
30
+
31
+ self.quarterly_index = TechProcessor.resample(
32
+ self.daily_index,
33
+ period= 'QE',
34
+ technical_indicators = self.basic_indexes
35
+ )
36
+
37
+ self.yearly_index = TechProcessor.resample(
38
+ self.daily_index,
39
+ period= 'YE',
40
+ technical_indicators = self.basic_indexes
41
+ )
42
+
43
+ def _get_ohlcv(self):
44
+ query = {'ticker': self.ticker}
45
+ ticker_full = list(self.collection.find(query))
46
+
47
+ if not ticker_full:
48
+ raise ValueError(f"No data found for ticker: {self.ticker}")
49
+
50
+ if 'daily_data' not in ticker_full[0] or ticker_full[0]['daily_data'] is None:
51
+ raise KeyError("Missing 'daily_data' in the retrieved data")
52
+
53
+ df = pd.DataFrame(ticker_full[0]['daily_data'])
54
+
55
+ selected_cols = ['date','open','high','low','close','volume']
56
+
57
+ return df[selected_cols]
58
+
59
+ def get_daily(self):
60
+
61
+ return self.daily_index
62
+
63
+ def get_weekly(self):
64
+
65
+ return self.weekly_index
66
+
67
+ def get_monthly(self):
68
+
69
+ return self.monthly_index
70
+
71
+ def get_quarterly(self):
72
+
73
+ return self.quarterly_index
74
+
75
+ def get_yearly(self):
76
+
77
+ return self.yearly_index
78
+
79
+ class TechProcessor:
80
+
81
+ @staticmethod
82
+ def cal_sma(closes: pd.Series, n_days: int) -> pd.Series:
83
+ return closes.rolling(window=n_days).mean()
84
+
85
+ @staticmethod
86
+ def cal_ema(closes: pd.Series, n_days: int) -> pd.Series:
87
+ return closes.ewm(span=n_days, adjust=False).mean()
88
+
89
+ @staticmethod
90
+ def cal_rsi(closes: pd.Series, n_days: int) -> pd.Series:
91
+ delta = closes.diff(1)
92
+ gain = (delta.where(delta > 0, 0)).rolling(window=n_days).mean()
93
+ loss = (-delta.where(delta < 0, 0)).rolling(window=n_days).mean()
94
+ rs = gain / loss
95
+ return 100 - (100 / (1 + rs))
96
+
97
+ @staticmethod
98
+ def cal_macd(ema12s: pd.Series, ema26s: pd.Series) -> pd.Series:
99
+ return ema12s - ema26s
100
+
101
+ @staticmethod
102
+ def cal_single_line(macds: pd.Series, n_days: int = 9) -> pd.Series:
103
+ return macds.ewm(span=n_days, adjust=False).mean()
104
+
105
+ @staticmethod
106
+ def cal_bollinger_bands(closes: pd.Series, n_days: int = 20) -> pd.DataFrame:
107
+ middle = closes.rolling(window=n_days).mean()
108
+ upper = middle + 2 * closes.rolling(window=n_days).std()
109
+ lower = middle - 2 * closes.rolling(window=n_days).std()
110
+ percent_b = (closes - lower) / (upper - lower)
111
+
112
+ return pd.DataFrame(
113
+ {
114
+ 'middle': middle,
115
+ 'upper': upper,
116
+ "lower": lower,
117
+ "%b": percent_b
118
+ }
119
+ )
120
+
121
+ @staticmethod
122
+ def cal_atr(highes: pd.Series, lows: pd.Series, closes: pd.Series, n_days: int) -> pd.Series:
123
+ high_low = highes - lows
124
+ high_close = (highes - closes.shift(1)).abs()
125
+ low_close = (lows - closes.shift(1)).abs()
126
+
127
+ true_range = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
128
+ atr = true_range.rolling(window=n_days, min_periods=1).mean()
129
+
130
+ return atr
131
+
132
+ @staticmethod
133
+ def check_tech_trend(ema5: float, ema20: float, ema40: float) -> str:
134
+ if ema5 > ema20 > ema40:
135
+ return '穩定上升期'
136
+ elif ema20 > ema5 > ema40:
137
+ return '牛市結束期'
138
+ elif ema20 > ema40 > ema5:
139
+ return '熊市入口期'
140
+ elif ema40 > ema20 > ema5:
141
+ return '穩定下跌期'
142
+ elif ema40 > ema5 > ema20:
143
+ return '熊市結束期'
144
+ elif ema5 > ema40 > ema20:
145
+ return '牛市入口期'
146
+ else:
147
+ return '未定義'
148
+
149
+ @staticmethod
150
+ def check_day_trading(
151
+ close_today: float,
152
+ close_yesterday: float,
153
+ today_atr: float,
154
+ today_rsi7: float,
155
+ target_value=270,
156
+ atr_thred=7.5
157
+ ) -> str:
158
+ reasons = []
159
+
160
+ # 檢查規則 1: Close 今日 - Close 昨日
161
+ if close_today - close_yesterday <= 0:
162
+ reasons.append('今日收盤價未高於昨日收盤價')
163
+
164
+ # 檢查規則 2: 今日 Close 是否大於目標值
165
+ if close_today < target_value:
166
+ reasons.append(f'今日收盤價未達到目標值{target_value}')
167
+
168
+ # 檢查規則 3: ATR 是否大於 atr_thred
169
+ if today_atr < atr_thred:
170
+ reasons.append(f'ATR 值小於 {atr_thred}')
171
+
172
+ # 檢查規則 4: RSI7 是否大於等於 40
173
+ if today_rsi7 < 40:
174
+ reasons.append('RSI7 值小於 40')
175
+
176
+ # 根據檢查結果返回
177
+ if not reasons:
178
+ return '今日此股票為好的當沖標的'
179
+ else:
180
+ return f'今日此股票並非好的當沖標的, 原因: {", ".join(reasons)}'
181
+
182
+ @staticmethod
183
+ def cal_basic_index(ohlcvs:pd.DataFrame):
184
+
185
+ # SMA
186
+ ohlcvs['SMA5'] = TechProcessor.cal_sma(ohlcvs['close'], 5)
187
+ ohlcvs['SMA20'] = TechProcessor.cal_sma(ohlcvs['close'], 20)
188
+ ohlcvs['SMA60'] = TechProcessor.cal_sma(ohlcvs['close'], 40)
189
+
190
+ # EMA
191
+ ohlcvs['EMA5'] = TechProcessor.cal_ema(ohlcvs['close'], 5)
192
+ ohlcvs['EMA20'] = TechProcessor.cal_ema(ohlcvs['close'], 20)
193
+ ohlcvs['EMA40'] = TechProcessor.cal_ema(ohlcvs['close'], 40)
194
+
195
+ ohlcvs['EMA12'] = TechProcessor.cal_ema(ohlcvs['close'], 12)
196
+ ohlcvs['EMA26'] = TechProcessor.cal_ema(ohlcvs['close'], 26)
197
+
198
+ # RSI
199
+ ohlcvs['RSI7'] = TechProcessor.cal_rsi(ohlcvs['close'], 7)
200
+ ohlcvs['RSI14'] = TechProcessor.cal_rsi(ohlcvs['close'], 14)
201
+ ohlcvs['RSI21'] = TechProcessor.cal_rsi(ohlcvs['close'], 21)
202
+
203
+ # MACD
204
+ ohlcvs['MACD'] = TechProcessor.cal_macd(ohlcvs['EMA12'], ohlcvs['EMA26'])
205
+ ohlcvs['Signal Line'] = TechProcessor.cal_single_line(ohlcvs['MACD'], 9)
206
+
207
+ # BANDS
208
+ bands = TechProcessor.cal_bollinger_bands(ohlcvs['close'], 20)
209
+ ohlcvs['Middle Band'] = bands['middle']
210
+ ohlcvs['Upper Band'] = bands['upper']
211
+ ohlcvs['Lower Band'] = bands['lower']
212
+ ohlcvs['%b'] = bands['%b']
213
+ ohlcvs['BBW'] = (ohlcvs["Upper Band"] - ohlcvs["Lower Band"]) / ohlcvs["Middle Band"]
214
+
215
+ # ATR
216
+ ohlcvs['ATR'] = TechProcessor.cal_atr(ohlcvs['high'],ohlcvs['low'],ohlcvs['close'],14)
217
+
218
+ # EMA CYCLE
219
+ ohlcvs['EMA Cycle'] = ohlcvs.apply(
220
+ lambda row: TechProcessor.check_tech_trend(row['EMA5'], row['EMA20'], row['EMA40']),
221
+ axis=1
222
+ )
223
+ guidance_map = {
224
+ '穩定上升期': "三條移動平均線都左下右上, 買方優勢, 三線間隔越來越遠時, 進一步強攻",
225
+ '牛市結束期': "ema20 & 40 左下右上, ema5 緩慢下滑, 行情仍強, 賣出條件為 ema5 持續下跌, ema20 停止上漲",
226
+ '熊市入口期': "全數出清穩定上升期布局的多頭部位, 考慮提早佈局建立空頭部位",
227
+ '穩定下跌期': "三條移動平均線都是左上右下, 賣方優勢, 三線間隔越來越遠時, 進一步強攻",
228
+ '熊市結束期': "ema20 & 40 左上右下, ema5 緩慢上升, 行情仍走弱, 布局買進的條件是 ema 持續上漲, ema20 停止下降, 幾乎持平",
229
+ '牛市入口期': "全數出清穩定下跌期布局的空頭部位, 考慮提早佈局多頭部位",
230
+ '未定義': "無對應指導"
231
+ }
232
+
233
+ ohlcvs['EMA Cycle Instructions'] = ohlcvs['EMA Cycle'].map(guidance_map)
234
+
235
+ # DAY TRADE SELECTING
236
+ ohlcvs['close_yesterday'] = ohlcvs['close'].shift(1)
237
+ ohlcvs['Day Trading Signal'] = ohlcvs.apply(
238
+ lambda row: TechProcessor.check_day_trading(
239
+ close_today=row['close'],
240
+ close_yesterday=row['close_yesterday'], # 使用前一天的收盤價
241
+ today_atr=row['ATR'],
242
+ today_rsi7=row['RSI7']
243
+ ),
244
+ axis=1
245
+ )
246
+
247
+ return ohlcvs
248
+
249
+ @staticmethod
250
+ def resample(df: pd.DataFrame, period='W', technical_indicators=None, date_col='date'):
251
+ """
252
+ 將 DataFrame 中的技術指標數據重新取樣為指定的時間週期。
253
+ 參數:
254
+ df (pd.DataFrame): 包含技術指標的日線 DataFrame,必須包含 datetime 索引或指定的日期列。
255
+ period (str): 重新取樣的時間週期,如 'W', 'ME', 'QE', 'YE'。
256
+ technical_indicators (list): 需要重新取樣的技術指標列名。
257
+ date_col (str): 包含日期的列名(如果不是索引)。
258
+
259
+ 返回:
260
+ pd.DataFrame: 重新取樣後的 DataFrame。
261
+ """
262
+ # 標記輸入的日期是否是索引還是列
263
+ original_index = df.index.name == date_col if df.index.name else False
264
+
265
+ # 檢查是否需要將日期列設置為索引
266
+ if not original_index and date_col in df.columns:
267
+ df[date_col] = pd.to_datetime(df[date_col])
268
+ df.set_index(date_col, inplace=True)
269
+
270
+ # 過濾掉非數字型的列
271
+ numeric_df = df.select_dtypes(include='number')
272
+
273
+ # 預設的聚合方式
274
+ agg_dict = {
275
+ 'open': 'first',
276
+ 'high': 'max',
277
+ 'low': 'min',
278
+ 'close': 'last',
279
+ 'volume': 'sum'
280
+ }
281
+
282
+ # 如果沒有提供 technical_indicators,設置默認技術指標
283
+ if technical_indicators is None:
284
+ technical_indicators = numeric_df.columns.tolist()
285
+
286
+ # 將技術指標的聚合方式設置為 'mean'
287
+ for indicator in technical_indicators:
288
+ if indicator in numeric_df.columns:
289
+ agg_dict[indicator] = 'mean'
290
+
291
+ # 過濾出存在於 DataFrame 中的列
292
+ existing_cols = {col: agg_dict[col] for col in agg_dict if col in numeric_df.columns}
293
+
294
+ # 確保索引為 DatetimeIndex,進行重新取樣
295
+ if not isinstance(df.index, pd.DatetimeIndex):
296
+ raise TypeError("The DataFrame index must be a DatetimeIndex for resampling.")
297
+
298
+ resampled_df = numeric_df.resample(period).agg(existing_cols)
299
+
300
+ # 如果原始日期是列而非索引,將日期從索引還原為列並重置索引為範圍索引
301
+ if not original_index:
302
+ resampled_df.reset_index(inplace=True)
303
+
304
+ return resampled_df
305
+
306
+
307
+
308
+
309
+
310
+
311
+
312
+
@@ -0,0 +1,89 @@
1
+ from .base import StatsFetcher, StatsProcessor
2
+ from datetime import datetime, timedelta, date
3
+ import pandas as pd
4
+ from utils import StatsDateTime
5
+
6
+
7
+ class ValueFetcher(StatsFetcher):
8
+
9
+ def __init__(self, ticker: str):
10
+ super().__init__(ticker)
11
+
12
+ def prepare_query(self, start_date, end_date):
13
+ pipeline = super().prepare_query()
14
+
15
+ pipeline.append({
16
+ "$project": {
17
+ "_id": 0,
18
+ "ticker": 1,
19
+ "company_name": 1,
20
+ "daily_data": {
21
+ "$map": {
22
+ "input": {
23
+ "$filter": {
24
+ "input": "$daily_data",
25
+ "as": "daily",
26
+ "cond": {
27
+ "$and": [{
28
+ "$gte": ["$$daily.date", start_date]
29
+ }, {
30
+ "$lte": ["$$daily.date", end_date]
31
+ }]
32
+ }
33
+ }
34
+ },
35
+ "as": "daily_item",
36
+ "in": {
37
+ "date": "$$daily_item.date",
38
+ "close": "$$daily_item.close",
39
+ "P_B": "$$daily_item.P_B",
40
+ "P_E": "$$daily_item.P_E",
41
+ "P_FCF": "$$daily_item.P_FCF",
42
+ "P_S": "$$daily_item.P_S",
43
+ "EV_OPI": "$$daily_item.EV_OPI",
44
+ "EV_EBIT": "$$daily_item.EV_EBIT",
45
+ "EV_EBITDA": "$$daily_item.EV_EBITDA",
46
+ "EV_S": "$$daily_item.EV_S"
47
+ }
48
+ }
49
+ },
50
+ "yearly_data": 1
51
+ }
52
+ })
53
+
54
+ return pipeline
55
+
56
+ def query_data(self):
57
+ today = StatsDateTime.get_today()
58
+
59
+ this_year = today.year - 1911
60
+ start_date = (today.date - timedelta(days=31))
61
+ end_date = today.date
62
+
63
+ fetched_data = self.collect_data(start_date, end_date)
64
+
65
+ fetched_data['daily_data'] = fetched_data['daily_data'][-1]
66
+
67
+ fetched_data['yearly_data'] = ValueProcessor.transform_to_df(
68
+ fetched_data['daily_data'],
69
+ fetched_data['yearly_data'],
70
+ )
71
+
72
+ return fetched_data
73
+
74
+
75
+ class ValueProcessor(StatsProcessor):
76
+
77
+ @staticmethod
78
+ def transform_to_df(daily_dict, yearly_dict):
79
+ latest_data = {"year": f"過去4季"}
80
+
81
+ latest_data.update(daily_dict)
82
+ latest_data.pop("date")
83
+ latest_data.pop("close")
84
+
85
+ yearly_dict.append(latest_data)
86
+
87
+ yearly_dict = pd.DataFrame.from_dict(yearly_dict)
88
+
89
+ return yearly_dict
neurostats_API/main.py ADDED
@@ -0,0 +1,27 @@
1
+ import pandas as pd
2
+
3
+ from utils import StatsFetcher
4
+
5
+ pd.options.display.max_rows = 4
6
+ pd.options.display.max_columns = 4
7
+
8
+ if __name__ == "__main__":
9
+ import pprint
10
+ pp = pprint.PrettyPrinter(indent=2)
11
+
12
+ ticker = "2330"
13
+ start_date = "2018-01-01"
14
+ end_date = "2199-12-31"
15
+
16
+ fetcher = StatsFetcher()
17
+
18
+ # pp.pprint(fetcher.query_seasonal_data(ticker, start_date, end_date, 'balance_sheet'))
19
+ # fetcher.get_balance_sheet(ticker)
20
+ query_data = fetcher.get_profit_lose(ticker)
21
+ print("{")
22
+ for key, value in query_data.items():
23
+ print(f"\t\"{key}\":")
24
+
25
+ print(f"{value}")
26
+ print("}")
27
+ # pp.pprint(fetcher.get_balance_sheet(ticker))
@@ -0,0 +1,2 @@
1
+ from .fetcher import StatsFetcher
2
+ from .datetime import StatsDateTime
@@ -0,0 +1,21 @@
1
+ from datetime import datetime
2
+
3
+ class StatsDateTime():
4
+
5
+ def __init__(self, date, year, month, day, season):
6
+ self.date = date
7
+ self.year = year
8
+ self.month = month
9
+ self.day = day
10
+ self.season = season
11
+
12
+ @classmethod
13
+ def get_today(cls):
14
+ today = datetime.today()
15
+ this_year = today.year
16
+ this_month = today.month
17
+ this_day = today.day
18
+ this_season = ((this_month - 1) // 3) + 1
19
+
20
+ return StatsDateTime(today, this_year, this_month, this_day,
21
+ this_season)
@@ -0,0 +1,10 @@
1
+ import os
2
+ from pymongo import MongoClient
3
+ from dotenv import load_dotenv
4
+
5
+ # 載入 .env 檔案
6
+ load_dotenv()
7
+
8
+ # 創建並共享 MongoClient 連線
9
+ mongo_uri = os.getenv('MONGO_URI', 'mongodb://localhost:27017')
10
+ shared_client = MongoClient(mongo_uri)