commit;
This commit is contained in:
		
							parent
							
								
									cce06d8710
								
							
						
					
					
						commit
						c4c4e8622f
					
				
							
								
								
									
										45
									
								
								src/app.py
								
								
								
								
							
							
						
						
									
										45
									
								
								src/app.py
								
								
								
								
							|  | @ -2999,7 +2999,7 @@ def run_batch_stock_price_collection(): | ||||||
| 
 | 
 | ||||||
| @app.route('/scheduler/batch_hk_stock_price/collection', methods=['GET']) | @app.route('/scheduler/batch_hk_stock_price/collection', methods=['GET']) | ||||||
| def run_batch_hk_stock_price_collection(): | def run_batch_hk_stock_price_collection(): | ||||||
|     """批量采集A股行情并保存到数据库""" |     """批量采集港股行情并保存到数据库""" | ||||||
|     try: |     try: | ||||||
|         fetch_and_store_hk_stock_data() |         fetch_and_store_hk_stock_data() | ||||||
|         return jsonify({"status": "success", "message": "批量采集A股行情并保存到数据库成功"}) |         return jsonify({"status": "success", "message": "批量采集A股行情并保存到数据库成功"}) | ||||||
|  | @ -3039,23 +3039,38 @@ def get_portfolio_industry_allocation(): | ||||||
| def get_notice_list(): | def get_notice_list(): | ||||||
|     """获取重要提醒列表""" |     """获取重要提醒列表""" | ||||||
|     try: |     try: | ||||||
|         # 模拟数据 - 实际项目中应该从数据库或外部API获取 |         # 导入提醒服务 | ||||||
|         mock_notices = [ |         from src.valuation_analysis.notice_service import NoticeService | ||||||
|             "上证指数突破3200点,市场情绪回暖", |  | ||||||
|             "北向资金今日净流入85.6亿元", |  | ||||||
|             "科技板块PE估值处于历史低位", |  | ||||||
|             "新能源概念股集体上涨,涨幅超3%", |  | ||||||
|             "医药板块回调,建议关注低吸机会", |  | ||||||
|             "融资融券余额连续三日增长", |  | ||||||
|             "消费板块资金流入明显", |  | ||||||
|             "市场恐贪指数回升至65", |  | ||||||
|             "机器人概念板块技术面突破", |  | ||||||
|             "先进封装概念获政策支持" |  | ||||||
|         ] |  | ||||||
|          |          | ||||||
|  |         # 创建提醒服务实例 | ||||||
|  |         notice_service = NoticeService() | ||||||
|  |          | ||||||
|  |         # 获取动态提醒数据 | ||||||
|  |         result = notice_service.get_dynamic_notices() | ||||||
|  |          | ||||||
|  |         if result.get("success"): | ||||||
|             return jsonify({ |             return jsonify({ | ||||||
|                 "status": "success", |                 "status": "success", | ||||||
|             "data": mock_notices |                 "data": result["data"] | ||||||
|  |             }) | ||||||
|  |         else: | ||||||
|  |             # 如果动态提醒失败,返回默认提醒 | ||||||
|  |             logger.warning(f"动态提醒获取失败: {result.get('message')},使用默认提醒") | ||||||
|  |             default_notices = [ | ||||||
|  |                 "📈 上证指数突破3200点,市场情绪回暖", | ||||||
|  |                 "💰 北向资金今日净流入85.6亿元", | ||||||
|  |                 "📊 科技板块PE估值处于历史低位", | ||||||
|  |                 "🔥 新能源概念股集体上涨,涨幅超3%", | ||||||
|  |                 "⚠️ 医药板块回调,建议关注低吸机会", | ||||||
|  |                 "📈 融资融券余额连续三日增长", | ||||||
|  |                 "💰 消费板块资金流入明显", | ||||||
|  |                 "📊 市场恐贪指数回升至65", | ||||||
|  |                 "🤖 机器人概念板块技术面突破", | ||||||
|  |                 "📦 先进封装概念获政策支持" | ||||||
|  |             ] | ||||||
|  |             return jsonify({ | ||||||
|  |                 "status": "success", | ||||||
|  |                 "data": default_notices | ||||||
|             }) |             }) | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         logger.error(f"获取提醒列表失败: {str(e)}") |         logger.error(f"获取提醒列表失败: {str(e)}") | ||||||
|  |  | ||||||
|  | @ -99,7 +99,7 @@ class FundamentalAnalyzer: | ||||||
|         self.chat_bot = ChatBot(model_type="online_bot") |         self.chat_bot = ChatBot(model_type="online_bot") | ||||||
|         # 使用离线模型进行其他分析 |         # 使用离线模型进行其他分析 | ||||||
|         self.offline_bot = OfflineChatBot(platform="volc", model_type="offline_model") |         self.offline_bot = OfflineChatBot(platform="volc", model_type="offline_model") | ||||||
|         # 千问打杂 |         # GLM打杂 | ||||||
|         # self.offline_bot_tl_qw = OfflineChatBot(platform="tl_qw_private", model_type="qwq") |         # self.offline_bot_tl_qw = OfflineChatBot(platform="tl_qw_private", model_type="qwq") | ||||||
|         self.offline_bot_tl_qw = OfflineChatBot(platform="tl_qw_private", model_type="GLM") |         self.offline_bot_tl_qw = OfflineChatBot(platform="tl_qw_private", model_type="GLM") | ||||||
|          |          | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ XUEQIU_HEADERS = { | ||||||
|     'Accept-Encoding': 'gzip, deflate, br, zstd', |     'Accept-Encoding': 'gzip, deflate, br, zstd', | ||||||
|     'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', |     'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', | ||||||
|     'Client-Version': 'v2.44.75', |     'Client-Version': 'v2.44.75', | ||||||
|     'Cookie': 'cookiesu=811743062689927; device_id=33fa3c7fca4a65f8f4354e10ed6b7470; smidV2=20250327160437f244626e8b47ca2a7992f30f389e4e790074ae48656a22f10; HMACCOUNT=8B64A2E3C307C8C0; s=c611ttmqlj; xq_is_login=1; u=8493411634; bid=4065a77ca57a69c83405d6e591ab5449_m8r2nhs8; __utma=1.434320573.1747189698.1747189698.1747189698.1; __utmc=1; __utmz=1.1747189698.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); snbim_minify=true; _c_WBKFRo=dsWgHR8i8KGPbIyhFlN51PHOzVuuNytvUAFppfkD; _nb_ioWEgULi=; Hm_lvt_1db88642e346389874251b5a1eded6e3=1751936369; xq_a_token=ada154d4707b8d3f8aa521ff0c960aa7f81cbf9e; xqat=ada154d4707b8d3f8aa521ff0c960aa7f81cbf9e; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjg0OTM0MTE2MzQsImlzcyI6InVjIiwiZXhwIjoxNzU2MDAyNjgyLCJjdG0iOjE3NTM0MTA2ODI0MTQsImNpZCI6ImQ5ZDBuNEFadXAifQ.AlnzQSY7oGKGABfaQcFLg0lAJsDdvBMiwUbgpCMCBlbx6VZPKhzERxWiylQb4dFIyyECvRRJ73SbO9cD46fAqgzOgTxArNHtTKD4lQapTnyb11diDADnpb_nzzaRr4k_BYQRKXWtcJxdUMzde2WLy-eAkSf76QkXmKrwS3kvRm5gfqhdye44whw5XMEGoZ_lXHzGLWGz_PludHZp6W3v-wwZc_0wLU6cTb_KdrwWUWT_8jw5JHXnJEmuZmQI8QWf60DtiHIYCYXarxv8XtyHK7lLKhIAa3C2QmGWw5wv2HGz4I5DPqm2uMPKumgkQxycfAk56-RWviLZ8LAPF-XcbA; xq_r_token=92527e51353f90ba14d5fd16581e5a7a2780baa2; acw_tc=0a27aa0f17542694317833912e006564153fcd1bb89f49a865e382d9953601; is_overseas=0; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1754269439; .thumbcache_f24b8bbe5a5934237bbc0eda20c1b6e7=HS+RscPvXRUz1ypZekks1pgGkAHHlHsHVuftTbDQCbUUaFqtm9BV4h7ghR2d5Nh+YD29otSyz2svRiKWvOJqgQ%3D%3D; ssxmod_itna=1-eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q0P6Dw1PtDCuq4wQWiYMrK4N4hGRtDl=YoDZDGFdDqx0Ei6Fi7HKzYhtBoqzWKjw_wv5YlCZMPO8//1P9PQCNzkOQ4hviDB3DbqDy/dePxYYjDBYD74G_DDeDixdDj4GmDGYtOeDFfCuNq6R5dxDwDB=DmMIbfeDEDG3D0fbeCLRYwDDBDGUFxtaDG4Gf0mDD0wDAo0jooDGWfnu4s6mkeFKN57G3x0tWDBL5QvG3x/lnoGWNVtlfkS2FkPGuDG6Ogl0kDqQO3i2AfP4KGGIm0iBPKY_5leOQDqQe4YwQGDpl0xliO7Gm0DOGDz0G4ixqYw1n0aSpwhixgPXieD1NZcX3ZXDK4rm0IlvYRGImxqnmmlG4eK40w4Am1BqGYeeGn5ixXWa3m2b/DDgi3YD; ssxmod_itna2=1-eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q0P6Dw1PtDCuq4wQWiYMrK4N4hGbYDiPbY44h7ie03dz7=3xDlouSdLRKl=Q_2YStYQ7OzOy_RBQ1oeziI2pkPsD8RSfPnSw5L7G4xcSPKKMxxoCD6zTiVCud28rNOm2tL7qASSMTjB2GcYPxzSRi94n0Kgjd6C6jKOMh5rMtOfkR2l8TGOPL277=81u9MRkBgIwRxDwx6iYEE4omE9FE1lonhzib3BUC6PD', |     'Cookie': 'cookiesu=811743062689927; device_id=33fa3c7fca4a65f8f4354e10ed6b7470; smidV2=20250327160437f244626e8b47ca2a7992f30f389e4e790074ae48656a22f10; HMACCOUNT=8B64A2E3C307C8C0; s=c611ttmqlj; xq_is_login=1; u=8493411634; bid=4065a77ca57a69c83405d6e591ab5449_m8r2nhs8; __utma=1.434320573.1747189698.1747189698.1747189698.1; __utmc=1; __utmz=1.1747189698.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); snbim_minify=true; _c_WBKFRo=dsWgHR8i8KGPbIyhFlN51PHOzVuuNytvUAFppfkD; _nb_ioWEgULi=; xq_a_token=ada154d4707b8d3f8aa521ff0c960aa7f81cbf9e; xqat=ada154d4707b8d3f8aa521ff0c960aa7f81cbf9e; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjg0OTM0MTE2MzQsImlzcyI6InVjIiwiZXhwIjoxNzU2MDAyNjgyLCJjdG0iOjE3NTM0MTA2ODI0MTQsImNpZCI6ImQ5ZDBuNEFadXAifQ.AlnzQSY7oGKGABfaQcFLg0lAJsDdvBMiwUbgpCMCBlbx6VZPKhzERxWiylQb4dFIyyECvRRJ73SbO9cD46fAqgzOgTxArNHtTKD4lQapTnyb11diDADnpb_nzzaRr4k_BYQRKXWtcJxdUMzde2WLy-eAkSf76QkXmKrwS3kvRm5gfqhdye44whw5XMEGoZ_lXHzGLWGz_PludHZp6W3v-wwZc_0wLU6cTb_KdrwWUWT_8jw5JHXnJEmuZmQI8QWf60DtiHIYCYXarxv8XtyHK7lLKhIAa3C2QmGWw5wv2HGz4I5DPqm2uMPKumgkQxycfAk56-RWviLZ8LAPF-XcbA; xq_r_token=92527e51353f90ba14d5fd16581e5a7a2780baa2; acw_tc=1a0c655917546366986673411e68d25d3c69c1719d6d1d6283c7271cc1529f; is_overseas=0; Hm_lvt_1db88642e346389874251b5a1eded6e3=1754636834; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1754636837; .thumbcache_f24b8bbe5a5934237bbc0eda20c1b6e7=Hvg6Ac+qmPnDgzOvFuCePWwm7reK8TPoE9ayL8cyLnFg+Jhg1RJO2WnkeH2T8Q18+iV9bDh+UAq222GxdelHBg%3D%3D; ssxmod_itna=1-eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q0P6Dw1PtDCuqbKOOQYMxPsMKjqDsqze4GzDiLPGhDBWAFdYjdqN4NCtAoqzWWF2ruqe8bOZqKKFS96SM6sXUGQKhexGLDY=DCuXiieGGU4GwDGoD34DiDDpLD03Db4D_nWrD7ORQMluokjeDQ4GyDiUk3ObDm4DfDDLorA6osQ4DGqDSFcyTxD3DfRb4DDN4CIDu_mDDbObt5jcbUx7OBCGxIeDMixGXzGC4InyRNvDrgjMXvzEKH1aDtqD9_au4XxKdr3NEAEP4KGGpC0inpge_5neOQDqix1oeee4eQvxQ5O7Gv0DOGDz0G4ix_jwP_RUWjiihW9PeGAShXZ=E/ZND6q3mi40weUmXjmvYIzSQzWDW9wsemhYedCrwihQYbKYvWRD3YD; ssxmod_itna2=1-eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q0P6Dw1PtDCuqbKOOQYMxPsMKe4DWhzmxhTKRDjR_xWs_DDs6KmhfHjRKnZkBxNA3TIO4Arip5wU2kO0SwUfkEzryfSk6Rzud3ARD49fiKFd344obYvCv1lxYhY3qdzQe3vWD', | ||||||
|     'Referer': 'https://weibo.com/u/7735765253', |     'Referer': 'https://weibo.com/u/7735765253', | ||||||
|     'Sec-Ch-Ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"', |     'Sec-Ch-Ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"', | ||||||
|     'Sec-Ch-Ua-Mobile': '?0', |     'Sec-Ch-Ua-Mobile': '?0', | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -216,22 +216,5 @@ | ||||||
| <script src="/static/js/echarts.min.js"></script> | <script src="/static/js/echarts.min.js"></script> | ||||||
| <script src="/static/js/jquery.min.js"></script> | <script src="/static/js/jquery.min.js"></script> | ||||||
| <script src="/static/js/bigscreen.js"></script> | <script src="/static/js/bigscreen.js"></script> | ||||||
| <script> |  | ||||||
| // document.getElementById('fullscreen-btn').onclick = function() { |  | ||||||
| //     function launchFullScreen(element) { |  | ||||||
| //         if(element.requestFullscreen) { |  | ||||||
| //             element.requestFullscreen(); |  | ||||||
| //         } else if(element.mozRequestFullScreen) { |  | ||||||
| //             element.mozRequestFullScreen(); |  | ||||||
| //         } else if(element.webkitRequestFullscreen) { |  | ||||||
| //             element.webkitRequestFullscreen(); |  | ||||||
| //         } else if(element.msRequestFullscreen) { |  | ||||||
| //             element.msRequestFullscreen(); |  | ||||||
| //         } |  | ||||||
| //     } |  | ||||||
| //     launchFullScreen(document.documentElement); |  | ||||||
| //     this.style.display = 'none'; // 全屏后隐藏按钮 |  | ||||||
| // }; |  | ||||||
| </script> |  | ||||||
| </body> | </body> | ||||||
| </html> | </html> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,781 @@ | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="zh-CN"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="UTF-8"> | ||||||
|  |     <title>资金与行业估值大屏</title> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | ||||||
|  |     <link rel="stylesheet" href="/static/css/bootstrap.min.css"> | ||||||
|  |     <style> | ||||||
|  |         html, body {  | ||||||
|  |             height: 100%;  | ||||||
|  |             padding-left: 5px; | ||||||
|  |             padding-right: 5px; | ||||||
|  |             padding-top: 5px; | ||||||
|  |         } | ||||||
|  |         body {  | ||||||
|  |             background: #f7f7fa;  | ||||||
|  |             color: #222;  | ||||||
|  |             min-height: 100vh;  | ||||||
|  |         } | ||||||
|  |         .container-fluid {  | ||||||
|  |             min-height: 100vh;  | ||||||
|  |             padding: 0;  | ||||||
|  |         } | ||||||
|  |         .row.d-flex {  | ||||||
|  |             height: 28vh;  | ||||||
|  |             margin-left: 0;  | ||||||
|  |             margin-right: 0;  | ||||||
|  |         } | ||||||
|  |         .row.d-flex2 {  | ||||||
|  |             height: 69vh;  | ||||||
|  |             margin-left: 0;  | ||||||
|  |             margin-right: 0;  | ||||||
|  |         } | ||||||
|  |         .col-3.d-flex { | ||||||
|  |             padding-left: 2px;  | ||||||
|  |             padding-right: 2px; | ||||||
|  |             border: 1.5px solid #c7c6c6; | ||||||
|  |             border-radius: 8px; | ||||||
|  |             box-sizing: border-box; | ||||||
|  |         } | ||||||
|  |         .chart-box { | ||||||
|  |             background: #fff; | ||||||
|  |             border-radius: 8px; | ||||||
|  |             padding: 4px 4px 2px 4px; | ||||||
|  |             box-shadow: 0 2px 8px #e0e0e0; | ||||||
|  |             height: 100%; | ||||||
|  |             display: flex; | ||||||
|  |             flex-direction: column; | ||||||
|  |             justify-content: flex-start; | ||||||
|  |         } | ||||||
|  |         .tight-box {  | ||||||
|  |             margin: 0;  | ||||||
|  |             padding: 2px 2px 1px 2px;  | ||||||
|  |         } | ||||||
|  |         .chart-title {  | ||||||
|  |             font-size: 0.95rem;  | ||||||
|  |             color: #333;  | ||||||
|  |             margin-bottom: 2px;  | ||||||
|  |             text-align: center;  | ||||||
|  |         } | ||||||
|  |         .small-title {  | ||||||
|  |             font-size: 0.85rem;  | ||||||
|  |             margin-bottom: 2px;  | ||||||
|  |             margin-top: 2px;  | ||||||
|  |             text-align: center;  | ||||||
|  |             color: #666; | ||||||
|  |         } | ||||||
|  |         .chart-container {  | ||||||
|  |             width: 100%;  | ||||||
|  |             flex: 1 1 0;  | ||||||
|  |             min-height: 0;  | ||||||
|  |             background: #f9f9fb;  | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 点击后的左侧容器样式 */ | ||||||
|  |         .col-3.d-flex .chart-box.tight-box { | ||||||
|  |             height: 100%; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .col-3.d-flex .chart-container { | ||||||
|  |             height: 45%; | ||||||
|  |             min-height: 200px; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .holdings-container { | ||||||
|  |             width: 100%; | ||||||
|  |             height: 75px; | ||||||
|  |             background: #f9f9fb; | ||||||
|  |             padding: 4px; | ||||||
|  |             overflow-y: auto; | ||||||
|  |             font-size: 11px; | ||||||
|  |             line-height: 1.3; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .holding-item { | ||||||
|  |             display: flex; | ||||||
|  |             justify-content: space-between; | ||||||
|  |             align-items: center; | ||||||
|  |             padding: 3px 4px; | ||||||
|  |             margin-bottom: 2px; | ||||||
|  |             background: #fff; | ||||||
|  |             border-radius: 3px; | ||||||
|  |             border-left: 3px solid #5470c6; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .holding-name { | ||||||
|  |             font-weight: bold; | ||||||
|  |             color: #333; | ||||||
|  |             flex: 1; | ||||||
|  |             overflow: hidden; | ||||||
|  |             text-overflow: ellipsis; | ||||||
|  |             white-space: nowrap; | ||||||
|  |             font-size: 11px; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .holding-amount { | ||||||
|  |             color: #666; | ||||||
|  |             font-size: 10px; | ||||||
|  |             margin-left: 4px; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .notice-container { | ||||||
|  |             width: 100%; | ||||||
|  |             flex: 1 1 0; | ||||||
|  |             min-height: 0; | ||||||
|  |             background: #f9f9fb; | ||||||
|  |             overflow-y: auto; /* 改为垂直滚动 */ | ||||||
|  |             position: relative; | ||||||
|  |             scrollbar-width: thin; /* Firefox */ | ||||||
|  |             scrollbar-color: #c1c1c1 #f1f1f1; /* Firefox */ | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* Webkit浏览器的滚动条样式 */ | ||||||
|  |         .notice-container::-webkit-scrollbar { | ||||||
|  |             width: 6px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .notice-container::-webkit-scrollbar-track { | ||||||
|  |             background: #f1f1f1; | ||||||
|  |             border-radius: 3px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .notice-container::-webkit-scrollbar-thumb { | ||||||
|  |             background: #c1c1c1; | ||||||
|  |             border-radius: 3px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .notice-container::-webkit-scrollbar-thumb:hover { | ||||||
|  |             background: #a8a8a8; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .notice-content { | ||||||
|  |             padding: 10px; | ||||||
|  |             /* 移除自动滚动动画 */ | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .notice-item { | ||||||
|  |             padding: 8px 0; | ||||||
|  |             border-bottom: 1px solid #eee; | ||||||
|  |             font-size: 12px; | ||||||
|  |             color: #333; | ||||||
|  |             line-height: 1.4; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .notice-item:last-child { | ||||||
|  |             border-bottom: none; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /* 移除自动滚动动画 */ | ||||||
|  |         /* @keyframes scrollNotice { | ||||||
|  |             0% { | ||||||
|  |                 transform: translateY(0); | ||||||
|  |             } | ||||||
|  |             100% { | ||||||
|  |                 transform: translateY(-100%); | ||||||
|  |             } | ||||||
|  |         } */ | ||||||
|  | 
 | ||||||
|  |         /* 弹窗样式 */ | ||||||
|  |         .modal-overlay { | ||||||
|  |             position: fixed; | ||||||
|  |             top: 0; | ||||||
|  |             left: 0; | ||||||
|  |             width: 100%; | ||||||
|  |             height: 100%; | ||||||
|  |             background-color: rgba(0, 0, 0, 0.7); | ||||||
|  |             z-index: 1000; | ||||||
|  |             display: flex; | ||||||
|  |             justify-content: center; | ||||||
|  |             align-items: center; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .modal-content { | ||||||
|  |             background: white; | ||||||
|  |             border-radius: 8px; | ||||||
|  |             width: 80%; | ||||||
|  |             max-width: 800px; | ||||||
|  |             max-height: 80%; | ||||||
|  |             display: flex; | ||||||
|  |             flex-direction: column; | ||||||
|  |             box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .modal-header { | ||||||
|  |             display: flex; | ||||||
|  |             justify-content: space-between; | ||||||
|  |             align-items: center; | ||||||
|  |             padding: 20px; | ||||||
|  |             border-bottom: 1px solid #eee; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .modal-header h3 { | ||||||
|  |             margin: 0; | ||||||
|  |             color: #333; | ||||||
|  |             font-size: 18px; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .modal-close { | ||||||
|  |             background: none; | ||||||
|  |             border: none; | ||||||
|  |             font-size: 24px; | ||||||
|  |             cursor: pointer; | ||||||
|  |             color: #666; | ||||||
|  |             padding: 0; | ||||||
|  |             width: 30px; | ||||||
|  |             height: 30px; | ||||||
|  |             display: flex; | ||||||
|  |             align-items: center; | ||||||
|  |             justify-content: center; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .modal-close:hover { | ||||||
|  |             color: #333; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .modal-body { | ||||||
|  |             flex: 1; | ||||||
|  |             overflow: hidden; | ||||||
|  |             padding: 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .modal-notice-content { | ||||||
|  |             height: 100%; | ||||||
|  |             overflow-y: auto; | ||||||
|  |             padding: 20px; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .modal-notice-item { | ||||||
|  |             padding: 12px 0; | ||||||
|  |             border-bottom: 1px solid #f0f0f0; | ||||||
|  |             font-size: 14px; | ||||||
|  |             line-height: 1.6; | ||||||
|  |             color: #333; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .modal-notice-item:last-child { | ||||||
|  |             border-bottom: none; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .modal-holdings-content { | ||||||
|  |             height: 100%; | ||||||
|  |             overflow-y: auto; | ||||||
|  |             padding: 20px; | ||||||
|  |             max-height: 70vh; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .holdings-table { | ||||||
|  |             width: 100%; | ||||||
|  |             border-collapse: collapse; | ||||||
|  |             font-size: 14px; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .holdings-table th, | ||||||
|  |         .holdings-table td { | ||||||
|  |             padding: 8px 12px; | ||||||
|  |             text-align: left; | ||||||
|  |             border-bottom: 1px solid #eee; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .holdings-table th { | ||||||
|  |             background-color: #f8f9fa; | ||||||
|  |             font-weight: bold; | ||||||
|  |             color: #333; | ||||||
|  |             position: sticky; | ||||||
|  |             top: 0; | ||||||
|  |             z-index: 10; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .holdings-table tr:hover { | ||||||
|  |             background-color: #f5f5f5; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .stock-row { | ||||||
|  |             cursor: pointer; | ||||||
|  |             background-color: #f8f9fa; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .stock-row:hover { | ||||||
|  |             background-color: #e9ecef !important; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .detail-row { | ||||||
|  |             background-color: #fafafa; | ||||||
|  |             display: none; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .detail-row.show { | ||||||
|  |             display: table-row; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .detail-content { | ||||||
|  |             padding: 10px; | ||||||
|  |             background-color: #fff; | ||||||
|  |             border-left: 3px solid #5470c6; | ||||||
|  |             margin: 5px 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .expand-icon { | ||||||
|  |             margin-right: 5px; | ||||||
|  |             transition: transform 0.3s; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .expand-icon.expanded { | ||||||
|  |             transform: rotate(90deg); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .holdings-summary { | ||||||
|  |             margin-bottom: 20px; | ||||||
|  |             padding: 15px; | ||||||
|  |             background-color: #f8f9fa; | ||||||
|  |             border-radius: 5px; | ||||||
|  |             border-left: 4px solid #5470c6; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .holdings-summary h4 { | ||||||
|  |             margin: 0 0 10px 0; | ||||||
|  |             color: #333; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .holdings-summary p { | ||||||
|  |             margin: 5px 0; | ||||||
|  |             color: #666; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /* 平板响应式 */ | ||||||
|  |         @media (max-width: 1200px) { | ||||||
|  |             .row.d-flex { height: auto; } | ||||||
|  |             .chart-box { height: auto; min-height: 180px; } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /* 移动端响应式 */ | ||||||
|  |         @media (max-width: 768px) { | ||||||
|  |             html, body { | ||||||
|  |                 padding: 2px; | ||||||
|  |             } | ||||||
|  |             .container-fluid { | ||||||
|  |                 padding: 0; | ||||||
|  |             } | ||||||
|  |             .row.d-flex, .row.d-flex2 { | ||||||
|  |                 flex-direction: column !important; | ||||||
|  |                 height: auto !important; | ||||||
|  |                 margin: 0; | ||||||
|  |             } | ||||||
|  |             .col-3.d-flex { | ||||||
|  |                 width: 100% !important; | ||||||
|  |                 max-width: 100% !important; | ||||||
|  |                 min-width: 0 !important; | ||||||
|  |                 margin-bottom: 8px; | ||||||
|  |                 padding: 1px 0; | ||||||
|  |             } | ||||||
|  |             .chart-box, .tight-box { | ||||||
|  |                 min-height: 200px; | ||||||
|  |                 height: auto !important; | ||||||
|  |                 padding: 2px; | ||||||
|  |             } | ||||||
|  |             .chart-title { | ||||||
|  |                 font-size: 0.9rem; | ||||||
|  |                 margin-bottom: 1px; | ||||||
|  |             } | ||||||
|  |             .small-title { | ||||||
|  |                 font-size: 0.8rem; | ||||||
|  |                 margin-bottom: 1px; | ||||||
|  |                 margin-top: 1px; | ||||||
|  |             } | ||||||
|  |             .chart-container { | ||||||
|  |                 min-height: 180px; | ||||||
|  |             } | ||||||
|  |             /* 调整概念卡片的布局 */ | ||||||
|  |             .tight-box .chart-container { | ||||||
|  |                 min-height: 150px; | ||||||
|  |                 min-width: 0; | ||||||
|  |                 width: 100%; | ||||||
|  |                 box-sizing: border-box; | ||||||
|  |             } | ||||||
|  |             /* 优化图表间距 */ | ||||||
|  |             .tight-box .chart-title { | ||||||
|  |                 padding: 1px 0; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /* 小屏手机响应式 */ | ||||||
|  |         @media (max-width: 480px) { | ||||||
|  |             .chart-box, .tight-box { | ||||||
|  |                 min-height: 180px; | ||||||
|  |             } | ||||||
|  |             .chart-container { | ||||||
|  |                 min-height: 160px; | ||||||
|  |             } | ||||||
|  |             .tight-box .chart-container { | ||||||
|  |                 min-height: 130px; | ||||||
|  |             } | ||||||
|  |             .chart-title { | ||||||
|  |                 font-size: 0.85rem; | ||||||
|  |             } | ||||||
|  |             .small-title { | ||||||
|  |                 font-size: 0.75rem; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 标的详情容器样式 */ | ||||||
|  |         .stock-detail-container { | ||||||
|  |             height: 100%; | ||||||
|  |             overflow-y: auto; | ||||||
|  |             padding: 8px; | ||||||
|  |             background: #f9f9fb; | ||||||
|  |             border-radius: 5px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 合并后的标的详情容器样式 */ | ||||||
|  |         .stock-detail-container-merged { | ||||||
|  |             height: 600px; | ||||||
|  |             overflow-y: auto; | ||||||
|  |             padding: 15px; | ||||||
|  |             background: #f9f9fb; | ||||||
|  |             border-radius: 5px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .stock-details-list { | ||||||
|  |             display: flex; | ||||||
|  |             flex-direction: column; | ||||||
|  |             gap: 10px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .stock-detail-container-merged .stock-details-list { | ||||||
|  |             display: flex; | ||||||
|  |             flex-direction: column; | ||||||
|  |             gap: 15px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .stock-detail-item { | ||||||
|  |             background: #fff; | ||||||
|  |             border-radius: 6px; | ||||||
|  |             padding: 12px; | ||||||
|  |             box-shadow: 0 1px 4px rgba(0,0,0,0.1); | ||||||
|  |             border-left: 3px solid #5470c6; | ||||||
|  |             margin-bottom: 10px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .stock-detail-item h4 { | ||||||
|  |             margin: 0 0 10px 0; | ||||||
|  |             color: #333; | ||||||
|  |             font-size: 14px; | ||||||
|  |             font-weight: bold; | ||||||
|  |             border-bottom: 1px solid #f0f0f0; | ||||||
|  |             padding-bottom: 5px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .stock-detail-container-merged .stock-detail-item h4 { | ||||||
|  |             font-size: 16px; | ||||||
|  |             margin-bottom: 15px; | ||||||
|  |             padding-bottom: 8px; | ||||||
|  |             color: #2c3e50; | ||||||
|  |             border-bottom: 2px solid #3498db; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 新增:持仓和因子的左右布局容器 */ | ||||||
|  |         .factor-holding-row { | ||||||
|  |             display: flex; | ||||||
|  |             gap: 15px; | ||||||
|  |             margin-bottom: 10px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .holding-section, .factor-section { | ||||||
|  |             flex: 1; | ||||||
|  |             min-width: 0; /* 防止内容溢出 */ | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .correction-section { | ||||||
|  |             margin-bottom: 10px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .correction-section:last-child { | ||||||
|  |             margin-bottom: 0; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 标题样式 */ | ||||||
|  |         .factor-section h5, .holding-section h5, .correction-section h5 { | ||||||
|  |             margin: 0 0 5px 0; | ||||||
|  |             color: #555; | ||||||
|  |             font-size: 12px; | ||||||
|  |             font-weight: bold; | ||||||
|  |             background: #f8f9fa; | ||||||
|  |             padding: 3px 6px; | ||||||
|  |             border-radius: 3px; | ||||||
|  |             border-left: 2px solid #007bff; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .stock-detail-container-merged .factor-section h5, | ||||||
|  |         .stock-detail-container-merged .holding-section h5, | ||||||
|  |         .stock-detail-container-merged .correction-section h5 { | ||||||
|  |             font-size: 14px; | ||||||
|  |             padding: 6px 10px; | ||||||
|  |             margin-bottom: 10px; | ||||||
|  |             background: #ecf0f1; | ||||||
|  |             border-radius: 4px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 保留原有的detail-section样式用于其他地方 */ | ||||||
|  |         .detail-section { | ||||||
|  |             margin-bottom: 10px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .detail-section:last-child { | ||||||
|  |             margin-bottom: 0; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .detail-section h5 { | ||||||
|  |             margin: 0 0 5px 0; | ||||||
|  |             color: #555; | ||||||
|  |             font-size: 12px; | ||||||
|  |             font-weight: bold; | ||||||
|  |             background: #f8f9fa; | ||||||
|  |             padding: 3px 6px; | ||||||
|  |             border-radius: 3px; | ||||||
|  |             border-left: 2px solid #007bff; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .stock-detail-container-merged .detail-section h5 { | ||||||
|  |             font-size: 14px; | ||||||
|  |             padding: 6px 10px; | ||||||
|  |             margin-bottom: 10px; | ||||||
|  |             background: #ecf0f1; | ||||||
|  |             border-radius: 4px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .factor-details, .holding-details, .correction-details { | ||||||
|  |             padding: 5px; | ||||||
|  |             background: #fafafa; | ||||||
|  |             border-radius: 3px; | ||||||
|  |             margin-top: 3px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .factor-details p, .holding-details p, .correction-details p { | ||||||
|  |             margin: 2px 0; | ||||||
|  |             font-size: 11px; | ||||||
|  |             color: #666; | ||||||
|  |             line-height: 1.3; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .stock-detail-container-merged .factor-details p, | ||||||
|  |         .stock-detail-container-merged .holding-details p, | ||||||
|  |         .stock-detail-container-merged .correction-details p { | ||||||
|  |             font-size: 13px; | ||||||
|  |             margin: 4px 0; | ||||||
|  |             line-height: 1.5; | ||||||
|  |             color: #34495e; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .holding-detail-item { | ||||||
|  |             margin-bottom: 3px; | ||||||
|  |             padding: 3px; | ||||||
|  |             background: #f0f8ff; | ||||||
|  |             border-radius: 2px; | ||||||
|  |             border-left: 2px solid #17a2b8; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .correction-detail-item { | ||||||
|  |             margin-bottom: 5px; | ||||||
|  |             padding: 5px; | ||||||
|  |             background: #fff3cd; | ||||||
|  |             border-radius: 2px; | ||||||
|  |             border-left: 2px solid #ffc107; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .correction-detail-item p { | ||||||
|  |             margin: 1px 0; | ||||||
|  |             font-size: 10px; | ||||||
|  |             color: #856404; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 响应式调整 */ | ||||||
|  |         @media (max-width: 768px) { | ||||||
|  |             .stock-detail-container-merged { | ||||||
|  |                 height: 500px; | ||||||
|  |                 padding: 10px; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             .col-3.d-flex .chart-container { | ||||||
|  |                 height: 40%; | ||||||
|  |                 min-height: 150px; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             .stock-detail-item { | ||||||
|  |                 padding: 10px; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             .stock-detail-item h4 { | ||||||
|  |                 font-size: 14px; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             .detail-section h5 { | ||||||
|  |                 font-size: 12px; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             .factor-details, .holding-details, .correction-details { | ||||||
|  |                 padding: 5px; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             .factor-details p, .holding-details p, .correction-details p { | ||||||
|  |                 font-size: 11px; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             /* 移动端:因子和持仓改为上下布局 */ | ||||||
|  |             .factor-holding-row { | ||||||
|  |                 flex-direction: column; | ||||||
|  |                 gap: 10px; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             .factor-section, .holding-section { | ||||||
|  |                 flex: none; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 重置按钮样式 */ | ||||||
|  |         #resetViewBtn { | ||||||
|  |             background: rgba(255, 255, 255, 0.9); | ||||||
|  |             border: 1px solid #007bff; | ||||||
|  |             color: #007bff; | ||||||
|  |             font-size: 12px; | ||||||
|  |             padding: 5px 10px; | ||||||
|  |             border-radius: 15px; | ||||||
|  |             box-shadow: 0 2px 5px rgba(0,0,0,0.2); | ||||||
|  |             transition: all 0.3s ease; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         #resetViewBtn:hover { | ||||||
|  |             background: #007bff; | ||||||
|  |             color: white; | ||||||
|  |             transform: translateY(-1px); | ||||||
|  |             box-shadow: 0 3px 8px rgba(0,0,0,0.3); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         /* 强制第一个容器占据全宽 */ | ||||||
|  |         .col-3.d-flex.expanded { | ||||||
|  |             width: 100% !important; | ||||||
|  |             max-width: 100% !important; | ||||||
|  |             flex: 1 !important; | ||||||
|  |             flex-grow: 1 !important; | ||||||
|  |             flex-shrink: 0 !important; | ||||||
|  |         } | ||||||
|  |     </style> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  | <div class="container-fluid"> | ||||||
|  | 
 | ||||||
|  | <!-- 添加重置按钮 --> | ||||||
|  | <div style="position: fixed; top: 10px; right: 10px; z-index: 1000;"> | ||||||
|  |     <button id="resetViewBtn" class="btn btn-sm btn-outline-primary" style="display: none;"> | ||||||
|  |         返回默认视图 | ||||||
|  |     </button> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <div class="row d-flex"> | ||||||
|  |         <div class="col-3 d-flex"> | ||||||
|  |             <div class="chart-box w-100"> | ||||||
|  |                 <div class="chart-title">行业持仓占比</div> | ||||||
|  |                 <div id="portfolioChart" class="chart-container"></div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="col-3 d-flex"> | ||||||
|  |             <div class="chart-box w-100"> | ||||||
|  |                 <div class="chart-title">重要提醒</div> | ||||||
|  |                 <div id="noticeBox" class="notice-container" style="cursor: pointer;"> | ||||||
|  |                     <div class="notice-content"> | ||||||
|  |                         <div class="notice-item">加载数据中...</div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="col-3 d-flex"> | ||||||
|  |             <div class="chart-box w-100"> | ||||||
|  |                 <div class="chart-title">融资融券数据监控</div> | ||||||
|  |                 <div id="rzrqChart" class="chart-container"></div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="col-3 d-flex"> | ||||||
|  |             <div class="chart-box w-100"> | ||||||
|  |                 <div class="chart-title">市场恐贪指数</div> | ||||||
|  |                 <div id="fearGreedChart" class="chart-container"></div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="row d-flex2"> | ||||||
|  |         <div class="col-3 d-flex"> | ||||||
|  |             <div class="chart-box tight-box w-100"> | ||||||
|  |                 <div class="chart-title small-title">行业1-历史PE分析</div> | ||||||
|  |                 <div id="peChart_xjfz" class="chart-container"></div> | ||||||
|  |                 <div class="chart-title small-title" style="margin-top:2px;">行业1-拥挤度</div> | ||||||
|  |                 <div id="crowdChart_xjfz" class="chart-container"></div> | ||||||
|  |                 <!-- <div class="chart-title small-title" style="margin-top:2px;">行业1-持仓标的</div> --> | ||||||
|  |                 <div id="holdings_xjfz" class="holdings-container"></div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="col-3 d-flex"> | ||||||
|  |             <div class="chart-box tight-box w-100"> | ||||||
|  |                 <div class="chart-title small-title">行业2-历史PE分析</div> | ||||||
|  |                 <div id="peChart_xp" class="chart-container"></div> | ||||||
|  |                 <div class="chart-title small-title" style="margin-top:2px;">行业2-拥挤度</div> | ||||||
|  |                 <div id="crowdChart_xp" class="chart-container"></div> | ||||||
|  |                 <!-- <div class="chart-title small-title" style="margin-top:2px;">行业2-持仓标的</div> --> | ||||||
|  |                 <div id="holdings_xp" class="holdings-container"></div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="col-3 d-flex"> | ||||||
|  |             <div class="chart-box tight-box w-100"> | ||||||
|  |                 <div class="chart-title small-title">行业3-历史PE分析</div> | ||||||
|  |                 <div id="peChart_xfdz" class="chart-container"></div> | ||||||
|  |                 <div class="chart-title small-title" style="margin-top:2px;">行业3-拥挤度</div> | ||||||
|  |                 <div id="crowdChart_xfdz" class="chart-container"></div> | ||||||
|  |                 <!-- <div class="chart-title small-title" style="margin-top:2px;">行业3-持仓标的</div> --> | ||||||
|  |                 <div id="holdings_xfdz" class="holdings-container"></div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="col-3 d-flex"> | ||||||
|  |             <div class="chart-box tight-box w-100"> | ||||||
|  |                 <div class="chart-title small-title">行业4-历史PE分析</div> | ||||||
|  |                 <div id="peChart_jqr" class="chart-container"></div> | ||||||
|  |                 <div class="chart-title small-title" style="margin-top:2px;">行业4-拥挤度</div> | ||||||
|  |                 <div id="crowdChart_jqr" class="chart-container"></div> | ||||||
|  |                 <!-- <div class="chart-title small-title" style="margin-top:2px;">行业4-持仓标的</div> --> | ||||||
|  |                 <div id="holdings_jqr" class="holdings-container"></div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <!-- 弹窗结构 --> | ||||||
|  | <div id="noticeModal" class="modal-overlay" style="display: none;"> | ||||||
|  |     <div class="modal-content"> | ||||||
|  |         <div class="modal-header"> | ||||||
|  |             <h3>重要提醒详情</h3> | ||||||
|  |             <button class="modal-close" onclick="closeNoticeModal()">×</button> | ||||||
|  |         </div> | ||||||
|  |         <div class="modal-body"> | ||||||
|  |             <div id="modalNoticeContent" class="modal-notice-content"> | ||||||
|  |                 <!-- 动态内容 --> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <!-- 行业持仓详情弹窗 --> | ||||||
|  | <div id="industryHoldingsModal" class="modal-overlay" style="display: none;"> | ||||||
|  |     <div class="modal-content"> | ||||||
|  |         <div class="modal-header"> | ||||||
|  |             <h3 id="industryHoldingsTitle">行业持仓详情</h3> | ||||||
|  |             <button class="modal-close" onclick="closeIndustryHoldingsModal()">×</button> | ||||||
|  |         </div> | ||||||
|  |         <div class="modal-body"> | ||||||
|  |             <div id="industryHoldingsContent" class="modal-holdings-content"> | ||||||
|  |                 <!-- 动态内容 --> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <script src="/static/js/echarts.min.js"></script> | ||||||
|  | <script src="/static/js/jquery.min.js"></script> | ||||||
|  | <script src="/static/js/bigscreen_v2.js"></script> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
|  | @ -834,7 +834,7 @@ class IndustryAnalyzer: | ||||||
|      |      | ||||||
|     def batch_calculate_industry_crowding(self, industries: List[str], concepts: List[str] = None) -> None: |     def batch_calculate_industry_crowding(self, industries: List[str], concepts: List[str] = None) -> None: | ||||||
|         """ |         """ | ||||||
|         批量计算多个行业和概念板块的拥挤度指标 |         批量计算多个行业和概念板块的拥挤度指标,并生成个股关联表 | ||||||
|          |          | ||||||
|         Args: |         Args: | ||||||
|             industries: 行业列表 |             industries: 行业列表 | ||||||
|  | @ -878,7 +878,9 @@ class IndustryAnalyzer: | ||||||
|                     if stocks: |                     if stocks: | ||||||
|                         concept_stocks[concept] = stocks |                         concept_stocks[concept] = stocks | ||||||
|              |              | ||||||
|             # 5. 批量计算行业拥挤度 |             # 5. 批量计算行业拥挤度并生成个股关联数据 | ||||||
|  |             industry_crowding_data = [] | ||||||
|  |              | ||||||
|             for industry, stocks in industry_stocks.items(): |             for industry, stocks in industry_stocks.items(): | ||||||
|                 try: |                 try: | ||||||
|                     # 计算行业成交额 |                     # 计算行业成交额 | ||||||
|  | @ -911,6 +913,23 @@ class IndustryAnalyzer: | ||||||
|                         ex=86400 |                         ex=86400 | ||||||
|                     ) |                     ) | ||||||
|                      |                      | ||||||
|  |                     # 获取最新的拥挤度数据 | ||||||
|  |                     latest_data = df.iloc[-1] if len(df) > 0 else None | ||||||
|  |                     if latest_data is not None: | ||||||
|  |                         # 生成信号 | ||||||
|  |                         crowding_value = latest_data['percentile'] | ||||||
|  |                         signal = self._generate_crowding_signal(crowding_value) | ||||||
|  |                          | ||||||
|  |                         # 为每个股票生成关联记录 | ||||||
|  |                         for stock_code in stocks: | ||||||
|  |                             industry_crowding_data.append({ | ||||||
|  |                                 'stock_code': stock_code, | ||||||
|  |                                 'industry_name': industry, | ||||||
|  |                                 'crowding_value': crowding_value, | ||||||
|  |                                 'trade_signal': signal, | ||||||
|  |                                 'last_trade_date': latest_data['trade_date'] | ||||||
|  |                             }) | ||||||
|  |                      | ||||||
|                     logger.info(f"成功计算行业 {industry} 的拥挤度指标,共 {len(df)} 条记录") |                     logger.info(f"成功计算行业 {industry} 的拥挤度指标,共 {len(df)} 条记录") | ||||||
|                 except Exception as e: |                 except Exception as e: | ||||||
|                     logger.error(f"计算行业 {industry} 的拥挤度指标时出错: {str(e)}") |                     logger.error(f"计算行业 {industry} 的拥挤度指标时出错: {str(e)}") | ||||||
|  | @ -955,9 +974,105 @@ class IndustryAnalyzer: | ||||||
|                         logger.error(f"计算概念板块 {concept} 的拥挤度指标时出错: {str(e)}") |                         logger.error(f"计算概念板块 {concept} 的拥挤度指标时出错: {str(e)}") | ||||||
|                         continue |                         continue | ||||||
|              |              | ||||||
|  |             # 7. 更新行业拥挤度个股关联表 | ||||||
|  |             if industry_crowding_data: | ||||||
|  |                 self._update_industry_crowding_stocks_table(industry_crowding_data) | ||||||
|  |                          | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             logger.error(f"批量计算行业拥挤度指标失败: {str(e)}") |             logger.error(f"批量计算行业拥挤度指标失败: {str(e)}") | ||||||
|      |      | ||||||
|  |     def _generate_crowding_signal(self, crowding_value: float) -> str: | ||||||
|  |         """ | ||||||
|  |         根据拥挤度数值生成信号 | ||||||
|  |          | ||||||
|  |         Args: | ||||||
|  |             crowding_value: 拥挤度数值(百分比) | ||||||
|  |              | ||||||
|  |         Returns: | ||||||
|  |             信号字符串 | ||||||
|  |         """ | ||||||
|  |         if crowding_value < 10: | ||||||
|  |             return "强烈买入" | ||||||
|  |         elif crowding_value < 20: | ||||||
|  |             return "买入" | ||||||
|  |         elif crowding_value > 90: | ||||||
|  |             return "强烈卖出" | ||||||
|  |         elif crowding_value > 80: | ||||||
|  |             return "卖出" | ||||||
|  |         else: | ||||||
|  |             return "中性" | ||||||
|  |      | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  |     def _update_industry_crowding_stocks_table(self, industry_crowding_data: List[Dict]) -> None: | ||||||
|  |         """ | ||||||
|  |         更新行业拥挤度个股关联表 | ||||||
|  |          | ||||||
|  |         Args: | ||||||
|  |             industry_crowding_data: 行业拥挤度数据列表 | ||||||
|  |         """ | ||||||
|  |         try: | ||||||
|  |             if not industry_crowding_data: | ||||||
|  |                 logger.warning("没有行业拥挤度数据需要更新") | ||||||
|  |                 return | ||||||
|  |              | ||||||
|  |             # 清空现有数据 | ||||||
|  |             delete_query = "DELETE FROM industry_crowding_stocks" | ||||||
|  |              | ||||||
|  |             # 构建插入语句 | ||||||
|  |             insert_query = """ | ||||||
|  |                 INSERT INTO industry_crowding_stocks  | ||||||
|  |                 (stock_code, industry_name, crowding_value, trade_signal, last_trade_date)  | ||||||
|  |                 VALUES (:stock_code, :industry_name, :crowding_value, :trade_signal, :last_trade_date) | ||||||
|  |             """ | ||||||
|  |              | ||||||
|  |             inserted_count = 0 | ||||||
|  |              | ||||||
|  |             with self.engine.connect() as conn: | ||||||
|  |                 # 开始事务 | ||||||
|  |                 trans = conn.begin() | ||||||
|  |                 try: | ||||||
|  |                     # 清空表 | ||||||
|  |                     conn.execute(text(delete_query)) | ||||||
|  |                      | ||||||
|  |                     # 逐条插入数据 | ||||||
|  |                     for item in industry_crowding_data: | ||||||
|  |                         # 检查该股票和行业的组合是否已存在 | ||||||
|  |                         check_query = text(""" | ||||||
|  |                         SELECT COUNT(*) FROM industry_crowding_stocks  | ||||||
|  |                         WHERE stock_code = :stock_code AND industry_name = :industry_name | ||||||
|  |                         """) | ||||||
|  |                         result = conn.execute(check_query, { | ||||||
|  |                             "stock_code": item['stock_code'],  | ||||||
|  |                             "industry_name": item['industry_name'] | ||||||
|  |                         }).scalar() | ||||||
|  |                          | ||||||
|  |                         if result > 0:  # 数据已存在,执行更新 | ||||||
|  |                             update_query = text(""" | ||||||
|  |                             UPDATE industry_crowding_stocks SET | ||||||
|  |                                 crowding_value = :crowding_value, | ||||||
|  |                                 trade_signal = :trade_signal, | ||||||
|  |                                 last_trade_date = :last_trade_date | ||||||
|  |                             WHERE stock_code = :stock_code AND industry_name = :industry_name | ||||||
|  |                             """) | ||||||
|  |                             conn.execute(update_query, item) | ||||||
|  |                         else:  # 数据不存在,执行插入 | ||||||
|  |                             conn.execute(text(insert_query), item) | ||||||
|  |                          | ||||||
|  |                         inserted_count += 1 | ||||||
|  |                      | ||||||
|  |                     # 提交事务 | ||||||
|  |                     trans.commit() | ||||||
|  |                      | ||||||
|  |                     logger.info(f"成功更新行业拥挤度个股关联表,共 {inserted_count} 条记录") | ||||||
|  |                 except Exception as e: | ||||||
|  |                     # 回滚事务 | ||||||
|  |                     trans.rollback() | ||||||
|  |                     raise e | ||||||
|  |                      | ||||||
|  |         except Exception as e: | ||||||
|  |             logger.error(f"更新行业拥挤度个股关联表失败: {str(e)}")  | ||||||
|  |      | ||||||
|     def filter_crowding_by_percentile(self, min_percentile: float, max_percentile: float) -> dict: |     def filter_crowding_by_percentile(self, min_percentile: float, max_percentile: float) -> dict: | ||||||
|         """ |         """ | ||||||
|         查询所有缓存中的行业和概念板块拥挤度,筛选最后一个交易日拥挤度百分位在[min, max]区间的行业/概念。 |         查询所有缓存中的行业和概念板块拥挤度,筛选最后一个交易日拥挤度百分位在[min, max]区间的行业/概念。 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,278 @@ | ||||||
|  | """ | ||||||
|  | 重要提醒服务模块 | ||||||
|  | 
 | ||||||
|  | 提供动态的重要提醒信息生成功能,包括: | ||||||
|  | 1. 行业拥挤度风险提醒 | ||||||
|  | 2. 行业持仓占比风险提醒 | ||||||
|  | 3. 个股持仓风险提醒 | ||||||
|  | 4. 融资融券风险提醒 | ||||||
|  | 5. 市场恐贪指数风险提醒 | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | import logging | ||||||
|  | import requests | ||||||
|  | from typing import Dict, List, Optional | ||||||
|  | from datetime import datetime | ||||||
|  | 
 | ||||||
|  | from .portfolio_analyzer import PortfolioAnalyzer | ||||||
|  | from .industry_analysis import IndustryAnalyzer | ||||||
|  | from .eastmoney_rzrq_collector import EastmoneyRzrqCollector | ||||||
|  | from .fear_greed_index import FearGreedIndexManager | ||||||
|  | 
 | ||||||
|  | # 配置日志 | ||||||
|  | logger = logging.getLogger("notice_service") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class NoticeService: | ||||||
|  |     """重要提醒服务类""" | ||||||
|  | 
 | ||||||
|  |     def __init__(self): | ||||||
|  |         """初始化提醒服务""" | ||||||
|  |         self.portfolio_analyzer = PortfolioAnalyzer() | ||||||
|  |         self.industry_analyzer = IndustryAnalyzer() | ||||||
|  |         self.rzrq_collector = EastmoneyRzrqCollector() | ||||||
|  |         self.fear_greed_manager = FearGreedIndexManager() | ||||||
|  |         logger.info("重要提醒服务初始化完成") | ||||||
|  | 
 | ||||||
|  |     def get_dynamic_notices(self) -> Dict: | ||||||
|  |         """ | ||||||
|  |         获取动态的重要提醒列表 | ||||||
|  |          | ||||||
|  |         Returns: | ||||||
|  |             包含提醒信息的字典 | ||||||
|  |         """ | ||||||
|  |         try: | ||||||
|  |             notices = [] | ||||||
|  |              | ||||||
|  |             # 1. 检查行业拥挤度风险 | ||||||
|  |             crowding_notices = self._check_industry_crowding_risk() | ||||||
|  |             notices.extend(crowding_notices) | ||||||
|  |              | ||||||
|  |             # 2. 检查行业持仓占比风险 | ||||||
|  |             allocation_notices = self._check_industry_allocation_risk() | ||||||
|  |             notices.extend(allocation_notices) | ||||||
|  |              | ||||||
|  |             # 3. 检查个股持仓风险 | ||||||
|  |             stock_notices = self._check_stock_holding_risk() | ||||||
|  |             notices.extend(stock_notices) | ||||||
|  |              | ||||||
|  |             # 4. 检查融资融券风险 | ||||||
|  |             rzrq_notices = self._check_rzrq_risk() | ||||||
|  |             notices.extend(rzrq_notices) | ||||||
|  |              | ||||||
|  |             # 5. 检查市场恐贪指数风险 | ||||||
|  |             fear_greed_notices = self._check_fear_greed_risk() | ||||||
|  |             notices.extend(fear_greed_notices) | ||||||
|  |              | ||||||
|  |             # 如果没有风险提醒,添加一些市场信息 | ||||||
|  |             if not notices: | ||||||
|  |                 notices = self._get_market_info_notices() | ||||||
|  |              | ||||||
|  |             return { | ||||||
|  |                 "success": True, | ||||||
|  |                 "data": notices | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |         except Exception as e: | ||||||
|  |             logger.error(f"获取动态提醒失败: {e}") | ||||||
|  |             return { | ||||||
|  |                 "success": False, | ||||||
|  |                 "message": f"获取动态提醒失败: {str(e)}", | ||||||
|  |                 "data": [] | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |     def _check_industry_crowding_risk(self) -> List[str]: | ||||||
|  |         """ | ||||||
|  |         检查行业拥挤度风险 | ||||||
|  |          | ||||||
|  |         Returns: | ||||||
|  |             拥挤度风险提醒列表 | ||||||
|  |         """ | ||||||
|  |         notices = [] | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # 获取持仓行业数据 | ||||||
|  |             portfolio_result = self.portfolio_analyzer.analyze_portfolio_allocation() | ||||||
|  |             if not portfolio_result.get("success"): | ||||||
|  |                 return notices | ||||||
|  |              | ||||||
|  |             industries = portfolio_result["data"]["industries"] | ||||||
|  |              | ||||||
|  |             # 检查每个持仓行业的拥挤度 | ||||||
|  |             for industry_data in industries: | ||||||
|  |                 industry_name = industry_data["industry"] | ||||||
|  |                  | ||||||
|  |                 # 获取行业拥挤度数据 | ||||||
|  |                 crowding_result = self.industry_analyzer.get_industry_analysis( | ||||||
|  |                     industry_name, "pe", None | ||||||
|  |                 ) | ||||||
|  |                  | ||||||
|  |                 if crowding_result.get("success") and "crowding" in crowding_result: | ||||||
|  |                     crowding_data = crowding_result["crowding"] | ||||||
|  |                     current_percentile = crowding_data["current"]["percentile"] | ||||||
|  |                      | ||||||
|  |                     if current_percentile >= 80: | ||||||
|  |                         notices.append(f"⚠️ {industry_name}拥挤度过高({current_percentile:.1f}%),建议减仓") | ||||||
|  |                     elif current_percentile <= 20: | ||||||
|  |                         notices.append(f"💰 {industry_name}拥挤度较低({current_percentile:.1f}%),可考虑加仓") | ||||||
|  |                          | ||||||
|  |         except Exception as e: | ||||||
|  |             logger.error(f"检查行业拥挤度风险失败: {e}") | ||||||
|  |          | ||||||
|  |         return notices | ||||||
|  | 
 | ||||||
|  |     def _check_industry_allocation_risk(self) -> List[str]: | ||||||
|  |         """ | ||||||
|  |         检查行业持仓占比风险 | ||||||
|  |          | ||||||
|  |         Returns: | ||||||
|  |             行业持仓占比风险提醒列表 | ||||||
|  |         """ | ||||||
|  |         notices = [] | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # 获取持仓行业数据 | ||||||
|  |             portfolio_result = self.portfolio_analyzer.analyze_portfolio_allocation() | ||||||
|  |             if not portfolio_result.get("success"): | ||||||
|  |                 return notices | ||||||
|  |              | ||||||
|  |             industries = portfolio_result["data"]["industries"] | ||||||
|  |             total_amount = portfolio_result["data"]["total_amount"] | ||||||
|  |              | ||||||
|  |             # 检查行业持仓占比 | ||||||
|  |             for industry_data in industries: | ||||||
|  |                 industry_name = industry_data["industry"] | ||||||
|  |                 industry_amount = industry_data["amount"] | ||||||
|  |                 industry_ratio = (industry_amount / total_amount) * 100 | ||||||
|  |                  | ||||||
|  |                 if industry_ratio > 50: | ||||||
|  |                     notices.append(f"⚠️ {industry_name}持仓占比过高({industry_ratio:.1f}%),建议分散风险") | ||||||
|  |                      | ||||||
|  |         except Exception as e: | ||||||
|  |             logger.error(f"检查行业持仓占比风险失败: {e}") | ||||||
|  |          | ||||||
|  |         return notices | ||||||
|  | 
 | ||||||
|  |     def _check_stock_holding_risk(self) -> List[str]: | ||||||
|  |         """ | ||||||
|  |         检查个股持仓风险 | ||||||
|  |          | ||||||
|  |         Returns: | ||||||
|  |             个股持仓风险提醒列表 | ||||||
|  |         """ | ||||||
|  |         notices = [] | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # 获取持仓摘要数据 | ||||||
|  |             summary_result = self.portfolio_analyzer.get_portfolio_summary() | ||||||
|  |             if not summary_result.get("success"): | ||||||
|  |                 return notices | ||||||
|  |              | ||||||
|  |             project_details = summary_result["data"]["project_details"] | ||||||
|  |              | ||||||
|  |             # 检查个股保证金金额 | ||||||
|  |             for project in project_details: | ||||||
|  |                 project_name = project["project_name"] | ||||||
|  |                 margin_amount = project["margin_amount"] | ||||||
|  |                  | ||||||
|  |                 if margin_amount > 2000000:  # 200万 | ||||||
|  |                     notices.append(f"⚠️ {project_name}保证金过高({margin_amount/10000:.1f}万),建议控制仓位") | ||||||
|  |                      | ||||||
|  |         except Exception as e: | ||||||
|  |             logger.error(f"检查个股持仓风险失败: {e}") | ||||||
|  |          | ||||||
|  |         return notices | ||||||
|  | 
 | ||||||
|  |     def _check_rzrq_risk(self) -> List[str]: | ||||||
|  |         """ | ||||||
|  |         检查融资融券风险 | ||||||
|  |          | ||||||
|  |         Returns: | ||||||
|  |             融资融券风险提醒列表 | ||||||
|  |         """ | ||||||
|  |         notices = [] | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # 获取融资融券数据 | ||||||
|  |             rzrq_result = self.rzrq_collector.get_chart_data(limit_days=90) | ||||||
|  |             if not rzrq_result.get("success"): | ||||||
|  |                 return notices | ||||||
|  |              | ||||||
|  |             # 计算当前值在历史数据中的百分位 | ||||||
|  |             series_data = rzrq_result["series"] | ||||||
|  |             if not series_data: | ||||||
|  |                 return notices | ||||||
|  |              | ||||||
|  |             main_series = series_data[0]  # 融资融券余额合计 | ||||||
|  |             data_values = main_series["data"] | ||||||
|  |              | ||||||
|  |             # 过滤有效数据 | ||||||
|  |             valid_data = [v for v in data_values if v is not None and v != 0] | ||||||
|  |             if not valid_data: | ||||||
|  |                 return notices | ||||||
|  |              | ||||||
|  |             current_value = data_values[-1] | ||||||
|  |             sorted_data = sorted(valid_data) | ||||||
|  |             current_index = next((i for i, v in enumerate(sorted_data) if v >= current_value), len(sorted_data)) | ||||||
|  |             percentile = (current_index / len(sorted_data)) * 100 | ||||||
|  |              | ||||||
|  |             if percentile >= 80: | ||||||
|  |                 notices.append(f"⚠️ 融资融券余额处于高位({percentile:.1f}%),市场情绪过热") | ||||||
|  |             elif percentile <= 20: | ||||||
|  |                 notices.append(f"💰 融资融券余额处于低位({percentile:.1f}%),市场情绪低迷") | ||||||
|  |                  | ||||||
|  |         except Exception as e: | ||||||
|  |             logger.error(f"检查融资融券风险失败: {e}") | ||||||
|  |          | ||||||
|  |         return notices | ||||||
|  | 
 | ||||||
|  |     def _check_fear_greed_risk(self) -> List[str]: | ||||||
|  |         """ | ||||||
|  |         检查市场恐贪指数风险 | ||||||
|  |          | ||||||
|  |         Returns: | ||||||
|  |             市场恐贪指数风险提醒列表 | ||||||
|  |         """ | ||||||
|  |         notices = [] | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # 获取恐贪指数数据 | ||||||
|  |             fear_greed_result = self.fear_greed_manager.get_index_data(None, None, 180) | ||||||
|  |             if not fear_greed_result.get("success"): | ||||||
|  |                 return notices | ||||||
|  |              | ||||||
|  |             values = fear_greed_result["values"] | ||||||
|  |             if not values: | ||||||
|  |                 return notices | ||||||
|  |              | ||||||
|  |             current_value = values[-1] | ||||||
|  |              | ||||||
|  |             if current_value >= 80: | ||||||
|  |                 notices.append(f"⚠️ 市场恐贪指数过高({current_value:.1f}),市场情绪贪婪") | ||||||
|  |             elif current_value <= 20: | ||||||
|  |                 notices.append(f"💰 市场恐贪指数过低({current_value:.1f}),市场情绪恐惧") | ||||||
|  |                  | ||||||
|  |         except Exception as e: | ||||||
|  |             logger.error(f"检查市场恐贪指数风险失败: {e}") | ||||||
|  |          | ||||||
|  |         return notices | ||||||
|  | 
 | ||||||
|  |     def _get_market_info_notices(self) -> List[str]: | ||||||
|  |         """ | ||||||
|  |         获取市场信息提醒(当没有风险提醒时使用) | ||||||
|  |          | ||||||
|  |         Returns: | ||||||
|  |             市场信息提醒列表 | ||||||
|  |         """ | ||||||
|  |         return [ | ||||||
|  |             "📈 上证指数突破3200点,市场情绪回暖", | ||||||
|  |             "💰 北向资金今日净流入85.6亿元", | ||||||
|  |             "📊 科技板块PE估值处于历史低位", | ||||||
|  |             "🔥 新能源概念股集体上涨,涨幅超3%", | ||||||
|  |             "⚠️ 医药板块回调,建议关注低吸机会", | ||||||
|  |             "📈 融资融券余额连续三日增长", | ||||||
|  |             "💰 消费板块资金流入明显", | ||||||
|  |             "📊 市场恐贪指数回升至65", | ||||||
|  |             "🤖 机器人概念板块技术面突破", | ||||||
|  |             "📦 先进封装概念获政策支持" | ||||||
|  |         ]  | ||||||
|  | @ -224,7 +224,7 @@ class PortfolioAnalyzer: | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|              |              | ||||||
|             logger.info(f"成功分析持仓行业分配,总金额: {total_amount:.2f}万元,共{len(industries_data)}个行业") |             logger.info(f"成功分析持仓行业分配,总金额: {total_amount:.2f}元,共{len(industries_data)}个行业") | ||||||
|             return result |             return result | ||||||
|              |              | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue