ezKit 1.9.4__tar.gz → 1.9.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ezKit
3
- Version: 1.9.4
3
+ Version: 1.9.6
4
4
  Summary: Easy Kit
5
5
  Author: septvean
6
6
  Author-email: septvean@gmail.com
@@ -14,9 +14,13 @@ def up_down_analysis(
14
14
  ) -> list | pd.DataFrame | None:
15
15
  """涨停跌停数据"""
16
16
 
17
- if not utils.isTrue(target, str):
18
- logger.error(f"error type: {target}")
19
- return None
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
20
24
 
21
25
  info: str = "获取涨停池股票"
22
26
  match True:
@@ -113,11 +117,11 @@ def latest_data(
113
117
 
114
118
  # 判断参数类型
115
119
  match True:
116
- case True if True not in [isinstance(payload, str), isinstance(payload, dict)]:
117
- logger.error("Incorrect function argument type: payload")
120
+ case True if not utils.isTrue(payload, (str, dict)):
121
+ logger.error("argument error: payload")
118
122
  return None
119
- case True if False in [isinstance(data_type, str), utils.isTrue(data_type, str)]:
120
- logger.error("Incorrect function argument type: data_type")
123
+ case True if not utils.isTrue(data_type, str):
124
+ logger.error("argument error: data_type")
121
125
  return None
122
126
  case _:
123
127
  pass
@@ -273,12 +277,18 @@ def latest_data(
273
277
  # --------------------------------------------------------------------------------------------------
274
278
 
275
279
 
276
- def plate_codes(plate: str) -> list | None:
280
+ def plate_codes(
281
+ plate: str
282
+ ) -> list | None:
277
283
  """获取板块成分股代码"""
278
284
 
279
- if not utils.isTrue(plate, str):
280
- logger.error("Incorrect function argument type: plate")
281
- return None
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
282
292
 
283
293
  info: str = "获取板块成分股代码"
284
294
 
@@ -70,24 +70,6 @@ def sendemail(
70
70
  path 图片路径
71
71
  """
72
72
 
73
- # 参数判断
74
- # match True:
75
- # case True if utils.vTrue(smtp, dict) == False:
76
- # logger.error('ERROR!! {} is not dictionary or none'.format('smtp'))
77
- # return False
78
- # case True if utils.vTrue(sender, dict) == False:
79
- # logger.error('ERROR!! {} is not dictionary or none'.format('sender'))
80
- # return False
81
- # case True if (utils.vTrue(recipients, str) == False) and (utils.vTrue(recipients, list) == False):
82
- # logger.error('ERROR!! {} is not list or none'.format('recipients'))
83
- # return False
84
- # case True if utils.vTrue(subject, str) == False:
85
- # logger.error('ERROR!! {} is not string or none'.format('subject'))
86
- # return False
87
- # case True if utils.vTrue(html_file, str) == False:
88
- # logger.error('ERROR!! {} is not string or none'.format('html_file'))
89
- # return False
90
-
91
73
  logger.success("sendemail start")
92
74
 
93
75
  try:
@@ -12,7 +12,10 @@ from sqlalchemy.engine import Engine
12
12
  from . import utils
13
13
 
14
14
 
15
- def coderename(target: str | dict, restore: bool = False) -> str | dict | None:
15
+ def coderename(
16
+ target: str | dict,
17
+ restore: bool = False
18
+ ) -> str | dict | None:
16
19
  """代码重命名"""
17
20
 
18
21
  # 正向:
@@ -22,18 +25,10 @@ def coderename(target: str | dict, restore: bool = False) -> str | dict | None:
22
25
  # coderename('sz000001', restore=True) => '000001'
23
26
  # coderename({'code': 'sz000001', 'name': '平安银行'}) => {'code': '000001', 'name': '平安银行'}
24
27
 
25
- # 判断参数类型
28
+ # 判断参数是否正确
26
29
  match True:
27
- case True if True not in [isinstance(target, str), isinstance(target, dict)]:
28
- logger.error("argument type error: target")
29
- return None
30
- case _:
31
- pass
32
-
33
- # 判断参数数据
34
- match True:
35
- case True if True not in [utils.isTrue(target, str), utils.isTrue(target, dict)]:
36
- logger.error("argument data error: data")
30
+ case True if not utils.isTrue(target, (str, dict)):
31
+ logger.error("argument error: target")
37
32
  return None
38
33
  case _:
39
34
  pass
@@ -85,7 +80,10 @@ def coderename(target: str | dict, restore: bool = False) -> str | dict | None:
85
80
  # --------------------------------------------------------------------------------------------------
86
81
 
87
82
 
88
- def kdj_vector(df: DataFrame, cp: int = 9, sp1: int = 3, sp2: int = 3) -> DataFrame | None:
83
+ def kdj_vector(
84
+ df: DataFrame,
85
+ kdj_options: tuple[int, int, int] = (9, 3, 3)
86
+ ) -> DataFrame | None:
89
87
  """KDJ计算器"""
90
88
 
91
89
  # 计算周期:Calculation Period, 也可使用 Lookback Period 表示回溯周期, 指用于计算指标值的时间周期.
@@ -96,20 +94,23 @@ def kdj_vector(df: DataFrame, cp: int = 9, sp1: int = 3, sp2: int = 3) -> DataFr
96
94
  # 有采用 ewm 使用 com=2 的, 但是如果使用 com=2 在默认值的情况下KDJ值是正确的.
97
95
  # 但是非默认值, 比如调整参数, 尝试慢速 KDJ 时就不对了, 最终采用 alpha = 1/m 的情况, 对比同花顺数据, 是正确的.
98
96
 
99
- # 判断参数类型
97
+ # 判断参数是否正确
100
98
  match True:
101
- case True if not isinstance(df, DataFrame):
102
- logger.error("argument type error: df")
99
+ case True if not utils.isTrue(df, DataFrame):
100
+ logger.error("argument error: df")
101
+ return None
102
+ case True if not any([utils.isTrue(kdj_options, tuple), all(utils.isTrue(item, int) for item in kdj_options)]):
103
+ logger.error("argument error: kdj_options")
103
104
  return None
104
105
  case _:
105
106
  pass
106
107
 
107
108
  try:
108
- low_list = df['low'].rolling(cp).min()
109
- high_list = df['high'].rolling(cp).max()
109
+ low_list = df['low'].rolling(kdj_options[0]).min()
110
+ high_list = df['high'].rolling(kdj_options[0]).max()
110
111
  rsv = (df['close'] - low_list) / (high_list - low_list) * 100
111
- df['K'] = rsv.ewm(alpha=1 / sp1, adjust=False).mean()
112
- df['D'] = df['K'].ewm(alpha=1 / sp2, adjust=False).mean()
112
+ df['K'] = rsv.ewm(alpha=1 / kdj_options[1], adjust=False).mean()
113
+ df['D'] = df['K'].ewm(alpha=1 / kdj_options[2], adjust=False).mean()
113
114
  df['J'] = (3 * df['K']) - (2 * df['D'])
114
115
  return df
115
116
  except Exception as e:
@@ -127,6 +128,20 @@ def data_vector(
127
128
  ) -> DataFrame | None:
128
129
  """数据运算"""
129
130
 
131
+ # 判断参数是否正确
132
+ match True:
133
+ case True if not utils.isTrue(df, DataFrame):
134
+ logger.error("argument error: df")
135
+ return None
136
+ case True if not any([utils.isTrue(macd_options, tuple), all(utils.isTrue(item, int) for item in macd_options)]):
137
+ logger.error("argument error: macd_options")
138
+ return None
139
+ case True if not any([utils.isTrue(kdj_options, tuple), all(utils.isTrue(item, int) for item in kdj_options)]):
140
+ logger.error("argument error: kdj_options")
141
+ return None
142
+ case _:
143
+ pass
144
+
130
145
  try:
131
146
 
132
147
  # 数据为空
@@ -176,7 +191,7 @@ def data_vector(
176
191
  # ------------------------------------------------------------------------------------------
177
192
 
178
193
  # 计算 KDJ: : 默认参数 9 3 3
179
- kdj_data = kdj_vector(df, kdj_options[0], kdj_options[1], kdj_options[2])
194
+ kdj_data = kdj_vector(df, kdj_options)
180
195
 
181
196
  if kdj_data is not None:
182
197
 
@@ -226,12 +241,29 @@ def get_code_name_from_akshare() -> DataFrame | None:
226
241
  # --------------------------------------------------------------------------------------------------
227
242
 
228
243
 
229
- def get_stock_data_from_akshare(code: str) -> DataFrame | None:
230
- """从 akshare 获取数据"""
231
- info = f"获取股票所有数据: {code}"
244
+ def get_stock_data_from_akshare(
245
+ code: str,
246
+ adjust: str = "qfq",
247
+ period: str = "daily",
248
+ start_date: str = "19700101",
249
+ end_date: str = "20500101",
250
+ timeout: float = 10
251
+ ) -> DataFrame | None:
252
+ """从 akshare 获取股票数据"""
253
+ info = f"获取股票数据: {code}"
232
254
  try:
233
255
  logger.info(f"{info} ......")
234
- df: DataFrame = ak.stock_zh_a_daily(symbol=code, adjust="qfq")
256
+ # https://akshare.akfamily.xyz/data/stock/stock.html#id22
257
+ df: DataFrame = ak.stock_zh_a_hist(symbol=code, adjust=adjust, period=period, start_date=start_date, end_date=end_date, timeout=timeout)
258
+ df = df.rename(columns={
259
+ "日期": "date",
260
+ "开盘": "open",
261
+ "收盘": "close",
262
+ "最高": "high",
263
+ "最低": "low",
264
+ "成交量": "volume",
265
+ "成交额": "turnover"
266
+ })
235
267
  df = df.round({'turnover': 4})
236
268
  logger.success(f"{info} [成功]")
237
269
  return df[['date', 'open', 'close', 'high', 'low', 'volume', 'turnover']].copy()
@@ -244,17 +276,37 @@ def get_stock_data_from_akshare(code: str) -> DataFrame | None:
244
276
  # --------------------------------------------------------------------------------------------------
245
277
 
246
278
 
247
- def save_data_to_database(engine: Engine, code: str) -> bool:
248
- """保存股票数据到数据库"""
249
- info: str = "保存股票数据到数据库"
279
+ def save_data_to_database(engine: Engine, code: str, latest: bool = False) -> bool:
280
+ """保存股票所有数据到数据库"""
281
+
282
+ # 默认将所有数据保存到数据库中的表里
283
+ # 如果 latest 为 True, 插入最新的数据到数据库中的表里
284
+ # 即: 将最后一条数据插入到数据库中的表里
285
+
286
+ info: str = "保存股票所有数据到数据库"
287
+
288
+ if utils.isTrue(latest, bool):
289
+ info = "保存股票最新数据到数据库"
290
+
250
291
  try:
292
+
251
293
  logger.info(f"{info} ......")
294
+
252
295
  df: DataFrame | None = get_stock_data_from_akshare(code)
296
+
253
297
  if df is None:
254
298
  return False
255
- df.to_sql(name=code, con=engine, if_exists="replace", index=False)
299
+
300
+ if utils.isTrue(latest, bool):
301
+ df = df.tail(1)
302
+ df.to_sql(name=code, con=engine, if_exists="append", index=False)
303
+ else:
304
+ df.to_sql(name=code, con=engine, if_exists="replace", index=False)
305
+
256
306
  logger.success(f"{info} [成功]")
307
+
257
308
  return True
309
+
258
310
  except Exception as e:
259
311
  logger.success(f"{info} [失败]")
260
312
  logger.exception(e)
@@ -50,6 +50,15 @@ def isTrue(
50
50
  # 判断变量类型: isinstance(x, str)
51
51
  #
52
52
  # 函数使用 callable(func) 判断
53
+ #
54
+ # 判断多个类型:
55
+ #
56
+ # isTrue("abc", (str, int))
57
+ # isTrue("abc", (str | int))
58
+ #
59
+ # all() 用于检查一个可迭代对象(如列表、元组、集合等)中的 所有 元素是否为 真值 (truthy), 所有元素为真, 返回 True
60
+ # any() 用于检查一个可迭代对象(如列表、元组、集合等)中的 某个 元素是否为 真值 (truthy), 某个元素为真, 返回 True
61
+ # 与 all() 作用相反的 not any(), 可以用来检查所有元素是否为 假值 (falsy), any() 中所有元素为假, not any() 返回 True
53
62
 
54
63
  try:
55
64
 
@@ -100,33 +109,39 @@ def os_environ(
100
109
  name: str,
101
110
  value: Any = None
102
111
  ) -> Any:
103
- """
104
- 系统变量
112
+ """系统变量"""
113
+
114
+ # 伪全局变量
115
+ # Python 没有全局变量, 多个文件无法调用同一个变量.
116
+ # 为了解决这个问题, 将变量设置为系统变量, 从而实现多个文件调用同一个变量.
117
+
118
+ # 判断参数是否正确
119
+ match True:
120
+ case True if not isTrue(name, str):
121
+ logger.error("argument error: name")
122
+ return None
123
+ case _:
124
+ pass
105
125
 
106
- 伪全局变量
107
- Python 没有全局变量, 多个文件无法调用同一个变量.
108
- 为了解决这个问题, 将变量设置为系统变量, 从而实现多个文件调用同一个变量.
109
- """
110
126
  try:
111
127
 
112
128
  # 变量名添加一个前缀, 防止和系统中其它变量名冲突
113
129
  _variable_name = f'PYTHON_VARIABLE_{name}'
114
130
 
131
+ # 如果 value 的值是 None, 则从系统环境获取变量数据
115
132
  if value is None:
116
133
 
117
134
  _data = os.environ.get(_variable_name)
118
135
 
119
136
  # 判断是否有数据
120
- if _data:
121
- try:
122
- # 如果环境变量有值, 使用 json.loads() 解析
123
- parsed_data = json.loads(_data)
124
- return parsed_data
125
- except json.JSONDecodeError:
126
- return None
127
- else:
137
+ if _data is None or not isTrue(_data, str):
128
138
  return None
129
139
 
140
+ # 使用 json.loads() 解析数据
141
+ parsed_data = json.loads(_data)
142
+ return parsed_data
143
+
144
+ # 如果 value 的值不是 None, 则保存数据到系统环境变量
130
145
  _data = json.dumps(value)
131
146
  os.environ[_variable_name] = _data
132
147
 
@@ -143,29 +158,52 @@ def os_environ(
143
158
  def mam_of_numbers(
144
159
  numbers: list | tuple,
145
160
  dest_type: str | None = None
146
- ) -> tuple[int | float, int | float, int | float] | tuple[None, None, None]:
147
- """
148
- (maximum, average, minimum)
161
+ ) -> tuple | None:
162
+ """返回一组数字中的 最大值(maximum), 平均值(average), 最小值(minimum)"""
149
163
 
150
- 返回一组数字中的 最大值(maximum), 平均值(average), 最小值(minimum)
151
- numbers 数字列表 (仅支持 list 和 tuple, 不支 set)
152
- dest_type 目标类型 (将数字列表中的数字转换成统一的类型)
153
- """
164
+ # numbers 数字列表 (仅支持 list 和 tuple, 不支 set)
165
+ # dest_type 目标类型 (将数字列表中的数字转换成统一的类型)
166
+
167
+ # 判断参数是否正确
168
+ match True:
169
+ case True if not isTrue(numbers, (list, tuple)):
170
+ logger.error("argument error: numbers")
171
+ return None
172
+ case True if not any([isTrue(dest_type, str), dest_type is None]):
173
+ logger.error("argument error: dest_type")
174
+ return None
175
+ case _:
176
+ pass
154
177
 
155
178
  try:
179
+
156
180
  _numbers = deepcopy(numbers)
181
+
182
+ # 转换数据类型
157
183
  match True:
158
- case True if dest_type == 'float':
184
+ case True if dest_type == "float":
159
185
  _numbers = [float(i) for i in numbers]
160
- case True if dest_type == 'int':
186
+ case True if dest_type == "int":
161
187
  _numbers = [int(i) for i in numbers]
188
+ case _:
189
+ pass
190
+
191
+ # 提取数据
162
192
  _num_max = max(_numbers)
163
- _num_avg = sum(_numbers) / len(_numbers)
193
+ _num_avg = f"{sum(_numbers) / len(_numbers):.2f}"
164
194
  _num_min = min(_numbers)
195
+
196
+ if dest_type == int:
197
+ _num_avg = int(_num_avg)
198
+ else:
199
+ _num_avg = float(_num_avg)
200
+
201
+ # 返回数据
165
202
  return _num_max, _num_avg, _num_min
203
+
166
204
  except Exception as e:
167
205
  logger.exception(e)
168
- return None, None, None
206
+ return None
169
207
 
170
208
 
171
209
  # --------------------------------------------------------------------------------------------------
@@ -187,10 +225,10 @@ def step_number_for_split_equally(
187
225
 
188
226
  [1, 2, 3, 4, 5, 6, 7, 8, 9]
189
227
 
190
- 分成 2 份 -> [[1, 2, 3, 4, 5], [6, 7, 8, 9]] -> 返回 5
191
- 分成 3 份 -> [[1, 2, 3], [4, 5, 6], [7, 8, 9]] -> 返回 3
192
- 分成 4 份 -> [[1, 2, 3], [4, 5], [6, 7], [8, 9]] -> 返回 3
193
- 分成 5 份 -> [[1, 2], [3, 4], [5, 6], [7, 8], [9]] -> 返回 2
228
+ 分成 2 份 -> [[1, 2, 3, 4, 5], [6, 7, 8, 9]] -> 返回 5
229
+ 分成 3 份 -> [[1, 2, 3], [4, 5, 6], [7, 8, 9]] -> 返回 3
230
+ 分成 4 份 -> [[1, 2, 3], [4, 5], [6, 7], [8, 9]] -> 返回 3
231
+ 分成 5 份 -> [[1, 2], [3, 4], [5, 6], [7, 8], [9]] -> 返回 2
194
232
  """
195
233
  try:
196
234
  if integer % split_equally_number == 0:
@@ -411,27 +449,16 @@ def list_print_by_step(
411
449
  ) -> bool:
412
450
  """根据 步长 和 分隔符 有规律的打印列表中的数据"""
413
451
 
414
- # 判断参数类型
415
- match True:
416
- case True if not isinstance(data, list):
417
- logger.error("argument type error: data")
418
- return False
419
- case True if not isinstance(step, int):
420
- logger.error("argument type error: step")
421
- return False
422
- case True if not isinstance(separator, str):
423
- logger.error("argument type error: separator")
424
- return False
425
- case _:
426
- pass
427
-
428
- # 判断参数数据
452
+ # 判断参数是否正确
429
453
  match True:
430
454
  case True if not isTrue(data, list):
431
- logger.error("argument data error: data")
455
+ logger.error("argument error: data")
432
456
  return False
433
457
  case True if not isTrue(step, int):
434
- logger.error("argument data error: step")
458
+ logger.error("argument error: step")
459
+ return False
460
+ case True if not isTrue(separator, str):
461
+ logger.error("argument error: separator")
435
462
  return False
436
463
  case _:
437
464
  pass
@@ -943,7 +970,7 @@ def datetime_local_to_timezone(
943
970
  tz: datetime.timezone = datetime.timezone.utc
944
971
  ) -> datetime.datetime | None:
945
972
  """
946
- Local datetime to TimeZone datetime (默认转换为 UTC datetime)
973
+ Local datetime to TimeZone datetime(默认转换为 UTC datetime)
947
974
  replace(tzinfo=None) 移除结尾的时区信息
948
975
  """
949
976
  try:
@@ -960,7 +987,7 @@ def datetime_utc_to_timezone(
960
987
  tz: Any = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo
961
988
  ) -> datetime.datetime | None:
962
989
  """
963
- UTC datetime to TimeZone datetime (默认转换为 Local datetime)
990
+ UTC datetime to TimeZone datetime(默认转换为 Local datetime)
964
991
  replace(tzinfo=None) 移除结尾的时区信息
965
992
  """
966
993
  try:
@@ -1173,25 +1200,25 @@ def delete_directory(
1173
1200
  """
1174
1201
  delete directory
1175
1202
 
1176
- https://docs.python.org/3/library/os.html#os.rmdir
1203
+ https: // docs.python.org / 3 / library / os.html # os.rmdir
1177
1204
 
1178
1205
  os.rmdir(path, *, dir_fd=None)
1179
1206
 
1180
- Remove (delete) the directory path.
1207
+ Remove(delete) the directory path.
1181
1208
 
1182
1209
  If the directory does not exist or is not empty, an FileNotFoundError or an OSError is raised respectively.
1183
1210
 
1184
1211
  In order to remove whole directory trees, shutil.rmtree() can be used.
1185
1212
 
1186
- https://docs.python.org/3/library/shutil.html#shutil.rmtree
1213
+ https: // docs.python.org / 3 / library / shutil.html # shutil.rmtree
1187
1214
 
1188
1215
  shutil.rmtree(path, ignore_errors=False, onerror=None)
1189
1216
 
1190
- Delete an entire directory tree; path must point to a directory (but not a symbolic link to a directory).
1217
+ Delete an entire directory tree; path must point to a directory(but not a symbolic link to a directory).
1191
1218
 
1192
1219
  If ignore_errors is true, errors resulting from failed removals will be ignored;
1193
1220
 
1194
- if false or omitted, such errors are handled by calling a handler specified by onerror or, if that is omitted, they raise an exception.
1221
+ if false or omitted, such errors are handled by calling a handler specified by onerror or , if that is omitted, they raise an exception.
1195
1222
  """
1196
1223
  try:
1197
1224
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ezKit
3
- Version: 1.9.4
3
+ Version: 1.9.6
4
4
  Summary: Easy Kit
5
5
  Author: septvean
6
6
  Author-email: septvean@gmail.com
@@ -3,7 +3,7 @@ from setuptools import find_packages, setup
3
3
 
4
4
  setup(
5
5
  name='ezKit',
6
- version='1.9.4',
6
+ version='1.9.6',
7
7
  author='septvean',
8
8
  author_email='septvean@gmail.com',
9
9
  description='Easy Kit',
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