aishare-txt 1.0.0__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.
- AIShareTxt/__init__.py +63 -0
- AIShareTxt/ai/__init__.py +11 -0
- AIShareTxt/ai/client.py +313 -0
- AIShareTxt/ai/providers/__init__.py +8 -0
- AIShareTxt/core/__init__.py +17 -0
- AIShareTxt/core/analyzer.py +495 -0
- AIShareTxt/core/config.py +324 -0
- AIShareTxt/core/data_fetcher.py +466 -0
- AIShareTxt/core/report_generator.py +783 -0
- AIShareTxt/docs/AI_INTEGRATION.md +212 -0
- AIShareTxt/docs/README_/321/211/320/227/320/235/321/206/320/256/320/224/321/210/320/277/342/224/244/321/206/320/250/320/236.md +202 -0
- AIShareTxt/docs//321/211/320/227/320/235/321/206/320/256/320/224/321/205/320/276/320/234/321/206/320/230/320/240/321/206/320/220/342/225/227/321/207/342/225/227/320/243.md +148 -0
- AIShareTxt/examples/legacy_api.py +259 -0
- AIShareTxt/indicators/__init__.py +15 -0
- AIShareTxt/indicators/technical_indicators.py +507 -0
- AIShareTxt/tests/__init__.py +11 -0
- AIShareTxt/utils/__init__.py +17 -0
- AIShareTxt/utils/stock_list.py +305 -0
- AIShareTxt/utils/utils.py +578 -0
- aishare_txt-1.0.0.dist-info/METADATA +395 -0
- aishare_txt-1.0.0.dist-info/RECORD +25 -0
- aishare_txt-1.0.0.dist-info/WHEEL +5 -0
- aishare_txt-1.0.0.dist-info/entry_points.txt +2 -0
- aishare_txt-1.0.0.dist-info/licenses/LICENSE +194 -0
- aishare_txt-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
工具类和辅助函数
|
|
5
|
+
提供通用的工具方法和辅助功能
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import os
|
|
10
|
+
import logging
|
|
11
|
+
import logging.config
|
|
12
|
+
import logging.handlers
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from ..core.config import IndicatorConfig as Config
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LoggerManager:
|
|
18
|
+
"""日志管理器"""
|
|
19
|
+
|
|
20
|
+
_initialized = False
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def setup_logging(cls, log_level=None, log_to_file=None, log_to_console=None):
|
|
24
|
+
"""
|
|
25
|
+
设置日志配置
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
log_level (str): 日志级别
|
|
29
|
+
log_to_file (bool): 是否记录到文件
|
|
30
|
+
log_to_console (bool): 是否显示到控制台
|
|
31
|
+
"""
|
|
32
|
+
if cls._initialized:
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
config = Config()
|
|
36
|
+
|
|
37
|
+
# 使用参数或默认配置
|
|
38
|
+
if log_level is None:
|
|
39
|
+
log_level = config.DEFAULT_LOG_LEVEL
|
|
40
|
+
if log_to_file is None:
|
|
41
|
+
log_to_file = config.LOG_TO_FILE
|
|
42
|
+
if log_to_console is None:
|
|
43
|
+
log_to_console = config.LOG_TO_CONSOLE
|
|
44
|
+
|
|
45
|
+
# 创建日志目录
|
|
46
|
+
if log_to_file:
|
|
47
|
+
log_dir = config.LOG_FILE_PATH
|
|
48
|
+
if not os.path.exists(log_dir):
|
|
49
|
+
os.makedirs(log_dir)
|
|
50
|
+
|
|
51
|
+
# 更新日志文件路径
|
|
52
|
+
logging_config = config.LOGGING_CONFIG.copy()
|
|
53
|
+
for handler_name, handler_config in logging_config['handlers'].items():
|
|
54
|
+
if 'filename' in handler_config:
|
|
55
|
+
filename = handler_config['filename']
|
|
56
|
+
handler_config['filename'] = os.path.join(log_dir, filename)
|
|
57
|
+
else:
|
|
58
|
+
# 如果不记录到文件,移除文件处理器
|
|
59
|
+
logging_config = config.LOGGING_CONFIG.copy()
|
|
60
|
+
for logger_name, logger_config in logging_config['loggers'].items():
|
|
61
|
+
handlers = logger_config.get('handlers', [])
|
|
62
|
+
logger_config['handlers'] = [h for h in handlers if h == 'console']
|
|
63
|
+
|
|
64
|
+
# 如果不显示到控制台,移除控制台处理器
|
|
65
|
+
if not log_to_console:
|
|
66
|
+
for logger_name, logger_config in logging_config['loggers'].items():
|
|
67
|
+
handlers = logger_config.get('handlers', [])
|
|
68
|
+
logger_config['handlers'] = [h for h in handlers if h != 'console']
|
|
69
|
+
|
|
70
|
+
# 设置日志级别
|
|
71
|
+
for logger_name, logger_config in logging_config['loggers'].items():
|
|
72
|
+
logger_config['level'] = log_level
|
|
73
|
+
|
|
74
|
+
# 应用日志配置
|
|
75
|
+
try:
|
|
76
|
+
logging.config.dictConfig(logging_config)
|
|
77
|
+
cls._initialized = True
|
|
78
|
+
except Exception as e:
|
|
79
|
+
# 如果配置失败,使用基本配置
|
|
80
|
+
logging.basicConfig(
|
|
81
|
+
level=getattr(logging, log_level),
|
|
82
|
+
format='[%(levelname)s] %(message)s',
|
|
83
|
+
handlers=[logging.StreamHandler()] if log_to_console else []
|
|
84
|
+
)
|
|
85
|
+
cls._initialized = True
|
|
86
|
+
print(f"警告:日志配置失败,使用基本配置: {e}")
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def get_logger(cls, name=None):
|
|
90
|
+
"""
|
|
91
|
+
获取logger实例
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
name (str): logger名称
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
logging.Logger: logger实例
|
|
98
|
+
"""
|
|
99
|
+
if not cls._initialized:
|
|
100
|
+
cls.setup_logging()
|
|
101
|
+
|
|
102
|
+
if name is None:
|
|
103
|
+
name = 'stock_analyzer'
|
|
104
|
+
elif not name.startswith('stock_analyzer'):
|
|
105
|
+
name = f'stock_analyzer.{name}'
|
|
106
|
+
|
|
107
|
+
return logging.getLogger(name)
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def set_log_level(cls, level):
|
|
111
|
+
"""
|
|
112
|
+
设置日志级别
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
level (str): 日志级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
116
|
+
"""
|
|
117
|
+
if not cls._initialized:
|
|
118
|
+
cls.setup_logging()
|
|
119
|
+
|
|
120
|
+
# 更新所有stock_analyzer相关的logger
|
|
121
|
+
for logger_name in ['stock_analyzer', 'stock_analyzer.data_fetcher',
|
|
122
|
+
'stock_analyzer.technical_indicators', 'stock_analyzer.report_generator',
|
|
123
|
+
'stock_analyzer.utils']:
|
|
124
|
+
logger = logging.getLogger(logger_name)
|
|
125
|
+
logger.setLevel(getattr(logging, level.upper()))
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def add_file_handler(cls, filename, level='DEBUG'):
|
|
129
|
+
"""
|
|
130
|
+
添加文件处理器
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
filename (str): 文件名
|
|
134
|
+
level (str): 日志级别
|
|
135
|
+
"""
|
|
136
|
+
config = Config()
|
|
137
|
+
|
|
138
|
+
# 创建文件处理器
|
|
139
|
+
handler = logging.handlers.RotatingFileHandler(
|
|
140
|
+
filename=filename,
|
|
141
|
+
maxBytes=config.LOG_MAX_SIZE,
|
|
142
|
+
backupCount=config.LOG_BACKUP_COUNT,
|
|
143
|
+
encoding='utf-8'
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# 设置格式
|
|
147
|
+
formatter = logging.Formatter(
|
|
148
|
+
'%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s',
|
|
149
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
150
|
+
)
|
|
151
|
+
handler.setFormatter(formatter)
|
|
152
|
+
handler.setLevel(getattr(logging, level.upper()))
|
|
153
|
+
|
|
154
|
+
# 添加到所有stock_analyzer相关的logger
|
|
155
|
+
for logger_name in ['stock_analyzer', 'stock_analyzer.data_fetcher',
|
|
156
|
+
'stock_analyzer.technical_indicators', 'stock_analyzer.report_generator',
|
|
157
|
+
'stock_analyzer.utils']:
|
|
158
|
+
logger = logging.getLogger(logger_name)
|
|
159
|
+
logger.addHandler(handler)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class Utils:
|
|
163
|
+
"""工具类"""
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def format_number(value, precision=2, default=0):
|
|
167
|
+
"""
|
|
168
|
+
安全地格式化数字
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
value: 待格式化的值
|
|
172
|
+
precision (int): 小数位数
|
|
173
|
+
default: 默认值
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
float: 格式化后的数字
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
if value is None:
|
|
180
|
+
return default
|
|
181
|
+
return round(float(value), precision)
|
|
182
|
+
except (ValueError, TypeError):
|
|
183
|
+
return default
|
|
184
|
+
|
|
185
|
+
@staticmethod
|
|
186
|
+
def safe_divide(numerator, denominator, default=0):
|
|
187
|
+
"""
|
|
188
|
+
安全除法,避免除零错误
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
numerator: 分子
|
|
192
|
+
denominator: 分母
|
|
193
|
+
default: 除零时的默认值
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
float: 除法结果
|
|
197
|
+
"""
|
|
198
|
+
try:
|
|
199
|
+
if denominator == 0:
|
|
200
|
+
return default
|
|
201
|
+
return numerator / denominator
|
|
202
|
+
except (TypeError, ZeroDivisionError):
|
|
203
|
+
return default
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def validate_stock_code(stock_code):
|
|
207
|
+
"""
|
|
208
|
+
验证股票代码格式
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
stock_code (str): 股票代码
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
bool: 是否有效
|
|
215
|
+
"""
|
|
216
|
+
if not stock_code or not isinstance(stock_code, str):
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
# 去除空格
|
|
220
|
+
stock_code = stock_code.strip()
|
|
221
|
+
|
|
222
|
+
# 检查长度和数字格式
|
|
223
|
+
if len(stock_code) != 6 or not stock_code.isdigit():
|
|
224
|
+
return False
|
|
225
|
+
|
|
226
|
+
# 检查首位数字是否合法(0、3、6开头)
|
|
227
|
+
first_digit = stock_code[0]
|
|
228
|
+
if first_digit not in ['0', '3', '6']:
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
return True
|
|
232
|
+
|
|
233
|
+
@staticmethod
|
|
234
|
+
def get_current_time():
|
|
235
|
+
"""
|
|
236
|
+
获取当前时间字符串
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
str: 格式化的当前时间
|
|
240
|
+
"""
|
|
241
|
+
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def print_progress(message, step=None, total=None):
|
|
245
|
+
"""
|
|
246
|
+
打印进度信息
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
message (str): 消息内容
|
|
250
|
+
step (int): 当前步骤
|
|
251
|
+
total (int): 总步骤数
|
|
252
|
+
"""
|
|
253
|
+
if step is not None and total is not None:
|
|
254
|
+
progress = f"[{step}/{total}] "
|
|
255
|
+
else:
|
|
256
|
+
progress = ""
|
|
257
|
+
|
|
258
|
+
print(f"{progress}{message}")
|
|
259
|
+
|
|
260
|
+
@staticmethod
|
|
261
|
+
def handle_keyboard_interrupt():
|
|
262
|
+
"""处理键盘中断"""
|
|
263
|
+
print("\n\n程序被用户中断")
|
|
264
|
+
return True
|
|
265
|
+
|
|
266
|
+
@staticmethod
|
|
267
|
+
def parse_command_line_args():
|
|
268
|
+
"""
|
|
269
|
+
解析命令行参数
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
str or None: 股票代码,如果没有提供则返回None
|
|
273
|
+
"""
|
|
274
|
+
if len(sys.argv) > 1:
|
|
275
|
+
stock_code = sys.argv[1].strip()
|
|
276
|
+
if Utils.validate_stock_code(stock_code):
|
|
277
|
+
return stock_code
|
|
278
|
+
else:
|
|
279
|
+
print(f"错误:股票代码 '{stock_code}' 格式不正确")
|
|
280
|
+
print("正确格式:6位数字,如 000001")
|
|
281
|
+
return None
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
@staticmethod
|
|
285
|
+
def get_user_input(prompt, quit_words=None):
|
|
286
|
+
"""
|
|
287
|
+
获取用户输入
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
prompt (str): 提示信息
|
|
291
|
+
quit_words (list): 退出命令列表
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
str or None: 用户输入,如果是退出命令则返回None
|
|
295
|
+
"""
|
|
296
|
+
if quit_words is None:
|
|
297
|
+
quit_words = ['quit', 'exit', 'q']
|
|
298
|
+
|
|
299
|
+
user_input = input(prompt).strip()
|
|
300
|
+
|
|
301
|
+
if user_input.lower() in quit_words:
|
|
302
|
+
return None
|
|
303
|
+
|
|
304
|
+
return user_input
|
|
305
|
+
|
|
306
|
+
@staticmethod
|
|
307
|
+
def confirm_action(message):
|
|
308
|
+
"""
|
|
309
|
+
确认操作
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
message (str): 确认消息
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
bool: 是否确认
|
|
316
|
+
"""
|
|
317
|
+
response = input(f"{message} (y/N): ").strip().lower()
|
|
318
|
+
return response in ['y', 'yes', '是']
|
|
319
|
+
|
|
320
|
+
@staticmethod
|
|
321
|
+
def format_large_number(value, unit='万'):
|
|
322
|
+
"""
|
|
323
|
+
格式化大数字(转换为万或亿)
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
value (float): 数值
|
|
327
|
+
unit (str): 单位 '万' 或 '亿'
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
str: 格式化后的字符串
|
|
331
|
+
"""
|
|
332
|
+
try:
|
|
333
|
+
if unit == '万':
|
|
334
|
+
return f"{value / 10000:.2f}万"
|
|
335
|
+
elif unit == '亿':
|
|
336
|
+
return f"{value / 100000000:.2f}亿"
|
|
337
|
+
else:
|
|
338
|
+
return f"{value:.2f}"
|
|
339
|
+
except (TypeError, ValueError):
|
|
340
|
+
return "0"
|
|
341
|
+
|
|
342
|
+
@staticmethod
|
|
343
|
+
def check_data_quality(data, min_length=50):
|
|
344
|
+
"""
|
|
345
|
+
检查数据质量
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
data: 数据对象
|
|
349
|
+
min_length (int): 最小数据长度
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
tuple: (是否通过检查, 错误消息)
|
|
353
|
+
"""
|
|
354
|
+
if data is None:
|
|
355
|
+
return False, "数据为空"
|
|
356
|
+
|
|
357
|
+
if hasattr(data, '__len__'):
|
|
358
|
+
if len(data) < min_length:
|
|
359
|
+
return False, f"数据长度不足,需要至少{min_length}条记录,当前只有{len(data)}条"
|
|
360
|
+
|
|
361
|
+
return True, "数据质量检查通过"
|
|
362
|
+
|
|
363
|
+
@staticmethod
|
|
364
|
+
def log_error(error, context=""):
|
|
365
|
+
"""
|
|
366
|
+
记录错误信息
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
error: 错误对象或消息
|
|
370
|
+
context (str): 错误上下文
|
|
371
|
+
"""
|
|
372
|
+
logger = LoggerManager.get_logger('utils')
|
|
373
|
+
if context:
|
|
374
|
+
logger.error(f"错误在 {context}: {str(error)}")
|
|
375
|
+
else:
|
|
376
|
+
logger.error(f"错误: {str(error)}")
|
|
377
|
+
|
|
378
|
+
@staticmethod
|
|
379
|
+
def create_separator(char='=', length=60):
|
|
380
|
+
"""
|
|
381
|
+
创建分隔符
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
char (str): 分隔符字符
|
|
385
|
+
length (int): 长度
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
str: 分隔符字符串
|
|
389
|
+
"""
|
|
390
|
+
return char * length
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class DataValidator:
|
|
394
|
+
"""数据验证器"""
|
|
395
|
+
|
|
396
|
+
@staticmethod
|
|
397
|
+
def validate_price_data(data):
|
|
398
|
+
"""
|
|
399
|
+
验证价格数据
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
data: 价格数据
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
tuple: (是否有效, 错误消息)
|
|
406
|
+
"""
|
|
407
|
+
required_columns = ['open', 'close', 'high', 'low', 'volume']
|
|
408
|
+
|
|
409
|
+
if data is None:
|
|
410
|
+
return False, "数据为空"
|
|
411
|
+
|
|
412
|
+
# 检查必需列
|
|
413
|
+
missing_columns = [col for col in required_columns if col not in data.columns]
|
|
414
|
+
if missing_columns:
|
|
415
|
+
return False, f"缺少必需列: {missing_columns}"
|
|
416
|
+
|
|
417
|
+
# 检查数据完整性
|
|
418
|
+
for col in required_columns:
|
|
419
|
+
if data[col].isnull().any():
|
|
420
|
+
return False, f"列 {col} 包含空值"
|
|
421
|
+
|
|
422
|
+
# 基本数据完整性检查(数据已经在获取阶段被清洗)
|
|
423
|
+
if len(data) == 0:
|
|
424
|
+
return False, "数据为空"
|
|
425
|
+
|
|
426
|
+
# 检查是否有足够的数据用于技术分析
|
|
427
|
+
if len(data) < 20:
|
|
428
|
+
return False, f"数据量不足,至少需要20条记录进行技术分析,当前只有{len(data)}条"
|
|
429
|
+
|
|
430
|
+
return True, "价格数据验证通过"
|
|
431
|
+
|
|
432
|
+
@staticmethod
|
|
433
|
+
def validate_indicators(indicators):
|
|
434
|
+
"""
|
|
435
|
+
验证指标数据
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
indicators (dict): 指标字典
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
tuple: (是否有效, 错误消息)
|
|
442
|
+
"""
|
|
443
|
+
if not indicators:
|
|
444
|
+
return False, "指标数据为空"
|
|
445
|
+
|
|
446
|
+
# 检查关键指标是否存在
|
|
447
|
+
key_indicators = ['current_price', 'date']
|
|
448
|
+
missing_indicators = [key for key in key_indicators if key not in indicators]
|
|
449
|
+
if missing_indicators:
|
|
450
|
+
return False, f"缺少关键指标: {missing_indicators}"
|
|
451
|
+
|
|
452
|
+
# 检查价格指标的合理性
|
|
453
|
+
current_price = indicators.get('current_price', 0)
|
|
454
|
+
if current_price <= 0:
|
|
455
|
+
return False, "当前价格必须为正数"
|
|
456
|
+
|
|
457
|
+
return True, "指标数据验证通过"
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class ErrorHandler:
|
|
461
|
+
"""错误处理器"""
|
|
462
|
+
|
|
463
|
+
@staticmethod
|
|
464
|
+
def handle_api_error(error, api_name="API"):
|
|
465
|
+
"""
|
|
466
|
+
处理API错误
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
error: 错误对象
|
|
470
|
+
api_name (str): API名称
|
|
471
|
+
"""
|
|
472
|
+
logger = LoggerManager.get_logger('utils')
|
|
473
|
+
error_msg = str(error)
|
|
474
|
+
logger.error(f"{api_name}调用失败: {error_msg}")
|
|
475
|
+
|
|
476
|
+
# 提供常见错误的解决建议
|
|
477
|
+
if "network" in error_msg.lower() or "connection" in error_msg.lower():
|
|
478
|
+
logger.info("建议:检查网络连接")
|
|
479
|
+
elif "timeout" in error_msg.lower():
|
|
480
|
+
logger.info("建议:稍后重试,可能是网络超时")
|
|
481
|
+
elif "rate" in error_msg.lower() or "limit" in error_msg.lower():
|
|
482
|
+
logger.info("建议:请求频率过高,稍后重试")
|
|
483
|
+
elif "auth" in error_msg.lower():
|
|
484
|
+
logger.info("建议:检查API认证信息")
|
|
485
|
+
else:
|
|
486
|
+
logger.info("建议:检查输入参数或稍后重试")
|
|
487
|
+
|
|
488
|
+
@staticmethod
|
|
489
|
+
def handle_calculation_error(error, calculation_name="计算"):
|
|
490
|
+
"""
|
|
491
|
+
处理计算错误
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
error: 错误对象
|
|
495
|
+
calculation_name (str): 计算名称
|
|
496
|
+
"""
|
|
497
|
+
logger = LoggerManager.get_logger('utils')
|
|
498
|
+
error_msg = str(error)
|
|
499
|
+
logger.error(f"{calculation_name}失败: {error_msg}")
|
|
500
|
+
|
|
501
|
+
if "insufficient" in error_msg.lower() or "not enough" in error_msg.lower():
|
|
502
|
+
logger.info("建议:数据量不足,需要更多历史数据")
|
|
503
|
+
elif "nan" in error_msg.lower() or "invalid" in error_msg.lower():
|
|
504
|
+
logger.info("建议:数据包含无效值,请检查数据质量")
|
|
505
|
+
else:
|
|
506
|
+
logger.info("建议:检查数据格式和完整性")
|
|
507
|
+
|
|
508
|
+
@staticmethod
|
|
509
|
+
def handle_file_error(error, operation="文件操作"):
|
|
510
|
+
"""
|
|
511
|
+
处理文件错误
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
error: 错误对象
|
|
515
|
+
operation (str): 操作名称
|
|
516
|
+
"""
|
|
517
|
+
logger = LoggerManager.get_logger('utils')
|
|
518
|
+
error_msg = str(error)
|
|
519
|
+
logger.error(f"{operation}失败: {error_msg}")
|
|
520
|
+
|
|
521
|
+
if "permission" in error_msg.lower():
|
|
522
|
+
logger.info("建议:检查文件权限")
|
|
523
|
+
elif "not found" in error_msg.lower():
|
|
524
|
+
logger.info("建议:检查文件路径是否正确")
|
|
525
|
+
elif "disk" in error_msg.lower() or "space" in error_msg.lower():
|
|
526
|
+
logger.info("建议:检查磁盘空间")
|
|
527
|
+
else:
|
|
528
|
+
logger.info("建议:检查文件路径和权限")
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
class PerformanceMonitor:
|
|
532
|
+
"""性能监控器"""
|
|
533
|
+
|
|
534
|
+
def __init__(self):
|
|
535
|
+
self.start_time = None
|
|
536
|
+
self.checkpoints = {}
|
|
537
|
+
|
|
538
|
+
def start(self):
|
|
539
|
+
"""开始计时"""
|
|
540
|
+
logger = LoggerManager.get_logger('utils')
|
|
541
|
+
self.start_time = datetime.now()
|
|
542
|
+
logger.info(f"性能监控开始: {self.start_time.strftime('%H:%M:%S')}")
|
|
543
|
+
|
|
544
|
+
def checkpoint(self, name):
|
|
545
|
+
"""记录检查点"""
|
|
546
|
+
logger = LoggerManager.get_logger('utils')
|
|
547
|
+
if self.start_time is None:
|
|
548
|
+
logger.warning("请先调用start()方法")
|
|
549
|
+
return
|
|
550
|
+
|
|
551
|
+
current_time = datetime.now()
|
|
552
|
+
elapsed = (current_time - self.start_time).total_seconds()
|
|
553
|
+
self.checkpoints[name] = elapsed
|
|
554
|
+
logger.info(f"检查点 [{name}]: {elapsed:.2f}秒")
|
|
555
|
+
|
|
556
|
+
def finish(self):
|
|
557
|
+
"""结束计时"""
|
|
558
|
+
logger = LoggerManager.get_logger('utils')
|
|
559
|
+
if self.start_time is None:
|
|
560
|
+
logger.warning("请先调用start()方法")
|
|
561
|
+
return
|
|
562
|
+
|
|
563
|
+
end_time = datetime.now()
|
|
564
|
+
total_elapsed = (end_time - self.start_time).total_seconds()
|
|
565
|
+
logger.info(f"性能监控结束: {end_time.strftime('%H:%M:%S')}")
|
|
566
|
+
logger.info(f"总耗时: {total_elapsed:.2f}秒")
|
|
567
|
+
|
|
568
|
+
if self.checkpoints:
|
|
569
|
+
logger.info("\n检查点详情:")
|
|
570
|
+
for name, elapsed in self.checkpoints.items():
|
|
571
|
+
percentage = (elapsed / total_elapsed) * 100
|
|
572
|
+
logger.info(f" {name}: {elapsed:.2f}秒 ({percentage:.1f}%)")
|
|
573
|
+
|
|
574
|
+
return total_elapsed
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
# 向后兼容的别名
|
|
578
|
+
Logger = LoggerManager
|