commit;
This commit is contained in:
		
							parent
							
								
									8f73099e18
								
							
						
					
					
						commit
						03450116ce
					
				
							
								
								
									
										29
									
								
								src/app.py
								
								
								
								
							
							
						
						
									
										29
									
								
								src/app.py
								
								
								
								
							|  | @ -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) | ||||
|  |  | |||
|  | @ -1477,6 +1477,13 @@ class FundamentalAnalyzer: | |||
|                - 长期持有:公司具备长期稳定的盈利能力、行业地位稳固、长期成长性好 | ||||
|                - 不建议投资:存在明显风险因素、基本面恶化、估值过高、行业前景不佳或者存在退市风险 | ||||
|              | ||||
|             请注意: | ||||
|             1. 请完全基于提供的分析结果中的最新数据进行分析,不要使用任何历史数据或过时信息 | ||||
|             2. 如果分析结果中包含2024年或2025年的数据,请优先使用这些最新数据 | ||||
|             3. 避免使用"2023年"等历史时间点的数据,除非分析结果中明确提供了这些数据 | ||||
|             4. 重点关注公司最新的业务发展、财务表现和市场定位 | ||||
|             5. 在分析行业环境时,请使用最新的行业数据和竞争格局信息 | ||||
|              | ||||
|             请提供专业、客观的分析,突出关键信息,避免冗长描述。重点关注投资价值和风险。在输出投资建议时,请明确指出是短期持有、中期持有、长期持有还是不建议投资。 | ||||
|              | ||||
|             各维度分析结果: | ||||
|  |  | |||
|  | @ -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', | ||||
|  |  | |||
|  | @ -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  # 添加实时股价数据 | ||||
|             } | ||||
|  |  | |||
|  | @ -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]: | ||||
|         """ | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue