Daily Stock Analysis for OpenClaw 基于 LLM 的 A/H/美股智能分析 Skill,提供技术面分析和 AI 决策建议。 功能特性 1. 多市场支持 - A股、港股、美股 2. 技术面分析 - MA5/10/20、MACD、RSI、乖离率 3. 趋势交易 - 多头排列判断、买入信号评分 4. AI 决策 - DeepSeek/Gemini/OpenAI 深度分析 5. 数据源集成 - 可选 market-data skill 快速使用 配置 1. 复制配置模板: 2. 填入 DeepSeek API Key: 3. (可选) 启用 market-data skill 数据源: 返回数据 项目信息 - 开源协议 : MIT - 项目地址 : https://github.com/yourusername/stock-daily-analysis - 原项目 : https://github.com/ZhuLinsen/daily stock analysis --- ⚠️ 免责声明 : 本项目仅供学习研究,不构成投资建议。股市有风险,投资需谨慎。

, code))\n\n\ndef _is_hk_code(stock_code: str) -> bool:\n \"\"\"判断是否为港股代码(5位数字)\"\"\"\n code = stock_code.lower()\n if code.startswith('hk'):\n numeric_part = code[2:]\n return numeric_part.isdigit() and 1 \u003c= len(numeric_part) \u003c= 5\n return code.isdigit() and len(code) == 5\n\n\ndef _is_etf_code(stock_code: str) -> bool:\n \"\"\"判断是否为 ETF 代码\"\"\"\n etf_prefixes = ('51', '52', '56', '58', '15', '16', '18')\n return stock_code.startswith(etf_prefixes) and len(stock_code) == 6\n\n\ndef normalize_code(stock_code: str) -> tuple:\n \"\"\"\n 标准化股票代码\n \n Returns:\n tuple: (market, code)\n - market: 'a', 'hk', 'us'\n - code: 标准化后的代码\n \"\"\"\n code = stock_code.strip()\n \n if _is_us_code(code):\n return 'us', code.upper()\n \n if _is_hk_code(code):\n # 去除 hk 前缀,返回5位数字\n if code.lower().startswith('hk'):\n code = code[2:]\n return 'hk', code.zfill(5)\n \n # A股默认处理\n return 'a', code\n\n\ndef get_daily_data(stock_code: str, days: int = 60) -> Optional[pd.DataFrame]:\n \"\"\"\n 获取股票日线数据\n \n Args:\n stock_code: 股票代码\n days: 获取天数\n \n Returns:\n DataFrame 包含 OHLCV 数据,失败返回 None\n \"\"\"\n market, code = normalize_code(stock_code)\n \n end_date = datetime.now()\n start_date = end_date - timedelta(days=days * 2)\n \n try:\n if market == 'us':\n return _fetch_us_data(code, start_date, end_date)\n elif market == 'hk':\n return _fetch_hk_data(code, start_date, end_date)\n else:\n return _fetch_a_stock_data(code, start_date, end_date)\n except Exception as e:\n logger.error(f\"获取 {stock_code} 数据失败: {e}\")\n return None\n\n\ndef _fetch_a_stock_data(stock_code: str, start_date: datetime, end_date: datetime) -> pd.DataFrame:\n \"\"\"获取 A 股数据\"\"\"\n start_str = start_date.strftime('%Y%m%d')\n end_str = end_date.strftime('%Y%m%d')\n \n if _is_etf_code(stock_code):\n df = ak.fund_etf_hist_em(\n symbol=stock_code,\n period=\"daily\",\n start_date=start_str,\n end_date=end_str,\n adjust=\"qfq\"\n )\n else:\n df = ak.stock_zh_a_hist(\n symbol=stock_code,\n period=\"daily\",\n start_date=start_str,\n end_date=end_str,\n adjust=\"qfq\"\n )\n \n return _standardize_columns(df)\n\n\ndef _fetch_hk_data(stock_code: str, start_date: datetime, end_date: datetime) -> pd.DataFrame:\n \"\"\"获取港股数据\"\"\"\n start_str = start_date.strftime('%Y%m%d')\n end_str = end_date.strftime('%Y%m%d')\n \n df = ak.stock_hk_hist(\n symbol=stock_code,\n period=\"daily\",\n start_date=start_str,\n end_date=end_str,\n adjust=\"qfq\"\n )\n \n return _standardize_columns(df)\n\n\ndef _fetch_us_data(stock_code: str, start_date: datetime, end_date: datetime) -> pd.DataFrame:\n \"\"\"获取美股数据\"\"\"\n df = ak.stock_us_daily(symbol=stock_code, adjust=\"qfq\")\n \n if df is None or df.empty:\n return pd.DataFrame()\n \n # 按日期过滤\n df['date'] = pd.to_datetime(df['date'])\n df = df[(df['date'] >= start_date) & (df['date'] \u003c= end_date)]\n \n # 标准化列名\n df = df.rename(columns={\n 'date': '日期',\n 'open': '开盘',\n 'high': '最高',\n 'low': '最低',\n 'close': '收盘',\n 'volume': '成交量'\n })\n \n # 计算涨跌幅和成交额\n if '收盘' in df.columns:\n df['涨跌幅'] = df['收盘'].pct_change() * 100\n df['涨跌幅'] = df['涨跌幅'].fillna(0)\n \n if '成交量' in df.columns and '收盘' in df.columns:\n df['成交额'] = df['成交量'] * df['收盘']\n \n return _standardize_columns(df)\n\n\ndef _standardize_columns(df: pd.DataFrame) -> pd.DataFrame:\n \"\"\"标准化 DataFrame 列名\"\"\"\n if df is None or df.empty:\n return pd.DataFrame()\n \n column_mapping = {\n '日期': 'date',\n '开盘': 'open',\n '收盘': 'close',\n '最高': 'high',\n '最低': 'low',\n '成交量': 'volume',\n '成交额': 'amount',\n '涨跌幅': 'pct_chg',\n }\n \n df = df.rename(columns=column_mapping)\n \n # 确保日期格式正确\n if 'date' in df.columns:\n df['date'] = pd.to_datetime(df['date'])\n \n # 数值转换\n for col in ['open', 'high', 'low', 'close', 'volume', 'amount', 'pct_chg']:\n if col in df.columns:\n df[col] = pd.to_numeric(df[col], errors='coerce')\n \n # 去除空值行\n df = df.dropna(subset=['close', 'volume'])\n \n # 按日期排序\n df = df.sort_values('date', ascending=True).reset_index(drop=True)\n \n return df\n\n\ndef get_realtime_quote(stock_code: str) -> Optional[StockQuote]:\n \"\"\"\n 获取实时行情\n \n Args:\n stock_code: 股票代码\n \n Returns:\n StockQuote 对象,失败返回 None\n \"\"\"\n market, code = normalize_code(stock_code)\n \n try:\n if market == 'us':\n return None # 美股暂不支持实时行情\n elif market == 'hk':\n return _get_hk_realtime_quote(code)\n else:\n return _get_a_stock_realtime_quote(code)\n except Exception as e:\n logger.warning(f\"获取实时行情失败 {stock_code}: {e}\")\n return None\n\n\ndef _get_a_stock_realtime_quote(stock_code: str) -> Optional[StockQuote]:\n \"\"\"获取 A 股实时行情\"\"\"\n try:\n df = ak.stock_zh_a_spot_em()\n row = df[df['代码'] == stock_code]\n \n if row.empty:\n return None\n \n row = row.iloc[0]\n \n return StockQuote(\n code=stock_code,\n name=str(row.get('名称', '')),\n price=float(row.get('最新价', 0)) if pd.notna(row.get('最新价')) else 0,\n change_pct=float(row.get('涨跌幅', 0)) if pd.notna(row.get('涨跌幅')) else 0,\n change_amount=float(row.get('涨跌额', 0)) if pd.notna(row.get('涨跌额')) else 0,\n volume=int(row.get('成交量', 0)) if pd.notna(row.get('成交量')) else 0,\n amount=float(row.get('成交额', 0)) if pd.notna(row.get('成交额')) else 0,\n open_price=float(row.get('今开', 0)) if pd.notna(row.get('今开')) else 0,\n high=float(row.get('最高', 0)) if pd.notna(row.get('最高')) else 0,\n low=float(row.get('最低', 0)) if pd.notna(row.get('最低')) else 0,\n volume_ratio=float(row.get('量比', 0)) if pd.notna(row.get('量比')) else None,\n turnover_rate=float(row.get('换手率', 0)) if pd.notna(row.get('换手率')) else None,\n pe_ratio=float(row.get('市盈率-动态', 0)) if pd.notna(row.get('市盈率-动态')) else None,\n pb_ratio=float(row.get('市净率', 0)) if pd.notna(row.get('市净率')) else None,\n total_mv=float(row.get('总市值', 0)) if pd.notna(row.get('总市值')) else None,\n circ_mv=float(row.get('流通市值', 0)) if pd.notna(row.get('流通市值')) else None,\n )\n except Exception as e:\n logger.warning(f\"获取 A 股实时行情失败: {e}\")\n return None\n\n\ndef _get_hk_realtime_quote(stock_code: str) -> Optional[StockQuote]:\n \"\"\"获取港股实时行情\"\"\"\n try:\n df = ak.stock_hk_spot_em()\n row = df[df['代码'] == stock_code]\n \n if row.empty:\n return None\n \n row = row.iloc[0]\n \n return StockQuote(\n code=stock_code,\n name=str(row.get('名称', '')),\n price=float(row.get('最新价', 0)) if pd.notna(row.get('最新价')) else 0,\n change_pct=float(row.get('涨跌幅', 0)) if pd.notna(row.get('涨跌幅')) else 0,\n change_amount=float(row.get('涨跌额', 0)) if pd.notna(row.get('涨跌额')) else 0,\n volume=int(row.get('成交量', 0)) if pd.notna(row.get('成交量')) else 0,\n amount=float(row.get('成交额', 0)) if pd.notna(row.get('成交额')) else 0,\n volume_ratio=float(row.get('量比', 0)) if pd.notna(row.get('量比')) else None,\n turnover_rate=float(row.get('换手率', 0)) if pd.notna(row.get('换手率')) else None,\n pe_ratio=float(row.get('市盈率', 0)) if pd.notna(row.get('市盈率')) else None,\n pb_ratio=float(row.get('市净率', 0)) if pd.notna(row.get('市净率')) else None,\n )\n except Exception as e:\n logger.warning(f\"获取港股实时行情失败: {e}\")\n return None\n\n\ndef get_chip_distribution(stock_code: str) -> Optional[ChipDistribution]:\n \"\"\"\n 获取筹码分布数据(仅 A 股)\n \n Args:\n stock_code: 股票代码\n \n Returns:\n ChipDistribution 对象,失败返回 None\n \"\"\"\n market, code = normalize_code(stock_code)\n \n if market != 'a' or _is_etf_code(code):\n return None\n \n try:\n df = ak.stock_cyq_em(symbol=code)\n \n if df is None or df.empty:\n return None\n \n latest = df.iloc[-1]\n \n return ChipDistribution(\n profit_ratio=float(latest.get('获利比例', 0)) if pd.notna(latest.get('获利比例')) else 0,\n avg_cost=float(latest.get('平均成本', 0)) if pd.notna(latest.get('平均成本')) else 0,\n concentration_90=float(latest.get('90%集中度', 0)) if pd.notna(latest.get('90%集中度')) else 0,\n concentration_70=float(latest.get('70%集中度', 0)) if pd.notna(latest.get('70%集中度')) else 0,\n )\n except Exception as e:\n logger.warning(f\"获取筹码分布失败 {stock_code}: {e}\")\n return None\n\n\ndef get_stock_name(stock_code: str) -> str:\n \"\"\"获取股票名称\"\"\"\n quote = get_realtime_quote(stock_code)\n if quote and quote.name:\n return quote.name\n \n # 默认返回代码\n return stock_code\n","content_type":"text/x-python; charset=utf-8","language":"python","size":11519,"content_sha256":"21d8c24b4771e113d2287454ba493dae818312ee0132e5c781caaa3c893a7b9c"},{"filename":"scripts/market_data_bridge.py","content":"# -*- coding: utf-8 -*-\n\"\"\"\nMarket Data Skill 集成模块\n使用已有的 market-data skill 获取行情数据\n\"\"\"\n\nimport json\nimport logging\nimport subprocess\nimport pandas as pd\nfrom pathlib import Path\nfrom typing import Optional, Dict, Any, List\n\nlogger = logging.getLogger(__name__)\n\n\nclass MarketDataFetcher:\n \"\"\"\n 集成 market-data skill 的数据获取器\n \"\"\"\n \n def __init__(self, skill_path: Optional[str] = None):\n if skill_path is None:\n # 默认相对路径\n skill_path = Path(__file__).parent.parent.parent / \"market-data\"\n else:\n skill_path = Path(skill_path)\n \n self.skill_path = skill_path\n self.script_path = skill_path / \"scripts\" / \"quote_cn_pro.py\"\n \n def is_available(self) -> bool:\n \"\"\"检查 market-data skill 是否可用\"\"\"\n return self.script_path.exists()\n \n def get_kline_data(self, code: str, count: int = 60, period: str = \"day\") -> Optional[pd.DataFrame]:\n \"\"\"\n 获取 K 线数据\n \n Args:\n code: 股票代码\n count: 数据条数\n period: 周期 (day/week/month/5min)\n \n Returns:\n DataFrame 包含 OHLCV 数据\n \"\"\"\n if not self.is_available():\n logger.warning(f\"market-data skill 不可用: {self.script_path}\")\n return None\n \n try:\n cmd = [\n \"python3\", str(self.script_path),\n code,\n \"--kline\",\n \"--count\", str(count),\n \"--period\", period\n ]\n \n result = subprocess.run(\n cmd,\n capture_output=True,\n text=True,\n timeout=30,\n cwd=str(self.skill_path)\n )\n \n if result.returncode != 0:\n logger.warning(f\"获取 K 线数据失败: {result.stderr}\")\n return None\n \n # 解析输出\n return self._parse_kline_output(result.stdout)\n \n except Exception as e:\n logger.error(f\"调用 market-data skill 失败: {e}\")\n return None\n \n def _parse_kline_output(self, output: str) -> Optional[pd.DataFrame]:\n \"\"\"解析 K 线输出\"\"\"\n lines = output.strip().split('\\n')\n data = []\n \n for line in lines:\n line = line.strip()\n if not line or line.startswith('📈') or line.startswith('📡') or line.startswith('='):\n continue\n \n # 尝试解析 K 线数据行\n # 格式: 日期 开盘 收盘 最高 最低 成交量 成交额 ...\n parts = line.split()\n if len(parts) >= 6:\n try:\n data.append({\n 'date': parts[0],\n 'open': float(parts[1]),\n 'close': float(parts[2]),\n 'high': float(parts[3]),\n 'low': float(parts[4]),\n 'volume': float(parts[5]),\n })\n except (ValueError, IndexError):\n continue\n \n if data:\n df = pd.DataFrame(data)\n df['date'] = pd.to_datetime(df['date'])\n return df\n \n return None\n \n def get_daily_summary(self, code: str) -> Optional[Dict[str, Any]]:\n \"\"\"\n 获取日线总结\n \n Returns:\n 包含价格、均线等数据的字典\n \"\"\"\n if not self.is_available():\n return None\n \n try:\n cmd = [\n \"python3\", str(self.script_path),\n code,\n \"--daily\"\n ]\n \n result = subprocess.run(\n cmd,\n capture_output=True,\n text=True,\n timeout=30,\n cwd=str(self.skill_path)\n )\n \n if result.returncode != 0:\n return None\n \n return self._parse_daily_output(result.stdout)\n \n except Exception as e:\n logger.error(f\"获取日线数据失败: {e}\")\n return None\n \n def _parse_daily_output(self, output: str) -> Dict[str, Any]:\n \"\"\"解析日线输出\"\"\"\n result = {}\n lines = output.split('\\n')\n \n for line in lines:\n line = line.strip()\n \n # 提取当前价格\n if '当前价格:' in line:\n try:\n result['price'] = float(line.split(':')[1].split()[0])\n except:\n pass\n \n # 提取涨跌幅\n elif '涨跌:' in line:\n try:\n parts = line.split(':')[1].strip()\n if '+' in parts:\n result['change_pct'] = float(parts.split('%')[0].replace('+', ''))\n elif '-' in parts:\n result['change_pct'] = -float(parts.split('%')[0].replace('-', ''))\n except:\n pass\n \n # 提取均线\n elif 'MA5:' in line:\n try:\n result['ma5'] = float(line.split(':')[1].strip())\n except:\n pass\n elif 'MA10:' in line:\n try:\n result['ma10'] = float(line.split(':')[1].strip())\n except:\n pass\n elif 'MA20:' in line:\n try:\n result['ma20'] = float(line.split(':')[1].strip())\n except:\n pass\n \n return result\n\n\ndef create_data_fetcher(config: Optional[Dict] = None) -> Any:\n \"\"\"\n 工厂函数:创建合适的数据获取器\n \n 优先使用 market-data skill,如果不可用则回退到 akshare\n \"\"\"\n if config is None:\n config = {}\n \n use_market_data = config.get('data', {}).get('use_market_data_skill', True)\n \n if use_market_data:\n skill_path = config.get('data', {}).get('market_data_skill_path', '../market-data')\n fetcher = MarketDataFetcher(skill_path)\n \n if fetcher.is_available():\n logger.info(\"使用 market-data skill 获取数据\")\n return fetcher\n else:\n logger.warning(f\"market-data skill 不可用,回退到 akshare\")\n \n # 回退到 akshare\n from scripts.data_fetcher import get_daily_data, get_realtime_quote\n return None # 使用 None 表示使用默认的 akshare 方式\n","content_type":"text/x-python; charset=utf-8","language":"python","size":6755,"content_sha256":"b96824d58855957cfa02435407347b47b4bb69343f3ccd472d68d03f0025b30f"},{"filename":"scripts/notifier.py","content":"# -*- coding: utf-8 -*-\n\"\"\"\n通知/输出处理模块\n负责格式化分析报告并输出结果\n\"\"\"\n\nimport logging\nfrom typing import Dict, Any, List, Optional\nfrom dataclasses import dataclass\n\nlogger = logging.getLogger(__name__)\n\n\n@dataclass\nclass AnalysisReport:\n \"\"\"分析报告数据结构\"\"\"\n code: str\n name: str\n sentiment_score: int\n trend_prediction: str\n operation_advice: str\n decision_type: str\n confidence_level: str\n technical_summary: Dict[str, Any]\n ai_analysis: Optional[str] = None\n risk_warning: str = \"\"\n buy_reason: str = \"\"\n support_levels: List[float] = None\n resistance_levels: List[float] = None\n \n def __post_init__(self):\n if self.support_levels is None:\n self.support_levels = []\n if self.resistance_levels is None:\n self.resistance_levels = []\n\n\ndef format_analysis_report(report: AnalysisReport) -> str:\n \"\"\"\n 格式化分析报告为文本\n \n Args:\n report: 分析报告数据\n \n Returns:\n 格式化后的报告文本\n \"\"\"\n lines = [\n f\"{'='*50}\",\n f\"📊 {report.name} ({report.code}) 分析报告\",\n f\"{'='*50}\",\n \"\",\n f\"【核心结论】\",\n f\" 操作建议: {report.operation_advice}\",\n f\" 趋势预测: {report.trend_prediction}\",\n f\" 情绪评分: {report.sentiment_score}/100\",\n f\" 置信度: {report.confidence_level}\",\n \"\",\n f\"【技术面分析】\",\n ]\n \n # 技术指标\n tech = report.technical_summary\n if 'current_price' in tech:\n lines.append(f\" 当前价格: {tech.get('current_price', 'N/A')}\")\n \n if 'ma5' in tech:\n lines.append(f\" MA5: {tech.get('ma5', 'N/A'):.2f} (乖离率: {tech.get('bias_ma5', 0):+.2f}%)\")\n if 'ma10' in tech:\n lines.append(f\" MA10: {tech.get('ma10', 'N/A'):.2f} (乖离率: {tech.get('bias_ma10', 0):+.2f}%)\")\n if 'ma20' in tech:\n lines.append(f\" MA20: {tech.get('ma20', 'N/A'):.2f}\")\n \n if 'trend_status' in tech:\n lines.append(f\" 趋势状态: {tech.get('trend_status', 'N/A')}\")\n \n if 'volume_status' in tech:\n lines.append(f\" 量能状态: {tech.get('volume_status', 'N/A')}\")\n \n if 'macd_status' in tech:\n lines.append(f\" MACD: {tech.get('macd_status', 'N/A')}\")\n \n if 'rsi_status' in tech:\n lines.append(f\" RSI: {tech.get('rsi_status', 'N/A')}\")\n \n lines.append(\"\")\n \n # 支撑压力位\n if report.support_levels:\n lines.append(f\"【支撑位】\")\n for level in report.support_levels[:3]:\n lines.append(f\" - {level:.2f}\")\n lines.append(\"\")\n \n if report.resistance_levels:\n lines.append(f\"【压力位】\")\n for level in report.resistance_levels[:3]:\n lines.append(f\" - {level:.2f}\")\n lines.append(\"\")\n \n # 买入理由\n if report.buy_reason:\n lines.append(f\"【买入理由】\")\n lines.append(f\" {report.buy_reason}\")\n lines.append(\"\")\n \n # 风险警告\n if report.risk_warning:\n lines.append(f\"【风险提示】\")\n lines.append(f\" {report.risk_warning}\")\n lines.append(\"\")\n \n # AI 分析\n if report.ai_analysis:\n lines.append(f\"【AI 分析】\")\n lines.append(f\" {report.ai_analysis}\")\n lines.append(\"\")\n \n lines.append(f\"{'='*50}\")\n \n return \"\\n\".join(lines)\n\n\ndef format_dashboard_report(reports: List[AnalysisReport]) -> str:\n \"\"\"\n 格式化决策仪表盘报告(多股票汇总)\n \n Args:\n reports: 分析报告列表\n \n Returns:\n 格式化的仪表盘报告\n \"\"\"\n if not reports:\n return \"暂无分析报告\"\n \n # 统计\n buy_count = sum(1 for r in reports if r.decision_type == 'buy')\n hold_count = sum(1 for r in reports if r.decision_type == 'hold')\n sell_count = sum(1 for r in reports if r.decision_type == 'sell')\n \n lines = [\n f\"{'='*60}\",\n f\"📊 股票分析决策仪表盘\",\n f\"{'='*60}\",\n \"\",\n f\"分析股票数: {len(reports)} 只\",\n f\"🟢 买入: {buy_count} 🟡 观望: {hold_count} 🔴 卖出: {sell_count}\",\n \"\",\n f\"{'='*60}\",\n ]\n \n for report in reports:\n emoji = \"🟢\" if report.decision_type == 'buy' else \"🟡\" if report.decision_type == 'hold' else \"🔴\"\n lines.append(f\"{emoji} {report.name} ({report.code})\")\n lines.append(f\" 建议: {report.operation_advice} | 评分: {report.sentiment_score}/100\")\n lines.append(f\" 趋势: {report.trend_prediction}\")\n \n # 添加关键技术指标\n tech = report.technical_summary\n key_info = []\n \n if 'bias_ma5' in tech:\n key_info.append(f\"乖离率: {tech['bias_ma5']:+.1f}%\")\n if 'macd_status' in tech:\n key_info.append(f\"MACD: {tech['macd_status']}\")\n \n if key_info:\n lines.append(f\" 关键指标: {' | '.join(key_info)}\")\n \n lines.append(\"\")\n \n lines.append(f\"{'='*60}\")\n \n return \"\\n\".join(lines)\n\n\ndef create_report_from_result(result: Dict[str, Any]) -> AnalysisReport:\n \"\"\"\n 从分析结果字典创建报告对象\n \n Args:\n result: 分析结果字典\n \n Returns:\n AnalysisReport 对象\n \"\"\"\n technical = result.get('technical_indicators', {})\n ai_result = result.get('ai_analysis', {})\n \n # 确定决策类型\n advice = ai_result.get('operation_advice', '观望')\n if advice in ['买入', '加仓', '强烈买入']:\n decision_type = 'buy'\n elif advice in ['卖出', '减仓', '强烈卖出']:\n decision_type = 'sell'\n else:\n decision_type = 'hold'\n \n return AnalysisReport(\n code=result.get('code', ''),\n name=result.get('name', ''),\n sentiment_score=ai_result.get('sentiment_score', 50),\n trend_prediction=ai_result.get('trend_prediction', '震荡'),\n operation_advice=advice,\n decision_type=decision_type,\n confidence_level=ai_result.get('confidence_level', '中'),\n technical_summary=technical,\n ai_analysis=ai_result.get('analysis_summary', ''),\n risk_warning=ai_result.get('risk_warning', ''),\n buy_reason=ai_result.get('buy_reason', ''),\n support_levels=technical.get('support_levels', []),\n resistance_levels=technical.get('resistance_levels', []),\n )\n\n\ndef print_report(report: AnalysisReport) -> None:\n \"\"\"打印分析报告到控制台\"\"\"\n print(format_analysis_report(report))\n\n\ndef print_dashboard(reports: List[AnalysisReport]) -> None:\n \"\"\"打印决策仪表盘到控制台\"\"\"\n print(format_dashboard_report(reports))\n","content_type":"text/x-python; charset=utf-8","language":"python","size":6802,"content_sha256":"4fad7bcf48702e49fec6b500798db04176d5fdf092cbfd00a83b96c326fac413"},{"filename":"scripts/run.sh","content":"#!/bin/bash\n# 每日股票分析 - 运行脚本\n\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_DIR=\"$SCRIPT_DIR/daily_stock_analysis\"\nVENV_DIR=\"$PROJECT_DIR/.venv\"\n\necho \"=== 每日股票智能分析 ===\"\necho \"时间: $(date '+%Y-%m-%d %H:%M:%S')\"\necho \"\"\n\n# 检查项目是否存在\nif [ ! -d \"$PROJECT_DIR\" ]; then\n echo \"❌ 错误: 项目未安装\"\n echo \"请先运行: ./setup.sh\"\n exit 1\nfi\n\n# 检查环境变量配置\nif [ ! -f \"$PROJECT_DIR/.env\" ]; then\n echo \"❌ 错误: 未找到 .env 配置文件\"\n echo \"请执行: cd $PROJECT_DIR && cp .env.example .env\"\n echo \"然后编辑 .env 配置 API Key 和股票列表\"\n exit 1\nfi\n\ncd \"$PROJECT_DIR\"\n\n# 运行分析\necho \"→ 开始分析...\"\n\"$VENV_DIR/bin/python\" main.py\n\necho \"\"\necho \"✅ 分析完成!\"\necho \"\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":824,"content_sha256":"a690382ffd59475bb8169a0224b7b5aa4af446e0fe4fc2c03014aa408b5d7c06"},{"filename":"scripts/setup.sh","content":"#!/bin/bash\n# 每日股票分析 - 安装脚本\n\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_DIR=\"$SCRIPT_DIR/daily_stock_analysis\"\nVENV_DIR=\"$PROJECT_DIR/.venv\"\nREPO_URL=\"https://github.com/ZhuLinsen/daily_stock_analysis.git\"\n\necho \"=== 每日股票智能分析系统 - 安装 ===\"\necho \"\"\n\n# 检查 Python\nif ! command -v python3 &> /dev/null; then\n echo \"❌ 错误: 需要 Python 3.8+\"\n exit 1\nfi\n\nPYTHON_VERSION=$(python3 --version | cut -d' ' -f2 | cut -d'.' -f1,2)\necho \"✓ Python 版本: $PYTHON_VERSION\"\n\n# 克隆或更新项目\nif [ -d \"$PROJECT_DIR\" ]; then\n echo \"→ 项目已存在,更新代码...\"\n cd \"$PROJECT_DIR\"\n git pull origin main\nelse\n echo \"→ 克隆项目仓库...\"\n git clone \"$REPO_URL\" \"$PROJECT_DIR\"\n cd \"$PROJECT_DIR\"\nfi\n\n# 创建虚拟环境\nif [ ! -d \"$VENV_DIR\" ]; then\n echo \"→ 创建虚拟环境...\"\n python3 -m venv \"$VENV_DIR\"\nfi\n\n# 安装依赖\necho \"→ 安装 Python 依赖...\"\n\"$VENV_DIR/bin/pip\" install -q -r requirements.txt\n\necho \"\"\necho \"✅ 安装完成!\"\necho \"\"\necho \"下一步:\"\necho \" 1. cd $PROJECT_DIR\"\necho \" 2. cp .env.example .env\"\necho \" 3. 编辑 .env 配置 API Key 和股票列表\"\necho \" 4. cd $SCRIPT_DIR && ./run.sh\"\necho \"\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1260,"content_sha256":"23048e782b1f0b948a5f4b761290fc869cbd07bb042362ef24ad6fa5a3560c38"},{"filename":"scripts/trend_analyzer.py","content":"# -*- coding: utf-8 -*-\n\"\"\"\n趋势交易分析器 - 基于交易理念的技术分析\n\n核心原则:\n1. 严进策略 - 不追高,追求每笔交易成功率\n2. 趋势交易 - MA5>MA10>MA20 多头排列,顺势而为\n3. 买点偏好 - 在 MA5/MA10 附近回踩买入\n\"\"\"\n\nimport logging\nfrom dataclasses import dataclass, field\nfrom typing import Optional, List, Dict, Any\nfrom enum import Enum\n\nimport pandas as pd\nimport numpy as np\n\nlogger = logging.getLogger(__name__)\n\n\nclass TrendStatus(Enum):\n \"\"\"趋势状态枚举\"\"\"\n STRONG_BULL = \"强势多头\"\n BULL = \"多头排列\"\n WEAK_BULL = \"弱势多头\"\n CONSOLIDATION = \"盘整\"\n WEAK_BEAR = \"弱势空头\"\n BEAR = \"空头排列\"\n STRONG_BEAR = \"强势空头\"\n\n\nclass VolumeStatus(Enum):\n \"\"\"量能状态枚举\"\"\"\n HEAVY_VOLUME_UP = \"放量上涨\"\n HEAVY_VOLUME_DOWN = \"放量下跌\"\n SHRINK_VOLUME_UP = \"缩量上涨\"\n SHRINK_VOLUME_DOWN = \"缩量回调\"\n NORMAL = \"量能正常\"\n\n\nclass BuySignal(Enum):\n \"\"\"买入信号枚举\"\"\"\n STRONG_BUY = \"强烈买入\"\n BUY = \"买入\"\n HOLD = \"持有\"\n WAIT = \"观望\"\n SELL = \"卖出\"\n STRONG_SELL = \"强烈卖出\"\n\n\nclass MACDStatus(Enum):\n \"\"\"MACD状态枚举\"\"\"\n GOLDEN_CROSS_ZERO = \"零轴上金叉\"\n GOLDEN_CROSS = \"金叉\"\n BULLISH = \"多头\"\n CROSSING_UP = \"上穿零轴\"\n CROSSING_DOWN = \"下穿零轴\"\n BEARISH = \"空头\"\n DEATH_CROSS = \"死叉\"\n\n\nclass RSIStatus(Enum):\n \"\"\"RSI状态枚举\"\"\"\n OVERBOUGHT = \"超买\"\n STRONG_BUY = \"强势买入\"\n NEUTRAL = \"中性\"\n WEAK = \"弱势\"\n OVERSOLD = \"超卖\"\n\n\n@dataclass\nclass TrendAnalysisResult:\n \"\"\"趋势分析结果\"\"\"\n code: str\n \n # 趋势判断\n trend_status: TrendStatus = TrendStatus.CONSOLIDATION\n ma_alignment: str = \"\"\n trend_strength: float = 0.0\n \n # 均线数据\n ma5: float = 0.0\n ma10: float = 0.0\n ma20: float = 0.0\n ma60: float = 0.0\n current_price: float = 0.0\n \n # 乖离率\n bias_ma5: float = 0.0\n bias_ma10: float = 0.0\n bias_ma20: float = 0.0\n \n # 量能分析\n volume_status: VolumeStatus = VolumeStatus.NORMAL\n volume_ratio_5d: float = 0.0\n volume_trend: str = \"\"\n \n # 支撑压力\n support_ma5: bool = False\n support_ma10: bool = False\n resistance_levels: List[float] = field(default_factory=list)\n support_levels: List[float] = field(default_factory=list)\n \n # MACD 指标\n macd_dif: float = 0.0\n macd_dea: float = 0.0\n macd_bar: float = 0.0\n macd_status: MACDStatus = MACDStatus.BULLISH\n macd_signal: str = \"\"\n \n # RSI 指标\n rsi_6: float = 0.0\n rsi_12: float = 0.0\n rsi_24: float = 0.0\n rsi_status: RSIStatus = RSIStatus.NEUTRAL\n rsi_signal: str = \"\"\n \n # 买入信号\n buy_signal: BuySignal = BuySignal.WAIT\n signal_score: int = 0\n signal_reasons: List[str] = field(default_factory=list)\n risk_factors: List[str] = field(default_factory=list)\n \n def to_dict(self) -> Dict[str, Any]:\n \"\"\"转换为字典\"\"\"\n return {\n 'code': self.code,\n 'trend_status': self.trend_status.value,\n 'ma_alignment': self.ma_alignment,\n 'trend_strength': self.trend_strength,\n 'ma5': self.ma5,\n 'ma10': self.ma10,\n 'ma20': self.ma20,\n 'ma60': self.ma60,\n 'current_price': self.current_price,\n 'bias_ma5': self.bias_ma5,\n 'bias_ma10': self.bias_ma10,\n 'bias_ma20': self.bias_ma20,\n 'volume_status': self.volume_status.value,\n 'volume_ratio_5d': self.volume_ratio_5d,\n 'volume_trend': self.volume_trend,\n 'support_ma5': self.support_ma5,\n 'support_ma10': self.support_ma10,\n 'buy_signal': self.buy_signal.value,\n 'signal_score': self.signal_score,\n 'signal_reasons': self.signal_reasons,\n 'risk_factors': self.risk_factors,\n 'macd_status': self.macd_status.value,\n 'macd_signal': self.macd_signal,\n 'rsi_status': self.rsi_status.value,\n 'rsi_signal': self.rsi_signal,\n }\n\n\nclass StockTrendAnalyzer:\n \"\"\"\n 股票趋势分析器\n \n 基于交易理念:\n 1. 趋势判断 - MA5>MA10>MA20 多头排列\n 2. 乖离率检测 - 不追高,偏离 MA5 超过 5% 不买\n 3. 量能分析 - 偏好缩量回调\n 4. MACD/RSI 指标分析\n \"\"\"\n \n BIAS_THRESHOLD = 5.0 # 乖离率阈值\n VOLUME_SHRINK_RATIO = 0.7\n VOLUME_HEAVY_RATIO = 1.5\n MA_SUPPORT_TOLERANCE = 0.02\n \n MACD_FAST = 12\n MACD_SLOW = 26\n MACD_SIGNAL = 9\n \n RSI_SHORT = 6\n RSI_MID = 12\n RSI_LONG = 24\n RSI_OVERBOUGHT = 70\n RSI_OVERSOLD = 30\n \n def analyze(self, df: pd.DataFrame, code: str) -> TrendAnalysisResult:\n \"\"\"\n 分析股票趋势\n \n Args:\n df: 包含 OHLCV 数据的 DataFrame\n code: 股票代码\n \n Returns:\n TrendAnalysisResult 分析结果\n \"\"\"\n result = TrendAnalysisResult(code=code)\n \n if df is None or df.empty or len(df) \u003c 20:\n logger.warning(f\"{code} 数据不足,无法进行趋势分析\")\n result.risk_factors.append(\"数据不足,无法完成分析\")\n return result\n \n # 确保数据按日期排序\n df = df.sort_values('date').reset_index(drop=True)\n \n # 计算指标\n df = self._calculate_mas(df)\n df = self._calculate_macd(df)\n df = self._calculate_rsi(df)\n \n # 获取最新数据\n latest = df.iloc[-1]\n result.current_price = float(latest['close'])\n result.ma5 = float(latest['MA5'])\n result.ma10 = float(latest['MA10'])\n result.ma20 = float(latest['MA20'])\n result.ma60 = float(latest.get('MA60', 0))\n \n # 分析各项\n self._analyze_trend(df, result)\n self._calculate_bias(result)\n self._analyze_volume(df, result)\n self._analyze_support_resistance(df, result)\n self._analyze_macd(df, result)\n self._analyze_rsi(df, result)\n self._generate_signal(result)\n \n return result\n \n def _calculate_mas(self, df: pd.DataFrame) -> pd.DataFrame:\n \"\"\"计算均线\"\"\"\n df = df.copy()\n df['MA5'] = df['close'].rolling(window=5, min_periods=1).mean()\n df['MA10'] = df['close'].rolling(window=10, min_periods=1).mean()\n df['MA20'] = df['close'].rolling(window=20, min_periods=1).mean()\n if len(df) >= 60:\n df['MA60'] = df['close'].rolling(window=60, min_periods=1).mean()\n else:\n df['MA60'] = df['MA20']\n return df\n \n def _calculate_macd(self, df: pd.DataFrame) -> pd.DataFrame:\n \"\"\"计算 MACD 指标\"\"\"\n df = df.copy()\n \n ema_fast = df['close'].ewm(span=self.MACD_FAST, adjust=False).mean()\n ema_slow = df['close'].ewm(span=self.MACD_SLOW, adjust=False).mean()\n \n df['MACD_DIF'] = ema_fast - ema_slow\n df['MACD_DEA'] = df['MACD_DIF'].ewm(span=self.MACD_SIGNAL, adjust=False).mean()\n df['MACD_BAR'] = (df['MACD_DIF'] - df['MACD_DEA']) * 2\n \n return df\n \n def _calculate_rsi(self, df: pd.DataFrame) -> pd.DataFrame:\n \"\"\"计算 RSI 指标\"\"\"\n df = df.copy()\n \n for period in [self.RSI_SHORT, self.RSI_MID, self.RSI_LONG]:\n delta = df['close'].diff()\n gain = delta.where(delta > 0, 0)\n loss = -delta.where(delta \u003c 0, 0)\n \n avg_gain = gain.rolling(window=period, min_periods=1).mean()\n avg_loss = loss.rolling(window=period, min_periods=1).mean()\n \n rs = avg_gain / avg_loss\n rsi = 100 - (100 / (1 + rs))\n \n df[f'RSI_{period}'] = rsi.fillna(50)\n \n return df\n \n def _analyze_trend(self, df: pd.DataFrame, result: TrendAnalysisResult) -> None:\n \"\"\"分析趋势状态\"\"\"\n ma5, ma10, ma20 = result.ma5, result.ma10, result.ma20\n \n if ma5 > ma10 > ma20:\n # 检查趋势强度\n if len(df) >= 5:\n prev = df.iloc[-5]\n prev_spread = (prev['MA5'] - prev['MA20']) / prev['MA20'] * 100 if prev['MA20'] > 0 else 0\n curr_spread = (ma5 - ma20) / ma20 * 100 if ma20 > 0 else 0\n \n if curr_spread > prev_spread and curr_spread > 5:\n result.trend_status = TrendStatus.STRONG_BULL\n result.ma_alignment = \"强势多头排列,均线发散上行\"\n result.trend_strength = 90\n else:\n result.trend_status = TrendStatus.BULL\n result.ma_alignment = \"多头排列 MA5>MA10>MA20\"\n result.trend_strength = 75\n else:\n result.trend_status = TrendStatus.BULL\n result.ma_alignment = \"多头排列 MA5>MA10>MA20\"\n result.trend_strength = 75\n \n elif ma5 > ma10 and ma10 \u003c= ma20:\n result.trend_status = TrendStatus.WEAK_BULL\n result.ma_alignment = \"弱势多头,MA5>MA10 但 MA10≤MA20\"\n result.trend_strength = 55\n \n elif ma5 \u003c ma10 \u003c ma20:\n if len(df) >= 5:\n prev = df.iloc[-5]\n prev_spread = (prev['MA20'] - prev['MA5']) / prev['MA5'] * 100 if prev['MA5'] > 0 else 0\n curr_spread = (ma20 - ma5) / ma5 * 100 if ma5 > 0 else 0\n \n if curr_spread > prev_spread and curr_spread > 5:\n result.trend_status = TrendStatus.STRONG_BEAR\n result.ma_alignment = \"强势空头排列,均线发散下行\"\n result.trend_strength = 10\n else:\n result.trend_status = TrendStatus.BEAR\n result.ma_alignment = \"空头排列 MA5\u003cMA10\u003cMA20\"\n result.trend_strength = 25\n else:\n result.trend_status = TrendStatus.BEAR\n result.ma_alignment = \"空头排列 MA5\u003cMA10\u003cMA20\"\n result.trend_strength = 25\n \n elif ma5 \u003c ma10 and ma10 >= ma20:\n result.trend_status = TrendStatus.WEAK_BEAR\n result.ma_alignment = \"弱势空头,MA5\u003cMA10 但 MA10≥MA20\"\n result.trend_strength = 40\n \n else:\n result.trend_status = TrendStatus.CONSOLIDATION\n result.ma_alignment = \"均线缠绕,趋势不明\"\n result.trend_strength = 50\n \n def _calculate_bias(self, result: TrendAnalysisResult) -> None:\n \"\"\"计算乖离率\"\"\"\n price = result.current_price\n \n if result.ma5 > 0:\n result.bias_ma5 = (price - result.ma5) / result.ma5 * 100\n if result.ma10 > 0:\n result.bias_ma10 = (price - result.ma10) / result.ma10 * 100\n if result.ma20 > 0:\n result.bias_ma20 = (price - result.ma20) / result.ma20 * 100\n \n def _analyze_volume(self, df: pd.DataFrame, result: TrendAnalysisResult) -> None:\n \"\"\"分析量能\"\"\"\n if len(df) \u003c 5:\n return\n \n latest = df.iloc[-1]\n vol_5d_avg = df['volume'].iloc[-6:-1].mean()\n \n if vol_5d_avg > 0:\n result.volume_ratio_5d = float(latest['volume']) / vol_5d_avg\n \n # 判断价格变化\n if len(df) >= 2:\n prev_close = df.iloc[-2]['close']\n price_change = (latest['close'] - prev_close) / prev_close * 100\n \n # 量能状态判断\n if result.volume_ratio_5d >= self.VOLUME_HEAVY_RATIO:\n if price_change > 0:\n result.volume_status = VolumeStatus.HEAVY_VOLUME_UP\n result.volume_trend = \"放量上涨,多头力量强劲\"\n else:\n result.volume_status = VolumeStatus.HEAVY_VOLUME_DOWN\n result.volume_trend = \"放量下跌,注意风险\"\n elif result.volume_ratio_5d \u003c= self.VOLUME_SHRINK_RATIO:\n if price_change > 0:\n result.volume_status = VolumeStatus.SHRINK_VOLUME_UP\n result.volume_trend = \"缩量上涨,上攻动能不足\"\n else:\n result.volume_status = VolumeStatus.SHRINK_VOLUME_DOWN\n result.volume_trend = \"缩量回调,洗盘特征明显\"\n else:\n result.volume_status = VolumeStatus.NORMAL\n result.volume_trend = \"量能正常\"\n \n def _analyze_support_resistance(self, df: pd.DataFrame, result: TrendAnalysisResult) -> None:\n \"\"\"分析支撑压力位\"\"\"\n price = result.current_price\n \n # 检查 MA5 支撑\n if result.ma5 > 0:\n ma5_distance = abs(price - result.ma5) / result.ma5\n if ma5_distance \u003c= self.MA_SUPPORT_TOLERANCE and price >= result.ma5:\n result.support_ma5 = True\n result.support_levels.append(result.ma5)\n \n # 检查 MA10 支撑\n if result.ma10 > 0:\n ma10_distance = abs(price - result.ma10) / result.ma10\n if ma10_distance \u003c= self.MA_SUPPORT_TOLERANCE and price >= result.ma10:\n result.support_ma10 = True\n if result.ma10 not in result.support_levels:\n result.support_levels.append(result.ma10)\n \n # MA20 作为重要支撑\n if result.ma20 > 0 and price >= result.ma20:\n result.support_levels.append(result.ma20)\n \n # 近期高点作为压力\n if len(df) >= 20:\n recent_high = df['high'].iloc[-20:].max()\n if recent_high > price:\n result.resistance_levels.append(recent_high)\n \n def _analyze_macd(self, df: pd.DataFrame, result: TrendAnalysisResult) -> None:\n \"\"\"分析 MACD 指标\"\"\"\n if len(df) \u003c self.MACD_SLOW:\n result.macd_signal = \"数据不足\"\n return\n \n latest = df.iloc[-1]\n prev = df.iloc[-2]\n \n result.macd_dif = float(latest['MACD_DIF'])\n result.macd_dea = float(latest['MACD_DEA'])\n result.macd_bar = float(latest['MACD_BAR'])\n \n # 判断金叉死叉\n prev_dif_dea = prev['MACD_DIF'] - prev['MACD_DEA']\n curr_dif_dea = result.macd_dif - result.macd_dea\n \n is_golden_cross = prev_dif_dea \u003c= 0 and curr_dif_dea > 0\n is_death_cross = prev_dif_dea >= 0 and curr_dif_dea \u003c 0\n is_crossing_up = prev['MACD_DIF'] \u003c= 0 and result.macd_dif > 0\n is_crossing_down = prev['MACD_DIF'] >= 0 and result.macd_dif \u003c 0\n \n if is_golden_cross and result.macd_dif > 0:\n result.macd_status = MACDStatus.GOLDEN_CROSS_ZERO\n result.macd_signal = \"⭐ 零轴上金叉,强烈买入信号!\"\n elif is_crossing_up:\n result.macd_status = MACDStatus.CROSSING_UP\n result.macd_signal = \"⚡ DIF上穿零轴,趋势转强\"\n elif is_golden_cross:\n result.macd_status = MACDStatus.GOLDEN_CROSS\n result.macd_signal = \"✅ 金叉,趋势向上\"\n elif is_death_cross:\n result.macd_status = MACDStatus.DEATH_CROSS\n result.macd_signal = \"❌ 死叉,趋势向下\"\n elif is_crossing_down:\n result.macd_status = MACDStatus.CROSSING_DOWN\n result.macd_signal = \"⚠️ DIF下穿零轴,趋势转弱\"\n elif result.macd_dif > 0 and result.macd_dea > 0:\n result.macd_status = MACDStatus.BULLISH\n result.macd_signal = \"✓ 多头排列,持续上涨\"\n elif result.macd_dif \u003c 0 and result.macd_dea \u003c 0:\n result.macd_status = MACDStatus.BEARISH\n result.macd_signal = \"⚠ 空头排列,持续下跌\"\n else:\n result.macd_status = MACDStatus.BULLISH\n result.macd_signal = \"MACD 中性区域\"\n \n def _analyze_rsi(self, df: pd.DataFrame, result: TrendAnalysisResult) -> None:\n \"\"\"分析 RSI 指标\"\"\"\n if len(df) \u003c self.RSI_LONG:\n result.rsi_signal = \"数据不足\"\n return\n \n latest = df.iloc[-1]\n \n result.rsi_6 = float(latest[f'RSI_{self.RSI_SHORT}'])\n result.rsi_12 = float(latest[f'RSI_{self.RSI_MID}'])\n result.rsi_24 = float(latest[f'RSI_{self.RSI_LONG}'])\n \n rsi_mid = result.rsi_12\n \n if rsi_mid > self.RSI_OVERBOUGHT:\n result.rsi_status = RSIStatus.OVERBOUGHT\n result.rsi_signal = f\"⚠️ RSI超买({rsi_mid:.1f}>70),短期回调风险高\"\n elif rsi_mid > 60:\n result.rsi_status = RSIStatus.STRONG_BUY\n result.rsi_signal = f\"✅ RSI强势({rsi_mid:.1f}),多头力量充足\"\n elif rsi_mid >= 40:\n result.rsi_status = RSIStatus.NEUTRAL\n result.rsi_signal = f\"RSI中性({rsi_mid:.1f}),震荡整理中\"\n elif rsi_mid >= self.RSI_OVERSOLD:\n result.rsi_status = RSIStatus.WEAK\n result.rsi_signal = f\"⚡ RSI弱势({rsi_mid:.1f}),关注反弹\"\n else:\n result.rsi_status = RSIStatus.OVERSOLD\n result.rsi_signal = f\"⭐ RSI超卖({rsi_mid:.1f}\u003c30),反弹机会大\"\n \n def _generate_signal(self, result: TrendAnalysisResult) -> None:\n \"\"\"生成买入信号和综合评分\"\"\"\n score = 0\n reasons = []\n risks = []\n \n # 趋势评分(30分)\n trend_scores = {\n TrendStatus.STRONG_BULL: 30,\n TrendStatus.BULL: 26,\n TrendStatus.WEAK_BULL: 18,\n TrendStatus.CONSOLIDATION: 12,\n TrendStatus.WEAK_BEAR: 8,\n TrendStatus.BEAR: 4,\n TrendStatus.STRONG_BEAR: 0,\n }\n trend_score = trend_scores.get(result.trend_status, 12)\n score += trend_score\n \n if result.trend_status in [TrendStatus.STRONG_BULL, TrendStatus.BULL]:\n reasons.append(f\"✅ {result.trend_status.value},顺势做多\")\n elif result.trend_status in [TrendStatus.BEAR, TrendStatus.STRONG_BEAR]:\n risks.append(f\"⚠️ {result.trend_status.value},不宜做多\")\n \n # 乖离率评分(20分)\n bias = result.bias_ma5\n if bias \u003c 0:\n if bias > -3:\n score += 20\n reasons.append(f\"✅ 价格略低于MA5({bias:.1f}%),回踩买点\")\n elif bias > -5:\n score += 16\n reasons.append(f\"✅ 价格回踩MA5({bias:.1f}%),观察支撑\")\n else:\n score += 8\n risks.append(f\"⚠️ 乖离率过大({bias:.1f}%),可能破位\")\n elif bias \u003c 2:\n score += 18\n reasons.append(f\"✅ 价格贴近MA5({bias:.1f}%),介入好时机\")\n elif bias \u003c self.BIAS_THRESHOLD:\n score += 14\n reasons.append(f\"⚡ 价格略高于MA5({bias:.1f}%),可小仓介入\")\n else:\n score += 4\n risks.append(f\"❌ 乖离率过高({bias:.1f}%>5%),严禁追高!\")\n \n # 量能评分(15分)\n volume_scores = {\n VolumeStatus.SHRINK_VOLUME_DOWN: 15,\n VolumeStatus.HEAVY_VOLUME_UP: 12,\n VolumeStatus.NORMAL: 10,\n VolumeStatus.SHRINK_VOLUME_UP: 6,\n VolumeStatus.HEAVY_VOLUME_DOWN: 0,\n }\n vol_score = volume_scores.get(result.volume_status, 8)\n score += vol_score\n \n if result.volume_status == VolumeStatus.SHRINK_VOLUME_DOWN:\n reasons.append(\"✅ 缩量回调,主力洗盘\")\n elif result.volume_status == VolumeStatus.HEAVY_VOLUME_DOWN:\n risks.append(\"⚠️ 放量下跌,注意风险\")\n \n # 支撑评分(10分)\n if result.support_ma5:\n score += 5\n reasons.append(\"✅ MA5支撑有效\")\n if result.support_ma10:\n score += 5\n reasons.append(\"✅ MA10支撑有效\")\n \n # MACD 评分(15分)\n macd_scores = {\n MACDStatus.GOLDEN_CROSS_ZERO: 15,\n MACDStatus.GOLDEN_CROSS: 12,\n MACDStatus.CROSSING_UP: 10,\n MACDStatus.BULLISH: 8,\n MACDStatus.BEARISH: 2,\n MACDStatus.CROSSING_DOWN: 0,\n MACDStatus.DEATH_CROSS: 0,\n }\n macd_score = macd_scores.get(result.macd_status, 5)\n score += macd_score\n \n if result.macd_status in [MACDStatus.GOLDEN_CROSS_ZERO, MACDStatus.GOLDEN_CROSS]:\n reasons.append(f\"✅ {result.macd_signal}\")\n elif result.macd_status in [MACDStatus.DEATH_CROSS, MACDStatus.CROSSING_DOWN]:\n risks.append(f\"⚠️ {result.macd_signal}\")\n else:\n reasons.append(result.macd_signal)\n \n # RSI 评分(10分)\n rsi_scores = {\n RSIStatus.OVERSOLD: 10,\n RSIStatus.STRONG_BUY: 8,\n RSIStatus.NEUTRAL: 5,\n RSIStatus.WEAK: 3,\n RSIStatus.OVERBOUGHT: 0,\n }\n rsi_score = rsi_scores.get(result.rsi_status, 5)\n score += rsi_score\n \n if result.rsi_status in [RSIStatus.OVERSOLD, RSIStatus.STRONG_BUY]:\n reasons.append(f\"✅ {result.rsi_signal}\")\n elif result.rsi_status == RSIStatus.OVERBOUGHT:\n risks.append(f\"⚠️ {result.rsi_signal}\")\n else:\n reasons.append(result.rsi_signal)\n \n # 综合判断\n result.signal_score = score\n result.signal_reasons = reasons\n result.risk_factors = risks\n \n if score >= 75 and result.trend_status in [TrendStatus.STRONG_BULL, TrendStatus.BULL]:\n result.buy_signal = BuySignal.STRONG_BUY\n elif score >= 60 and result.trend_status in [TrendStatus.STRONG_BULL, TrendStatus.BULL, TrendStatus.WEAK_BULL]:\n result.buy_signal = BuySignal.BUY\n elif score >= 45:\n result.buy_signal = BuySignal.HOLD\n elif score >= 30:\n result.buy_signal = BuySignal.WAIT\n elif result.trend_status in [TrendStatus.BEAR, TrendStatus.STRONG_BEAR]:\n result.buy_signal = BuySignal.STRONG_SELL\n else:\n result.buy_signal = BuySignal.SELL\n\n\ndef analyze_stock(df: pd.DataFrame, code: str) -> TrendAnalysisResult:\n \"\"\"便捷函数:分析单只股票\"\"\"\n analyzer = StockTrendAnalyzer()\n return analyzer.analyze(df, code)\n","content_type":"text/x-python; charset=utf-8","language":"python","size":22740,"content_sha256":"a70f26d03ae15690761cbd96d44b527b4388fd58f866fd14be696d1a1c326031"},{"filename":"scripts/update.sh","content":"#!/bin/bash\n# 每日股票分析 - 更新脚本\n\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_DIR=\"$SCRIPT_DIR/daily_stock_analysis\"\n\necho \"=== 更新每日股票分析项目 ===\"\necho \"\"\n\nif [ ! -d \"$PROJECT_DIR\" ]; then\n echo \"❌ 错误: 项目未安装\"\n exit 1\nfi\n\ncd \"$PROJECT_DIR\"\n\n# 备份当前配置\nif [ -f \".env\" ]; then\n echo \"→ 备份当前配置...\"\n cp .env .env.backup.$(date +%Y%m%d_%H%M%S)\nfi\n\n# 拉取最新代码\necho \"→ 拉取最新代码...\"\ngit pull origin main\n\n# 更新依赖\necho \"→ 更新依赖...\"\npip3 install -q -r requirements.txt\n\necho \"\"\necho \"✅ 更新完成!\"\necho \"\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":653,"content_sha256":"e6e7650897b19763db366188e8cac488e306bb6282bc772d72efa688b5fe2e83"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Daily Stock Analysis for OpenClaw","type":"text"}]},{"type":"paragraph","content":[{"text":"基于 LLM 的 A/H/美股智能分析 Skill,提供技术面分析和 AI 决策建议。","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"功能特性","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"多市场支持","type":"text","marks":[{"type":"strong"}]},{"text":" - A股、港股、美股","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"技术面分析","type":"text","marks":[{"type":"strong"}]},{"text":" - MA5/10/20、MACD、RSI、乖离率","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"趋势交易","type":"text","marks":[{"type":"strong"}]},{"text":" - 多头排列判断、买入信号评分","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"AI 决策","type":"text","marks":[{"type":"strong"}]},{"text":" - DeepSeek/Gemini/OpenAI 深度分析","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"数据源集成","type":"text","marks":[{"type":"strong"}]},{"text":" - 可选 market-data skill","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"快速使用","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"from scripts.analyzer import analyze_stock, analyze_stocks\n\n# 单只分析\nresult = analyze_stock('600519')\nprint(result['ai_analysis']['operation_advice'])\n\n# 批量分析\nresults = analyze_stocks(['600362', '601318', '159892'])","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"配置","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"复制配置模板:","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cp config.example.json config.json","type":"text"}]},{"type":"ordered_list","attrs":{"order":2,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"填入 DeepSeek API Key:","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"ai\": {\n \"provider\": \"openai\",\n \"api_key\": \"sk-your-deepseek-key\",\n \"base_url\": \"https://api.deepseek.com/v1\",\n \"model\": \"deepseek-chat\"\n }\n}","type":"text"}]},{"type":"ordered_list","attrs":{"order":3,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"(可选) 启用 market-data skill 数据源:","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"data\": {\n \"use_market_data_skill\": true,\n \"market_data_skill_path\": \"../market-data\"\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"返回数据","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"{\n 'code': '600519',\n 'name': '贵州茅台',\n 'technical_indicators': {\n 'trend_status': '强势多头',\n 'ma5': 1500.0, 'ma10': 1480.0, 'ma20': 1450.0,\n 'bias_ma5': 2.5,\n 'macd_status': '金叉',\n 'rsi_status': '强势买入',\n 'buy_signal': '买入',\n 'signal_score': 75\n },\n 'ai_analysis': {\n 'sentiment_score': 75,\n 'operation_advice': '买入',\n 'confidence_level': '高',\n 'target_price': '1550',\n 'stop_loss': '1420'\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"项目信息","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"开源协议","type":"text","marks":[{"type":"strong"}]},{"text":": MIT","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"项目地址","type":"text","marks":[{"type":"strong"}]},{"text":": https://github.com/yourusername/stock-daily-analysis","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"原项目","type":"text","marks":[{"type":"strong"}]},{"text":": https://github.com/ZhuLinsen/daily_stock_analysis","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"paragraph","content":[{"text":"⚠️ ","type":"text"},{"text":"免责声明","type":"text","marks":[{"type":"strong"}]},{"text":": 本项目仅供学习研究,不构成投资建议。股市有风险,投资需谨慎。","type":"text"}]}]},"metadata":{"date":"2026-11-02","name":"stock-daily-analysis","author":"@skillopedia","source":{"stars":242,"repo_name":"stock-daily-analysis-skill","origin_url":"https://github.com/chjm-ai/stock-daily-analysis-skill/blob/HEAD/SKILL.md","repo_owner":"chjm-ai","body_sha256":"8240f825afc434760139ccdae334b67c416e93346c94663303457999ce36006b","cluster_key":"f7f9530409670826536923bb724a82b2caf28f809cdd20dd796075ff41c2be92","clean_bundle":{"format":"clean-skill-bundle-v1","source":"chjm-ai/stock-daily-analysis-skill/SKILL.md","text_bytes":62918,"source_commit":"af5e467b90ce9d682084801280cd01fe90ef5b85","skills_sh_rank":10379,"attachment_count":9,"text_attachments":9,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"finance-legal-compliance","category_label":"Finance"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"finance-legal-compliance","import_tag":"skills-sh-top20k-2026-11","description":"LLM驱动的每日股票分析系统。支持A股/港股/美股自选股智能分析,生成决策仪表盘和大盘复盘报告。提供技术面分析(均线、MACD、RSI、乖离率)、趋势判断、买入信号评分。可与market-data skill集成获取更稳定的ETF数据。触发词:股票分析、分析股票、每日分析、技术面分析。"}},"renderedAt":1793671779600}

Daily Stock Analysis for OpenClaw 基于 LLM 的 A/H/美股智能分析 Skill,提供技术面分析和 AI 决策建议。 功能特性 1. 多市场支持 - A股、港股、美股 2. 技术面分析 - MA5/10/20、MACD、RSI、乖离率 3. 趋势交易 - 多头排列判断、买入信号评分 4. AI 决策 - DeepSeek/Gemini/OpenAI 深度分析 5. 数据源集成 - 可选 market-data skill 快速使用 配置 1. 复制配置模板: 2. 填入 DeepSeek API Key: 3. (可选) 启用 market-data skill 数据源: 返回数据 项目信息 - 开源协议 : MIT - 项目地址 : https://github.com/yourusername/stock-daily-analysis - 原项目 : https://github.com/ZhuLinsen/daily stock analysis --- ⚠️ 免责声明 : 本项目仅供学习研究,不构成投资建议。股市有风险,投资需谨慎。