ezKit 1.9.11__py3-none-any.whl → 1.10.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ezKit
3
- Version: 1.9.11
3
+ Version: 1.10.0
4
4
  Summary: Easy Kit
5
5
  Author: septvean
6
6
  Author-email: septvean@gmail.com
@@ -2,19 +2,17 @@ ezKit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  ezKit/bottle.py,sha256=usKK1wVaZw4_D-4VwMYmOIc8jtz4TrpM30nck59HMFw,180178
3
3
  ezKit/bottle_extensions.py,sha256=3reEQVZuHklXTl6r7F8kiBFFPb0RaAGc3mYJJnrMDjQ,1129
4
4
  ezKit/cipher.py,sha256=0T_StbjiNI4zgrjVgcfU-ffKgu1waBA9UDudAnqFcNM,2896
5
- ezKit/cls.py,sha256=e7_72kv0Q_o023xcjKNtrkfKg7frABQvCF_JjoHV94U,10800
6
5
  ezKit/database.py,sha256=Rc4RgjHOOtf5dMLvMkK1beRfbIai5E1x4HTsDwKsA-Q,6822
7
6
  ezKit/http.py,sha256=i3Kn5AMAMicDMcDjxKKZU7zqEKTU88Ec9_LwCuBJy-0,1801
8
7
  ezKit/mongo.py,sha256=dOm_1wXEPp_e8Ml5Qq78M7FDNrQUAZaThzVIiiLJJwk,2393
9
8
  ezKit/qywx.py,sha256=X_H4fzP-iEqeDEbumr7D1bXi6dxczaxfO8iyutzy02s,7171
10
9
  ezKit/redis.py,sha256=g2_V4jvq0djRc20jLZkgeAeF_bYrq-Rbl_kHcCUPZcA,1965
11
10
  ezKit/sendemail.py,sha256=tRXCsJm_RfTJ9xEWe_lTQ5kOs2JxHGPXvq0oWA7prq0,7263
12
- ezKit/stock.py,sha256=J-fM2XH2q7G6-Q30N8clqLIv7sSn87_fN1qjLOiTrFk,12088
13
11
  ezKit/token.py,sha256=HKREyZj_T2S8-aFoFIrBXTaCKExQq4zE66OHXhGHqQg,1750
14
12
  ezKit/utils.py,sha256=TDsL3PRkQy6NdZgphkgwacbWvHqEmq4LOkfNzmxV4DY,42682
15
13
  ezKit/xftp.py,sha256=XyIdr_2rxRVLqPofG6fIYWhAMVsFwTyp46dg5P9FLW4,7774
16
- ezKit-1.9.11.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
17
- ezKit-1.9.11.dist-info/METADATA,sha256=O05DZpUqxTH6u_llpwm7s7FCxb1EgTqMwX_xjYx3fcw,191
18
- ezKit-1.9.11.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
19
- ezKit-1.9.11.dist-info/top_level.txt,sha256=aYLB_1WODsqNTsTFWcKP-BN0KCTKcV-HZJ4zlHkCFw8,6
20
- ezKit-1.9.11.dist-info/RECORD,,
14
+ ezKit-1.10.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
15
+ ezKit-1.10.0.dist-info/METADATA,sha256=O02PkOxx-UBmJsu2R7TIJNrTw2d4GvsLsbCgvmHmgp0,191
16
+ ezKit-1.10.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
17
+ ezKit-1.10.0.dist-info/top_level.txt,sha256=aYLB_1WODsqNTsTFWcKP-BN0KCTKcV-HZJ4zlHkCFw8,6
18
+ ezKit-1.10.0.dist-info/RECORD,,
ezKit/cls.py DELETED
@@ -1,313 +0,0 @@
1
- """财联社数据"""
2
- import re
3
-
4
- import pandas as pd
5
- import requests
6
- from loguru import logger
7
-
8
- from . import stock, utils
9
-
10
-
11
- def up_down_analysis(
12
- target: str = "up_pool",
13
- df: bool = False
14
- ) -> list | pd.DataFrame | None:
15
- """涨停跌停数据"""
16
-
17
- # 判断参数是否正确
18
- match True:
19
- case True if not utils.isTrue(target, str):
20
- logger.error("argument error: target")
21
- return None
22
- case _:
23
- pass
24
-
25
- info: str = "获取涨停池股票"
26
- match True:
27
- case True if target == "up_pool":
28
- info = "获取涨停池股票"
29
- case True if target == "continuous_up_pool":
30
- info = "获取连板池股票"
31
- case True if target == "up_open_pool":
32
- info = "获取炸板池股票"
33
- case True if target == "down_pool":
34
- info = "获取跌停池股票"
35
- case _:
36
- pass
37
-
38
- try:
39
- logger.info(f"{info} ......")
40
-
41
- user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
42
- headers = {"User-Agent": user_agent}
43
-
44
- # 涨停池: https://x-quote.cls.cn/quote/index/up_down_analysis?rever=1&way=last_px&type=up_pool
45
- # 连板池: https://x-quote.cls.cn/quote/index/up_down_analysis?rever=1&way=last_px&type=continuous_up_pool
46
- # 炸板池: https://x-quote.cls.cn/quote/index/up_down_analysis?rever=1&way=last_px&type=up_open_pool
47
- # 跌停池: https://x-quote.cls.cn/quote/index/up_down_analysis?rever=1&way=last_px&type=down_pool
48
- api = f"https://x-quote.cls.cn/quote/index/up_down_analysis?rever=1&way=last_px&type={target}"
49
-
50
- response = requests.get(api, headers=headers, timeout=10)
51
-
52
- response_dict: dict = response.json()
53
-
54
- result: list = []
55
-
56
- for i in response_dict["data"]:
57
-
58
- # if re.match(r"^(sz00|sh60)", i["secu_code"]):
59
- # print(i["secu_code"])
60
-
61
- # if re.search(r"ST|银行", i["secu_name"]):
62
- # print(i["secu_name"])
63
-
64
- # 主板, 非ST, 非银行, 非证券
65
- if (not re.match(r"^(sz00|sh60)", i["secu_code"])) or re.search(r"ST|银行|证券", i["secu_name"]):
66
- continue
67
-
68
- if target in ["up_pool", "up_pool"]:
69
- result.append({
70
- "code": stock.coderename(i["secu_code"], restore=True),
71
- "name": i["secu_name"],
72
- "up_days": i["limit_up_days"],
73
- "reason": i["up_reason"]
74
- })
75
-
76
- if target in ["up_open_pool", "down_pool"]:
77
- result.append({
78
- "code": stock.coderename(i["secu_code"], restore=True),
79
- "name": i["secu_name"]
80
- })
81
-
82
- if not utils.isTrue(df, bool):
83
- logger.success(f"{info} [成功]")
84
- return result
85
-
86
- # data: pd.DataFrame = pd.DataFrame(response_dict["data"], columns=["secu_code", "secu_name", "limit_up_days", "up_reason"])
87
- # data = data.rename(columns={"secu_code": "code", "secu_name": "name", "limit_up_days": "up_days", "up_reason": "reason"})
88
-
89
- return pd.DataFrame(data=pd.DataFrame(result))
90
-
91
- except Exception as e:
92
- logger.error(f"{info} [失败]")
93
- logger.exception(e)
94
- return None
95
-
96
-
97
- # --------------------------------------------------------------------------------------------------
98
-
99
-
100
- def latest_data(
101
- payload: str | dict,
102
- data_type: str = "stock",
103
- df: bool = False
104
- ) -> list | pd.DataFrame | None:
105
- """股票或板块的最新数据"""
106
-
107
- # 热门板块
108
- # https://www.cls.cn/hotPlate
109
- # 行业板块
110
- # https://x-quote.cls.cn/web_quote/plate/plate_list?rever=1&way=change&type=industry
111
- # 概念板块
112
- # https://x-quote.cls.cn/web_quote/plate/plate_list?rever=1&way=change&type=concept
113
- # 地域板块
114
- # https://x-quote.cls.cn/web_quote/plate/plate_list?rever=1&way=change&type=area
115
-
116
- # ----------------------------------------------------------------------------------------------
117
-
118
- # 判断参数类型
119
- match True:
120
- case True if not utils.isTrue(payload, (str, dict)):
121
- logger.error("argument error: payload")
122
- return None
123
- case True if not utils.isTrue(data_type, str):
124
- logger.error("argument error: data_type")
125
- return None
126
- case _:
127
- pass
128
-
129
- # ----------------------------------------------------------------------------------------------
130
-
131
- # 判断数据类型. 数据类型: 个股, 板块 (产业链: industry)
132
- if data_type not in ["stock", "plate"]:
133
- logger.error("data_type error")
134
- return None
135
-
136
- # ----------------------------------------------------------------------------------------------
137
-
138
- # 日志信息
139
-
140
- # 个股 (默认)
141
- info: str = "获取股票最新数据"
142
-
143
- # 板块
144
- if data_type == "plate":
145
- info = "获取板块最新数据"
146
-
147
- # match True:
148
- # case True if data_type == "plate":
149
- # info = "获取板块最新数据"
150
- # case True if data_type == "industry":
151
- # info = "获取产业链最新数据"
152
- # case _:
153
- # pass
154
-
155
- # ----------------------------------------------------------------------------------------------
156
-
157
- try:
158
-
159
- logger.info(f"{info} ......")
160
-
161
- # ------------------------------------------------------------------------------------------
162
-
163
- # HTTP User Agent
164
- user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
165
-
166
- # HTTP Headers
167
- headers = {"User-Agent": user_agent}
168
-
169
- # ------------------------------------------------------------------------------------------
170
-
171
- # 请求参数
172
- params: dict = {}
173
-
174
- # 默认请求参数
175
- if isinstance(payload, str) and utils.isTrue(payload, str):
176
- params = {"secu_code": payload}
177
-
178
- # 请求参数
179
- if isinstance(payload, dict) and utils.isTrue(payload, dict):
180
- params = payload
181
-
182
- # ------------------------------------------------------------------------------------------
183
-
184
- # 不直接在API后面跟参数, 使用 params 传递参数
185
-
186
- # API: 股票
187
- # api: str = f"https://x-quote.cls.cn/quote/stock/basic?secu_code={code}"
188
- api: str = "https://x-quote.cls.cn/quote/stock/basic"
189
-
190
- # API: 板块
191
- if data_type == "plate":
192
- # api = f"https://x-quote.cls.cn/web_quote/plate/stocks?secu_code={code}"
193
- api = "https://x-quote.cls.cn/web_quote/plate/stocks"
194
-
195
- # match True:
196
- # case True if data_type == "plate":
197
- # # 板块
198
- # # api = f"https://x-quote.cls.cn/web_quote/plate/stocks?secu_code={code}"
199
- # api = "https://x-quote.cls.cn/web_quote/plate/stocks"
200
- # case True if data_type == "industry":
201
- # # 产业链
202
- # # api = f"https://x-quote.cls.cn/web_quote/plate/industry?secu_code={code}"
203
- # api = "https://x-quote.cls.cn/web_quote/plate/industry"
204
- # case _:
205
- # pass
206
-
207
- # ------------------------------------------------------------------------------------------
208
-
209
- # 获取数据
210
- # response = requests.get(api, headers=headers, timeout=10)
211
- response = requests.get(api, headers=headers, params=params, timeout=10)
212
-
213
- # 转换数据类型
214
- response_dict: dict = response.json()
215
-
216
- # 判断数据是否正确
217
- if True not in [utils.isTrue(response_dict["data"], dict), utils.isTrue(response_dict["data"], list)]:
218
- logger.error(f"{info} [失败]")
219
- return None
220
-
221
- # ------------------------------------------------------------------------------------------
222
-
223
- # 个股
224
-
225
- if data_type == "stock":
226
-
227
- # 停牌, 返回 None
228
- if response_dict["data"]["trade_status"] == "STOPT":
229
- logger.error(f"{info} [停牌]")
230
- return None
231
-
232
- # pd.DataFrame 数据
233
- if utils.isTrue(df, bool):
234
- df_data = {
235
- # "date": [pd.to_datetime(date_today)],
236
- "open": [float(response_dict["data"]["open_px"])],
237
- "close": [float(response_dict["data"]["last_px"])],
238
- "high": [float(response_dict["data"]["high_px"])],
239
- "low": [float(response_dict["data"]["low_px"])],
240
- "volume": [int(response_dict["data"]["business_amount"])],
241
- "turnover": [float(response_dict["data"]["tr"])]
242
- }
243
- logger.success(f"{info} [成功]")
244
- return pd.DataFrame(data=df_data)
245
-
246
- # 默认返回的数据
247
- logger.success(f"{info} [成功]")
248
- return response_dict["data"]
249
-
250
- # ------------------------------------------------------------------------------------------
251
-
252
- # 板块
253
-
254
- # 板块数据不能转换为 pd.DataFrame
255
- if (data_type == "plate") and utils.isTrue(df, bool):
256
- logger.error(f"{info} [错误]")
257
- return None
258
-
259
- # 数据结果
260
- result: list = []
261
-
262
- # 筛选 主板, 非ST, 非银行, 非证券 的股票
263
- for i in response_dict["data"]["stocks"]:
264
- if (re.match(r"^(sz00|sh60)", i["secu_code"])) and (not re.search(r"ST|银行|证券", i["secu_name"])):
265
- result.append(i)
266
-
267
- # 返回数据
268
- logger.success(f"{info} [成功]")
269
- return result
270
-
271
- except Exception as e:
272
- logger.error(f"{info} [失败]")
273
- logger.exception(e)
274
- return None
275
-
276
-
277
- # --------------------------------------------------------------------------------------------------
278
-
279
-
280
- def plate_codes(
281
- plate: str
282
- ) -> list | None:
283
- """获取板块成分股代码"""
284
-
285
- # 判断参数是否正确
286
- match True:
287
- case True if not utils.isTrue(plate, str):
288
- logger.error("argument error: plate")
289
- return None
290
- case _:
291
- pass
292
-
293
- info: str = "获取板块成分股代码"
294
-
295
- try:
296
-
297
- logger.info(f"{info} ......")
298
-
299
- items = latest_data(payload=plate, data_type="plate")
300
-
301
- if isinstance(items, list):
302
- codes: list = [stock.coderename(i["secu_code"], restore=True) for i in items]
303
- codes.sort()
304
- logger.success(f"{info} [成功]")
305
- return codes
306
-
307
- logger.error(f"{info} [失败]")
308
- return None
309
-
310
- except Exception as e:
311
- logger.error(f"{info} [失败]")
312
- logger.exception(e)
313
- return None
ezKit/stock.py DELETED
@@ -1,345 +0,0 @@
1
- """股票"""
2
- import re
3
- from copy import deepcopy
4
-
5
- import akshare as ak
6
- import numpy as np
7
- import talib as ta
8
- from loguru import logger
9
- from pandas import DataFrame
10
- from sqlalchemy.engine import Engine
11
-
12
- from . import utils
13
-
14
-
15
- def coderename(
16
- target: str | dict,
17
- restore: bool = False
18
- ) -> str | dict | None:
19
- """代码重命名"""
20
-
21
- # 正向:
22
- # coderename('000001') => 'sz000001'
23
- # coderename({'code': '000001', 'name': '平安银行'}) => {'code': 'sz000001', 'name': '平安银行'}
24
- # 反向:
25
- # coderename('sz000001', restore=True) => '000001'
26
- # coderename({'code': 'sz000001', 'name': '平安银行'}) => {'code': '000001', 'name': '平安银行'}
27
-
28
- # 判断参数是否正确
29
- match True:
30
- case True if not utils.isTrue(target, (str, dict)):
31
- logger.error("argument error: target")
32
- return None
33
- case _:
34
- pass
35
-
36
- try:
37
-
38
- # 初始化
39
- code_object: dict = {}
40
- code_name: str | dict = ""
41
-
42
- # 判断 target 是 string 还是 dictionary
43
- if isinstance(target, str) and utils.isTrue(target, str):
44
- code_name = target
45
- elif isinstance(target, dict) and utils.isTrue(target, dict):
46
- code_object = deepcopy(target)
47
- code_name = str(deepcopy(target["code"]))
48
- else:
49
- return None
50
-
51
- # 是否还原
52
- if utils.isTrue(restore, bool):
53
- if len(code_name) == 8 and re.match(r"^(sz|sh)", code_name):
54
- code_name = deepcopy(code_name[2:8])
55
- else:
56
- return None
57
- else:
58
- if code_name[0:2] == "00":
59
- code_name = f"sz{code_name}"
60
- elif code_name[0:2] == "60":
61
- code_name = f"sh{code_name}"
62
- else:
63
- return None
64
-
65
- # 返回结果
66
- if utils.isTrue(target, str):
67
- return code_name
68
-
69
- if utils.isTrue(target, dict):
70
- code_object["code"] = code_name
71
- return code_object
72
-
73
- return None
74
-
75
- except Exception as e:
76
- logger.exception(e)
77
- return None
78
-
79
-
80
- # --------------------------------------------------------------------------------------------------
81
-
82
-
83
- def kdj_vector(
84
- df: DataFrame,
85
- kdj_options: tuple[int, int, int] = (9, 3, 3)
86
- ) -> DataFrame | None:
87
- """KDJ计算器"""
88
-
89
- # 计算周期:Calculation Period, 也可使用 Lookback Period 表示回溯周期, 指用于计算指标值的时间周期.
90
- # 移动平均周期: Smoothing Period 或 Moving Average Period, 指对指标进行平滑处理时采用的周期.
91
- # 同花顺默认参数: 9 3 3
92
- # https://www.daimajiaoliu.com/daima/4ed4ffa26100400
93
- # 说明: KDJ 指标的中文名称又叫随机指标, 融合了动量观念、强弱指标和移动平均线的一些优点, 能够比较迅速、快捷、直观地研判行情, 被广泛用于股市的中短期趋势分析.
94
- # 有采用 ewm 使用 com=2 的, 但是如果使用 com=2 在默认值的情况下KDJ值是正确的.
95
- # 但是非默认值, 比如调整参数, 尝试慢速 KDJ 时就不对了, 最终采用 alpha = 1/m 的情况, 对比同花顺数据, 是正确的.
96
-
97
- # 检查参数
98
- if isinstance(df, DataFrame) and df.empty:
99
- logger.error("argument error: df")
100
- return None
101
-
102
- if not utils.check_arguments([(kdj_options, tuple, "kdj_options")]):
103
- return None
104
-
105
- if not all(utils.isTrue(item, int) for item in kdj_options):
106
- logger.error("argument error: kdj_options")
107
- return None
108
-
109
- try:
110
- low_list = df['low'].rolling(kdj_options[0]).min()
111
- high_list = df['high'].rolling(kdj_options[0]).max()
112
- rsv = (df['close'] - low_list) / (high_list - low_list) * 100
113
- df['K'] = rsv.ewm(alpha=1 / kdj_options[1], adjust=False).mean()
114
- df['D'] = df['K'].ewm(alpha=1 / kdj_options[2], adjust=False).mean()
115
- df['J'] = (3 * df['K']) - (2 * df['D'])
116
- return df
117
- except Exception as e:
118
- logger.exception(e)
119
- return None
120
-
121
-
122
- # --------------------------------------------------------------------------------------------------
123
-
124
-
125
- def data_vector(
126
- df: DataFrame,
127
- macd_options: tuple[int, int, int] = (12, 26, 9),
128
- kdj_options: tuple[int, int, int] = (9, 3, 3)
129
- ) -> DataFrame | None:
130
- """数据运算"""
131
-
132
- # 检查参数
133
- if isinstance(df, DataFrame) and df.empty:
134
- logger.error("argument error: df")
135
- return None
136
-
137
- if not utils.check_arguments([(macd_options, tuple, "macd_options"), (kdj_options, tuple, "kdj_options")]):
138
- return None
139
-
140
- if not all(utils.isTrue(item, int) for item in macd_options):
141
- logger.error("argument error: macd_options")
142
- return None
143
-
144
- if not all(utils.isTrue(item, int) for item in kdj_options):
145
- logger.error("argument error: kdj_options")
146
- return None
147
-
148
- try:
149
-
150
- # ------------------------------------------------------------------------------------------
151
-
152
- # 计算均线: 3,7日均线
153
- # pylint: disable=E1101
154
- # df['SMA03'] = ta.SMA(df['close'], timeperiod=3) # type: ignore
155
- # df['SMA07'] = ta.SMA(df['close'], timeperiod=7) # type: ignore
156
-
157
- # 3,7日均线金叉: 0 无, 1 金叉, 2 死叉
158
- # df['SMA37_X'] = 0
159
- # sma37_position = df['SMA03'] > df['SMA07']
160
- # df.loc[sma37_position[(sma37_position is True) & (sma37_position.shift() is False)].index, 'SMA37_X'] = 1 # type: ignore
161
- # df.loc[sma37_position[(sma37_position is False) & (sma37_position.shift() is True)].index, 'SMA37_X'] = 2 # type: ignore
162
-
163
- # 计算均线: 20,25日均线
164
- # df['SMA20'] = ta.SMA(df['close'], timeperiod=20) # type: ignore
165
- # df['SMA25'] = ta.SMA(df['close'], timeperiod=25) # type: ignore
166
-
167
- # 20,25日均线金叉: 0 无, 1 金叉, 2 死叉
168
- # df['SMA225_X'] = 0
169
- # sma225_position = df['SMA20'] > df['SMA25']
170
- # df.loc[sma225_position[(sma225_position is True) & (sma225_position.shift() is False)].index, 'SMA225_X'] = 1 # type: ignore
171
- # df.loc[sma225_position[(sma225_position is False) & (sma225_position.shift() is True)].index, 'SMA225_X'] = 2 # type: ignore
172
-
173
- # ------------------------------------------------------------------------------------------
174
-
175
- # 计算 MACD: 默认参数 12 26 9
176
- macd_dif, macd_dea, macd_bar = ta.MACD( # type: ignore
177
- df['close'].values,
178
- fastperiod=macd_options[0],
179
- slowperiod=macd_options[1],
180
- signalperiod=macd_options[2]
181
- )
182
-
183
- macd_dif[np.isnan(macd_dif)], macd_dea[np.isnan(macd_dea)], macd_bar[np.isnan(macd_bar)] = 0, 0, 0
184
-
185
- # https://www.bilibili.com/read/cv10185856
186
- df['MACD'] = 2 * (macd_dif - macd_dea)
187
- df['MACD_DIF'] = macd_dif
188
- df['MACD_DEA'] = macd_dea
189
-
190
- # 初始化 MACD_X 列(0 无, 1 金叉, 2 死叉)
191
- df['MACD_X'] = 0
192
-
193
- # 计算 MACD 条件
194
- macd_position = df['MACD_DIF'] > df['MACD_DEA']
195
-
196
- # 设置 MACD_X = 1: 从 False 变为 True 的位置
197
- df.loc[macd_position & ~macd_position.shift(fill_value=False), 'MACD_X'] = 1
198
-
199
- # 设置 MACD_X = 2: 从 True 变为 False 的位置
200
- df.loc[~macd_position & macd_position.shift(fill_value=False), 'MACD_X'] = 2
201
-
202
- # 将浮点数限制为小数点后两位
203
- df['MACD'] = df['MACD'].round(2)
204
- df['MACD_DIF'] = df['MACD_DIF'].round(2)
205
- df['MACD_DEA'] = df['MACD_DEA'].round(2)
206
-
207
- # ------------------------------------------------------------------------------------------
208
-
209
- # # 计算 KDJ: : 默认参数 9 3 3
210
- kdj_data = kdj_vector(df, kdj_options)
211
-
212
- if kdj_data is not None:
213
-
214
- # KDJ 数据
215
- df['K'] = kdj_data['K'].values
216
- df['D'] = kdj_data['D'].values
217
- df['J'] = kdj_data['J'].values
218
-
219
- # 初始化 KDJ_X 列(0 无, 1 金叉, 2 死叉)
220
- df['KDJ_X'] = 0
221
-
222
- # 计算 MACD 条件
223
- kdj_position = df['J'] > df['D']
224
-
225
- # 设置 KDJ_X = 1: 从 False 变为 True 的位置
226
- df.loc[kdj_position & ~kdj_position.shift(fill_value=False), 'KDJ_X'] = 1
227
-
228
- # 设置 KDJ_X = 2: 从 True 变为 False 的位置
229
- df.loc[~kdj_position & kdj_position.shift(fill_value=False), 'KDJ_X'] = 2
230
-
231
- # 将浮点数限制为小数点后两位
232
- df['K'] = df['K'].round(2)
233
- df['D'] = df['D'].round(2)
234
- df['J'] = df['J'].round(2)
235
-
236
- # ------------------------------------------------------------------------------------------
237
-
238
- return df
239
-
240
- except Exception as e:
241
- logger.exception(e)
242
- return None
243
-
244
-
245
- # --------------------------------------------------------------------------------------------------
246
-
247
-
248
- def get_code_name_from_akshare() -> DataFrame | None:
249
- """获取股票代码和名称"""
250
- info = "获取股票代码和名称"
251
- try:
252
- logger.info(f"{info} ......")
253
- df: DataFrame = ak.stock_info_a_code_name()
254
- if df.empty:
255
- logger.error(f"{info} [失败]")
256
- return None
257
- # 排除 ST、证券和银行
258
- # https://towardsdatascience.com/8-ways-to-filter-pandas-dataframes-d34ba585c1b8
259
- df = df[df.code.str.contains("^00|^60") & ~df.name.str.contains("ST|证券|银行")]
260
- logger.success(f"{info} [成功]")
261
- return df
262
- except Exception as e:
263
- logger.error(f"{info} [失败]")
264
- logger.exception(e)
265
- return None
266
-
267
-
268
- # --------------------------------------------------------------------------------------------------
269
-
270
-
271
- def get_stock_data_from_akshare(
272
- code: str,
273
- adjust: str = "qfq",
274
- period: str = "daily",
275
- start_date: str = "19700101",
276
- end_date: str = "20500101",
277
- timeout: float = 10
278
- ) -> DataFrame | None:
279
- """从 akshare 获取股票数据"""
280
- info = f"获取股票数据: {code}"
281
- try:
282
- logger.info(f"{info} ......")
283
- # https://akshare.akfamily.xyz/data/stock/stock.html#id22
284
- df: DataFrame = ak.stock_zh_a_hist(symbol=code, adjust=adjust, period=period, start_date=start_date, end_date=end_date, timeout=timeout)
285
- df = df.rename(columns={
286
- "日期": "date",
287
- "开盘": "open",
288
- "收盘": "close",
289
- "最高": "high",
290
- "最低": "low",
291
- "成交量": "volume"
292
- })
293
- logger.success(f"{info} [成功]")
294
- return df[['date', 'open', 'close', 'high', 'low', 'volume']].copy()
295
- except Exception as e:
296
- logger.error(f"{info} [失败]")
297
- logger.exception(e)
298
- return None
299
-
300
-
301
- # --------------------------------------------------------------------------------------------------
302
-
303
-
304
- def save_data_to_database(engine: Engine, code: str, latest: bool = False) -> bool:
305
- """保存股票所有数据到数据库"""
306
-
307
- # 默认将所有数据保存到数据库中的表里
308
- # 如果 latest 为 True, 插入最新的数据到数据库中的表里
309
- # 即: 将最后一条数据插入到数据库中的表里
310
-
311
- info: str = "保存股票所有数据到数据库"
312
-
313
- if utils.isTrue(latest, bool):
314
- info = "保存股票最新数据到数据库"
315
-
316
- try:
317
-
318
- logger.info(f"{info} ......")
319
-
320
- df: DataFrame | None = get_stock_data_from_akshare(code)
321
-
322
- if df is None:
323
- logger.error(f"{info} [获取数据失败]")
324
- return False
325
-
326
- name = coderename(code)
327
-
328
- if not isinstance(name, str):
329
- logger.error(f"{info} [代码名称转换失败]")
330
- return False
331
-
332
- if utils.isTrue(latest, bool):
333
- df = df.tail(1)
334
- df.to_sql(name=name, con=engine, if_exists="append", index=False)
335
- else:
336
- df.to_sql(name=name, con=engine, if_exists="replace", index=False)
337
-
338
- logger.success(f"{info} [成功]")
339
-
340
- return True
341
-
342
- except Exception as e:
343
- logger.success(f"{info} [失败]")
344
- logger.exception(e)
345
- return False
File without changes