diff --git a/src/app.py b/src/app.py index 98b8ef7..5e3f486 100644 --- a/src/app.py +++ b/src/app.py @@ -1,12 +1,12 @@ import sys import os -from datetime import datetime, timedelta, time +from datetime import datetime, timedelta import pandas as pd import uuid import json from threading import Thread -from sqlalchemy import create_engine, text -from src.fundamentals_llm.fundamental_analysis_database import get_analysis_result, get_db +from sqlalchemy import text +from src.fundamentals_llm.fundamental_analysis_database import get_db # 添加项目根目录到 Python 路径 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -19,7 +19,7 @@ import logging from src.fundamentals_llm.enterprise_screener import EnterpriseScreener # 导入股票回测器 -from src.stock_analysis_v2 import run_backtest, StockBacktester +from src.stock_analysis_v2 import run_backtest # 导入PE/PB估值分析器 from src.valuation_analysis.pe_pb_analysis import ValuationAnalyzer @@ -42,9 +42,6 @@ from src.valuation_analysis.index_analyzer import IndexAnalyzer # 导入股票日线数据采集器 from src.scripts.stock_daily_data_collector import collect_stock_daily_data -from utils.distributed_lock import DistributedLock -from valuation_analysis.industry_analysis import redis_client - from valuation_analysis.financial_analysis import FinancialAnalyzer from src.valuation_analysis.stock_price_collector import StockPriceCollector @@ -186,224 +183,159 @@ def run_backtest_task(task_id, stocks_buy_dates, end_date): backtest_tasks[task_id]['error'] = str(e) logger.error(f"回测任务 {task_id} 失败:{str(e)}") -def initialize_stock_price_schedule(): - """ - 初始化实时股价数据采集定时任务 - """ - # 创建分布式锁 - price_lock = DistributedLock(redis_client, "stock_price_collector", expire_time=3600) # 1小时过期 - - # 尝试获取锁 - if not price_lock.acquire(): - logger.info("其他服务器正在运行实时股价数据采集任务,本服务器跳过") - return None - +@app.route('/scheduler/stockRealtimePrice/collection', methods=['GET']) +def update_stock_realtime_price(): + """更新实时股价数据 周内的9点半、10点半、11点半、2点、3点各更新一次""" try: - from apscheduler.schedulers.background import BackgroundScheduler - from apscheduler.triggers.cron import CronTrigger - - # 创建定时任务调度器 - scheduler = BackgroundScheduler() - - def is_trading_time(): - """判断当前是否为交易时间""" - now = datetime.now() - current_time = now.time() - - # 定义交易时间段 - morning_start = time(9, 25) # 上午开盘前5分钟 - morning_end = time(11, 30) # 上午收盘 - afternoon_start = time(13, 0) # 下午开盘 - afternoon_end = time(15, 0) # 下午收盘 - - # 判断是否为工作日 - if now.weekday() >= 5: # 5是周六,6是周日 - return False - - # 判断是否在交易时间段内 - is_morning = morning_start <= current_time <= morning_end - is_afternoon = afternoon_start <= current_time <= afternoon_end - - return is_morning or is_afternoon - - def update_stock_price(): - """更新实时股价数据""" - if not is_trading_time(): - return - - try: - collector = StockPriceCollector() - collector.update_latest_data() - except Exception as e: - logger.error(f"更新实时股价数据失败: {e}") - - # 添加定时任务 - scheduler.add_job( - func=update_stock_price, - trigger='interval', - minutes=60, - id='stock_price_update', - name='实时股价数据采集', - replace_existing=True - ) - - # 启动调度器 - scheduler.start() - logger.info("实时股价数据采集定时任务已初始化,将在交易时间内每60分钟执行一次") - return scheduler - + collector = StockPriceCollector() + collector.update_latest_data() except Exception as e: - logger.error(f"初始化实时股价数据采集定时任务失败: {str(e)}") - price_lock.release() - return None + logger.error(f"更新实时股价数据失败: {e}") + return jsonify({ + "status": "success" + }), 200 -def initialize_rzrq_collector_schedule(): - """初始化融资融券数据采集定时任务""" - # 创建分布式锁 - rzrq_lock = DistributedLock(redis_client, "em_rzrq_collector", expire_time=3600) # 1小时过期 - - # 尝试获取锁 - if not rzrq_lock.acquire(): - logger.info("其他服务器正在运行融资融券数据采集任务,本服务器跳过") - return None - - try: - from apscheduler.schedulers.background import BackgroundScheduler - from apscheduler.triggers.cron import CronTrigger - - # 创建定时任务调度器 - scheduler = BackgroundScheduler() - - # 添加每天下午5点执行的任务 - scheduler.add_job( - func=run_rzrq_initial_collection, - trigger=CronTrigger(hour=18, minute=40), - id='rzrq_daily_update', - name='每日更新融资融券数据', - replace_existing=True - ) - - # 启动调度器 - scheduler.start() - logger.info("融资融券数据采集定时任务已初始化,将在每天18:00执行") - return scheduler - except Exception as e: - logger.error(f"初始化融资融券数据采集定时任务失败: {str(e)}") - rzrq_lock.release() - return None -def initialize_stock_daily_collector_schedule(): - """初始化股票日线数据采集定时任务""" - # 创建分布式锁 - stock_daily_lock = DistributedLock(redis_client, "stock_daily_collector", expire_time=3600) # 1小时过期 - - # 尝试获取锁 - if not stock_daily_lock.acquire(): - logger.info("其他服务器正在运行股票日线数据采集任务,本服务器跳过") - return None - - try: - from apscheduler.schedulers.background import BackgroundScheduler - from apscheduler.triggers.cron import CronTrigger - - # 创建定时任务调度器 - scheduler = BackgroundScheduler() - - # 添加每天下午5点执行的任务 - scheduler.add_job( - func=run_stock_daily_collection, - trigger=CronTrigger(hour=15, minute=40), - id='stock_daily_update', - name='每日更新股票日线数据', - replace_existing=True - ) - - # 启动调度器 - scheduler.start() - logger.info("股票日线数据采集定时任务已初始化,将在每天15:40执行") - return scheduler - except Exception as e: - logger.error(f"初始化股票日线数据采集定时任务失败: {str(e)}") - stock_daily_lock.release() - return None - -def run_stock_daily_collection(): - """执行股票日线数据采集任务""" +@app.route('/scheduler/stockDaily/collection', methods=['GET']) +def run_stock_daily_collection1(): + """执行股票日线数据采集任务 下午3点四十开始""" try: logger.info("开始执行股票日线数据采集") - # 获取当天日期 today = datetime.now().strftime('%Y-%m-%d') - + # 定义数据库连接地址 db_url = 'mysql+pymysql://root:Chlry#$.8@192.168.18.199:3306/db_gp_cj' - - # 在新线程中执行采集任务,避免阻塞主线程 - def collection_task(): - try: - # 执行采集 - collect_stock_daily_data(db_url, today) - logger.info(f"股票日线数据采集完成,日期: {today}") - except Exception as e: - logger.error(f"执行股票日线数据采集任务失败: {str(e)}") - - # 创建并启动线程 - thread = Thread(target=collection_task) - thread.daemon = True - thread.start() - - return True + collect_stock_daily_data(db_url, today) except Exception as e: logger.error(f"启动股票日线数据采集任务失败: {str(e)}") - return False + return jsonify({ + "status": "success" + }), 200 -def run_rzrq_initial_collection(): - """执行融资融券数据更新采集""" + +@app.route('/scheduler/rzrq/collection', methods=['GET']) +def run_rzrq_initial_collection1(): + """执行融资融券数据更新采集 下午7点开始""" try: - logger.info("开始执行融资融券数据更新采集") - - # 生成任务ID - task_id = f"rzrq-{uuid.uuid4().hex[:16]}" - - # 记录任务信息 - rzrq_tasks[task_id] = { - 'status': 'running', - 'created_at': datetime.now().isoformat(), - 'type': 'initial_collection', - 'message': '开始执行融资融券数据更新采集' - } - - # 在新线程中执行采集任务 - def collection_task(): - try: - # 执行采集 - result = em_rzrq_collector.initial_data_collection() - - if result: - rzrq_tasks[task_id]['status'] = 'completed' - rzrq_tasks[task_id]['message'] = '融资融券数据更新完成' - logger.info(f"融资融券数据更新任务 {task_id} 完成") - else: - rzrq_tasks[task_id]['status'] = 'failed' - rzrq_tasks[task_id]['message'] = '融资融券数据更新失败' - logger.error(f"融资融券数据更新任务 {task_id} 失败") - except Exception as e: - rzrq_tasks[task_id]['status'] = 'failed' - rzrq_tasks[task_id]['message'] = f'执行失败: {str(e)}' - logger.error(f"执行融资融券数据更新线程中出错: {str(e)}") - - # 创建并启动线程 - thread = Thread(target=collection_task) - thread.daemon = True - thread.start() - - return task_id + # 执行采集 + em_rzrq_collector.initial_data_collection() except Exception as e: logger.error(f"启动融资融券数据更新任务失败: {str(e)}") - if 'task_id' in locals(): - rzrq_tasks[task_id]['status'] = 'failed' - rzrq_tasks[task_id]['message'] = f'启动失败: {str(e)}' - return None + return jsonify({ + "status": "success" + }), 200 + + +@app.route('/scheduler/industry/crowding', methods=['GET']) +def precalculate_industry_crowding1(): + """预计算部分行业和概念板块的拥挤度指标 晚上10点开始""" + try: + from src.valuation_analysis.industry_analysis import IndustryAnalyzer + + analyzer = IndustryAnalyzer() + # 固定行业和概念板块 + industries = ["IT设备", "消费电子", "半导体", "军工电子", "专用设备", "乘用车", "产业互联网", "元器件", "光学光电", "医疗器械", "医疗服务", "汽车零部件", "航天装备", "自动化设备"] + concepts = ["先进封装", "芯片", "消费电子概念", "机器人概念"] + + # 计算行业拥挤度 + for industry in industries: + try: + analyzer.get_industry_crowding_index(industry, use_cache=False) + except Exception as e: + logger.error(f"预计算行业 {industry} 的拥挤度指标时出错: {str(e)}") + continue + # 计算概念板块拥挤度 + for concept in concepts: + try: + analyzer.get_industry_crowding_index(concept, use_cache=False, is_concept=True) + except Exception as e: + logger.error(f"预计算概念板块 {concept} 的拥挤度指标时出错: {str(e)}") + continue + logger.info("指定行业和概念板块的拥挤度指标预计算完成") + except Exception as e: + logger.error(f"预计算行业拥挤度指标失败: {str(e)}") + return jsonify({ + "status": "success" + }), 200 + + +@app.route('/scheduler/financial/analysis', methods=['GET']) +def scheduler_financial_analysis(): + """预计算所有股票的财务分析数据 早晚各一次""" + try: + from src.valuation_analysis.financial_analysis import FinancialAnalyzer + + analyzer = FinancialAnalyzer() + analyzer.analyze_financial_data('601021.SH', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('601021.SH', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('601021.SH', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('600483.SH', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('600483.SH', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('600483.SH', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('688596.SH', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('688596.SH', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('688596.SH', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('002747.SZ', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('002747.SZ', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('002747.SZ', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('688012.SH', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('688012.SH', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('688012.SH', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('603658.SH', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('603658.SH', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('603658.SH', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('002409.SZ', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('002409.SZ', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('002409.SZ', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('600584.SH', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('600584.SH', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('600584.SH', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('603055.SH', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('603055.SH', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('603055.SH', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('601138.SH', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('601138.SH', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('601138.SH', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('603659.SH', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('603659.SH', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('603659.SH', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('688072.SH', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('688072.SH', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('688072.SH', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('688008.SH', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('688008.SH', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('688008.SH', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('300661.SZ', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('300661.SZ', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('300661.SZ', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('603986.SH', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('603986.SH', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('603986.SH', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + analyzer.analyze_financial_data('000733.SZ', current_year = '2024-12-31', previous_year = '2023-12-31', force_update=True) + analyzer.analyze_financial_data('000733.SZ', current_year = '2023-12-31', previous_year = '2022-12-31', force_update=True) + analyzer.analyze_financial_data('000733.SZ', current_year = '2022-12-31', previous_year = '2021-12-31', force_update=True) + + except Exception as e: + logger.error(f"预计算所有股票的财务分析数据失败: {str(e)}") + return jsonify({ + "status": "success" + }), 200 @app.route('/') def index(): @@ -1551,7 +1483,7 @@ def valuation_analysis(): 估值分析接口 - 获取股票的PE/PB估值分析数据 参数: - - stock_code: 股票代码(和stock_name二选一) + - stock_code: 股票代码(支持两种格式:SH601021或601021.SH) - stock_name: 股票名称(和stock_code二选一) - start_date: 开始日期(可选,默认为2018-01-01) - industry_name: 行业名称(可选) @@ -1582,15 +1514,19 @@ def valuation_analysis(): "status": "error", "message": "请求格式错误: metric参数必须为'pe'或'pb'" }), 400 - - # 如果提供了stock_name但没有stock_code,则查询stock_code - if not stock_code and stock_name: - # 这里简化处理,实际项目中应该查询数据库获取股票代码 - return jsonify({ - "status": "error", - "message": "暂不支持通过股票名称查询,请提供股票代码" - }), 400 + # 处理股票代码格式 + if stock_code: + if '.' in stock_code: # 处理601021.SH格式 + code_parts = stock_code.split('.') + if len(code_parts) == 2: + stock_code = f"{code_parts[1]}{code_parts[0]}" + else: + return jsonify({ + "status": "error", + "message": "股票代码格式错误: 应为601021.SH格式" + }), 400 + # 验证日期格式 try: datetime.strptime(start_date, '%Y-%m-%d') @@ -1866,32 +1802,40 @@ def get_concept_list(): @app.route('/api/industry/analysis', methods=['GET']) def industry_analysis(): """ - 行业分析接口 - 获取行业的PE/PB/PS估值分析数据和拥挤度指标 + 行业/概念板块分析接口 - 获取行业或概念板块的PE/PB/PS估值分析数据和拥挤度指标 参数: - - industry_name: 行业名称 + - industry_name: 行业名称(与concept_name二选一) + - concept_name: 概念板块名称(与industry_name二选一) - metric: 估值指标,可选值为'pe'、'pb'或'ps',默认为'pe' - start_date: 开始日期(可选,默认为3年前) 返回: - 用于构建ECharts图表的行业估值数据对象,包含估值指标和拥挤度 + 用于构建ECharts图表的行业/概念板块估值数据对象,包含估值指标和拥挤度 注意: - - 行业PE/PB/PS计算中已剔除负值和极端值(如PE>1000) - - 所有百分位数据都是基于行业平均值计算的 + - 行业/概念板块PE/PB/PS计算中已剔除负值和极端值(如PE>1000) + - 所有百分位数据都是基于行业/概念板块平均值计算的 - 拥挤度数据固定使用最近3年的数据,不受start_date参数影响 """ try: # 解析参数 industry_name = request.args.get('industry_name') + concept_name = request.args.get('concept_name') metric = request.args.get('metric', 'pe') start_date = request.args.get('start_date') # 检查参数 - if not industry_name: + if not industry_name and not concept_name: return jsonify({ "status": "error", - "message": "请求格式错误: 需要提供industry_name参数" + "message": "请求格式错误: 需要提供industry_name或concept_name参数" + }), 400 + + if industry_name and concept_name: + return jsonify({ + "status": "error", + "message": "请求格式错误: industry_name和concept_name不能同时提供" }), 400 if metric not in ['pe', 'pb', 'ps']: @@ -1900,8 +1844,13 @@ def industry_analysis(): "message": "请求格式错误: metric参数必须为'pe'、'pb'或'ps'" }), 400 - # 获取行业分析数据 - result = industry_analyzer.get_industry_analysis(industry_name, metric, start_date) + # 获取分析数据 + if industry_name: + result = industry_analyzer.get_industry_analysis(industry_name, metric, start_date) + title_name = industry_name + else: + result = industry_analyzer.get_concept_analysis(concept_name, metric, start_date) + title_name = concept_name if not result.get('success', False): return jsonify({ @@ -1918,11 +1867,11 @@ def industry_analysis(): # 准备图例数据 legend_data = [ - f"{industry_name}行业平均{metric_name}", - f"行业平均{metric_name}历史最小值", - f"行业平均{metric_name}历史最大值", - f"行业平均{metric_name}历史Q1", - f"行业平均{metric_name}历史Q3" + f"{title_name}平均{metric_name}", + f"平均{metric_name}历史最小值", + f"平均{metric_name}历史最大值", + f"平均{metric_name}历史Q1", + f"平均{metric_name}历史Q3" ] # 构建结果 @@ -1930,7 +1879,7 @@ def industry_analysis(): "status": "success", "data": { "title": { - "text": f"{industry_name}行业历史{metric_name}分析", + "text": f"{title_name}历史{metric_name}分析", "subtext": f"当前{metric_name}百分位: {percentiles['percentile']:.2f}%(剔除负值及极端值)" }, "tooltip": { @@ -1984,7 +1933,7 @@ def industry_analysis(): ], "series": [ { - "name": f"{industry_name}行业平均{metric_name}", + "name": f"{title_name}平均{metric_name}", "type": "line", "data": valuation_data['avg_values'], "markLine": { @@ -1998,27 +1947,27 @@ def industry_analysis(): } }, { - "name": f"行业平均{metric_name}历史最小值", + "name": f"平均{metric_name}历史最小值", "type": "line", "data": valuation_data['min_values'], "lineStyle": {"width": 1, "opacity": 0.4, "color": "#28a745"}, "areaStyle": {"opacity": 0.1, "color": "#28a745"} }, { - "name": f"行业平均{metric_name}历史最大值", + "name": f"平均{metric_name}历史最大值", "type": "line", "data": valuation_data['max_values'], "lineStyle": {"width": 1, "opacity": 0.4, "color": "#dc3545"}, "areaStyle": {"opacity": 0.1, "color": "#dc3545"} }, { - "name": f"行业平均{metric_name}历史Q1", + "name": f"平均{metric_name}历史Q1", "type": "line", "data": valuation_data['q1_values'], "lineStyle": {"width": 1, "opacity": 0.6, "color": "#28a745"} }, { - "name": f"行业平均{metric_name}历史Q3", + "name": f"平均{metric_name}历史Q3", "type": "line", "data": valuation_data['q3_values'], "lineStyle": {"width": 1, "opacity": 0.6, "color": "#dc3545"} @@ -2048,7 +1997,7 @@ def industry_analysis(): } } - # 添加拥挤度指标(如果有)- 作为独立数据,不再添加到主图表series中 + # 添加拥挤度指标(如果有) if "crowding" in result: crowding_data = result["crowding"] current_crowding = crowding_data["current"] @@ -2070,7 +2019,7 @@ def industry_analysis(): return jsonify(response) except Exception as e: - logger.error(f"行业分析请求失败: {str(e)}") + logger.error(f"行业/概念板块分析请求失败: {str(e)}") return jsonify({ "status": "error", "message": f"分析失败: {str(e)}" @@ -2091,7 +2040,7 @@ def get_northbound_data(): """获取北向资金流向数据接口 参数: - - start_time: 可选,开始时间戳(秒) + - start_time: 可选 ,开始时间戳(秒) - end_time: 可选,结束时间戳(秒) 返回北向资金流向数据 @@ -2638,69 +2587,6 @@ def get_index_data(): logger.error(f"获取指数数据失败: {str(e)}") return jsonify({"status": "error", "message": str(e)}) -def initialize_industry_crowding_schedule(): - """初始化行业拥挤度指标预计算定时任务""" - # 创建分布式锁 - industry_crowding_lock = DistributedLock(redis_client, "industry_crowding_calculator", expire_time=3600) # 1小时过期 - - # 尝试获取锁 - if not industry_crowding_lock.acquire(): - logger.info("其他服务器正在运行行业拥挤度指标预计算任务,本服务器跳过") - return None - - try: - from apscheduler.schedulers.background import BackgroundScheduler - from apscheduler.triggers.cron import CronTrigger - - # 创建定时任务调度器 - scheduler = BackgroundScheduler() - - # 添加每天晚上10点执行的任务 - scheduler.add_job( - func=precalculate_industry_crowding, - trigger=CronTrigger(hour=20, minute=30), - id='industry_crowding_precalc', - name='预计算行业拥挤度指标', - replace_existing=True - ) - - # 启动调度器 - scheduler.start() - logger.info("行业拥挤度指标预计算定时任务已初始化,将在每天20:30执行") - return scheduler - except Exception as e: - logger.error(f"初始化行业拥挤度指标预计算定时任务失败: {str(e)}") - industry_crowding_lock.release() - return None - -def precalculate_industry_crowding(): - """预计算所有行业的拥挤度指标""" - try: - from .valuation_analysis.industry_analysis import IndustryAnalyzer - - analyzer = IndustryAnalyzer() - industries = analyzer.get_all_industries() - - for industry in industries: - try: - # 调用时设置 use_cache=False,强制重新计算 - df = analyzer.get_industry_crowding_index(industry, use_cache=False) - if not df.empty: - logger.info(f"成功预计算行业 {industry} 的拥挤度指标") - else: - logger.warning(f"行业 {industry} 的拥挤度指标计算失败") - except Exception as e: - logger.error(f"预计算行业 {industry} 的拥挤度指标时出错: {str(e)}") - continue - - logger.info("所有行业的拥挤度指标预计算完成") - except Exception as e: - logger.error(f"预计算行业拥挤度指标失败: {str(e)}") - finally: - # 释放分布式锁 - industry_crowding_lock = DistributedLock(redis_client, "industry_crowding_calculator") - industry_crowding_lock.release() - @app.route('/api/financial/analysis', methods=['GET']) def financial_analysis(): """ @@ -2708,6 +2594,9 @@ def financial_analysis(): 请求参数: stock_code: 股票代码 + force_update: 是否强制更新缓存(可选,默认为false) + current_year: 当前年份,格式为'YYYY-12-31'(可选,默认为'2024-12-31') + previous_year: 上一年份,格式为'YYYY-12-31'(可选,默认为'2023-12-31') 返回: 分析结果JSON @@ -2719,11 +2608,28 @@ def financial_analysis(): 'success': False, 'message': '缺少必要参数:stock_code' }), 400 - + analyzer = FinancialAnalyzer() - result = analyzer.analyze_financial_data(stock_code) + result2024 = analyzer.analyze_financial_data(stock_code, current_year = '2024-12-31', previous_year = '2023-12-31') + result2023 = analyzer.analyze_financial_data(stock_code, current_year = '2023-12-31', previous_year = '2022-12-31') + result2022 = analyzer.analyze_financial_data(stock_code, current_year = '2022-12-31', previous_year = '2021-12-31') + + # 合并2023和2022的is_better、change_rate、change和avg_score到2024的result + def merge_year_data(target, source, year): + for block in ["financial_strength", "profitability", "growth", "value_rating", "liquidity"]: + if block in target and block in source: + # avg_score + target[block][f"avg_score_{year}"] = source[block].get("avg_score") + # indicators + if "indicators" in target[block] and "indicators" in source[block]: + for t_item, s_item in zip(target[block]["indicators"], source[block]["indicators"]): + for k in ["is_better", "change_rate", "change"]: + if k in s_item: + t_item[f"{k}_{year}"] = s_item.get(k) + merge_year_data(result2024, result2023, "2023") + merge_year_data(result2024, result2022, "2022") - return jsonify(result) + return jsonify(result2024) except Exception as e: logger.error(f"财务分析失败: {str(e)}") @@ -2797,45 +2703,178 @@ def test_mongo_structure(): 'message': f'测试MongoDB结构失败: {str(e)}' }), 500 +@app.route('/api/stock/real_time_price', methods=['GET']) +def get_real_time_price(): + """获取股票实时价格接口 + + 参数: + - stock_code: 股票代码(必填) + + 返回: + { + "status": "success", + "data": { + "stock_code": "600000", + "stock_name": "浦发银行", + "current_price": 10.5, + "change_percent": 2.5, + "change_amount": 0.25, + "volume": 1234567, + "amount": 12345678.9, + "high": 10.8, + "low": 10.2, + "open": 10.3, + "pre_close": 10.25, + "update_time": "2024-01-20 14:30:00" + } + } + """ + try: + # 获取股票代码参数 + stock_code = request.args.get('stock_code') + + # 验证参数 + if not stock_code: + return jsonify({ + "status": "error", + "message": "缺少必要参数: stock_code" + }), 400 + + # 导入股票价格采集器 + from src.valuation_analysis.stock_price_collector import StockPriceCollector + + # 创建采集器实例 + collector = StockPriceCollector() + + # 获取实时价格数据 + price_data = collector.get_stock_price_data(stock_code) + + if not price_data: + return jsonify({ + "status": "error", + "message": f"获取股票 {stock_code} 的实时价格失败" + }), 404 + + # 构建响应数据 + response_data = { + "stock_code": stock_code, + "stock_name": price_data.get('stock_name'), + "current_price": price_data.get('current_price'), + "change_percent": price_data.get('change_percent'), + "change_amount": price_data.get('change_amount'), + "volume": price_data.get('volume'), + "amount": price_data.get('amount'), + "high": price_data.get('high'), + "low": price_data.get('low'), + "open": price_data.get('open'), + "pre_close": price_data.get('pre_close'), + "update_time": price_data.get('update_time') + } + + return jsonify({ + "status": "success", + "data": response_data + }) + + except Exception as e: + logger.error(f"获取股票实时价格异常: {str(e)}") + return jsonify({ + "status": "error", + "message": f"服务器错误: {str(e)}" + }), 500 + +@app.route('/bigscreen') +def bigscreen_page(): + """渲染大屏展示页面""" + return render_template('bigscreen.html') + +@app.route('/api/bigscreen_data', methods=['GET']) +def bigscreen_data(): + """聚合大屏所需的12张图数据,便于前端一次性加载""" + try: + # 资金流向 + north = hsgt_monitor.fetch_northbound_data() + south = hsgt_monitor.fetch_southbound_data() + # 融资融券 + rzrq = em_rzrq_collector.get_chart_data(limit_days=90) + # 恐贪指数 + fear_greed = fear_greed_manager.get_index_data(limit=180) + # 概念板块 + concepts = [ + ("先进封装", "xjfz"), + ("芯片", "xp"), + ("消费电子概念", "xfdz"), + ("机器人概念", "jqr") + ] + concept_data = {} + for cname, key in concepts: + res = industry_analyzer.get_concept_analysis(cname, 'pe', None) + if res.get('success'): + # PE主线 + pe = { + 'dates': res['valuation']['dates'], + 'values': res['valuation']['avg_values'] + } + # 拥挤度 + crowding = res.get('crowding', {}) + crowding_obj = { + 'dates': crowding.get('dates', []), + 'values': crowding.get('percentiles', []) + } if crowding else {'dates': [], 'values': []} + concept_data[key] = {'pe': pe, 'crowding': crowding_obj} + else: + concept_data[key] = {'pe': {'dates': [], 'values': []}, 'crowding': {'dates': [], 'values': []}} + return jsonify({ + 'status': 'success', + 'northbound': { + 'dates': north.get('times', []), + 'values': north.get('data', {}).get('total', []) + } if north.get('success') else {'dates': [], 'values': []}, + 'southbound': { + 'dates': south.get('times', []), + 'values': south.get('data', {}).get('total', []) + } if south.get('success') else {'dates': [], 'values': []}, + 'rzrq': { + 'dates': rzrq.get('dates', []), + 'values': rzrq.get('series', [{}])[0].get('data', []) + } if rzrq.get('success') and rzrq.get('series') else {'dates': [], 'values': []}, + 'fear_greed': { + 'dates': fear_greed.get('dates', []), + 'values': fear_greed.get('values', []) + } if fear_greed.get('success') else {'dates': [], 'values': []}, + 'concepts': concept_data + }) + except Exception as e: + logger.error(f"大屏数据聚合失败: {str(e)}") + return jsonify({'status': 'error', 'message': str(e)}) + +@app.route('/api/pep_stock_info_by_shortname', methods=['GET']) +def get_pep_stock_info_by_shortname(): + """根据股票简称查询pep_stock_info集合中的全部字段""" + short_name = request.args.get('short_name') + if not short_name: + return jsonify({'success': False, 'message': '缺少必要参数: short_name'}), 400 + try: + analyzer = FinancialAnalyzer() + result = analyzer.get_pep_stock_info_by_shortname(short_name) + return jsonify(result) + except Exception as e: + return jsonify({'success': False, 'message': f'服务器错误: {str(e)}'}), 500 + +@app.route('/api/pep_stock_info_by_code', methods=['GET']) +def get_pep_stock_info_by_code(): + """根据股票简称查询pep_stock_info集合中的全部字段""" + short_code = request.args.get('code') + if not short_code: + return jsonify({'success': False, 'message': '缺少必要参数: short_code'}), 400 + try: + analyzer = FinancialAnalyzer() + result = analyzer.get_pep_stock_info_by_code(short_code) + return jsonify(result) + except Exception as e: + return jsonify({'success': False, 'message': f'服务器错误: {str(e)}'}), 500 + if __name__ == '__main__': - """ - # 手动释放锁的方法(需要时取消注释) - # 创建锁实例 - rzrq_lock = DistributedLock(redis_client, "em_rzrq_collector") - stock_daily_lock = DistributedLock(redis_client, "stock_daily_collector") - industry_crowding_lock = DistributedLock(redis_client, "industry_crowding_calculator") - # 强制释放锁 - print("开始释放锁...") - - if rzrq_lock.release(): - print("成功释放融资融券采集器锁") - else: - print("融资融券采集器锁释放失败或不存在") - - if stock_daily_lock.release(): - print("成功释放股票日线采集器锁") - else: - print("股票日线采集器锁释放失败或不存在") - if industry_crowding_lock.release(): - print("成功释放行业拥挤度锁") - else: - print("行业拥挤度锁释放失败或不存在") - - print("锁释放操作完成") - """ - - # 初始化融资融券数据采集定时任务 - rzrq_scheduler = initialize_rzrq_collector_schedule() - - # 初始化股票日线数据采集定时任务 - stock_daily_scheduler = initialize_stock_daily_collector_schedule() - - # 初始化行业拥挤度指标预计算定时任务 - industry_crowding_scheduler = initialize_industry_crowding_schedule() - - # 初始化实时股价数据采集定时任务 - initialize_stock_price_schedule() - # 启动Web服务器 app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/src/fundamentals_llm/chat_bot.py b/src/fundamentals_llm/chat_bot.py index 2ced445..3c87086 100644 --- a/src/fundamentals_llm/chat_bot.py +++ b/src/fundamentals_llm/chat_bot.py @@ -132,7 +132,6 @@ class ChatBot: summary = ref.get('summary', '') url = ref.get('url', '') publish_time = ref.get('publish_time', '') - formatted_ref = [] if title: formatted_ref.append(f"标题:{title}") @@ -150,12 +149,12 @@ class ChatBot: logger.error(f"格式化参考资料时出错: {str(e)}") return str(ref) - def chat(self, user_input: str, temperature: float = 1.0, top_p: float = 0.7, max_tokens: int = 4096, frequency_penalty: float = 0.0) -> Dict[str, Any]: + def chat(self, user_input: str, temperature: float = 0.7, top_p: float = 0.7, max_tokens: int = 4096, frequency_penalty: float = 0.0) -> Dict[str, Any]: """与AI进行对话 Args: user_input: 用户输入的问题 - temperature: 控制输出的随机性,范围0-2,默认1.0 + temperature: 控制输出的随机性,范围0-2,默认0.7 top_p: 控制输出的多样性,范围0-1,默认0.7 max_tokens: 控制输出的最大长度,默认4096 frequency_penalty: 控制重复惩罚,范围-2到2,默认0.0 diff --git a/src/fundamentals_llm/fundamental_analysis.py b/src/fundamentals_llm/fundamental_analysis.py index 495954a..c020b66 100644 --- a/src/fundamentals_llm/fundamental_analysis.py +++ b/src/fundamentals_llm/fundamental_analysis.py @@ -1464,6 +1464,11 @@ class FundamentalAnalyzer: if result: all_results[dimension] = result.ai_response + # 获取当前时间 + current_time = datetime.now() + current_year = current_time.year + current_month = current_time.month + # 构建提示词 prompt = f"""请根据以下{stock_name}({stock_code})的各个维度分析结果,生成最终的投资建议,要求输出控制在300字以内,请严格按照以下格式输出: @@ -1477,12 +1482,7 @@ class FundamentalAnalyzer: - 长期持有:公司具备长期稳定的盈利能力、行业地位稳固、长期成长性好 - 不建议投资:存在明显风险因素、基本面恶化、估值过高、行业前景不佳或者存在退市风险 - 请注意: - 1. 请完全基于提供的分析结果中的最新数据进行分析,不要使用任何历史数据或过时信息 - 2. 如果分析结果中包含2024年或2025年的数据,请优先使用这些最新数据 - 3. 避免使用"2023年"等历史时间点的数据,除非分析结果中明确提供了这些数据 - 4. 重点关注公司最新的业务发展、财务表现和市场定位 - 5. 在分析行业环境时,请使用最新的行业数据和竞争格局信息 + 请注意:当前时间是{current_year}年{current_month}月,请基于这个时间点结合当前,分析未来的投资建议。 请提供专业、客观的分析,突出关键信息,避免冗长描述。重点关注投资价值和风险。在输出投资建议时,请明确指出是短期持有、中期持有、长期持有还是不建议投资。 diff --git a/src/scripts/config.py b/src/scripts/config.py index eea4f67..7a576c1 100644 --- a/src/scripts/config.py +++ b/src/scripts/config.py @@ -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; 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', + 'Cookie': 'cookiesu=811743062689927; device_id=33fa3c7fca4a65f8f4354e10ed6b7470; smidV2=20250327160437f244626e8b47ca2a7992f30f389e4e790074ae48656a22f10; HMACCOUNT=8B64A2E3C307C8C0; s=c611ttmqlj; xq_is_login=1; u=8493411634; bid=4065a77ca57a69c83405d6e591ab5449_m8r2nhs8; Hm_lvt_1db88642e346389874251b5a1eded6e3=1746410725; __utma=1.434320573.1747189698.1747189698.1747189698.1; __utmc=1; __utmz=1.1747189698.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); snbim_minify=true; acw_tc=0a27a9dd17489230816243798e0070441d5e7160c0ed179607143a953db903; xq_a_token=ef79e6da376751a4bf6c1538103e9894d44473e1; xqat=ef79e6da376751a4bf6c1538103e9894d44473e1; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjg0OTM0MTE2MzQsImlzcyI6InVjIiwiZXhwIjoxNzUxNTE1MDgxLCJjdG0iOjE3NDg5MjMwODE2NDQsImNpZCI6ImQ5ZDBuNEFadXAifQ.gQrIt4VI73JLUFGVSTKpXidhFIMwlusBKyrzYwClwCBszXCooQY3WnFqlbXqSX3SwnMapuveOFUM5sGIOoZ8oDF8cZYs3HDz5vezR-2nes9gfZr2nZcUfZzNRJ299wlX3Zis5NbnzNlfnisUhv9GUfEZjQ_Rs37B4qRbQZVC2kdN1Z0xB8j1MplSTOsYj4IliQntuaTo-8SBh-4zz5244dnF85xREBVxtFzzCtHUhn9B-mzxE81_42nwrDscvow-4_jtlJXlqbehiAFxld-dCWDXwmCju9lRWu_WzdoQe19n-c6jhCZZ1pU1JGsYyhIAsd1gV064jQ6FxfN38so1Eg; xq_r_token=30a80318ebcabffbe194e7deecb108b665e8c894; is_overseas=0; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1748923088; .thumbcache_f24b8bbe5a5934237bbc0eda20c1b6e7=b+jlfRtg2lC80dGHk9izZ9Od1QBbaKrdx1aAMbruXo2ULyhkygsXnhJoa7lOWNgnQAphRKw3864D5K+U2pTL5g%3D%3D; ssxmod_itna=eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q0shqDyji2YsBGdTYKYUxGXxN4xiNDAc40iDC3WLPeUpx5h5o5Gmxt3qU6P5b48r89Y4sKs=BkpxKFTG4SQW4odeGLDY=DCTKKSMiD4b3Dt4DIDAYDDxDWm4DLDYoDY3uexGPo2mTNpm2bD0YDzqDgD7jbmeDEDG3D0bbetGDo1Q4DGqDSWZHTxD3Dffb4DDN4zIG0GmDDbrR=qmcbC=7O9Wtox0tWDBL5YvysdVC441TXpw8w7WaaxBQD7d9Q5na7fCW13rWkYY0Yeoe7hx+BxYrKch4SbKOAYY7hq7hR0D3E5YD5QADW0D/hQ7Emh07hiY7xdUginMzSTblushiee2YKbK5nYO0t3Ede7d46DqEQMA557QODdNG4WG+slx5bhWiDD; ssxmod_itna2=eqGxBDnGKYuxcD4kDRgxYq7ueYKS8DBP01Dp2xQyP08D60DB40Q0shqDyji2YsBGdTYKY4xDfiOYiiBq4YDj44KWGfmoD/8okGxAeG/0Dt6Q7D6cGudn3qfM5QntBLc5pp/FFY4hly50hUr5qB2v45io/FQi4eD', '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', @@ -79,7 +79,7 @@ MODEL_CONFIGS = { "base_url": "http://192.168.16.174:1234/v1/", "api_key": "none", "models": { - "glm-4": "glm-4-32b-0414-abliterated", + "GLM": "glm-4-32b-0414-abliterated", "qwen3": "qwen3-235b-a22b", } }, diff --git a/src/static/js/bigscreen.js b/src/static/js/bigscreen.js new file mode 100644 index 0000000..a05a725 --- /dev/null +++ b/src/static/js/bigscreen.js @@ -0,0 +1,269 @@ +$(function() { + // 1. 资金流向/融资融券/恐贪指数 + function fetchNorth() { + $.get('/api/hsgt/northbound', function(res) { + if(res.status === 'success') { + renderNorthChart(res.data); + } else { + $('#northChart').html('
=a)}}for(var h=this.__startIndex;h t.unconstrainedWidth?null:d:null;i.setStyle("width",f)}var g=i.getBoundingRect();o.width=g.width;var y=(i.style.margin||0)+2.1;o.height=g.height+y,o.y-=(o.height-c)/2}}}function _M(t){return"center"===t.position}function bM(t){var e,n,i=t.getData(),r=[],o=!1,a=(t.get("minShowLabelAngle")||0)*vM,s=i.getLayout("viewRect"),l=i.getLayout("r"),u=s.width,h=s.x,c=s.y,p=s.height;function d(t){t.ignore=!0}i.each((function(t){var s=i.getItemGraphicEl(t),c=s.shape,p=s.getTextContent(),f=s.getTextGuideLine(),g=i.getItemModel(t),y=g.getModel("label"),v=y.get("position")||g.get(["emphasis","label","position"]),m=y.get("distanceToLabelLine"),x=y.get("alignTo"),_=Ur(y.get("edgeDistance"),u),b=y.get("bleedMargin"),w=g.getModel("labelLine"),S=w.get("length");S=Ur(S,u);var M=w.get("length2");if(M=Ur(M,u),Math.abs(c.endAngle-c.startAngle)0?"right":"left":k>0?"left":"right"}var B=Math.PI,F=0,G=y.get("rotate");if(j(G))F=G*(B/180);else if("center"===v)F=0;else if("radial"===G||!0===G){F=k<0?-A+B:-A}else if("tangential"===G&&"outside"!==v&&"outer"!==v){var W=Math.atan2(k,L);W<0&&(W=2*B+W),L>0&&(W=B+W),F=W-B}if(o=!!F,p.x=I,p.y=T,p.rotation=F,p.setStyle({verticalAlign:"middle"}),P){p.setStyle({align:D});var H=p.states.select;H&&(H.x+=p.x,H.y+=p.y)}else{var Y=p.getBoundingRect().clone();Y.applyTransform(p.getComputedTransform());var X=(p.style.margin||0)+2.1;Y.y-=X/2,Y.height+=X,r.push({label:p,labelLine:f,position:v,len:S,len2:M,minTurnAngle:w.get("minTurnAngle"),maxSurfaceAngle:w.get("maxSurfaceAngle"),surfaceNormal:new De(k,L),linePoints:C,textAlign:D,labelDistance:m,labelAlignTo:x,edgeDistance:_,bleedMargin:b,rect:Y,unconstrainedWidth:Y.width,labelStyleWidth:p.style.width})}s.setTextConfig({inside:P})}})),!o&&t.get("avoidLabelOverlap")&&function(t,e,n,i,r,o,a,s){for(var l=[],u=[],h=Number.MAX_VALUE,c=-Number.MAX_VALUE,p=0;p