ezKit 1.9.12__tar.gz → 1.10.0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {ezkit-1.9.12/ezKit.egg-info → ezkit-1.10.0}/PKG-INFO +1 -1
- {ezkit-1.9.12 → ezkit-1.10.0/ezKit.egg-info}/PKG-INFO +1 -1
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit.egg-info/SOURCES.txt +0 -2
- {ezkit-1.9.12 → ezkit-1.10.0}/setup.py +1 -1
- ezkit-1.9.12/ezKit/cls.py +0 -313
- ezkit-1.9.12/ezKit/stock.py +0 -355
- {ezkit-1.9.12 → ezkit-1.10.0}/LICENSE +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/MANIFEST.in +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/README.md +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit/__init__.py +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit/bottle.py +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit/bottle_extensions.py +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit/cipher.py +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit/database.py +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit/http.py +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit/mongo.py +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit/qywx.py +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit/redis.py +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit/sendemail.py +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit/token.py +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit/utils.py +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit/xftp.py +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit.egg-info/dependency_links.txt +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit.egg-info/requires.txt +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/ezKit.egg-info/top_level.txt +0 -0
- {ezkit-1.9.12 → ezkit-1.10.0}/setup.cfg +0 -0
@@ -6,14 +6,12 @@ ezKit/__init__.py
|
|
6
6
|
ezKit/bottle.py
|
7
7
|
ezKit/bottle_extensions.py
|
8
8
|
ezKit/cipher.py
|
9
|
-
ezKit/cls.py
|
10
9
|
ezKit/database.py
|
11
10
|
ezKit/http.py
|
12
11
|
ezKit/mongo.py
|
13
12
|
ezKit/qywx.py
|
14
13
|
ezKit/redis.py
|
15
14
|
ezKit/sendemail.py
|
16
|
-
ezKit/stock.py
|
17
15
|
ezKit/token.py
|
18
16
|
ezKit/utils.py
|
19
17
|
ezKit/xftp.py
|
ezkit-1.9.12/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-1.9.12/ezKit/stock.py
DELETED
@@ -1,355 +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
|
-
# 代码名称转换
|
321
|
-
name = coderename(code)
|
322
|
-
|
323
|
-
if not isinstance(name, str):
|
324
|
-
logger.error(f"{info} [代码名称转换错误]")
|
325
|
-
return False
|
326
|
-
|
327
|
-
# 获取数据
|
328
|
-
df: DataFrame | None = get_stock_data_from_akshare(code)
|
329
|
-
|
330
|
-
if df is None:
|
331
|
-
logger.error(f"{info} [获取数据错误]")
|
332
|
-
return False
|
333
|
-
|
334
|
-
# 计算数据
|
335
|
-
df: DataFrame | None = data_vector(df)
|
336
|
-
|
337
|
-
if df is None:
|
338
|
-
logger.error(f"{info} [计算数据错误]")
|
339
|
-
return False
|
340
|
-
|
341
|
-
# 保存到数据库
|
342
|
-
if utils.isTrue(latest, bool):
|
343
|
-
df = df.tail(1)
|
344
|
-
df.to_sql(name=name, con=engine, if_exists="append", index=False)
|
345
|
-
else:
|
346
|
-
df.to_sql(name=name, con=engine, if_exists="replace", index=False)
|
347
|
-
|
348
|
-
logger.success(f"{info} [成功]")
|
349
|
-
|
350
|
-
return True
|
351
|
-
|
352
|
-
except Exception as e:
|
353
|
-
logger.success(f"{info} [失败]")
|
354
|
-
logger.exception(e)
|
355
|
-
return False
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|