feat: A股代码规则校验,优化报错提示

This commit is contained in:
Cassianvale
2025-03-05 10:51:59 +08:00
parent 9ce7847484
commit 6326157164
3 changed files with 195 additions and 47 deletions

View File

@@ -47,6 +47,25 @@ class StockAnalyzer:
end_date = datetime.now().strftime('%Y%m%d')
try:
# 验证股票代码格式
if market_type == 'A':
# 上海证券交易所股票代码以6开头
# 深圳证券交易所股票代码以0或3开头
# 科创板股票代码以688开头
# 北京证券交易所股票代码以8开头
valid_prefixes = ['0', '3', '6', '688', '8']
valid_format = False
for prefix in valid_prefixes:
if stock_code.startswith(prefix):
valid_format = True
break
if not valid_format:
error_msg = f"无效的A股股票代码格式: {stock_code}。A股代码应以0、3、6、688或8开头"
logger.error(f"[股票代码格式错误] {error_msg}")
raise ValueError(error_msg)
# 根据市场类型获取数据
if market_type == 'A':
df = ak.stock_zh_a_hist(
@@ -97,7 +116,12 @@ class StockAnalyzer:
return df.sort_values('date')
# except ValueError as ve:
# # 捕获格式验证错误
# logger.error(f"[股票代码格式错误] {str(ve)}")
# raise Exception(f"股票代码格式错误: {str(ve)}")
except Exception as e:
logger.error(f"[获取股票数据失败] {str(e)}")
raise Exception(f"获取股票数据失败: {str(e)}")
def calculate_ema(self, series, period):
@@ -252,12 +276,12 @@ class StockAnalyzer:
# 检查API配置
if not self.API_URL:
error_msg = "API URL未配置无法进行AI分析"
logger.error(error_msg)
logger.error(f"[API配置错误] {error_msg}")
return error_msg if not stream else (yield json.dumps({"error": error_msg}))
if not self.API_KEY:
error_msg = "API Key未配置无法进行AI分析"
logger.error(error_msg)
logger.error(f"[API配置错误] {error_msg}")
return error_msg if not stream else (yield json.dumps({"error": error_msg}))
# 标准化API URL
@@ -306,12 +330,12 @@ class StockAnalyzer:
error_text = response.text[:500] if response.text else "无响应内容"
error_msg = f"API请求失败: 状态码 {response.status_code}, 响应: {error_text}"
logger.error(error_msg)
logger.error(f"[API请求失败] {error_msg}")
yield json.dumps({"stock_code": stock_code, "error": error_msg})
except Exception as e:
error_msg = f"流式API请求异常: {str(e)}"
logger.error(error_msg)
logger.error(f"[流式API异常] {error_msg}")
logger.exception(e)
yield json.dumps({"stock_code": stock_code, "error": error_msg})
else:
@@ -342,18 +366,18 @@ class StockAnalyzer:
error_text = response.text[:500] if response.text else "无响应内容"
error_msg = f"API请求失败: 状态码 {response.status_code}, 响应: {error_text}"
logger.error(error_msg)
logger.error(f"[API请求失败] {error_msg}")
return error_msg
except Exception as e:
error_msg = f"非流式API请求异常: {str(e)}"
logger.error(error_msg)
logger.error(f"[非流式API异常] {error_msg}")
logger.exception(e)
return error_msg
except Exception as e:
error_msg = f"AI 分析过程中发生错误: {str(e)}"
logger.error(error_msg)
logger.error(f"[AI分析异常] {error_msg}")
logger.exception(e)
if stream:
@@ -422,7 +446,7 @@ class StockAnalyzer:
})
yield chunk_json
except json.JSONDecodeError as e:
logger.error(f"JSON解析错误: {str(e)}, 行内容: {data_content}")
logger.error(f"[JSON解析错误] {str(e)}, 行内容: {data_content}")
# 忽略无法解析的JSON
pass
else:
@@ -437,7 +461,7 @@ class StockAnalyzer:
except Exception as e:
error_msg = f"处理AI流式响应时出错: {str(e)}"
logger.error(error_msg)
logger.error(f"[流式响应异常] {error_msg}")
logger.exception(e)
yield json.dumps({"stock_code": stock_code, "error": error_msg})
@@ -457,13 +481,48 @@ class StockAnalyzer:
return '强烈建议卖出'
def analyze_stock(self, stock_code, market_type='A', stream=False):
"""分析单股票"""
try:
logger.info(f"开始分析股票: {stock_code}, 市场: {market_type}, 流式模式: {stream}")
"""分析单股票"""
logger.info(f"开始分析股票 {stock_code}, 市场类型: {market_type}, 流式模式: {stream}")
try:
# 获取股票数据
logger.debug(f"获取股票 {stock_code} 数据")
df = self.get_stock_data(stock_code, market_type)
try:
df = self.get_stock_data(stock_code, market_type)
except Exception as e:
# 捕获股票数据获取异常
error_msg = str(e)
logger.error(f"[数据获取异常] {error_msg}")
# 格式化错误响应
error_response = {
'stock_code': stock_code,
'error': error_msg,
'status': 'error',
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
if stream:
return (yield json.dumps(error_response))
else:
return error_response
# 检查数据是否为空
if df.empty:
error_msg = f"股票 {stock_code} 数据为空"
logger.error(f"[空数据] {error_msg}")
# 格式化错误响应
error_response = {
'stock_code': stock_code,
'error': error_msg,
'status': 'error',
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
if stream:
return (yield json.dumps(error_response))
else:
return error_response
# 计算技术指标
logger.debug(f"计算股票 {stock_code} 技术指标")
@@ -518,61 +577,108 @@ class StockAnalyzer:
return report
except Exception as e:
error_msg = f"分析股票 {stock_code} 时出错: {str(e)}"
logger.error(error_msg)
error_msg = f"分析股票 {stock_code} 时出错: {str(e)}\n"
logger.error(f"[股票分析异常] {error_msg}")
logger.exception(e)
if stream:
error_json = json.dumps({'stock_code': stock_code, 'error': error_msg})
logger.info(f"流式错误输出: {error_json}")
yield error_json
else:
raise
# 格式化错误响应
error_response = {
'stock_code': stock_code,
'error': error_msg,
'status': 'error',
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
def scan_market(self, stock_list, min_score=60, market_type='A', stream=False):
"""扫描市场,寻找符合条件的股票"""
logger.info(f"开始扫描市场,股票数量: {len(stock_list)}, 最低分数: {min_score}, 市场: {market_type}, 流式模式: {stream}")
if stream:
return (yield json.dumps(error_response))
else:
return error_response
def scan_stocks(self, stock_codes, market_type='A', min_score=60, stream=False):
"""扫描多只股票"""
logger.info(f"开始扫描 {len(stock_codes)} 只股票, 市场类型: {market_type}, 最低评分: {min_score}, 流式模式: {stream}")
if not stream:
recommendations = []
# 非流式模式
recommended_stocks = []
stock_count = 0
error_count = 0
for stock_code in stock_codes:
stock_count += 1
logger.info(f"扫描进度: {stock_count}/{len(stock_codes)}, 当前股票: {stock_code}")
for stock_code in stock_list:
try:
logger.debug(f"分析股票: {stock_code}")
report = self.analyze_stock(stock_code, market_type)
# 检查是否有错误
if isinstance(report, dict) and 'error' in report:
error_count += 1
logger.warning(f"[扫描错误] 股票 {stock_code}: {report['error']}")
continue
# 检查评分是否达到最低要求
if report['score'] >= min_score:
logger.info(f"股票 {stock_code} 评分 {report['score']} >= {min_score},添加到推荐列表")
recommendations.append(report)
recommended_stocks.append(report)
else:
logger.debug(f"股票 {stock_code} 评分 {report['score']} < {min_score},不添加到推荐列表")
except Exception as e:
logger.error(f"分析股票 {stock_code} 时出错: {str(e)}")
error_count += 1
error_msg = f"分析股票 {stock_code} 时出错: {str(e)}"
logger.error(f"[扫描异常] {error_msg}")
logger.exception(e)
# 添加错误信息到推荐列表,确保前端能看到错误
error_response = {
'stock_code': stock_code,
'error': error_msg,
'status': 'error',
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
recommended_stocks.append(error_response)
continue
# 按得分排序
recommendations.sort(key=lambda x: x['score'], reverse=True)
logger.info(f"扫描完成,找到 {len(recommendations)} 个推荐股票")
return recommendations
logger.info(f"扫描完成,共 {stock_count} 只股票,{error_count} 只出错,{len(recommended_stocks)} 只推荐")
return recommended_stocks
else:
# 流式处理每个股票
logger.info(f"开始流式扫描 {len(stock_list)} 只股票")
# 流式模式
stock_count = 0
for stock_code in stock_list:
error_count = 0
for stock_code in stock_codes:
stock_count += 1
logger.debug(f"流式分析股票 {stock_code} ({stock_count}/{len(stock_list)})")
logger.info(f"流式扫描进度: {stock_count}/{len(stock_codes)}, 当前股票: {stock_code}")
try:
# 分析单只股票并获取流式结果
chunk_count = 0
for chunk in self.analyze_stock(stock_code, market_type, stream=True):
chunk_count += 1
# 检查是否有错误信息
try:
chunk_data = json.loads(chunk)
if 'error' in chunk_data:
error_count += 1
logger.warning(f"[流式扫描错误] 股票 {stock_code}: {chunk_data['error']}")
except:
pass
yield chunk
logger.debug(f"股票 {stock_code} 流式分析完成,共 {chunk_count} 个块")
except Exception as e:
error_count += 1
error_msg = f"分析股票 {stock_code} 时出错: {str(e)}"
logger.error(error_msg)
logger.error(f"[流式扫描异常] {error_msg}")
logger.exception(e)
error_json = json.dumps({'stock_code': stock_code, 'error': error_msg})
# 格式化错误响应
error_response = {
'stock_code': stock_code,
'error': error_msg,
'status': 'error',
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
error_json = json.dumps(error_response)
logger.info(f"流式错误输出: {error_json}")
yield error_json
logger.info(f"流式扫描完成,处理了 {stock_count} 只股票")
logger.info(f"流式扫描完成,共处理 {stock_count} 只股票,{error_count} 只出错")

View File

@@ -665,14 +665,56 @@
if (chunk.error) {
// 添加或更新显示错误的卡片
let errorCard = document.getElementById(`error-${stockCode}`);
// 处理错误信息,提取关键部分
let errorMessage = chunk.error;
// 移除多余的错误前缀
errorMessage = errorMessage.replace(/获取股票数据失败: /g, '');
// 格式化错误信息
let formattedError = errorMessage;
// 针对特定类型的错误提供更友好的提示
if (errorMessage.includes('无效的A股股票代码格式')) {
formattedError = `<strong>股票代码格式错误</strong>: ${stockCode} 不是有效的A股代码<br>
<small>A股代码应以0、3、6、688或8开头</small>`;
} else if (errorMessage.includes('股票代码格式错误')) {
formattedError = `<strong>股票代码格式错误</strong>: ${errorMessage.split(':')[1] || errorMessage}`;
} else if (errorMessage.includes('数据为空')) {
formattedError = `<strong>数据获取失败</strong>: 未找到股票 ${stockCode} 的交易数据`;
}
if (!errorCard) {
errorCard = document.createElement('div');
errorCard.id = `error-${stockCode}`;
errorCard.className = 'bg-red-50 p-4 rounded-lg text-red-600';
errorCard.innerHTML = `分析股票 ${stockCode} 出错: ${chunk.error}`;
errorCard.className = 'bg-red-50 p-4 rounded-lg text-red-600 mb-4 flex items-start';
// 添加警告图标
errorCard.innerHTML = `
<div class="mr-3 flex-shrink-0">
<svg class="h-5 w-5 text-red-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path>
</svg>
</div>
<div>
<p class="text-sm font-medium">股票 ${stockCode} 分析失败</p>
<p class="mt-1 text-sm">${formattedError}</p>
</div>
`;
container.appendChild(errorCard);
} else {
errorCard.innerHTML = `分析股票 ${stockCode} 出错: ${chunk.error}`;
errorCard.innerHTML = `
<div class="mr-3 flex-shrink-0">
<svg class="h-5 w-5 text-red-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path>
</svg>
</div>
<div>
<p class="text-sm font-medium">股票 ${stockCode} 分析失败</p>
<p class="mt-1 text-sm">${formattedError}</p>
</div>
`;
}
return;
}

View File

@@ -88,7 +88,7 @@ def analyze():
logger.debug(f"开始处理批量股票的流式响应")
chunk_count = 0
for chunk in custom_analyzer.scan_market(
for chunk in custom_analyzer.scan_stocks(
[code.strip() for code in stock_codes],
min_score=0,
market_type=market_type,