This commit is contained in:
liao 2025-05-23 10:19:48 +08:00
parent 8f73099e18
commit 03450116ce
5 changed files with 130 additions and 35 deletions

View File

@ -236,12 +236,12 @@ def initialize_stock_price_schedule():
collector.update_latest_data()
except Exception as e:
logger.error(f"更新实时股价数据失败: {e}")
# 添加定时任务
scheduler.add_job(
func=update_stock_price,
trigger='interval',
minutes=5,
minutes=60,
id='stock_price_update',
name='实时股价数据采集',
replace_existing=True
@ -249,7 +249,7 @@ def initialize_stock_price_schedule():
# 启动调度器
scheduler.start()
logger.info("实时股价数据采集定时任务已初始化,将在交易时间内每5分钟执行一次")
logger.info("实时股价数据采集定时任务已初始化,将在交易时间内每60分钟执行一次")
return scheduler
except Exception as e:
@ -1452,6 +1452,22 @@ def comprehensive_analysis():
if 'db_session2' in locals() and db_session2 is not None: # 确保 db_session 已定义
db_session2.close() # <--- 关闭会话
# 获取企业市值信息
market_value_results = {}
try:
from src.valuation_analysis.stock_price_collector import StockPriceCollector
price_collector = StockPriceCollector()
for stock_code in input_stock_codes:
price_data = price_collector.get_stock_price_data(stock_code)
if price_data and 'total_market_value' in price_data:
market_value_results[stock_code] = price_data['total_market_value']
else:
market_value_results[stock_code] = None
except Exception as e:
logger.error(f"获取企业市值信息失败: {str(e)}")
market_value_results = {}
db_session = next(get_db())
# 筛选出传入列表中符合条件的股票
for code, name in all_stocks:
@ -1475,7 +1491,8 @@ def comprehensive_analysis():
"industry_space": industry_space, # 行业发展空间2:高速增长, 1:稳定经营, 0:不确定性大, -1:不利经营)
"financial_report_level": financial_report_level, # 经营质量2:优秀, 1:较好, 0:一般, -1:存在隐患,-2较大隐患
"pe_industry": pe_industry, # 个股在行业的PE水平-1:高于行业, 0:接近行业, 1:低于行业)
"tracks": track_results.get(code, []) # 添加赛道信息
"tracks": track_results.get(code, []), # 添加赛道信息
"market_value": market_value_results.get(code) # 添加企业市值信息
})
logger.info(f"筛选出 {len(filtered_stocks)} 个符合条件的股票")
@ -2675,7 +2692,7 @@ def precalculate_industry_crowding():
except Exception as e:
logger.error(f"预计算行业 {industry} 的拥挤度指标时出错: {str(e)}")
continue
logger.info("所有行业的拥挤度指标预计算完成")
except Exception as e:
logger.error(f"预计算行业拥挤度指标失败: {str(e)}")
@ -2819,6 +2836,6 @@ if __name__ == '__main__':
# 初始化实时股价数据采集定时任务
initialize_stock_price_schedule()
# 启动Web服务器
app.run(host='0.0.0.0', port=5000, debug=True)

View File

@ -1477,6 +1477,13 @@ class FundamentalAnalyzer:
- 长期持有公司具备长期稳定的盈利能力行业地位稳固长期成长性好
- 不建议投资存在明显风险因素基本面恶化估值过高行业前景不佳或者存在退市风险
请注意
1. 请完全基于提供的分析结果中的最新数据进行分析不要使用任何历史数据或过时信息
2. 如果分析结果中包含2024年或2025年的数据请优先使用这些最新数据
3. 避免使用"2023年"等历史时间点的数据除非分析结果中明确提供了这些数据
4. 重点关注公司最新的业务发展财务表现和市场定位
5. 在分析行业环境时请使用最新的行业数据和竞争格局信息
请提供专业客观的分析突出关键信息避免冗长描述重点关注投资价值和风险在输出投资建议时请明确指出是短期持有中期持有长期持有还是不建议投资
各维度分析结果

View File

@ -11,7 +11,7 @@ XUEQIU_HEADERS = {
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Client-Version': 'v2.44.75',
'Cookie': 'cookiesu=811743062689927; device_id=33fa3c7fca4a65f8f4354e10ed6b7470; HMACCOUNT=8B64A2E3C307C8C0; s=c611ttmqlj; xq_is_login=1; u=8493411634; bid=4065a77ca57a69c83405d6e591ab5449_m8r2nhs8; Hm_lvt_1db88642e346389874251b5a1eded6e3=1746410725; xq_a_token=660fb18cf1d15162da76deedc46b649370124dca; xqat=660fb18cf1d15162da76deedc46b649370124dca; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjg0OTM0MTE2MzQsImlzcyI6InVjIiwiZXhwIjoxNzQ5ODYxNjY5LCJjdG0iOjE3NDcyNjk2Njk0NDgsImNpZCI6ImQ5ZDBuNEFadXAifQ.jc_E9qvguLwBDASn1Z-KjGtU89pNJRwJq_hIaiR3r2re7-_xiXH8qhuhC3Se8rlfKGZ8sHsb3rSND_vnF7yMp90QQHdK_brSmlgd6_ltHmJfWSFNJvMk7F3s0yPjcpeMqeUTPFnZwKmoWwZVKEwdVBN8f25z6e9M2JjtSTZ2huADH_FdEn1rb9IU-H35z_MLWW1M7vB5xc2rh57yFIBnQoxu9OLfeETpeIpASP1UBeZXoQZ_v1gIWiFYItwuudIz0tPYzB-o2duRe31G0S_hNvEGl3HH4M5FjTyaPAq2PRuiZCyRF-25gHXBZnLcxyavZ1VAURfHng_377_IJNSXsw; xq_r_token=8a5dec9c93caf88d0e1f98f1d23ea1bb60eb6225; is_overseas=0; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1747356850; ssxmod_itna=eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40QuHhqDyGGdVmpmghQehYtDDsqze4GzDiLPGhDBWAFdYCdqt4NKWooqCWKCwdUme9Ill25QAClcymm=0Iil4OAe8oGLDY=DCTKK420iDYAEDBYD74G+DDeDiO3Dj4GmDGY=aeDFIQutVCRKdxDwDB=DmqG23ObDm4DfDDLorBD4Il2YDDtDAkaGNPDADA3doDDlYD84edb4DYpogQ0FdgahphuXIeDMixGXzAlzx9CnoiWtV/LfNf2aHPGuDG=OcC0Hh2bmRT3f8hGxYBY5QeOhx+BxorKq0DW7HRYqexx=CD=WKK7oQ7YBGxPG4KiKy7hAQd5dpOodYYrcqsMkbZMshieygdyhxogYO2deGd46DAQ5MA5VBxiT5/h4WB++l=Eet4D; ssxmod_itna2=eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40QuHhqDyGGdVmpmghQehY4Dfie4pCoTp35CT5NsKziGGtvkoYD',
'Cookie': 'cookiesu=811743062689927; device_id=33fa3c7fca4a65f8f4354e10ed6b7470; HMACCOUNT=8B64A2E3C307C8C0; s=c611ttmqlj; xq_is_login=1; u=8493411634; bid=4065a77ca57a69c83405d6e591ab5449_m8r2nhs8; Hm_lvt_1db88642e346389874251b5a1eded6e3=1746410725; xq_a_token=660fb18cf1d15162da76deedc46b649370124dca; xqat=660fb18cf1d15162da76deedc46b649370124dca; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjg0OTM0MTE2MzQsImlzcyI6InVjIiwiZXhwIjoxNzQ5ODYxNjY5LCJjdG0iOjE3NDcyNjk2Njk0NDgsImNpZCI6ImQ5ZDBuNEFadXAifQ.jc_E9qvguLwBDASn1Z-KjGtU89pNJRwJq_hIaiR3r2re7-_xiXH8qhuhC3Se8rlfKGZ8sHsb3rSND_vnF7yMp90QQHdK_brSmlgd6_ltHmJfWSFNJvMk7F3s0yPjcpeMqeUTPFnZwKmoWwZVKEwdVBN8f25z6e9M2JjtSTZ2huADH_FdEn1rb9IU-H35z_MLWW1M7vB5xc2rh57yFIBnQoxu9OLfeETpeIpASP1UBeZXoQZ_v1gIWiFYItwuudIz0tPYzB-o2duRe31G0S_hNvEGl3HH4M5FjTyaPAq2PRuiZCyRF-25gHXBZnLcxyavZ1VAURfHng_377_IJNSXsw; xq_r_token=8a5dec9c93caf88d0e1f98f1d23ea1bb60eb6225; snbim_minify=true; is_overseas=0; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1747905410; ssxmod_itna=eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40QuHhqDyGGTrlGiGbtOh01qDsqze4GzDiLPGhDBWAFdYCdqtsqfmmxXxyB+doh6odserKO5sg=EiqfqztqpiexCPGnD0=O77N4xYAEDBYD74G+DDeDiO3Dj4GmDGYd=eDFzjRQyl2edxDwDB=DmqG23grDm4DfDDL5xRD4zC2YDDtDAMWz5PDADA3ooDDlYGO44Lr4DYp52nXWdOaspxTXzeDMixGXzYlCgaCRo0TQy9LAN32TNPGuDG=H6e0ahrbicn0AP4KGGwQ0imPKY+5meOQDqixGYwQGGiGGetGe3qqjeKYw10G4ixqim2mpbK+h1iaIPeQAieNS1X5pXZP4rQ04Iv4zmQWvplG40P4Gw4CqRjwzlwGjPwlD3iho+qKlD4hi3YD; ssxmod_itna2=eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40QuHhqDyGGTrlGiGbtOh0P4DWhYebouIdHtBItz/DboqtwisfWD',
'Referer': 'https://weibo.com/u/7735765253',
'Sec-Ch-Ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'Sec-Ch-Ua-Mobile': '?0',

View File

@ -11,6 +11,7 @@ from pymongo import MongoClient
import logging
from typing import Dict, List, Optional, Union, Tuple
import json
import requests
from .config import DB_URL, MONGO_CONFIG, LOG_FILE
from .stock_price_collector import StockPriceCollector
@ -394,6 +395,69 @@ class FinancialAnalyzer:
'message': f'查询失败: {str(e)}'
}
def get_momentum_indicators(self, stock_code: str, industry_codes: List[str]) -> Dict:
"""
获取动量指标数据
Args:
stock_code: 目标股票代码
industry_codes: 行业股票代码列表
Returns:
动量指标数据字典
"""
try:
url = "http://192.168.18.42:5000/api/dify/getStockMomentumIndex"
payload = {
"code_list": industry_codes,
"target_code": stock_code
}
response = requests.post(url, json=payload)
if response.status_code != 200:
return {
'success': False,
'message': f'获取动量指标失败: HTTP {response.status_code}'
}
data = response.json()
# 计算OBV和NATR的rank_score
obv_rank_score = round((1 - data['obv_rank'] / len(industry_codes)) * 10, 3)
natr_rank_score = round((1 - data['natr_rank'] / len(industry_codes)) * 10, 3)
return {
'success': True,
'indicators': [
{
'key': 'obv',
'desc': '能量',
'value': round(data['OBV'], 3),
'rank_score': obv_rank_score
},
{
'key': 'form',
'desc': f'技术形态-{data["form"]}',
'value': round(data['form_probability'], 3),
'rank_score': round(data['form_probability'] * 10, 3) # 将概率转换为0-10的分数
},
{
'key': 'natr',
'desc': '阶段位置',
'value': round(data['NATR'], 3),
'rank_score': natr_rank_score
}
],
'avg_score': round((obv_rank_score + natr_rank_score + round(data['form_probability'] * 10, 3)) / 3, 3)
}
except Exception as e:
logger.error(f"获取动量指标失败: {str(e)}")
return {
'success': False,
'message': f'获取动量指标失败: {str(e)}'
}
def analyze_financial_data(self, stock_code: str) -> Dict:
"""
分析财务数据
@ -413,6 +477,12 @@ class FinancialAnalyzer:
industry_analyzer = IndustryAnalyzer()
concepts = industry_analyzer.get_stock_concepts(stock_code)
# 获取同行业股票列表
industry_stocks = self.get_industry_stocks(stock_code)
# 获取动量指标数据
momentum_result = self.get_momentum_indicators(stock_code, industry_stocks)
# 获取基础财务指标
base_result = self.extract_financial_indicators(stock_code)
if not base_result.get('success'):
@ -578,6 +648,7 @@ class FinancialAnalyzer:
'growth': growth_data,
'value_rating': process_indicators(value_rating_indicators),
'liquidity': process_indicators(liquidity_indicators),
'momentum': momentum_result.get('indicators', []), # 添加动量指标数据
'concepts': concepts, # 添加概念板块数据
'price_data': price_data # 添加实时股价数据
}

View File

@ -299,25 +299,25 @@ class IndustryAnalyzer:
# 检查缓存
if use_cache:
cache_key = f"industry_crowding:{industry_name}"
cached_data = redis_client.get(cache_key)
if cached_data:
try:
# 尝试解析缓存的JSON数据
cached_df_dict = json.loads(cached_data)
logger.info(f"从缓存获取行业 {industry_name} 的拥挤度数据")
# 将缓存的字典转换回DataFrame
df = pd.DataFrame(cached_df_dict)
# 确保trade_date列是日期类型
df['trade_date'] = pd.to_datetime(df['trade_date'])
return df
except Exception as cache_error:
logger.warning(f"解析缓存的拥挤度数据失败,将重新查询: {cache_error}")
cache_key = f"industry_crowding:{industry_name}"
cached_data = redis_client.get(cache_key)
if cached_data:
try:
# 尝试解析缓存的JSON数据
cached_df_dict = json.loads(cached_data)
logger.info(f"从缓存获取行业 {industry_name} 的拥挤度数据")
# 将缓存的字典转换回DataFrame
df = pd.DataFrame(cached_df_dict)
# 确保trade_date列是日期类型
df['trade_date'] = pd.to_datetime(df['trade_date'])
return df
except Exception as cache_error:
logger.warning(f"解析缓存的拥挤度数据失败,将重新查询: {cache_error}")
# 获取行业所有股票
stock_codes = self.get_industry_stocks(industry_name)
if not stock_codes:
@ -400,15 +400,15 @@ class IndustryAnalyzer:
# 缓存结果有效期1天86400秒
if use_cache:
try:
redis_client.set(
cache_key,
json.dumps(df_dict, default=str), # 使用default=str处理日期等特殊类型
ex=86400 # 1天的秒数
)
logger.info(f"已缓存行业 {industry_name} 的拥挤度数据有效期为1天")
except Exception as cache_error:
logger.warning(f"缓存行业拥挤度数据失败: {cache_error}")
try:
redis_client.set(
cache_key,
json.dumps(df_dict, default=str), # 使用default=str处理日期等特殊类型
ex=86400 # 1天的秒数
)
logger.info(f"已缓存行业 {industry_name} 的拥挤度数据有效期为1天")
except Exception as cache_error:
logger.warning(f"缓存行业拥挤度数据失败: {cache_error}")
logger.info(f"成功计算行业 {industry_name} 的拥挤度指标,共 {len(df)} 条记录")
return df
@ -512,7 +512,7 @@ class IndustryAnalyzer:
except Exception as e:
logger.error(f"获取行业综合分析失败: {e}")
return {"success": False, "message": f"获取行业综合分析失败: {e}"}
return {"success": False, "message": f"获取行业综合分析失败: {e}"}
def get_stock_concepts(self, stock_code: str) -> List[str]:
"""