Add stock-master skill: beginner-friendly stock technical analysis tool

Features:
- Hybrid data sources (Yahoo Finance + Alpha Vantage MCP)
- Easy-to-understand analysis reports for beginners
- RSI, MACD, KDJ, Bollinger Bands, OBV indicators
- K-line and trend pattern recognition
- Buy/sell signals with position suggestions
- Excel portfolio management
- Feishu (飞书) multi-table sync
- Supports US, HK, and A-share markets
This commit is contained in:
EagleF6432614 2026-01-28 16:36:41 +08:00
parent 69c0b1a067
commit 6942b4172c
20 changed files with 6064 additions and 0 deletions

View File

@ -40,6 +40,15 @@
"./skills/web-artifacts-builder",
"./skills/webapp-testing"
]
},
{
"name": "stock-master",
"description": "Beginner-friendly stock technical analysis tool with hybrid data sources (Yahoo Finance + Alpha Vantage MCP). Provides easy-to-understand analysis reports, buy/sell signals, Excel portfolio management, and Feishu sync. Supports US, HK, and A-share markets.",
"source": "./",
"strict": false,
"skills": [
"./skills/stock-master"
]
}
]
}

47
skills/stock-master/.gitignore vendored Normal file
View File

@ -0,0 +1,47 @@
# 敏感配置文件 (包含 API keys)
config.json
feishu_config.json
*_config.json
!*_config.example.json
# 个人数据
*.xlsx
*.xls
my_portfolio*
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
.env
.venv
venv/
ENV/
# IDE
.idea/
.vscode/
*.swp
*.swo
*.sublime-*
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
# Logs
*.log
logs/
# Testing
.pytest_cache/
.coverage
htmlcov/

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 eaglef6432614
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,203 @@
# Stock Master - 小白友好的股票技术分析工具
[![GitHub stars](https://img.shields.io/github/stars/EagleF6432614/stock-master?style=social)](https://github.com/EagleF6432614/stock-master)
[![GitHub forks](https://img.shields.io/github/forks/EagleF6432614/stock-master?style=social)](https://github.com/EagleF6432614/stock-master/fork)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
面向普通投资者的技术分析工具,用日常语言解释复杂指标,给出明确买卖建议。
## 特性
- **小白友好**: 用通俗语言解释 RSI、MACD、KDJ 等指标
- **混合数据源**: Yahoo Finance (实时) + Alpha Vantage MCP (专业指标)
- **多市场支持**: 美股、港股、A股
- **持仓管理**: Excel 表格管理,自动计算盈亏
- **飞书同步**: 支持同步到飞书多维表格
- **形态识别**: K线形态 + 趋势形态自动识别
## 安装方式
### 方式一:Claude Code Marketplace(推荐)
在 Claude Code 中运行:
```bash
# 1. 添加 marketplace
/plugin marketplace add EagleF6432614/stock-master
# 2. 安装 skill
/plugin install stock-master@stock-master-marketplace
```
安装后直接使用:
```
分析 AAPL 股票
```
### 方式二:手动安装
将仓库克隆到 Claude skills 目录:
```bash
git clone https://github.com/EagleF6432614/stock-master.git ~/.claude/skills/stock-master
```
---
## 快速开始
### 1. 安装依赖
```bash
pip install -r requirements.txt
```
### 2. 配置 Alpha Vantage MCP (可选)
在 Claude Code 中添加 MCP 服务器:
```bash
claude mcp add Alpha-Vantage --transport http --url "https://mcp.alphavantage.co/mcp?apikey=YOUR_API_KEY"
```
> 获取免费 API Key: https://www.alphavantage.co/support/#api-key
### 3. 作为 Claude Skill 使用
将此目录复制到 `~/.claude/skills/stock-master`,然后在 Claude Code 中:
```
用户: 分析 AAPL 股票
用户: 看看特斯拉能买吗
用户: 对比 NVDA 和 AMD
```
## 配置说明
### 基础配置
复制配置模板:
```bash
cp config.example.json config.json
cp feishu_config.example.json feishu_config.json # 如需飞书同步
```
编辑 `config.json`:
```json
{
"portfolio_path": "./my_portfolio.xlsx",
"feishu_config_path": "./feishu_config.json"
}
```
### 飞书配置 (可选)
如需同步到飞书多维表格,编辑 `feishu_config.json`:
```json
{
"APP_ID": "cli_xxx",
"APP_SECRET": "xxx",
"APP_TOKEN": "xxx",
"TABLE_ID": "tblxxx"
}
```
> 详细配置指南: [飞书开放平台文档](https://open.feishu.cn/document/server-docs/docs/bitable-v1/bitable-overview)
## 支持的指标
| 指标 | 数据源 | 说明 |
|------|--------|------|
| RSI | Alpha Vantage / 本地 | 相对强弱指数 |
| MACD | 本地计算 | 趋势动量指标 |
| KDJ | 本地计算 | 随机指标 |
| 布林带 | Alpha Vantage / 本地 | 波动区间 |
| OBV | 本地计算 | 量能指标 |
| ATR | 本地计算 | 动态止损 |
| 斐波那契 | 本地计算 | 支撑阻力位 |
## 形态识别
### K线形态
- 锤子线、上吊线
- 看涨/看跌吞没
- 早晨之星、黄昏之星
- 三只白兵、三只乌鸦
- 十字星
### 趋势形态
- 双底 (W底)、双顶 (M头)
- 头肩底、头肩顶
- 上升三角形、下降三角形
## 交易建议评分
| 分数 | 建议 | 仓位 |
|------|------|------|
| ≥6 | 强烈买入 | 30% |
| 3-5 | 建议买入 | 20% |
| -2~2 | 观望 | - |
| -3~-5 | 建议卖出 | - |
| ≤-6 | 强烈卖出 | - |
## 项目结构
```
stock-master/
├── SKILL.md # Claude Skill 定义
├── README.md # 本文件
├── LICENSE # MIT 许可证
├── requirements.txt # Python 依赖
├── config.example.json # 配置模板
├── feishu_config.example.json # 飞书配置模板
├── scripts/
│ ├── main.py # 主分析器
│ ├── indicators.py # 技术指标计算
│ ├── beginner_analyzer.py # 小白友好报告生成
│ ├── portfolio.py # 持仓管理
│ ├── feishu_sync.py # 飞书同步
│ └── feishu_init_tables.py # 飞书表结构初始化
└── references/
├── investment-wisdom.md # 投资智慧与哲学
├── portfolio-guide.md # Excel 持仓管理指南
├── feishu-guide.md # 飞书同步详细指南
├── mcp-tools.md # MCP 工具使用指南
├── scripts-guide.md # 脚本详细说明
└── changelog.md # 更新日志
```
## 详细文档
| 文档 | 说明 |
|------|------|
| [投资智慧](references/investment-wisdom.md) | 股民十大境界、交易原则、心态修炼 |
| [持仓管理](references/portfolio-guide.md) | Excel 持仓表格使用指南 |
| [飞书同步](references/feishu-guide.md) | 飞书多维表格配置与同步 |
| [MCP 工具](references/mcp-tools.md) | Alpha Vantage API 调用指南 |
## 风险提示
> **免责声明**: 本工具仅供学习和参考,不构成投资建议。
>
> - 股市有风险,投资需谨慎
> - 技术分析不能保证盈利
> - 请根据自身情况做出决策
## 贡献
欢迎提交 Issue 和 Pull Request!
## 许可证
MIT License - 详见 [LICENSE](LICENSE)
## 致谢
- [yfinance](https://github.com/ranaroussi/yfinance) - Yahoo Finance 数据
- [Alpha Vantage](https://www.alphavantage.co/) - 专业技术指标
- [Claude Code](https://claude.ai) - AI 编程助手
---
如果觉得有用,请给个 Star ⭐ 支持一下!

View File

@ -0,0 +1,135 @@
---
name: stock-master
description: 综合性股票技术分析工具,小白友好。采用混合数据源(Yahoo Finance + Alpha Vantage MCP),提供通俗易懂的分析报告、买卖点建议、Excel持仓管理。支持港股本地计算、ATR动态止损、KDJ随机指标、MACD/RSI背离检测、OBV量能分析、斐波那契支撑阻力位、K线形态识别(锤子线/吞没/十字星)、趋势形态识别(双底/头肩/三角形)。支持飞书多维表格同步。当用户请求股票分析、技术指标、交易建议、持仓分析时激活。
---
# Stock Master v3.5 - 小白友好版
面向普通投资者的技术分析工具,用日常语言解释指标,给出明确买卖建议。
> **GitHub**: https://github.com/EagleF6432614/stock-master
>
> 如果觉得有用,请给个 Star ⭐
## 快速参考
### 数据源策略
| 数据 | 美股 | 港股/A股 |
|------|------|----------|
| 价格 | Yahoo Finance | Yahoo Finance |
| RSI/布林带 | Alpha Vantage MCP | 本地计算 |
| MACD/KDJ/ATR/均线/OBV/形态 | 本地计算 | 本地计算 |
### 指标小白解读
**RSI** (相对强弱):
- <30 超卖 → "大甩卖,可能捡便宜"
- >70 超买 → "被抢购一空,小心回调"
**MACD**:
- 金叉 → "踩油门加速,买入信号"
- 死叉 → "松油门减速,卖出警告"
**KDJ** (随机指标):
- 金叉/J<0 → "绿灯亮了,短期买入"
- 死叉/J>100 → "红灯亮了,短期卖出"
**布林带**:
- 跌破下轨 → "橡皮筋拉太长,可能反弹"
- 突破上轨 → "涨过头了,可能回落"
**背离信号**:
- 底背离 → "价格创新低但动能减弱,反弹信号"
- 顶背离 → "价格创新高但动能减弱,回调信号"
**K线形态** [v3.4]:
- 锤子线/早晨之星/看涨吞没 → 底部反转信号
- 上吊线/黄昏之星/看跌吞没 → 顶部反转信号
- 三只白兵/三只乌鸦 → 强趋势确认
**趋势形态** [v3.4]:
- 双底(W底)/头肩底 → 底部反转,看涨
- 双顶(M头)/头肩顶 → 顶部反转,看跌
- 上升三角形 → 通常向上突破
- 下降三角形 → 通常向下突破
### 交易建议评分 (v3.4)
| 分数 | 建议 | 仓位 |
|------|------|------|
| ≥6 | 强烈买入 | 30% |
| 3-5 | 建议买入 | 20% |
| -2~2 | 观望 | - |
| -3~-5 | 建议卖出 | - |
| ≤-6 | 强烈卖出 | - |
### 形态信号权重 [v3.4]
| 形态类型 | 权重 |
|----------|------|
| 三只白兵/乌鸦、早晨/黄昏之星、头肩、双底双顶 | ±3 |
| 看涨/看跌吞没、上升/下降三角形 | ±2 |
| 锤子线、十字星等单K线形态 | ±1 |
## 使用流程
### 1. 分析单只股票
```
用户: "分析 TSLA" / "看看苹果" / "0700.HK 能买吗"
```
**执行步骤**:
1. 获取 Yahoo Finance 实时价格和历史数据
2. 美股: 调用 Alpha Vantage MCP 获取 RSI/布林带
3. 港股/A股或API失败: 使用 `scripts/indicators.py` 本地计算
4. 计算全部指标 (RSI/MACD/KDJ/OBV/背离/支撑阻力/形态)
5. 调用 `scripts/beginner_analyzer.py` 生成小白友好报告
### 2. 持仓管理
```
用户: "创建持仓表格" / "分析我的持仓" / "更新持仓价格"
```
**Excel 路径**: 可在 `config.json` 中配置,默认为当前目录下的 `my_portfolio.xlsx`
### 3. 对比多只股票
```
用户: "对比 AAPL 和 GOOGL" / "这几只哪个好: TSLA, NVDA"
```
### 4. 飞书同步 [v3.5]
```
用户: "同步分析结果到飞书" / "更新飞书持仓"
```
**飞书多维表格配置**: 请在 `feishu_config.json` 中配置你的飞书应用凭证和表格 ID。
**同步逻辑**: 本地分析 → 飞书(单向,飞书为主库)
## 配置说明
使用前请创建 `config.json` 文件(参考 `config.example.json`):
```json
{
"portfolio_path": "./my_portfolio.xlsx",
"feishu_config_path": "./feishu_config.json"
}
```
## 风险提示(必须包含)
每份报告必须包含:
1. 仅供参考,不构成投资建议
2. 股市有风险,投资需谨慎
3. 建议分批建仓,设置止损
## 详细文档
- 投资智慧与哲学: [references/investment-wisdom.md](references/investment-wisdom.md)
- Excel 持仓管理: [references/portfolio-guide.md](references/portfolio-guide.md)
- 飞书同步指南: [references/feishu-guide.md](references/feishu-guide.md)
- MCP 工具调用: [references/mcp-tools.md](references/mcp-tools.md)
- 脚本详细说明: [references/scripts-guide.md](references/scripts-guide.md)
- 更新日志: [references/changelog.md](references/changelog.md)
## 许可证
MIT License - 详见 [LICENSE](LICENSE)

View File

@ -0,0 +1,6 @@
{
"_comment": "Stock Master 配置文件示例 - 请复制为 config.json 并填入你的配置",
"portfolio_path": "./my_portfolio.xlsx",
"feishu_config_path": "./feishu_config.json"
}

View File

@ -0,0 +1,16 @@
{
"_comment": "飞书多维表格配置文件示例 - 请复制为 feishu_config.json 并填入你的配置",
"_docs": "获取这些信息请参考: https://open.feishu.cn/document/server-docs/docs/bitable-v1/bitable-overview",
"APP_ID": "your_app_id_here",
"APP_SECRET": "your_app_secret_here",
"APP_TOKEN": "your_bitable_app_token_here",
"TABLE_ID": "your_default_table_id_here",
"_table_ids": {
"_comment": "可选:配置多个表格 ID",
"signals": "技术信号表的 table_id",
"holdings": "持仓管理表的 table_id",
"trades": "交易记录表的 table_id"
}
}

View File

@ -0,0 +1,72 @@
# 更新日志
## v3.5.0 (2026-01-22)
- **飞书多维表格集成**: 支持将分析结果同步到飞书
- 技术信号表: 股票分析结果、评分、买卖建议
- 持仓管理表: 持仓记录、成本、盈亏
- 交易记录表: 买卖历史、触发信号
- **单向同步架构**: 本地分析 → 飞书(飞书为主库,避免冲突)
- **自动表结构初始化**: `feishu_init_tables.py` 一键创建表结构
- 新增 `scripts/feishu_sync.py` 飞书 API 封装
- 新增 `scripts/feishu_init_tables.py` 表结构初始化
## v3.4.1 (2026-01-22)
- **重命名**: stock-analyzer → stock-master
- **路径变更**: Excel 默认存储路径改为 `/Users/liyanda/Desktop/AI编程/stock master/`
## v3.4.0 (2026-01-22)
- **K线形态识别**: 识别经典K线形态
- 单K线: 十字星、锤子线、上吊线、射击之星、倒锤子
- 双K线: 看涨吞没、看跌吞没
- 三K线: 早晨之星、黄昏之星、三只白兵、三只乌鸦
- **趋势形态识别**: 识别经典图表形态
- 反转形态: 双底(W底)、双顶(M头)、头肩顶、头肩底
- 整理形态: 上升三角形、下降三角形、对称三角形
- **形态小白解读**: 用通俗语言解释每种形态的含义和操作建议
- **评分系统升级**: 形态信号纳入评分,强形态±3分,中等形态±2分,弱形态±1分
- 新增 `identify_candlestick_patterns()` 函数
- 新增 `identify_chart_patterns()` 函数
- 新增 `analyze_patterns()` 综合形态分析函数
- 新增 `explain_patterns_simple()` 形态解读函数
## v3.3.0 (2026-01-22)
- **KDJ 随机指标**: 国内股民最常用的短线指标,支持金叉/死叉/超买超卖判断
- **MACD/RSI 背离检测**: 自动检测顶背离和底背离,强烈反转信号
- **增强版支撑阻力位**: 斐波那契回撤位 + 扩展位 + 整数关口
- **OBV 量能指标**: 能量潮分析,检测资金流向
- **威廉指标 Williams %R**: 短期超买超卖判断
- **乖离率 BIAS**: 股价偏离均线程度分析
- **评分系统优化**: 调整阈值,背离信号权重最高 (±4分)
- **止损止盈优化**: 结合支撑阻力位优化价格建议
- 新增多个小白解读函数
## v3.2.0 (2026-01-21)
- **港股本地计算**: 自动检测港股,使用 Yahoo Finance 本地计算所有指标
- **优雅降级**: Alpha Vantage API 失败时自动回退到本地计算
- **ATR 动态止损**: 根据股票波动性自动调整止损距离
- **成交量分析**: 量价配合分析(放量/缩量 + 涨跌)
- **均线系统**: MA5/10/20/60 + 多空排列判断
- **仓位建议**: 根据信号强度建议仓位百分比(高信号30%/中20%/低10%)
- **风险收益比**: 自动计算并显示(默认目标 2.5:1)
- 新增 `scripts/indicators.py` 本地指标计算模块
## v3.1.0 (2026-01-21)
- 新增「交易建议」工作表自动生成功能
- 每次持仓分析后自动更新买卖点位到 Excel
- 新增 `update_trading_recommendations()` 函数
- 优化做T操作填写说明
## v3.0.0 (2026-01-21)
- 新增小白友好分析语言
- 新增买卖点价格建议
- 新增 Excel 持仓管理
- 新增持仓分析功能
- 支持简洁版/详细版报告
## v2.0.0 (2026-01-20)
- 混合数据源架构
- Alpha Vantage MCP 集成
- 数据源验证
## v1.0.0 (2025-10-23)
- 初始版本

View File

@ -0,0 +1,225 @@
# 飞书多维表格同步指南
Stock Master 支持将分析结果同步到飞书多维表格,实现云端管理。
## 功能概览
- **技术信号同步**:股票分析结果实时同步
- **持仓管理**:云端管理持仓数据
- **交易记录**:记录每次买卖操作
- **多端访问**:手机、电脑随时查看
## 前置准备
### 1. 创建飞书应用
1. 访问 [飞书开放平台](https://open.feishu.cn/)
2. 创建企业自建应用
3. 获取 `App ID` 和 `App Secret`
### 2. 配置应用权限
在应用的"权限管理"中添加以下权限:
- `bitable:app` - 多维表格应用权限
- `bitable:app:readonly` - 读取多维表格
### 3. 创建多维表格
1. 在飞书中创建新的多维表格
2. 记录 `App Token`(在表格 URL 中)
**URL 格式**:
```
https://xxx.feishu.cn/base/xxxxAPP_TOKENxxxx?table=tblXXXX
```
## 配置文件
创建 `feishu_config.json`:
```json
{
"APP_ID": "cli_xxxxxxxxx",
"APP_SECRET": "xxxxxxxxxxxxxxxx",
"APP_TOKEN": "xxxxxxxxxxxxxxxx",
"TABLE_ID": "tblXXXXXXXXXX"
}
```
| 字段 | 说明 | 获取方式 |
|------|------|----------|
| APP_ID | 应用 ID | 开放平台 → 应用凭证 |
| APP_SECRET | 应用密钥 | 开放平台 → 应用凭证 |
| APP_TOKEN | 多维表格 Token | 表格 URL 中获取 |
| TABLE_ID | 默认数据表 ID | 表格 URL 的 table 参数 |
## 初始化表结构
运行初始化脚本创建标准表结构:
```bash
python scripts/feishu_init_tables.py
```
或在 Claude 中说:
```
初始化飞书表格
```
### 自动创建的表
#### 1. 技术信号表
| 字段 | 类型 | 说明 |
|------|------|------|
| 股票代码 | 文本 | 股票代码 |
| 股票名称 | 文本 | 公司名称 |
| 当前价格 | 数字 | 最新价格 |
| 综合评分 | 数字 | -10 到 +10 |
| 操作建议 | 单选 | 强烈买入/建议买入/观望/建议卖出/强烈卖出 |
| RSI | 数字 | RSI 指标值 |
| MACD信号 | 单选 | 金叉/多头/空头/死叉 |
| KDJ信号 | 单选 | 金叉/超卖/中性/超买/死叉 |
| 背离信号 | 单选 | 无/底背离/顶背离 |
| 形态信号 | 多选 | 识别到的形态 |
| 止损价 | 数字 | 建议止损价 |
| 止盈价 | 数字 | 建议止盈价 |
| 分析理由 | 文本 | 详细分析理由 |
| 更新时间 | 日期 | 最后更新时间 |
#### 2. 持仓管理表
| 字段 | 类型 | 说明 |
|------|------|------|
| 股票代码 | 文本 | 股票代码 |
| 股票名称 | 文本 | 公司名称 |
| 市场 | 单选 | 美股/港股/A股 |
| 持仓数量 | 数字 | 持有股数 |
| 成本价 | 数字 | 买入均价 |
| 当前价 | 数字 | 最新价格 |
| 市值 | 数字 | 持仓市值 |
| 盈亏金额 | 数字 | 浮动盈亏 |
| 盈亏比例 | 数字 | 盈亏百分比 |
| 买入日期 | 日期 | 建仓日期 |
| 备注 | 文本 | 备注信息 |
#### 3. 交易记录表
| 字段 | 类型 | 说明 |
|------|------|------|
| 股票代码 | 文本 | 股票代码 |
| 交易类型 | 单选 | 买入/卖出/做T买/做T卖 |
| 交易价格 | 数字 | 成交价格 |
| 交易数量 | 数字 | 成交数量 |
| 交易金额 | 数字 | 成交金额 |
| 交易时间 | 日期 | 成交时间 |
| 触发信号 | 文本 | 触发交易的信号 |
| 备注 | 文本 | 交易备注 |
## 使用命令
### 同步分析结果
```
分析 AAPL 并同步到飞书
```
### 同步持仓
```
同步持仓到飞书
```
### 记录交易
```
记录交易:买入 TSLA 10股 @ $400
```
### 查看飞书数据
```
查看飞书持仓
```
## 同步逻辑
### 数据流向
```
本地分析 ────────────→ 飞书多维表格
(单向同步)
```
### 更新规则
- **技术信号**:按股票代码更新,已存在则覆盖
- **持仓数据**:按股票代码更新,已存在则覆盖
- **交易记录**:始终追加,不覆盖
## API 使用示例
### Python 代码调用
```python
from scripts.feishu_sync import FeishuBitable, sync_stock_signal
# 初始化连接
bitable = FeishuBitable()
# 同步信号
signal_data = {
'ticker': 'AAPL',
'name': '苹果',
'current_price': 185.5,
'score': 5,
'action': 'BUY',
'rsi': 45.2,
'macd_signal': '金叉',
'reasons': ['RSI 超卖', 'MACD 金叉']
}
sync_stock_signal(bitable, signal_data)
# 测试连接
from scripts.feishu_sync import test_connection
result = test_connection()
print(result)
```
### 快速同步
```python
from scripts.feishu_sync import quick_sync_signal
quick_sync_signal({
'ticker': 'TSLA',
'score': -3,
'action': 'SELL',
'rsi': 75,
'reasons': ['RSI 超买']
})
```
## 常见问题
### Q: 提示 "获取 token 失败"
A: 检查 APP_ID 和 APP_SECRET 是否正确,应用是否已发布。
### Q: 提示 "无权限访问"
A:
1. 检查应用权限是否已添加
2. 检查多维表格是否已授权给应用
3. 在表格设置中添加应用为协作者
### Q: 字段类型不匹配
A: 运行 `python scripts/feishu_init_tables.py` 重新初始化表结构。
### Q: 如何查看 Table ID?
A: 在飞书表格 URL 中:
```
https://xxx.feishu.cn/base/APP_TOKEN?table=TABLE_ID
```
## 安全建议
1. **不要提交配置文件**:`feishu_config.json` 已在 `.gitignore` 中
2. **定期轮换密钥**:建议定期更换 APP_SECRET
3. **最小权限原则**:只授予必要的权限
---
*如有问题,欢迎在 GitHub 提 Issue!*

View File

@ -0,0 +1,157 @@
# 投资智慧与交易哲学
> 本文档为投资感悟汇总,可作为分析时的智慧佐证。
## 目录
1. [股民的十大境界](#股民的十大境界)
2. [交易核心原则](#交易核心原则)
3. [技术分析智慧](#技术分析智慧)
4. [心态与纪律](#心态与纪律)
5. [人生哲学](#人生哲学)
---
## 股民的十大境界
股民的成长是一个漫长的修炼过程,从新手到高手,需要经历十个境界的蜕变。**能够达到最高境界的股民,万人之中或许才能有一人。**
### 第一境界:雄心勃勃的新股民
初入股市的新股民,雄心勃勃,对理财投资充满兴趣,想要在股市里面实现"钱生钱"。这样的股民通常都是在牛市中跑步进场的。
### 第二境界:初战告捷的痴迷者
新股民在牛市中初战告捷,赚了一点小钱,痴迷于每天买入卖出,对于股评家的话深信不疑,每天都会认真地看股评家们煞有其事的分析。
### 第三境界:开始怀疑的探索者
这个时候的新股民,发现股评家的话其实也是不可信的,时而准确时而不准。更重要的是,新股民发现在股市里面赚钱,并非一开始想的那么简单。于是开始找寻各种资料,学着分析公司基本面情况,也就是所谓的价值投资。但很多时候都会发现,公司的财报盈利都很好,但股票不但不涨,反而还下跌!感觉价值投资都是假的。
### 第四境界:苦心钻研的韭菜
这个境界算是从新股民晋升到了韭菜。他们开始苦心钻研各种技术指标,认真学习,按照指标操作,发现时灵时不灵,偶尔能够小赚,但更多的时候还是大亏!操作上也是一团混乱,时而短线,时而长线,对于指标懂的是越来越多,但资金却是越来越少!这个阶段的韭菜,要么就是对指标彻底失望,要么就是对股市开始绝望。
### 第五境界:迷茫的老韭菜 ⚠️ 关键分水岭
这是新韭菜变成老韭菜的阶段。处于这个境界的股民,对于股市也有了属于自己的认识,对于各种技术指标更是能够滔滔不绝讲出来它该怎么使用。但会陷入一个迷茫阶段,因为不论是根据指标操作,还是根据上市公司基本面情况去买入,总是亏损多过于盈利,怎么做怎么错,股市好像和自己在作对一样。
有许多人,都会从第一境界慢慢进入到这第五境界中,然后就一直停留在这个阶段。感觉什么技术指标、基本面都是假的,尤其是那些技术指标,假如不理会,随便乱买入的话,成功率居然还会高过依据技术指标买入。然后就认为,什么技术指标基本上通通都没有任何用处,炒股就是赌博!运气好了就赚钱,运气不好就亏钱!然后就放任自己,也不再继续学习了,就凭借直觉买入卖出,渐渐的,又是不知不觉地回到了以前追涨杀跌的日子里。
**第五个境界非常关键,许多股民朋友其实都是在这个境界之中,迷失了自己,也停下了脚步。**
### 第六境界:初成的高手
能够进入到第六境界的股民,一般就比较少了。第六境界的股民就算是高手了,他们拥有多年的股龄,通过不断的实战,逐渐摸索出来一套属于自己的交易模式。虽然还会亏损,但已经能够控制心态,将亏损视为是交易的一部分,用平常心来对待,交易的时候,盈多亏小。
### 第七境界:悟道的交易者
第七境界的股民,已经不看所谓的任何技术指标,只是简单看看上市公司的基本情况,明白这家公司是做什么的。然后就采取趋势波段交易的方法,当趋势向上,就买入持股不动,当趋势走坏,就直接清仓离场,然后等待下一次交易机会。**这种股民,其实就已经摸索出来属于自己的"道"了。**
### 第八境界:成熟稳定的交易者
第八境界的股民,已经有了属于自己的、完整独特的交易模式。他们不论别人怎么说,只相信自己。每天也只是在收盘后,看看买入股票的走势,不破趋势线,对于股价的波动涨跌,毫无任何心理波动。他们的交易模式越发成熟、稳定。
### 第九境界:手中有股,心中无股
第九境界的股民,就像是武侠小说之中所说的"手中有股,心里无股"。他们对于整个股市的波动,都了然于胸,炒股时,进退自如。
### 第十境界:财务自由的智者
第十境界的股民,不会再痴迷于股市,而是将股市当做一个投资场所,每年都可以实现固定盈利。通过这个境界的股民,早就已经达到了财务自由的阶段,对于股市更是十分了解。**他们已经明白,股市其实就是人生的道理!能够达到这个境界的股民,万人之中或许才能够有一人。**
---
## 交易核心原则
### 止损的艺术
- **3%左右就割肉离场**:如果追高买入后第二天跌了,证明看错了,立即止损认错
- **超出判断范围必须止损**:一旦超出判断的范围,说明后面的走势看不懂了
- **买入前想好止盈止损**:发现判断错误就必须执行交易纪律,否则就是跳上了失控的列车
### 仓位管理
- **永不满仓**:市场充满未知风险,不论如何看好一只股票,都要学会分批买卖
- **3136建仓法**:金字塔式买入方法,可以有效拉低持股成本
- **控制仓位是避免亏损的核心**:将风险控制到自己能够承受的范围里
### 交易纪律
- **上升的股票不做T**:买入后别乱动,想好止盈止损位置,连续大涨就果断卖掉
- **记录与复盘**:每次买入记录理由和逻辑,亏损离场后对比分析错在哪里
---
## 技术分析智慧
### K线与形态
- **得双底者得天下**:确定底部要看二次跌到这个价格时收什么样的K线组合
- **低位十字星**:连续下跌后收低位十字星,可能出现反转,意味着下跌结束
- **跳空缺口**:出现第一个向上的跳空缺口,是进入强势阶段的显著特点
- **穿头破脚**:连续上涨后高位放巨量,上破前日最高、跌破前日最低
- **周K线必看**:不论短线还是长线投资者,周K线都是必须要看的
### 成交量与换手率
- **成交量是汽油**:只有不断放量的股票,才能够走得更远
- **换手率保持范围**:连续上涨的股票,换手率都会保持在一个范围内
- **洗盘与出货的区别**:最大的区别就是在于成交量和换手率
### 主力资金逻辑
- **缩量下跌是温水煮青蛙**:主力资金在高位震荡时已派发筹码,缩量下跌迷惑散户
- **庄股特征明显**:绝对控盘的庄股,庄家可以随心所欲控制在几分钱的波动内
---
## 心态与纪律
### 核心心法
- **心态第一,技术第二**
- **控制情绪,控制欲望**:想要在市场里成功,这是第一个必须做到的要求
- **每临大事有静气**:能够克制住自己的脾性,任何事情都能够坦然平静地对待
### 牛熊市智慧
- **牛市重势,熊市重质**
- **牛市炒幻想,熊市炒确定性**
- **牛熊转换之际**:市场主力都会做出种种假象来迷惑你
- **历史总是惊人的相似,但又不会简单地重复**
### 复盘的重要性
- **复盘目的**:对股票池中的股票有了解,分析明天可能的走势
- **完整复盘至少两三个小时**:不打无准备之仗,不打无把握之仗
- **市场就是最好的老师**:要学会在市场里面学习
---
## 人生哲学
### 关于财富
- **财不入急门**:别想着一夜暴富,在股市里一夜暴富是不可能的
- **道不轻传,法不贱卖,师不顺路,医不叩门**
### 关于吃苦
- **穷并不是吃苦,穷就是穷**
- **吃苦的本质**:是一种自控能力、自制能力、坚持能力和深度思考的能力
- **为了完成某个目标,能够长时间地将精力集中在这个目标上**
### 关于学习
- **穷人之所以穷,就是因为他们不愿意去学习**
- **富人或成功的人,共同的特点就是喜欢学习**
### 关于人性
- **人的贪婪、恐惧、希望、幻想等等,永远都不会变**
- **太阳底下没有新鲜事**
- **真正的技术派:初级看K线,中级看整体,高级看人性**
- **读懂人性了,也就等于读懂市场了**
### 技术与心态
- **技术可以让你赚到钱,但绝对是小钱**
- **想要在市场里赚到大钱,必须有心态的支持**
- **低手追求高技术,高手追求以道驭术**
---
## 风险警示
- **炒股千万别融资,别加杠杆**:遇到风险时,连一点抗风险的机会都没有
- **投资是把钱扔出去,但必须能再弹回来**:这根线就叫做风险控制
- **凭借运气赚来的钱,凭实力还给了市场**
- **散户的优势是船小好调头**:顺应趋势才是散户要做的事情
---
## 结语:股市修行之路
- **炒股是一条修行的路**:只有不断提高自己,不断回头看看自己的缺点,最终才能够获得成功
- **看山是山,看山不是山,看山还是山**:炒股同样完全适用
- **涨多了肯定要跌,跌多了肯定要涨**:就像是潮起潮落一样
---
*欢迎贡献你的投资智慧!*

View File

@ -0,0 +1,93 @@
# Alpha Vantage MCP 工具调用指南
## 目录
- [获取 RSI](#获取-rsi)
- [获取布林带](#获取布林带)
- [获取报价](#获取报价)
- [响应解析](#响应解析)
---
## 获取 RSI
```python
mcp__Alpha-Vantage__TOOL_CALL(
tool_name="RSI",
arguments={"symbol": "TSLA", "interval": "daily",
"time_period": 14, "series_type": "close", "datatype": "json"}
)
```
**参数说明**:
- `symbol`: 股票代码(仅支持美股)
- `interval`: daily / weekly / monthly
- `time_period`: RSI 周期,通常 14
- `series_type`: close / open / high / low
---
## 获取布林带
```python
mcp__Alpha-Vantage__TOOL_CALL(
tool_name="BBANDS",
arguments={"symbol": "TSLA", "interval": "daily",
"time_period": 20, "series_type": "close",
"nbdevup": 2, "nbdevdn": 2, "datatype": "json"}
)
```
**参数说明**:
- `time_period`: 移动平均周期,通常 20
- `nbdevup`: 上轨标准差倍数,通常 2
- `nbdevdn`: 下轨标准差倍数,通常 2
---
## 获取报价
```python
mcp__Alpha-Vantage__TOOL_CALL(
tool_name="GLOBAL_QUOTE",
arguments={"symbol": "AAPL", "datatype": "json"}
)
```
---
## 响应解析
### RSI 响应示例
```json
{
"Meta Data": {
"Symbol": "TSLA",
"Indicator": "Relative Strength Index (RSI)"
},
"Technical Analysis: RSI": {
"2026-01-21": {"RSI": "45.2341"}
}
}
```
### 布林带响应示例
```json
{
"Technical Analysis: BBANDS": {
"2026-01-21": {
"Real Upper Band": "250.5432",
"Real Middle Band": "240.1234",
"Real Lower Band": "229.7036"
}
}
}
```
---
## 注意事项
1. **API 限制**: 免费版每天 25 次请求,注意控制调用频率
2. **延时**: 免费版数据有 15 分钟延时
3. **港股不支持**: Alpha Vantage 不支持港股,需使用本地计算
4. **MACD 不支持**: 免费版不支持 MACD,需本地计算

View File

@ -0,0 +1,152 @@
# Excel 持仓管理指南
Stock Master 提供完整的 Excel 持仓管理功能,帮助你跟踪投资组合。
## 快速开始
### 创建持仓表格
在 Claude 中说:
```
创建持仓表格
```
系统会自动创建包含以下 Sheet 的 Excel 文件:
## 表格结构
### Sheet 1: 持仓
| 列名 | 说明 | 填写方式 |
|------|------|----------|
| 股票代码 | 股票代码 | 手动填写 |
| 股票名称 | 公司名称 | 手动填写 |
| 持仓数量 | 当前持有股数 | 手动填写 |
| 买入均价 | 平均成本价 | 手动填写 |
| 当前价格 | 实时价格 | **自动更新** |
| 市值 | 持仓数量 × 当前价格 | **自动计算** |
| 盈亏金额 | 市值 - 成本 | **自动计算** |
| 盈亏比例 | 盈亏百分比 | **自动计算** |
| 买入日期 | 建仓日期 | 手动填写 |
| 备注 | 备注信息 | 手动填写 |
**股票代码格式**:
- 美股:`AAPL`, `TSLA`, `GOOGL`
- 港股:`0700.HK`, `9988.HK`
- A股:`600519.SS`(上海), `000001.SZ`(深圳)
### Sheet 2: 交易记录
| 列名 | 说明 |
|------|------|
| 日期 | 交易日期 |
| 股票代码 | 交易的股票 |
| 操作 | 买入 / 卖出 |
| 数量 | 交易股数 |
| 价格 | 成交价格 |
| 金额 | 交易总额 |
| 手续费 | 交易费用 |
| 备注 | 交易备注 |
### Sheet 3: 账户汇总
自动汇总的账户信息:
- 总投入
- 当前市值
- 总盈亏
- 收益率
- 最后更新时间
### Sheet 4: 使用说明
内置的使用指南。
## 使用命令
### 分析持仓
```
分析我的持仓
```
Claude 会:
1. 读取你的持仓数据
2. 获取每只股票的实时价格
3. 更新 Excel 中的价格和盈亏
4. 给出每只股票的技术分析建议
### 更新价格
```
更新持仓价格
```
只更新价格,不做分析。
### 持仓建议
```
我的持仓哪些该卖了?
```
根据技术指标给出持仓调整建议。
## 做T操作说明
持仓表只记录"结果",不需要记录每次做T操作。
**示例**:
- TSLA 做了几轮T,最终持仓 100股,均价 $380
- 直接填:数量=100,均价=380
- 均价可从券商 App 查看
如需追踪每次操作,可在"交易记录"表记录。
## 配置文件
### 默认路径
系统按以下顺序查找持仓文件:
1. 环境变量 `PORTFOLIO_PATH`
2. `config.json` 中的 `portfolio_path`
3. 当前目录的 `my_portfolio.xlsx`
### 自定义路径
创建 `config.json`:
```json
{
"portfolio_path": "/your/custom/path/portfolio.xlsx"
}
```
或设置环境变量:
```bash
export PORTFOLIO_PATH="/your/custom/path/portfolio.xlsx"
```
## 示例模板
创建后的持仓表包含示例数据,帮助理解格式:
```
| 股票代码 | 股票名称 | 持仓数量 | 买入均价 | 当前价格 | 市值 | 盈亏金额 | 盈亏比例 | 买入日期 | 备注 |
|----------|----------|----------|----------|----------|------|----------|----------|----------|------|
| AAPL | 苹果公司 | 10 | 180.00 | | | | | 2024-01-15 | 长期持有 |
| TSLA | 特斯拉 | 5 | 400.00 | | | | | 2024-06-01 | |
| GOOGL | 谷歌 | 3 | 150.00 | | | | | 2024-03-20 | |
```
> 提示:删除示例数据,填入你自己的持仓即可。
## 常见问题
### Q: 如何处理分红?
A: 分红后股价会除权,建议在备注中记录分红信息。
### Q: 多个账户怎么管理?
A: 可以创建多个 Excel 文件,分析时指定路径:
```
分析 /path/to/账户1.xlsx 的持仓
```
### Q: 港股如何处理汇率?
A: 目前按港币计算,建议在备注中记录汇率。
---
*如有问题,欢迎在 GitHub 提 Issue!*

View File

@ -0,0 +1,225 @@
# 脚本详细说明 v3.4
## 目录
- [main.py - 主协调器](#mainpy---主协调器)
- [beginner_analyzer.py - 小白分析模块](#beginner_analyzerpy---小白分析模块)
- [indicators.py - 本地指标计算](#indicatorspy---本地指标计算)
- [portfolio.py - 持仓管理](#portfoliopy---持仓管理)
---
## main.py - 主协调器
混合数据源架构的核心协调器。
### 数据源策略
| 数据 | 来源 | 原因 |
|------|------|------|
| 实时价格 | Yahoo Finance | 接近实时,无请求限制 |
| MACD | Yahoo + 本地计算 | AV 免费版不支持 |
| RSI | Alpha Vantage MCP | 官方预计算,准确 |
| 布林带 | Alpha Vantage MCP | 官方预计算,准确 |
| KDJ/OBV/背离/形态 | 本地计算 | AV 不提供 |
---
## beginner_analyzer.py - 小白分析模块
用通俗语言解释技术指标。
### 小白解读函数
| 函数 | 功能 |
|------|------|
| `explain_rsi_simple()` | RSI 通俗解释 |
| `explain_macd_simple()` | MACD 通俗解释 |
| `explain_bollinger_simple()` | 布林带通俗解释 |
| `explain_volume_simple()` | 成交量通俗解释 |
| `explain_ma_simple()` | 均线通俗解释 |
| `explain_atr_simple()` | ATR 波动性解释 |
| `explain_kdj_simple()` | KDJ 通俗解释 |
| `explain_divergence_simple()` | 背离信号解释 |
| `explain_obv_simple()` | OBV 量能解释 |
| `explain_williams_simple()` | 威廉指标解释 |
| `explain_bias_simple()` | 乖离率解释 |
| `explain_support_resistance_simple()` | 支撑阻力位解释 |
| `explain_candlestick_pattern_simple()` | [v3.4] K线形态解释 |
| `explain_chart_pattern_simple()` | [v3.4] 趋势形态解释 |
| `explain_patterns_simple()` | [v3.4] 综合形态解释 |
### 报告生成
| 函数 | 功能 |
|------|------|
| `generate_trading_recommendation()` | 综合交易建议 (含评分) |
| `format_simple_report()` | 简洁版报告 |
| `format_detailed_report()` | 详细版报告 |
### 评分规则 (v3.4)
| 分数范围 | 建议 |
|----------|------|
| ≥6 | 强烈买入 |
| 3-5 | 建议买入 |
| -2~2 | 观望 |
| -3~-5 | 建议卖出 |
| ≤-6 | 强烈卖出 |
### 评分权重
| 信号类型 | 权重 | 说明 |
|----------|------|------|
| 背离信号 | ±4 | 最强信号 |
| 强形态信号 | ±3 | [v3.4] 三只白兵/乌鸦、早晨/黄昏之星、头肩、双底双顶 |
| 金叉/死叉 | ±3 | 强信号 |
| 中等形态 | ±2 | [v3.4] 吞没、三角形 |
| 超买/超卖 | ±2~3 | 中强信号 |
| 弱形态/趋势确认 | ±1~2 | 辅助信号 |
---
## indicators.py - 本地指标计算
支持港股和 API 降级时的本地计算。
### 基础指标函数
| 函数 | 功能 |
|------|------|
| `calculate_rsi()` | 计算 RSI |
| `calculate_bollinger_bands()` | 计算布林带 |
| `calculate_macd()` | 计算 MACD |
| `calculate_atr()` | 计算 ATR |
| `calculate_ma_system()` | 计算均线系统 |
| `calculate_volume_analysis()` | 成交量分析 |
### v3.3 新增指标函数
| 函数 | 功能 |
|------|------|
| `calculate_kdj()` | KDJ 随机指标 |
| `calculate_obv()` | OBV 能量潮 |
| `calculate_williams_r()` | 威廉指标 |
| `calculate_bias()` | 乖离率 |
| `detect_divergence()` | 通用背离检测 |
| `detect_macd_divergence()` | MACD 背离检测 |
| `detect_rsi_divergence()` | RSI 背离检测 |
| `calculate_support_resistance_enhanced()` | 增强版支撑阻力位 |
### v3.4 形态识别函数
| 函数 | 功能 |
|------|------|
| `identify_candlestick_patterns()` | K线形态识别 |
| `identify_chart_patterns()` | 趋势形态识别 |
| `analyze_patterns()` | 综合形态分析 |
### K线形态识别
| 形态 | 信号 | 强度 |
|------|------|------|
| 三只白兵 | 看涨 | 非常强 |
| 三只乌鸦 | 看跌 | 非常强 |
| 早晨之星 | 看涨 | 强 |
| 黄昏之星 | 看跌 | 强 |
| 看涨吞没 | 看涨 | 强 |
| 看跌吞没 | 看跌 | 强 |
| 锤子线 | 看涨 | 中 |
| 上吊线 | 看跌 | 中 |
| 十字星 | 中性 | 弱 |
### 趋势形态识别
| 形态 | 信号 | 说明 |
|------|------|------|
| 双底(W底) | 看涨 | 经典底部反转 |
| 双顶(M头) | 看跌 | 经典顶部反转 |
| 头肩底 | 看涨 | 可靠底部反转 |
| 头肩顶 | 看跌 | 可靠顶部反转 |
| 上升三角形 | 看涨 | 通常向上突破 |
| 下降三角形 | 看跌 | 通常向下突破 |
| 对称三角形 | 中性 | 等待突破方向 |
### 支撑阻力位计算方法
1. **近期高低点** - 60日内最高最低
2. **斐波那契回撤** - 23.6%, 38.2%, 50%, 61.8%, 78.6%
3. **斐波那契扩展** - 127.2%, 161.8%, 200%
4. **整数关口** - 心理价位
### 综合分析
```python
analyze_stock_local(ticker, period='3mo')
```
返回所有指标的完整分析结果,包括:
- 基础指标 (RSI/MACD/布林带/ATR/均线/成交量)
- v3.3 新增 (KDJ/OBV/威廉/乖离率)
- 支撑阻力位 (斐波那契)
- 背离检测 (MACD/RSI)
- v3.4 形态识别 (K线形态/趋势形态)
- 动态止损建议
---
## portfolio.py - 持仓管理
Excel 持仓表管理。
### 核心函数
| 函数 | 功能 |
|------|------|
| `create_portfolio_template()` | 创建 Excel 模板 |
| `read_portfolio()` | 读取持仓数据 |
| `update_portfolio_prices()` | 更新实时价格 |
| `update_trading_recommendations()` | 更新交易建议表 |
| `format_portfolio_summary()` | 格式化持仓汇总 |
### 股票代码格式
- 美股: `AAPL`, `TSLA`, `GOOGL`
- 港股: `0700.HK`, `9988.HK`
- A股: `600519.SS` (上海), `000001.SZ` (深圳)
---
## feishu_sync.py - 飞书多维表格同步 [v3.5]
将分析结果同步到飞书多维表格。
### 核心类
| 类 | 功能 |
|------|------|
| `FeishuBitable` | 飞书多维表格 API 封装 |
### 同步函数
| 函数 | 功能 |
|------|------|
| `sync_stock_signal()` | 同步股票技术信号(覆盖更新) |
| `sync_holding()` | 同步持仓数据(覆盖更新) |
| `sync_trade_record()` | 同步交易记录(追加模式) |
| `batch_sync_signals()` | 批量同步多只股票 |
| `quick_sync_signal()` | 快速同步(一行调用) |
| `test_connection()` | 测试飞书连接 |
### 飞书表结构
| 表名 | Table ID | 用途 |
|------|----------|------|
| 数据表 | tbldtKCoANcpvitS | 技术信号 |
| 持仓管理 | tblHkx30pGT1QzOq | 持仓记录 |
| 交易记录 | tblTnlPDFmr5gmSV | 交易历史 |
### 使用示例
```python
from feishu_sync import FeishuBitable, sync_stock_signal
bitable = FeishuBitable()
sync_stock_signal(bitable, {
'ticker': 'AAPL',
'score': 5,
'action': '建议买入',
...
})
```
---
## feishu_init_tables.py - 表结构初始化
一键初始化飞书多维表格结构。
### 函数
| 函数 | 功能 |
|------|------|
| `init_signal_table()` | 初始化技术信号表字段 |
| `create_holdings_table()` | 创建持仓管理表 |
| `create_trades_table()` | 创建交易记录表 |
| `init_all_tables()` | 一键初始化所有表 |

View File

@ -0,0 +1,24 @@
# 股票分析器 Skill v3.0 - 依赖项
# ============================================
# 数据获取 - Yahoo Finance (实时价格 + MACD)
# ============================================
yfinance>=0.2.0
# ============================================
# 数据处理
# ============================================
pandas>=2.0.0
numpy>=1.24.0
# ============================================
# Excel 持仓管理
# ============================================
openpyxl>=3.1.0
# ============================================
# 注意事项
# ============================================
# 1. Alpha Vantage 数据通过 MCP 工具获取,无需额外安装
# 2. yfinance 用于实时价格和 MACD 计算
# 3. openpyxl 用于读写 Excel 持仓表格

View File

@ -0,0 +1,925 @@
"""
小白友好股票分析模块 v3.4
功能:
- 用通俗易懂的语言解释技术指标
- 综合分析给出买卖点建议
- 支持简洁版和详细版报告
- 港股本地计算支持
- ATR 动态止损
- 成交量/均线分析
- 仓位建议
v3.4 新增:
- K线形态识别小白解读(锤子线、吞没、十字星、早晨之星等)
- 趋势形态识别小白解读(双底、双顶、头肩顶/底、三角形)
- 形态信号纳入评分系统
v3.3:
- KDJ 随机指标小白解读
- 背离信号解读
- 支撑阻力位解读
- OBV 量能解读
- 威廉指标/乖离率解读
"""
from typing import Dict, List, Optional, Any, Tuple
from datetime import datetime, timedelta
from dataclasses import dataclass, field
@dataclass
class TradingSignal:
"""交易信号"""
action: str # BUY, SELL, HOLD
confidence: str # 高, 中, 低
buy_price: Optional[float] = None # 建议买入价
sell_price: Optional[float] = None # 建议卖出价
stop_loss: Optional[float] = None # 止损价
take_profit: Optional[float] = None # 止盈价
reasons: List[str] = field(default_factory=list) # 理由列表
# v3.2 字段
atr: Optional[float] = None # ATR 值
atr_percent: Optional[float] = None # ATR 百分比
risk_reward_ratio: Optional[float] = None # 风险收益比
suggested_position: Optional[float] = None # 建议仓位百分比
volume_signal: Optional[str] = None # 成交量信号
ma_trend: Optional[str] = None # 均线趋势
# v3.3 新增字段
kdj_signal: Optional[str] = None # KDJ 信号
divergence_signal: Optional[str] = None # 背离信号
obv_signal: Optional[str] = None # OBV 信号
support_price: Optional[float] = None # 最近支撑位
resistance_price: Optional[float] = None # 最近阻力位
score: Optional[int] = None # 综合评分
# v3.4 形态识别
patterns_signal: Optional[str] = None # 形态综合信号
patterns_count: Optional[int] = None # 识别到的形态数量
def explain_rsi_simple(rsi: float) -> str:
"""用小白语言解释 RSI"""
if rsi < 30:
return f"🟢 **超卖** ({rsi:.1f}) - 股票被卖得太多了,就像商场大甩卖,价格可能已经跌过头,是潜在的捡便宜机会"
elif rsi < 40:
return f"🟡 **偏弱** ({rsi:.1f}) - 股票有点疲软,买家不太积极,但还没到跌过头的程度"
elif rsi < 60:
return f"⚪ **中性** ({rsi:.1f}) - 买卖力量均衡,股票在正常波动,没有明显的超买或超卖"
elif rsi < 70:
return f"🟡 **偏强** ({rsi:.1f}) - 买家比较积极,股票走势还不错,但要注意别追高"
else:
return f"🔴 **超买** ({rsi:.1f}) - 股票被买得太多了,就像热门商品被抢购一空,价格可能涨过头,要小心回调"
def explain_macd_simple(macd_line: float, signal_line: float, histogram: float, prev_histogram: float = None) -> str:
"""用小白语言解释 MACD"""
# 判断趋势和交叉
if prev_histogram is not None:
if histogram > 0 and prev_histogram <= 0:
return f"🟢 **金叉出现** - 短期上涨动能超过了长期动能,就像汽车踩了油门开始加速,是买入信号"
elif histogram < 0 and prev_histogram >= 0:
return f"🔴 **死叉出现** - 短期动能开始减弱,就像汽车松了油门开始减速,是卖出警告"
if histogram > 0:
strength = "强劲" if histogram > 1 else "温和"
return f"🟢 **多头趋势** - 上涨动能{strength},就像顺风骑车,省力又快"
else:
strength = "明显" if histogram < -1 else "轻微"
return f"🔴 **空头趋势** - 下跌动能{strength},就像逆风骑车,需要更多力气"
def explain_bollinger_simple(price: float, upper: float, middle: float, lower: float) -> str:
"""用小白语言解释布林带"""
band_width = upper - lower
position = (price - lower) / band_width * 100 if band_width > 0 else 50
if price < lower:
return f"🟢 **跌破下轨** - 股价已经跌到了'地板'下面 (${lower:.2f}),就像橡皮筋拉得太长,可能要弹回来了"
elif price < lower + band_width * 0.2:
return f"🟢 **接近下轨** - 股价在'地板'附近 (${lower:.2f}),处于相对低位,可能是买入机会"
elif price > upper:
return f"🔴 **突破上轨** - 股价已经涨到了'天花板'上面 (${upper:.2f}),涨得有点猛,可能要回落"
elif price > upper - band_width * 0.2:
return f"🟡 **接近上轨** - 股价在'天花板'附近 (${upper:.2f}),处于相对高位,要注意回调风险"
else:
return f"⚪ **正常区间** - 股价在中间位置 (中轨 ${middle:.2f}),波动正常"
def explain_volume_simple(volume_ratio: float, pattern: str) -> str:
"""用小白语言解释成交量"""
if pattern == "放量上涨":
return f"📈 **放量上涨** (量比 {volume_ratio:.1f}) - 买盘积极涌入,像超市促销引来大批顾客,上涨动力充足"
elif pattern == "放量下跌":
return f"📉 **放量下跌** (量比 {volume_ratio:.1f}) - 卖盘大量涌出,像恐慌性抛售,需要警惕"
elif pattern == "缩量上涨":
return f"📈 **缩量上涨** (量比 {volume_ratio:.1f}) - 涨是涨了但买家不多,像没人气的促销,后劲可能不足"
elif pattern == "缩量下跌":
return f"📉 **缩量下跌** (量比 {volume_ratio:.1f}) - 跌但卖家也不多了,像甩卖接近尾声,可能快到底了"
elif pattern == "放量震荡":
return f"⚡ **放量震荡** (量比 {volume_ratio:.1f}) - 交易活跃但方向不明,多空在激烈博弈"
else:
return f"➡️ **量价平稳** (量比 {volume_ratio:.1f}) - 一切正常,没有异常信号"
def explain_ma_simple(arrangement: str, price_above: List[str], price_below: List[str]) -> str:
"""用小白语言解释均线"""
if arrangement == "多头排列":
return f"🟢 **多头排列** - 短期均线在上,长期均线在下,像排队的人越排越高,趋势向上"
elif arrangement == "空头排列":
return f"🔴 **空头排列** - 短期均线在下,长期均线在上,像滑梯往下滑,趋势向下"
else:
above_str = '/'.join(price_above) if price_above else "无"
below_str = '/'.join(price_below) if price_below else "无"
return f"🟡 **均线缠绕** - 均线交织在一起,方向不明朗(价格在 {above_str} 上方,在 {below_str} 下方)"
def explain_atr_simple(atr_percent: float) -> str:
"""用小白语言解释 ATR(波动性)"""
if atr_percent > 5:
return f"⚠️ **高波动** ({atr_percent:.1f}%) - 股价波动剧烈,像坐过山车,风险较大但机会也大"
elif atr_percent > 3:
return f"🔔 **中等波动** ({atr_percent:.1f}%) - 股价有一定波动,正常范围"
else:
return f"😌 **低波动** ({atr_percent:.1f}%) - 股价比较稳定,适合稳健型投资"
# ============================================
# v3.3 新增小白解读函数
# ============================================
def explain_kdj_simple(k: float, d: float, j: float, signal: str) -> str:
"""用小白语言解释 KDJ 随机指标"""
if signal == 'golden_cross':
return f"🟢 **KDJ 金叉** (K={k:.0f}, D={d:.0f}, J={j:.0f}) - 短期买入信号,像绿灯亮了可以出发"
elif signal == 'death_cross':
return f"🔴 **KDJ 死叉** (K={k:.0f}, D={d:.0f}, J={j:.0f}) - 短期卖出信号,像红灯亮了要刹车"
elif signal == 'overbought' or signal == 'high_zone':
return f"🔴 **KDJ 超买** (K={k:.0f}, D={d:.0f}, J={j:.0f}) - 短期涨太快了,像弹簧压得太紧可能要回弹"
elif signal == 'oversold' or signal == 'low_zone':
return f"🟢 **KDJ 超卖** (K={k:.0f}, D={d:.0f}, J={j:.0f}) - 短期跌太多了,像皮球落地可能要反弹"
else:
return f"⚪ **KDJ 中性** (K={k:.0f}, D={d:.0f}, J={j:.0f}) - 目前没有明显的超买超卖信号"
def explain_divergence_simple(divergence_type: str, indicator_name: str = "MACD") -> str:
"""用小白语言解释背离信号"""
if divergence_type == 'bullish':
return f"🟢 **{indicator_name} 底背离** - 价格在创新低,但{indicator_name}没有创新低,说明下跌动能在减弱,像马拉松跑到后面速度慢下来了,可能要反弹"
elif divergence_type == 'bearish':
return f"🔴 **{indicator_name} 顶背离** - 价格在创新高,但{indicator_name}没有创新高,说明上涨动能在减弱,像爬山快到顶了越来越吃力,小心回调"
else:
return f"⚪ **无{indicator_name}背离** - 价格和指标走势一致,趋势正常"
def explain_support_resistance_simple(
current_price: float,
nearest_support: dict,
nearest_resistance: dict
) -> str:
"""用小白语言解释支撑阻力位"""
parts = []
if nearest_support:
support_price = nearest_support['price']
distance_pct = (current_price - support_price) / current_price * 100
parts.append(f"📉 **最近支撑位**: ${support_price:.2f} (距离 {distance_pct:.1f}%) - 跌到这里可能会有买盘接住,像地板一样")
if nearest_resistance:
resist_price = nearest_resistance['price']
distance_pct = (resist_price - current_price) / current_price * 100
parts.append(f"📈 **最近阻力位**: ${resist_price:.2f} (距离 {distance_pct:.1f}%) - 涨到这里可能会有卖盘压制,像天花板一样")
if not parts:
return "暂无明确的支撑阻力位"
return "\n".join(parts)
def explain_obv_simple(signal: str, obv_trend: str, price_trend: str) -> str:
"""用小白语言解释 OBV 能量潮"""
if signal == 'confirmed_up':
return "🟢 **OBV 确认上涨** - 价格涨,资金也在流入,像涨潮一样水涨船高,趋势健康"
elif signal == 'confirmed_down':
return "🔴 **OBV 确认下跌** - 价格跌,资金也在流出,像退潮一样水落船低,趋势延续"
elif signal == 'bullish_divergence':
return "🟢 **OBV 底背离** - 价格在跌,但资金在悄悄流入,像有人在偷偷抄底,关注反弹机会"
elif signal == 'bearish_divergence':
return "🔴 **OBV 顶背离** - 价格在涨,但资金在悄悄流出,像有人在偷偷出货,警惕回调风险"
else:
return "⚪ **OBV 中性** - 量价关系正常"
def explain_williams_simple(wr: float, signal: str) -> str:
"""用小白语言解释威廉指标"""
if signal == 'overbought':
return f"🔴 **威廉指标超买** ({wr:.0f}) - 短期涨得太急,像冲刺跑太快要喘气,可能要回调"
elif signal == 'oversold':
return f"🟢 **威廉指标超卖** ({wr:.0f}) - 短期跌得太急,像跌倒了要爬起来,可能要反弹"
else:
return f"⚪ **威廉指标中性** ({wr:.0f}) - 目前处于正常区间"
def explain_bias_simple(bias6: float, signal: str) -> str:
"""用小白语言解释乖离率"""
if signal == 'overbought':
return f"🔴 **乖离率偏高** ({bias6:.1f}%) - 股价跑得比均线快太多了,像跑步冲太快会累,可能要回来休息(回调)"
elif signal == 'oversold':
return f"🟢 **乖离率偏低** ({bias6:.1f}%) - 股价跌得比均线远太多了,像橡皮筋拉太长会弹回来,可能要反弹"
else:
return f"⚪ **乖离率正常** ({bias6:.1f}%) - 股价和均线走得差不多齐,比较健康"
# ============================================
# v3.4 形态识别小白解读
# ============================================
# K线形态中英文映射
CANDLESTICK_NAMES = {
'doji': '十字星',
'hammer': '锤子线',
'hanging_man': '上吊线',
'bullish_engulfing': '看涨吞没',
'bearish_engulfing': '看跌吞没',
'morning_star': '早晨之星',
'evening_star': '黄昏之星',
'three_white_soldiers': '三只白兵',
'three_black_crows': '三只乌鸦',
'shooting_star': '射击之星',
'inverted_hammer': '倒锤子'
}
# 趋势形态中英文映射
CHART_PATTERN_NAMES = {
'double_bottom': '双底',
'double_top': '双顶',
'head_and_shoulders_top': '头肩顶',
'head_and_shoulders_bottom': '头肩底',
'ascending_triangle': '上升三角形',
'descending_triangle': '下降三角形',
'symmetric_triangle': '对称三角形'
}
def explain_candlestick_pattern_simple(pattern: dict) -> str:
"""用小白语言解释K线形态"""
pattern_type = pattern.get('pattern', '')
signal = pattern.get('signal', 'neutral')
strength = pattern.get('strength', 'medium')
cn_name = CANDLESTICK_NAMES.get(pattern_type, pattern_type)
# 强度描述
strength_desc = {
'very_strong': '非常强烈',
'strong': '强烈',
'medium': '中等',
'weak': '较弱'
}.get(strength, '中等')
# 各形态的小白解读
explanations = {
'doji': "⚪ **十字星** - 多空双方势均力敌,就像拔河比赛打成平手。当前趋势可能要变化,要密切观察",
'hammer': "🟢 **锤子线** (看涨) - 像一把锤子倒立,下影线很长说明下方有人接盘。出现在下跌后,是见底信号",
'hanging_man': "🔴 **上吊线** (看跌) - 形状像锤子但出现在上涨后,说明上方卖压开始出现。可能是见顶信号",
'bullish_engulfing': "🟢 **看涨吞没** (强信号) - 大阳线完全包住前一根阴线,像大鱼吃小鱼。买方力量占优,反转信号",
'bearish_engulfing': "🔴 **看跌吞没** (强信号) - 大阴线完全包住前一根阳线,卖方力量压倒买方。下跌信号",
'morning_star': "🟢 **早晨之星** (强信号) - 三根K线组成,像黎明前的启明星。典型的底部反转形态,买入机会",
'evening_star': "🔴 **黄昏之星** (强信号) - 三根K线组成,像日落前的昏星。典型的顶部反转形态,卖出信号",
'three_white_soldiers': "🟢 **三只白兵** (非常强) - 连续三根阳线稳步上涨,像士兵列队前进。强烈的上涨信号",
'three_black_crows': "🔴 **三只乌鸦** (非常强) - 连续三根阴线稳步下跌,像乌鸦报丧。强烈的下跌信号",
'shooting_star': "🔴 **射击之星** - 上影线很长,像流星划过。出现在上涨后说明上方抛压重,可能见顶",
'inverted_hammer': "🟢 **倒锤子** - 出现在下跌后的长上影线,买方尝试反攻。如果次日确认,是反转信号"
}
explanation = explanations.get(pattern_type, f"识别到 {cn_name} 形态")
return f"{explanation}\n ⭐ 信号强度: {strength_desc}"
def explain_chart_pattern_simple(pattern: dict) -> str:
"""用小白语言解释趋势形态"""
pattern_type = pattern.get('pattern', '')
signal = pattern.get('signal', 'neutral')
strength = pattern.get('strength', 'medium')
cn_name = CHART_PATTERN_NAMES.get(pattern_type, pattern_type)
# 强度描述
strength_desc = {
'very_strong': '非常可靠',
'strong': '比较可靠',
'medium': '参考意义',
'weak': '需要验证'
}.get(strength, '参考意义')
# 各形态的小白解读
explanations = {
'double_bottom': "🟢 **双底形态** (W底) - 股价两次探底后反弹,像字母W。经典的底部反转形态,突破颈线后看涨",
'double_top': "🔴 **双顶形态** (M头) - 股价两次冲高后回落,像字母M。经典的顶部形态,跌破颈线后看跌",
'head_and_shoulders_top': "🔴 **头肩顶** - 中间高两边低,像人的头和肩膀。是最可靠的顶部反转形态之一",
'head_and_shoulders_bottom': "🟢 **头肩底** - 中间低两边高,倒过来的头肩形态。是可靠的底部反转信号",
'ascending_triangle': "🟢 **上升三角形** - 底部逐步抬高,顶部水平。说明买方逐渐占优,通常向上突破",
'descending_triangle': "🔴 **下降三角形** - 顶部逐步降低,底部水平。说明卖方逐渐占优,通常向下突破",
'symmetric_triangle': "⚪ **对称三角形** - 高点降低、低点抬高,形成收敛。突破方向不确定,等待突破后跟进"
}
explanation = explanations.get(pattern_type, f"识别到 {cn_name} 形态")
return f"{explanation}\n ⭐ 可靠度: {strength_desc}"
def explain_patterns_simple(patterns_data: dict) -> str:
"""综合解释所有识别到的形态"""
if not patterns_data:
return "⚪ 暂未识别到明显形态"
all_patterns = patterns_data.get('all_patterns', [])
if not all_patterns:
return "⚪ 暂未识别到明显形态"
result_lines = ["📊 **形态识别结果**"]
# K线形态
candlestick = patterns_data.get('candlestick_patterns', [])
if candlestick:
result_lines.append("\n**K线形态:**")
for p in candlestick[:3]: # 最多显示3个
result_lines.append(f" • {explain_candlestick_pattern_simple(p)}")
# 趋势形态
chart = patterns_data.get('chart_patterns', [])
if chart:
result_lines.append("\n**趋势形态:**")
for p in chart[:2]: # 最多显示2个
result_lines.append(f" • {explain_chart_pattern_simple(p)}")
# 综合判断
signal = patterns_data.get('signal', 'neutral')
bullish = patterns_data.get('bullish_count', 0)
bearish = patterns_data.get('bearish_count', 0)
result_lines.append(f"\n**形态综合判断:**")
if signal == 'bullish':
result_lines.append(f" 🟢 看涨形态占优 (看涨{bullish}个 vs 看跌{bearish}个)")
elif signal == 'bearish':
result_lines.append(f" 🔴 看跌形态占优 (看跌{bearish}个 vs 看涨{bullish}个)")
else:
result_lines.append(f" ⚪ 形态信号中性 (看涨{bullish}个 vs 看跌{bearish}个)")
return "\n".join(result_lines)
def explain_trend_simple(prices: List[float], period_name: str = "近期") -> str:
"""用小白语言解释趋势"""
if len(prices) < 5:
return "数据不足,无法判断趋势"
start_price = prices[0]
end_price = prices[-1]
change_pct = (end_price - start_price) / start_price * 100
# 计算波动性
max_price = max(prices)
min_price = min(prices)
volatility = (max_price - min_price) / start_price * 100
if change_pct > 10:
trend = f"📈 **强势上涨** - {period_name}涨了 {change_pct:.1f}%,像坐电梯往上,势头很猛"
elif change_pct > 3:
trend = f"📈 **温和上涨** - {period_name}涨了 {change_pct:.1f}%,像爬楼梯,稳步向上"
elif change_pct > -3:
trend = f"➡️ **横盘震荡** - {period_name}基本持平 ({change_pct:+.1f}%),在一个区间内来回波动"
elif change_pct > -10:
trend = f"📉 **温和下跌** - {period_name}跌了 {abs(change_pct):.1f}%,像下楼梯,逐步下行"
else:
trend = f"📉 **大幅下跌** - {period_name}跌了 {abs(change_pct):.1f}%,跌势比较急"
# 添加波动性说明
if volatility > 20:
trend += f"\n ⚠️ 波动较大 ({volatility:.1f}%),坐过山车的感觉,风险较高"
elif volatility > 10:
trend += f"\n 🔔 波动中等 ({volatility:.1f}%),有起有伏但还算正常"
return trend
def calculate_support_resistance(prices: List[float], current_price: float) -> Dict[str, float]:
"""计算支撑位和阻力位"""
if len(prices) < 20:
return {}
# 简单方法:使用近期高低点
recent_high = max(prices[-20:])
recent_low = min(prices[-20:])
# 计算关键位置
result = {
'strong_resistance': recent_high, # 强阻力位 = 近期最高
'weak_resistance': current_price + (recent_high - current_price) * 0.382, # 斐波那契
'weak_support': current_price - (current_price - recent_low) * 0.382,
'strong_support': recent_low, # 强支撑位 = 近期最低
}
return result
def generate_trading_recommendation(
ticker: str,
current_price: float,
rsi: float,
macd_histogram: float,
prev_macd_histogram: float,
bb_upper: float,
bb_middle: float,
bb_lower: float,
prices_1m: List[float] = None,
prices_3m: List[float] = None,
# v3.2 参数
atr: float = None,
atr_percent: float = None,
volume_ratio: float = None,
volume_signal: str = None,
ma_trend: str = None,
ma_arrangement: str = None,
# v3.3 新增参数
kdj_k: float = None,
kdj_d: float = None,
kdj_j: float = None,
kdj_signal: str = None,
macd_divergence: str = None,
rsi_divergence: str = None,
obv_signal: str = None,
williams_signal: str = None,
bias_signal: str = None,
nearest_support: float = None,
nearest_resistance: float = None,
# v3.4 形态识别参数
patterns_signal: str = None, # 'bullish', 'bearish', 'neutral'
patterns_data: dict = None # 完整形态数据
) -> TradingSignal:
"""
综合分析生成交易建议 (v3.4)
综合策略:
1. RSI 超卖/超买
2. MACD 金叉/死叉
3. 布林带位置
4. 趋势判断
5. 成交量配合
6. 均线排列
7. ATR 动态止损
8. [v3.3] KDJ 随机指标
9. [v3.3] MACD/RSI 背离
10. [v3.3] OBV 量能
11. [v3.3] 威廉指标/乖离率
12. [v3.3] 支撑阻力位
13. [v3.4] K线形态 + 趋势形态
"""
reasons = []
buy_score = 0 # 正数倾向买入,负数倾向卖出
# === RSI 分析 ===
if rsi < 30:
buy_score += 3
reasons.append("✅ RSI 超卖 (<30),股价可能跌过头")
elif rsi < 40:
buy_score += 1
reasons.append("📍 RSI 偏低,股价相对便宜")
elif rsi > 70:
buy_score -= 3
reasons.append("⚠️ RSI 超买 (>70),股价可能涨过头")
elif rsi > 60:
buy_score -= 1
reasons.append("📍 RSI 偏高,追高需谨慎")
# === MACD 分析 ===
if macd_histogram > 0 and prev_macd_histogram <= 0:
buy_score += 3
reasons.append("✅ MACD 金叉,上涨动能启动")
elif macd_histogram < 0 and prev_macd_histogram >= 0:
buy_score -= 3
reasons.append("⚠️ MACD 死叉,下跌动能启动")
elif macd_histogram > 0:
buy_score += 1
reasons.append("📍 MACD 多头趋势")
else:
buy_score -= 1
reasons.append("📍 MACD 空头趋势")
# === 布林带分析 ===
if current_price < bb_lower:
buy_score += 2
reasons.append("✅ 跌破布林带下轨,可能超跌反弹")
elif current_price < bb_lower + (bb_middle - bb_lower) * 0.3:
buy_score += 1
reasons.append("📍 接近布林带下轨,相对低位")
elif current_price > bb_upper:
buy_score -= 2
reasons.append("⚠️ 突破布林带上轨,可能回调")
elif current_price > bb_upper - (bb_upper - bb_middle) * 0.3:
buy_score -= 1
reasons.append("📍 接近布林带上轨,追高风险")
# === KDJ 分析 (v3.3 新增) ===
if kdj_signal:
if kdj_signal == 'golden_cross':
buy_score += 3
reasons.append("✅ KDJ 金叉,短期买入信号")
elif kdj_signal == 'death_cross':
buy_score -= 3
reasons.append("⚠️ KDJ 死叉,短期卖出信号")
elif kdj_signal in ['oversold', 'low_zone']:
buy_score += 2
reasons.append("✅ KDJ 超卖,短期可能反弹")
elif kdj_signal in ['overbought', 'high_zone']:
buy_score -= 2
reasons.append("⚠️ KDJ 超买,短期可能回调")
# === 背离分析 (v3.3 新增) - 背离是强信号 ===
if macd_divergence == 'bullish':
buy_score += 4
reasons.append("🔥 MACD 底背离,强烈反弹信号")
elif macd_divergence == 'bearish':
buy_score -= 4
reasons.append("🔥 MACD 顶背离,强烈回调信号")
if rsi_divergence == 'bullish':
buy_score += 3
reasons.append("✅ RSI 底背离,动能转强")
elif rsi_divergence == 'bearish':
buy_score -= 3
reasons.append("⚠️ RSI 顶背离,动能转弱")
# === OBV 分析 (v3.3 新增) ===
if obv_signal:
if obv_signal == 'bullish_divergence':
buy_score += 2
reasons.append("📊 OBV 底背离,资金悄悄流入")
elif obv_signal == 'bearish_divergence':
buy_score -= 2
reasons.append("📊 OBV 顶背离,资金悄悄流出")
elif obv_signal == 'confirmed_up':
buy_score += 1
reasons.append("📊 OBV 确认上涨趋势")
elif obv_signal == 'confirmed_down':
buy_score -= 1
reasons.append("📊 OBV 确认下跌趋势")
# === 威廉指标/乖离率 (v3.3 新增) ===
if williams_signal == 'oversold':
buy_score += 1
reasons.append("📍 威廉指标超卖")
elif williams_signal == 'overbought':
buy_score -= 1
reasons.append("📍 威廉指标超买")
if bias_signal == 'oversold':
buy_score += 1
reasons.append("📍 乖离率偏低,可能反弹")
elif bias_signal == 'overbought':
buy_score -= 1
reasons.append("📍 乖离率偏高,可能回调")
# === 成交量分析 ===
if volume_signal:
if volume_signal == "bullish" and volume_ratio and volume_ratio > 1.5:
buy_score += 2
reasons.append(f"📊 放量上涨 (量比 {volume_ratio:.1f}),买盘积极")
elif volume_signal == "bearish" and volume_ratio and volume_ratio > 1.5:
buy_score -= 2
reasons.append(f"📊 放量下跌 (量比 {volume_ratio:.1f}),卖压较大")
elif volume_signal == "neutral" and volume_ratio and volume_ratio < 0.7:
if macd_histogram < 0:
buy_score += 1
reasons.append(f"📊 缩量下跌 (量比 {volume_ratio:.1f}),卖压减轻")
# === 均线分析 ===
if ma_arrangement:
if ma_arrangement == "多头排列":
buy_score += 2
reasons.append("📈 均线多头排列,趋势向上")
elif ma_arrangement == "空头排列":
buy_score -= 2
reasons.append("📉 均线空头排列,趋势向下")
# === 趋势分析 ===
if prices_1m and len(prices_1m) >= 5:
change_1m = (prices_1m[-1] - prices_1m[0]) / prices_1m[0] * 100
if change_1m < -15:
buy_score += 1
reasons.append(f"📍 近1月跌幅较大 ({change_1m:.1f}%),可能超跌")
elif change_1m > 15:
buy_score -= 1
reasons.append(f"📍 近1月涨幅较大 ({change_1m:.1f}%),注意追高")
# === 支撑阻力位分析 (v3.3 新增) ===
if nearest_support and nearest_resistance:
support_distance = (current_price - nearest_support) / current_price * 100
resist_distance = (nearest_resistance - current_price) / current_price * 100
if support_distance < 3: # 接近支撑位
buy_score += 1
reasons.append(f"📍 接近支撑位 ${nearest_support:.2f},可能有支撑")
if resist_distance < 3: # 接近阻力位
buy_score -= 1
reasons.append(f"📍 接近阻力位 ${nearest_resistance:.2f},可能有压力")
# === 形态识别分析 (v3.4 新增) ===
if patterns_data:
all_patterns = patterns_data.get('all_patterns', [])
# 按形态强度和类型评分
for pattern in all_patterns:
p_signal = pattern.get('signal', 'neutral')
p_strength = pattern.get('strength', 'medium')
p_name = pattern.get('pattern', '')
# 强信号形态(三只白兵/乌鸦、早晨/黄昏之星、头肩、双底双顶)
strong_patterns = ['three_white_soldiers', 'three_black_crows',
'morning_star', 'evening_star',
'head_and_shoulders_top', 'head_and_shoulders_bottom',
'double_bottom', 'double_top']
# 中等信号形态
medium_patterns = ['bullish_engulfing', 'bearish_engulfing',
'ascending_triangle', 'descending_triangle']
if p_name in strong_patterns:
if p_signal == 'bullish':
buy_score += 3
cn_name = CANDLESTICK_NAMES.get(p_name) or CHART_PATTERN_NAMES.get(p_name, p_name)
reasons.append(f"🔥 {cn_name}形态,强看涨信号")
elif p_signal == 'bearish':
buy_score -= 3
cn_name = CANDLESTICK_NAMES.get(p_name) or CHART_PATTERN_NAMES.get(p_name, p_name)
reasons.append(f"🔥 {cn_name}形态,强看跌信号")
elif p_name in medium_patterns:
if p_signal == 'bullish':
buy_score += 2
cn_name = CANDLESTICK_NAMES.get(p_name) or CHART_PATTERN_NAMES.get(p_name, p_name)
reasons.append(f"✅ {cn_name}形态,看涨信号")
elif p_signal == 'bearish':
buy_score -= 2
cn_name = CANDLESTICK_NAMES.get(p_name) or CHART_PATTERN_NAMES.get(p_name, p_name)
reasons.append(f"⚠️ {cn_name}形态,看跌信号")
else:
# 弱信号形态(锤子线、十字星等)
if p_signal == 'bullish':
buy_score += 1
cn_name = CANDLESTICK_NAMES.get(p_name) or CHART_PATTERN_NAMES.get(p_name, p_name)
reasons.append(f"📍 {cn_name}形态出现")
elif p_signal == 'bearish':
buy_score -= 1
cn_name = CANDLESTICK_NAMES.get(p_name) or CHART_PATTERN_NAMES.get(p_name, p_name)
reasons.append(f"📍 {cn_name}形态出现")
# === 生成建议 (v3.4 调整阈值) ===
if buy_score >= 6:
action = "BUY"
confidence = "高"
elif buy_score >= 3:
action = "BUY"
confidence = "中"
elif buy_score <= -6:
action = "SELL"
confidence = "高"
elif buy_score <= -3:
action = "SELL"
confidence = "中"
else:
action = "HOLD"
confidence = "中"
# === 计算价格建议 ===
if atr and atr_percent:
if atr_percent > 5:
atr_multiplier = 2.5 # 高波动
elif atr_percent > 3:
atr_multiplier = 2.0 # 中等波动
else:
atr_multiplier = 1.5 # 低波动
stop_loss = current_price - atr * atr_multiplier
take_profit = current_price + atr * atr_multiplier * 2.5
buy_price = current_price - atr * 0.5
risk = current_price - stop_loss
reward = take_profit - current_price
risk_reward = reward / risk if risk > 0 else 0
else:
buy_price = min(current_price * 0.97, bb_lower * 1.02)
stop_loss = bb_lower * 0.95
take_profit = bb_upper * 0.95
risk_reward = None
# 结合支撑位优化止损
if nearest_support and stop_loss > nearest_support:
stop_loss = nearest_support * 0.98 # 支撑位下方 2%
sell_price = max(current_price * 1.05, bb_upper * 0.98)
# 结合阻力位优化止盈
if nearest_resistance and take_profit > nearest_resistance:
take_profit = nearest_resistance * 0.98 # 阻力位下方 2%
# 仓位建议
if confidence == "高":
suggested_position = 30.0
elif confidence == "中":
suggested_position = 20.0
else:
suggested_position = 10.0
# 确定主要背离信号
divergence_signal = None
if macd_divergence in ['bullish', 'bearish']:
divergence_signal = f"MACD_{macd_divergence}"
elif rsi_divergence in ['bullish', 'bearish']:
divergence_signal = f"RSI_{rsi_divergence}"
return TradingSignal(
action=action,
confidence=confidence,
buy_price=round(buy_price, 2),
sell_price=round(sell_price, 2),
stop_loss=round(stop_loss, 2),
take_profit=round(take_profit, 2),
reasons=reasons,
# v3.2 字段
atr=atr,
atr_percent=atr_percent,
risk_reward_ratio=round(risk_reward, 2) if risk_reward else None,
suggested_position=suggested_position,
volume_signal=volume_signal,
ma_trend=ma_arrangement,
# v3.3 新增字段
kdj_signal=kdj_signal,
divergence_signal=divergence_signal,
obv_signal=obv_signal,
support_price=nearest_support,
resistance_price=nearest_resistance,
score=buy_score
)
def format_simple_report(
ticker: str,
name: str,
current_price: float,
change_pct: float,
rsi: float,
macd_histogram: float,
bb_upper: float,
bb_middle: float,
bb_lower: float,
signal: TradingSignal
) -> str:
"""生成简洁版报告(一屏看完)"""
# 涨跌颜色
change_emoji = "🟢" if change_pct >= 0 else "🔴"
# 信号颜色
if signal.action == "BUY":
action_text = "🟢 **建议买入**"
elif signal.action == "SELL":
action_text = "🔴 **建议卖出**"
else:
action_text = "⚪ **观望等待**"
report = f"""
## {ticker} ({name}) 简易分析
### 📊 当前状态
- **价格**: ${current_price:.2f} ({change_emoji} {change_pct:+.2f}%)
- **RSI**: {rsi:.1f} {'🟢超卖' if rsi < 30 else '🔴超买' if rsi > 70 else '⚪正常'}
- **MACD**: {'🟢多头' if macd_histogram > 0 else '🔴空头'}
- **位置**: 在布林带 {'下方' if current_price < bb_lower else '上方' if current_price > bb_upper else '中间'}
### 🎯 交易建议
{action_text} (置信度: {signal.confidence})
| 操作 | 建议价格 |
|------|----------|
| 买入价 | ${signal.buy_price:.2f} |
| 止损价 | ${signal.stop_loss:.2f} |
| 止盈价 | ${signal.take_profit:.2f} |
### 📝 理由
"""
for reason in signal.reasons[:4]: # 最多显示4条
report += f"- {reason}\n"
return report
def format_detailed_report(
ticker: str,
name: str,
current_price: float,
change_pct: float,
rsi: float,
macd_line: float,
signal_line: float,
macd_histogram: float,
prev_macd_histogram: float,
bb_upper: float,
bb_middle: float,
bb_lower: float,
prices_1m: List[float],
prices_3m: List[float],
signal: TradingSignal
) -> str:
"""生成详细版报告"""
# 基础信息
change_emoji = "🟢" if change_pct >= 0 else "🔴"
report = f"""
# {ticker} ({name}) 详细技术分析
---
## 一、价格概览
| 指标 | 数值 |
|------|------|
| 当前价格 | ${current_price:.2f} |
| 今日涨跌 | {change_emoji} {change_pct:+.2f}% |
| 布林带上轨 | ${bb_upper:.2f} |
| 布林带中轨 | ${bb_middle:.2f} |
| 布林带下轨 | ${bb_lower:.2f} |
---
## 二、技术指标解读
### RSI (相对强弱指数)
{explain_rsi_simple(rsi)}
### MACD (指数平滑异同移动平均线)
{explain_macd_simple(macd_line, signal_line, macd_histogram, prev_macd_histogram)}
| MACD 数值 | |
|-----------|--------|
| MACD 线 | {macd_line:.4f} |
| 信号线 | {signal_line:.4f} |
| 柱状图 | {macd_histogram:.4f} |
### 布林带位置
{explain_bollinger_simple(current_price, bb_upper, bb_middle, bb_lower)}
---
## 三、趋势分析
### 近1个月趋势
{explain_trend_simple(prices_1m, "近1个月") if prices_1m else "数据不足"}
### 近3个月趋势
{explain_trend_simple(prices_3m, "近3个月") if prices_3m else "数据不足"}
---
## 四、交易建议
### 综合判断
"""
if signal.action == "BUY":
report += "### 🟢 **建议买入**\n\n"
elif signal.action == "SELL":
report += "### 🔴 **建议卖出**\n\n"
else:
report += "### ⚪ **建议观望**\n\n"
report += f"**置信度**: {signal.confidence}\n\n"
report += "**分析理由**:\n"
for i, reason in enumerate(signal.reasons, 1):
report += f"{i}. {reason}\n"
report += f"""
### 价格建议
| 操作类型 | 建议价格 | 说明 |
|----------|----------|------|
| 🟢 买入价 | ${signal.buy_price:.2f} | 建议在此价格附近分批买入 |
| 🔴 止损价 | ${signal.stop_loss:.2f} | 跌破此价应止损离场 |
| 🎯 止盈价 | ${signal.take_profit:.2f} | 涨到此价可考虑部分止盈 |
---
## 五、风险提示
⚠️ **重要提醒**:
1. 以上分析仅供参考,不构成投资建议
2. 股市有风险,投资需谨慎
3. 建议分批建仓,不要一次性满仓
4. 设置好止损,控制风险
5. 技术分析有局限性,需结合基本面和市场环境
---
*分析时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}*
"""
return report

View File

@ -0,0 +1,228 @@
"""
飞书多维表格初始化脚本
功能:创建 Stock Master 所需的表结构
"""
import json
import requests
from feishu_sync import FeishuBitable
def create_field(bitable: FeishuBitable, field_name: str, field_type: int,
table_id: str = None, **kwargs) -> dict:
"""
创建字段
字段类型:
1: 文本
2: 数字
3: 单选
4: 多选
5: 日期
7: 复选框
"""
table_id = table_id or bitable.table_id
endpoint = f"/bitable/v1/apps/{bitable.app_token}/tables/{table_id}/fields"
data = {
"field_name": field_name,
"type": field_type
}
# 单选/多选需要配置选项
if field_type in [3, 4] and 'options' in kwargs:
data['property'] = {
'options': [{'name': opt} for opt in kwargs['options']]
}
# 数字字段配置
if field_type == 2 and 'formatter' in kwargs:
data['property'] = {'formatter': kwargs['formatter']}
return bitable._request('POST', endpoint, data)
def init_signal_table(bitable: FeishuBitable, table_id: str = None):
"""
初始化技术信号表字段
表结构:
- 股票代码 (文本)
- 股票名称 (文本)
- 当前价格 (数字)
- 综合评分 (数字)
- 操作建议 (单选: 强烈买入/建议买入/观望/建议卖出/强烈卖出)
- RSI (数字)
- MACD信号 (单选: 金叉/多头/空头/死叉)
- KDJ信号 (单选: 金叉/超卖/中性/超买/死叉)
- 背离信号 (单选: 无/底背离/顶背离)
- 形态信号 (多选: 锤子线/吞没/十字星/双底/双顶/头肩/三角形...)
- 止损价 (数字)
- 止盈价 (数字)
- 分析理由 (文本)
- 更新时间 (日期)
"""
table_id = table_id or bitable.table_id
fields_config = [
("股票代码", 1, {}),
("股票名称", 1, {}),
("当前价格", 2, {"formatter": "0.00"}),
("综合评分", 2, {"formatter": "0"}),
("操作建议", 3, {"options": ["强烈买入", "建议买入", "观望", "建议卖出", "强烈卖出"]}),
("RSI", 2, {"formatter": "0.0"}),
("MACD信号", 3, {"options": ["金叉", "多头", "空头", "死叉"]}),
("KDJ信号", 3, {"options": ["金叉", "超卖", "中性", "超买", "死叉"]}),
("背离信号", 3, {"options": ["无", "底背离", "顶背离"]}),
("形态信号", 4, {"options": [
"锤子线", "上吊线", "十字星", "看涨吞没", "看跌吞没",
"早晨之星", "黄昏之星", "三只白兵", "三只乌鸦",
"双底", "双顶", "头肩顶", "头肩底",
"上升三角形", "下降三角形", "对称三角形"
]}),
("止损价", 2, {"formatter": "0.00"}),
("止盈价", 2, {"formatter": "0.00"}),
("分析理由", 1, {}),
("更新时间", 5, {}),
]
print(f"正在初始化表 {table_id} 的字段...")
success = 0
failed = 0
for field_name, field_type, kwargs in fields_config:
try:
create_field(bitable, field_name, field_type, table_id, **kwargs)
print(f" ✓ 创建字段: {field_name}")
success += 1
except Exception as e:
if "already exist" in str(e).lower() or "已存在" in str(e):
print(f" - 字段已存在: {field_name}")
else:
print(f" ✗ 创建失败: {field_name} - {e}")
failed += 1
print(f"\n完成! 成功: {success}, 失败: {failed}")
return {"success": success, "failed": failed}
def create_holdings_table(bitable: FeishuBitable) -> str:
"""
创建持仓表
返回新表的 table_id
"""
fields = [
{"field_name": "股票代码", "type": 1},
{"field_name": "股票名称", "type": 1},
{"field_name": "市场", "type": 3, "property": {"options": [
{"name": "美股"}, {"name": "港股"}, {"name": "A股"}
]}},
{"field_name": "持仓数量", "type": 2},
{"field_name": "成本价", "type": 2, "property": {"formatter": "0.00"}},
{"field_name": "当前价", "type": 2, "property": {"formatter": "0.00"}},
{"field_name": "市值", "type": 2, "property": {"formatter": "0.00"}},
{"field_name": "盈亏金额", "type": 2, "property": {"formatter": "0.00"}},
{"field_name": "盈亏比例", "type": 2, "property": {"formatter": "0.00%"}},
{"field_name": "买入日期", "type": 5},
{"field_name": "备注", "type": 1},
]
endpoint = f"/bitable/v1/apps/{bitable.app_token}/tables"
data = {
"table": {
"name": "持仓管理",
"default_view_name": "全部持仓",
"fields": fields
}
}
result = bitable._request('POST', endpoint, data)
table_id = result.get('table_id')
print(f"创建持仓表成功: {table_id}")
return table_id
def create_trades_table(bitable: FeishuBitable) -> str:
"""
创建交易记录表
返回新表的 table_id
"""
fields = [
{"field_name": "股票代码", "type": 1},
{"field_name": "交易类型", "type": 3, "property": {"options": [
{"name": "买入"}, {"name": "卖出"}, {"name": "做T买"}, {"name": "做T卖"}
]}},
{"field_name": "交易价格", "type": 2, "property": {"formatter": "0.00"}},
{"field_name": "交易数量", "type": 2},
{"field_name": "交易金额", "type": 2, "property": {"formatter": "0.00"}},
{"field_name": "交易时间", "type": 5},
{"field_name": "触发信号", "type": 1},
{"field_name": "备注", "type": 1},
]
endpoint = f"/bitable/v1/apps/{bitable.app_token}/tables"
data = {
"table": {
"name": "交易记录",
"default_view_name": "全部记录",
"fields": fields
}
}
result = bitable._request('POST', endpoint, data)
table_id = result.get('table_id')
print(f"创建交易记录表成功: {table_id}")
return table_id
def init_all_tables(config_path: str = None):
"""
一键初始化所有表结构
"""
bitable = FeishuBitable(config_path)
print("=" * 50)
print("Stock Master 飞书多维表格初始化")
print("=" * 50)
# 1. 初始化主表(技术信号表)字段
print("\n[1/3] 初始化技术信号表...")
init_signal_table(bitable)
# 2. 创建持仓表
print("\n[2/3] 创建持仓管理表...")
try:
holdings_table_id = create_holdings_table(bitable)
except Exception as e:
if "already exist" in str(e).lower():
print(" 持仓表已存在")
else:
print(f" 创建失败: {e}")
# 3. 创建交易记录表
print("\n[3/3] 创建交易记录表...")
try:
trades_table_id = create_trades_table(bitable)
except Exception as e:
if "already exist" in str(e).lower():
print(" 交易记录表已存在")
else:
print(f" 创建失败: {e}")
print("\n" + "=" * 50)
print("初始化完成!")
print("=" * 50)
# 列出所有表
tables = bitable.list_tables()
print("\n当前表列表:")
for t in tables:
print(f" - {t['name']} (ID: {t['table_id']})")
if __name__ == "__main__":
init_all_tables()

View File

@ -0,0 +1,492 @@
"""
飞书多维表格同步模块 v1.0
功能:
- 连接飞书多维表格
- 同步持仓数据
- 同步技术信号
- 同步交易记录
数据流向:本地分析结果 → 飞书多维表格(单向同步)
配置说明:
请创建 feishu_config.json 文件,格式参考 feishu_config.example.json
"""
import json
import requests
from typing import Dict, List, Optional, Any, Union
from datetime import datetime
from pathlib import Path
import time
import os
def get_config_path() -> Path:
"""获取配置文件路径"""
# 优先使用环境变量
if os.environ.get('FEISHU_CONFIG_PATH'):
return Path(os.environ['FEISHU_CONFIG_PATH'])
# 其次使用当前目录
local_config = Path('./feishu_config.json')
if local_config.exists():
return local_config
# 最后使用用户目录
home_config = Path.home() / '.stock-master' / 'feishu_config.json'
if home_config.exists():
return home_config
raise FileNotFoundError(
"找不到飞书配置文件。请创建 feishu_config.json,"
"或设置环境变量 FEISHU_CONFIG_PATH"
)
def to_feishu_timestamp(dt: Union[str, datetime, int, None]) -> Optional[int]:
"""
将日期时间转换为飞书多维表格需要的毫秒时间戳
参数:
dt: 日期时间,可以是:
- datetime 对象
- 字符串 (格式: 'YYYY-MM-DD' 或 'YYYY-MM-DD HH:MM:SS')
- 毫秒时间戳 (int)
- None
返回:
毫秒时间戳 或 None
"""
if dt is None:
return None
if isinstance(dt, int):
# 已经是时间戳,检查是秒还是毫秒
if dt > 1e12: # 已经是毫秒
return dt
return dt * 1000 # 秒转毫秒
if isinstance(dt, datetime):
return int(dt.timestamp() * 1000)
if isinstance(dt, str):
# 尝试解析字符串
formats = [
'%Y-%m-%d %H:%M:%S',
'%Y-%m-%d %H:%M',
'%Y-%m-%d',
'%Y/%m/%d %H:%M:%S',
'%Y/%m/%d'
]
for fmt in formats:
try:
parsed = datetime.strptime(dt, fmt)
return int(parsed.timestamp() * 1000)
except ValueError:
continue
# 解析失败,返回当前时间
return int(time.time() * 1000)
return int(time.time() * 1000)
class FeishuBitable:
"""飞书多维表格 API 封装"""
BASE_URL = "https://open.feishu.cn/open-apis"
def __init__(self, config_path: str = None):
"""
初始化飞书连接
参数:
config_path: 配置文件路径,如不指定则自动查找
"""
if config_path is None:
config_path = get_config_path()
else:
config_path = Path(config_path)
if not config_path.exists():
raise FileNotFoundError(f"配置文件不存在: {config_path}")
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
self.app_id = config['APP_ID']
self.app_secret = config['APP_SECRET']
self.app_token = config['APP_TOKEN']
self.table_id = config['TABLE_ID']
self._tenant_access_token = None
self._token_expires_at = 0
def _get_tenant_access_token(self) -> str:
"""获取 tenant_access_token"""
import time
# 检查 token 是否过期
if self._tenant_access_token and time.time() < self._token_expires_at - 60:
return self._tenant_access_token
url = f"{self.BASE_URL}/auth/v3/tenant_access_token/internal"
payload = {
"app_id": self.app_id,
"app_secret": self.app_secret
}
response = requests.post(url, json=payload)
result = response.json()
if result.get('code') != 0:
raise Exception(f"获取 token 失败: {result.get('msg')}")
self._tenant_access_token = result['tenant_access_token']
self._token_expires_at = time.time() + result.get('expire', 7200)
return self._tenant_access_token
def _request(self, method: str, endpoint: str, data: dict = None) -> dict:
"""发送 API 请求"""
url = f"{self.BASE_URL}{endpoint}"
headers = {
"Authorization": f"Bearer {self._get_tenant_access_token()}",
"Content-Type": "application/json"
}
if method.upper() == 'GET':
response = requests.get(url, headers=headers, params=data)
elif method.upper() == 'POST':
response = requests.post(url, headers=headers, json=data)
elif method.upper() == 'PUT':
response = requests.put(url, headers=headers, json=data)
elif method.upper() == 'DELETE':
response = requests.delete(url, headers=headers, json=data)
else:
raise ValueError(f"不支持的 HTTP 方法: {method}")
result = response.json()
if result.get('code') != 0:
raise Exception(f"API 请求失败: {result.get('msg')}")
return result.get('data', {})
# ==========================================
# 表格操作
# ==========================================
def list_tables(self) -> List[dict]:
"""列出多维表格中的所有数据表"""
endpoint = f"/bitable/v1/apps/{self.app_token}/tables"
result = self._request('GET', endpoint)
return result.get('items', [])
def get_table_fields(self, table_id: str = None) -> List[dict]:
"""获取数据表的字段列表"""
table_id = table_id or self.table_id
endpoint = f"/bitable/v1/apps/{self.app_token}/tables/{table_id}/fields"
result = self._request('GET', endpoint)
return result.get('items', [])
def create_table(self, name: str, fields: List[dict]) -> dict:
"""
创建新数据表
参数:
name: 表名
fields: 字段定义列表
"""
endpoint = f"/bitable/v1/apps/{self.app_token}/tables"
data = {
"table": {
"name": name,
"default_view_name": "默认视图",
"fields": fields
}
}
return self._request('POST', endpoint, data)
# ==========================================
# 记录操作
# ==========================================
def list_records(self, table_id: str = None, page_size: int = 100,
filter_str: str = None) -> List[dict]:
"""
获取数据表记录
参数:
table_id: 表 ID
page_size: 每页数量
filter_str: 筛选条件
"""
table_id = table_id or self.table_id
endpoint = f"/bitable/v1/apps/{self.app_token}/tables/{table_id}/records"
params = {"page_size": page_size}
if filter_str:
params["filter"] = filter_str
result = self._request('GET', endpoint, params)
return result.get('items', [])
def create_record(self, fields: dict, table_id: str = None) -> dict:
"""
创建单条记录
参数:
fields: 字段值字典
table_id: 表 ID
"""
table_id = table_id or self.table_id
endpoint = f"/bitable/v1/apps/{self.app_token}/tables/{table_id}/records"
data = {"fields": fields}
return self._request('POST', endpoint, data)
def batch_create_records(self, records: List[dict], table_id: str = None) -> dict:
"""
批量创建记录
参数:
records: 记录列表,每条记录为 {"fields": {...}}
table_id: 表 ID
"""
table_id = table_id or self.table_id
endpoint = f"/bitable/v1/apps/{self.app_token}/tables/{table_id}/records/batch_create"
data = {"records": [{"fields": r} if "fields" not in r else r for r in records]}
return self._request('POST', endpoint, data)
def update_record(self, record_id: str, fields: dict, table_id: str = None) -> dict:
"""
更新单条记录
参数:
record_id: 记录 ID
fields: 要更新的字段值
table_id: 表 ID
"""
table_id = table_id or self.table_id
endpoint = f"/bitable/v1/apps/{self.app_token}/tables/{table_id}/records/{record_id}"
data = {"fields": fields}
return self._request('PUT', endpoint, data)
def delete_record(self, record_id: str, table_id: str = None) -> dict:
"""删除单条记录"""
table_id = table_id or self.table_id
endpoint = f"/bitable/v1/apps/{self.app_token}/tables/{table_id}/records/{record_id}"
return self._request('DELETE', endpoint)
def find_record_by_field(self, field_name: str, value: str,
table_id: str = None) -> Optional[dict]:
"""
根据字段值查找记录
参数:
field_name: 字段名
value: 字段值
table_id: 表 ID
返回:
找到的记录或 None
"""
records = self.list_records(table_id)
for record in records:
if record.get('fields', {}).get(field_name) == value:
return record
return None
# ==========================================
# Stock Master 同步函数
# ==========================================
def sync_stock_signal(bitable: FeishuBitable, signal_data: dict,
table_id: str = None) -> dict:
"""
同步单只股票的技术信号到飞书
参数:
bitable: FeishuBitable 实例
signal_data: 股票信号数据,格式:
{
'ticker': 'AAPL',
'name': '苹果',
'current_price': 185.5,
'score': 5,
'action': 'BUY',
'rsi': 45.2,
'macd_signal': '金叉',
'kdj_signal': '超卖',
'patterns': ['锤子线', '双底'],
'stop_loss': 175.0,
'take_profit': 200.0,
'reasons': ['RSI 超卖', 'MACD 金叉'],
'timestamp': '2026-01-22 15:30:00'
}
table_id: 表 ID
返回:
创建或更新的记录
"""
# 转换数据格式为飞书字段格式
fields = {
"股票代码": signal_data.get('ticker', ''),
"股票名称": signal_data.get('name', ''),
"当前价格": signal_data.get('current_price', 0),
"综合评分": signal_data.get('score', 0),
"操作建议": signal_data.get('action', 'HOLD'),
"RSI": signal_data.get('rsi', 50),
"MACD信号": signal_data.get('macd_signal', ''),
"KDJ信号": signal_data.get('kdj_signal', ''),
"背离信号": signal_data.get('divergence', '无'),
"形态信号": signal_data.get('patterns', []), # 多选字段
"止损价": signal_data.get('stop_loss', 0),
"止盈价": signal_data.get('take_profit', 0),
"分析理由": '\n'.join(signal_data.get('reasons', [])),
"更新时间": to_feishu_timestamp(signal_data.get('timestamp'))
}
# 查找是否已存在该股票的记录
existing = bitable.find_record_by_field("股票代码", fields["股票代码"], table_id)
if existing:
# 更新现有记录
record_id = existing['record_id']
return bitable.update_record(record_id, fields, table_id)
else:
# 创建新记录
return bitable.create_record(fields, table_id)
def sync_holding(bitable: FeishuBitable, holding_data: dict,
table_id: str = None) -> dict:
"""
同步持仓数据到飞书
参数:
holding_data: 持仓数据
"""
fields = {
"股票代码": holding_data.get('ticker', ''),
"股票名称": holding_data.get('name', ''),
"持仓数量": holding_data.get('quantity', 0),
"成本价": holding_data.get('cost_price', 0),
"当前价": holding_data.get('current_price', 0),
"盈亏金额": holding_data.get('profit_amount', 0),
"盈亏比例": holding_data.get('profit_ratio', 0),
"市场": holding_data.get('market', '美股'),
"买入日期": to_feishu_timestamp(holding_data.get('buy_date')),
"备注": holding_data.get('note', '')
}
existing = bitable.find_record_by_field("股票代码", fields["股票代码"], table_id)
if existing:
return bitable.update_record(existing['record_id'], fields, table_id)
else:
return bitable.create_record(fields, table_id)
def sync_trade_record(bitable: FeishuBitable, trade_data: dict,
table_id: str = None) -> dict:
"""
同步交易记录到飞书(追加模式)
参数:
trade_data: 交易数据
"""
fields = {
"股票代码": trade_data.get('ticker', ''),
"交易类型": trade_data.get('trade_type', '买入'), # 买入/卖出/做T
"交易价格": trade_data.get('price', 0),
"交易数量": trade_data.get('quantity', 0),
"交易金额": trade_data.get('amount', 0),
"交易时间": to_feishu_timestamp(trade_data.get('timestamp')),
"触发信号": trade_data.get('signal', ''),
"备注": trade_data.get('note', '')
}
# 交易记录始终追加,不覆盖
return bitable.create_record(fields, table_id)
def batch_sync_signals(bitable: FeishuBitable, signals: List[dict],
table_id: str = None) -> dict:
"""
批量同步股票信号
参数:
signals: 信号数据列表
"""
results = {
'success': 0,
'failed': 0,
'errors': []
}
for signal in signals:
try:
sync_stock_signal(bitable, signal, table_id)
results['success'] += 1
except Exception as e:
results['failed'] += 1
results['errors'].append({
'ticker': signal.get('ticker'),
'error': str(e)
})
return results
# ==========================================
# 便捷函数
# ==========================================
def quick_sync_signal(signal_data: dict, config_path: str = None) -> dict:
"""
快速同步单只股票信号(一行调用)
示例:
quick_sync_signal({
'ticker': 'AAPL',
'score': 5,
'action': 'BUY',
'rsi': 45,
...
})
"""
bitable = FeishuBitable(config_path)
return sync_stock_signal(bitable, signal_data)
def test_connection(config_path: str = None) -> dict:
"""
测试飞书连接
返回:
{'status': 'ok', 'tables': [...]} 或错误信息
"""
try:
bitable = FeishuBitable(config_path)
tables = bitable.list_tables()
return {
'status': 'ok',
'message': '连接成功',
'tables': [{'id': t['table_id'], 'name': t['name']} for t in tables]
}
except Exception as e:
return {
'status': 'error',
'message': str(e)
}
# ==========================================
# 测试
# ==========================================
if __name__ == "__main__":
print("测试飞书连接...")
result = test_connection()
print(json.dumps(result, ensure_ascii=False, indent=2))

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,827 @@
"""
股票分析器 Skill - 主协调器
混合数据源架构:
- Yahoo Finance: 实时价格数据 + MACD 计算
- Alpha Vantage MCP: RSI、Bollinger Bands 等技术指标
使用示例:
analyzer = StockAnalyzer()
result = analyzer.analyze("AAPL", ["RSI", "MACD", "Bollinger"])
print(result)
"""
from typing import List, Dict, Optional, Any, Tuple
from datetime import datetime, timedelta
from abc import ABC, abstractmethod
import json
class DataSource(ABC):
"""数据源抽象基类"""
@abstractmethod
def get_quote(self, ticker: str) -> Dict[str, Any]:
"""获取最新报价"""
pass
@abstractmethod
def get_indicator(self, ticker: str, indicator: str, **params) -> Dict[str, Any]:
"""获取技术指标"""
pass
class YahooFinanceSource(DataSource):
"""
Yahoo Finance 数据源
优势:
- 接近实时的价格数据
- 可自行计算 MACD(Alpha Vantage 免费版不支持)
- 无严格请求限制
"""
def __init__(self):
self.name = "Yahoo Finance"
self._yf = None
self._ta = None
def _ensure_imports(self):
"""延迟导入依赖库"""
if self._yf is None:
try:
import yfinance as yf
self._yf = yf
except ImportError:
raise ImportError("请安装 yfinance: pip install yfinance")
if self._ta is None:
try:
import pandas_ta as ta
self._ta = ta
except ImportError:
# pandas_ta 可选,MACD 可以手动计算
self._ta = None
def get_quote(self, ticker: str) -> Dict[str, Any]:
"""获取 Yahoo Finance 实时报价"""
self._ensure_imports()
stock = self._yf.Ticker(ticker)
info = stock.fast_info
return {
'source': self.name,
'ticker': ticker.upper(),
'price': float(info.last_price) if hasattr(info, 'last_price') else None,
'open': float(info.open) if hasattr(info, 'open') else None,
'high': float(info.day_high) if hasattr(info, 'day_high') else None,
'low': float(info.day_low) if hasattr(info, 'day_low') else None,
'volume': int(info.last_volume) if hasattr(info, 'last_volume') else None,
'previous_close': float(info.previous_close) if hasattr(info, 'previous_close') else None,
'timestamp': datetime.now().isoformat(),
'realtime': True # Yahoo 数据接近实时
}
def get_indicator(self, ticker: str, indicator: str, **params) -> Dict[str, Any]:
"""计算技术指标(使用 pandas_ta 或手动计算)"""
self._ensure_imports()
# 获取历史数据
stock = self._yf.Ticker(ticker)
period = params.get('period', '3mo')
hist = stock.history(period=period)
if hist.empty:
return {'error': f'无法获取 {ticker} 的历史数据'}
if indicator.upper() == 'MACD':
return self._calculate_macd(hist, params)
elif indicator.upper() == 'SMA':
return self._calculate_sma(hist, params)
elif indicator.upper() == 'EMA':
return self._calculate_ema(hist, params)
else:
return {'error': f'Yahoo 数据源不支持指标: {indicator}'}
def _calculate_macd(self, hist, params) -> Dict[str, Any]:
"""计算 MACD 指标"""
fast = params.get('fast_period', 12)
slow = params.get('slow_period', 26)
signal = params.get('signal_period', 9)
close = hist['Close']
# 计算 EMA
ema_fast = close.ewm(span=fast, adjust=False).mean()
ema_slow = close.ewm(span=slow, adjust=False).mean()
# MACD 线 = 快 EMA - 慢 EMA
macd_line = ema_fast - ema_slow
# 信号线 = MACD 的 EMA
signal_line = macd_line.ewm(span=signal, adjust=False).mean()
# 柱状图 = MACD - 信号线
histogram = macd_line - signal_line
# 获取最新值
latest_macd = float(macd_line.iloc[-1])
latest_signal = float(signal_line.iloc[-1])
latest_hist = float(histogram.iloc[-1])
prev_hist = float(histogram.iloc[-2]) if len(histogram) > 1 else 0
# 判断信号
if latest_hist > 0 and prev_hist <= 0:
macd_signal = 'bullish_crossover'
interpretation = 'MACD 金叉 - 看涨信号'
elif latest_hist < 0 and prev_hist >= 0:
macd_signal = 'bearish_crossover'
interpretation = 'MACD 死叉 - 看跌信号'
elif latest_hist > 0:
macd_signal = 'bullish'
interpretation = 'MACD 位于信号线上方 - 多头趋势'
else:
macd_signal = 'bearish'
interpretation = 'MACD 位于信号线下方 - 空头趋势'
return {
'source': 'Yahoo Finance (计算)',
'indicator': 'MACD',
'macd_line': round(latest_macd, 4),
'signal_line': round(latest_signal, 4),
'histogram': round(latest_hist, 4),
'signal': macd_signal,
'interpretation': interpretation,
'parameters': {'fast': fast, 'slow': slow, 'signal': signal},
'timestamp': hist.index[-1].isoformat() if hasattr(hist.index[-1], 'isoformat') else str(hist.index[-1])
}
def _calculate_sma(self, hist, params) -> Dict[str, Any]:
"""计算简单移动平均线"""
period = params.get('time_period', 20)
close = hist['Close']
sma = close.rolling(window=period).mean()
return {
'source': 'Yahoo Finance (计算)',
'indicator': 'SMA',
'value': round(float(sma.iloc[-1]), 4),
'period': period,
'timestamp': hist.index[-1].isoformat() if hasattr(hist.index[-1], 'isoformat') else str(hist.index[-1])
}
def _calculate_ema(self, hist, params) -> Dict[str, Any]:
"""计算指数移动平均线"""
period = params.get('time_period', 20)
close = hist['Close']
ema = close.ewm(span=period, adjust=False).mean()
return {
'source': 'Yahoo Finance (计算)',
'indicator': 'EMA',
'value': round(float(ema.iloc[-1]), 4),
'period': period,
'timestamp': hist.index[-1].isoformat() if hasattr(hist.index[-1], 'isoformat') else str(hist.index[-1])
}
class AlphaVantageMCPSource(DataSource):
"""
Alpha Vantage MCP 数据源
优势:
- 官方 API,数据稳定可靠
- 提供预计算的 RSI、Bollinger Bands 等指标
- 历史数据丰富(20+ 年)
限制:
- 免费版 15 分钟延时
- 每天 25 次请求限制
- MACD 需要 Premium 订阅
"""
def __init__(self):
self.name = "Alpha Vantage"
# 注意:实际调用通过 Claude MCP 工具完成
# 这里只是数据格式化和处理逻辑
def get_quote(self, ticker: str) -> Dict[str, Any]:
"""
获取 Alpha Vantage 报价
注意:实际调用需要通过 MCP 工具:
mcp__Alpha-Vantage__TOOL_CALL(
tool_name="GLOBAL_QUOTE",
arguments={"symbol": ticker, "datatype": "json"}
)
"""
# 这是数据格式化模板
return {
'source': self.name,
'ticker': ticker.upper(),
'price': None, # 从 MCP 响应填充
'open': None,
'high': None,
'low': None,
'volume': None,
'previous_close': None,
'change': None,
'change_percent': None,
'timestamp': None,
'realtime': False, # Alpha Vantage 免费版有 15 分钟延时
'mcp_tool': 'GLOBAL_QUOTE',
'mcp_args': {'symbol': ticker, 'datatype': 'json'}
}
def get_indicator(self, ticker: str, indicator: str, **params) -> Dict[str, Any]:
"""
获取 Alpha Vantage 技术指标
支持的免费指标: RSI, BBANDS, SMA, EMA, STOCH, ADX, CCI, AROON, MFI, OBV 等
Premium 指标: MACD (需要订阅)
"""
indicator = indicator.upper()
# 通用参数
interval = params.get('interval', 'daily')
time_period = params.get('time_period', 14)
series_type = params.get('series_type', 'close')
if indicator == 'RSI':
return {
'source': self.name,
'indicator': 'RSI',
'mcp_tool': 'RSI',
'mcp_args': {
'symbol': ticker,
'interval': interval,
'time_period': time_period,
'series_type': series_type,
'datatype': 'json'
}
}
elif indicator in ['BBANDS', 'BOLLINGER']:
return {
'source': self.name,
'indicator': 'BBANDS',
'mcp_tool': 'BBANDS',
'mcp_args': {
'symbol': ticker,
'interval': interval,
'time_period': params.get('time_period', 20),
'series_type': series_type,
'nbdevup': params.get('nbdevup', 2),
'nbdevdn': params.get('nbdevdn', 2),
'datatype': 'json'
}
}
elif indicator == 'SMA':
return {
'source': self.name,
'indicator': 'SMA',
'mcp_tool': 'SMA',
'mcp_args': {
'symbol': ticker,
'interval': interval,
'time_period': time_period,
'series_type': series_type,
'datatype': 'json'
}
}
elif indicator == 'EMA':
return {
'source': self.name,
'indicator': 'EMA',
'mcp_tool': 'EMA',
'mcp_args': {
'symbol': ticker,
'interval': interval,
'time_period': time_period,
'series_type': series_type,
'datatype': 'json'
}
}
else:
return {
'source': self.name,
'indicator': indicator,
'error': f'指标 {indicator} 可能需要 Premium 订阅或不支持',
'suggestion': '请使用 RSI, BBANDS, SMA, EMA 等免费指标'
}
class DataValidator:
"""数据验证器 - 对比不同数据源的数据一致性"""
PRICE_TOLERANCE = 0.01 # 价格容差 1%
@staticmethod
def compare_quotes(yahoo_quote: Dict, av_quote: Dict) -> Dict[str, Any]:
"""
对比 Yahoo Finance 和 Alpha Vantage 的报价数据
返回对比结果,如果发现不一致会标记出来
"""
result = {
'comparison_time': datetime.now().isoformat(),
'yahoo': yahoo_quote,
'alpha_vantage': av_quote,
'discrepancies': [],
'aligned': True
}
yahoo_price = yahoo_quote.get('price')
av_price = av_quote.get('price')
if yahoo_price and av_price:
diff_pct = abs(yahoo_price - av_price) / yahoo_price * 100
if diff_pct > DataValidator.PRICE_TOLERANCE * 100:
result['discrepancies'].append({
'field': 'price',
'yahoo_value': yahoo_price,
'av_value': av_price,
'difference_pct': round(diff_pct, 4),
'warning': f'价格差异 {diff_pct:.2f}% 超过容差 {DataValidator.PRICE_TOLERANCE*100}%'
})
result['aligned'] = False
# 对比其他字段
for field in ['open', 'high', 'low']:
y_val = yahoo_quote.get(field)
av_val = av_quote.get(field)
if y_val and av_val:
diff_pct = abs(y_val - av_val) / y_val * 100
if diff_pct > DataValidator.PRICE_TOLERANCE * 100:
result['discrepancies'].append({
'field': field,
'yahoo_value': y_val,
'av_value': av_val,
'difference_pct': round(diff_pct, 4)
})
result['aligned'] = False
return result
class StockAnalyzer:
"""
股票分析器 - 混合数据源架构
数据源策略:
- 价格数据:优先 Yahoo Finance(实时)
- RSI, Bollinger Bands:Alpha Vantage MCP(预计算,准确)
- MACD:Yahoo Finance + pandas 计算(AV 免费版不支持)
- 数据验证:对比两个数据源确保一致性
"""
def __init__(self, config: Optional[Dict] = None):
self.config = config or self._default_config()
self.yahoo = YahooFinanceSource()
self.alpha_vantage = AlphaVantageMCPSource()
self.validator = DataValidator()
print(f"[股票分析器] 已初始化 - 混合数据源模式")
print(f" - 价格数据: Yahoo Finance (实时)")
print(f" - RSI/Bollinger: Alpha Vantage MCP")
print(f" - MACD: Yahoo Finance (本地计算)")
def analyze(
self,
ticker: str,
indicators: Optional[List[str]] = None,
period: str = "3mo",
validate_data: bool = True
) -> Dict[str, Any]:
"""
执行股票技术分析
参数:
ticker: 股票代码 (如 "AAPL", "MSFT")
indicators: 要计算的指标列表 (默认: ["RSI", "MACD", "Bollinger"])
period: 分析时间周期 (默认: "3mo")
validate_data: 是否对比验证数据源 (默认: True)
返回:
包含价格、指标、信号和建议的分析结果字典
"""
indicators = indicators or ["RSI", "MACD", "Bollinger"]
print(f"\n[股票分析器] 正在分析 {ticker}...")
print(f" - 指标: {indicators}")
print(f" - 周期: {period}")
result = {
'ticker': ticker.upper(),
'timestamp': datetime.now().isoformat(),
'data_sources': {
'price': 'Yahoo Finance',
'indicators': {}
},
'price_data': {},
'indicators': {},
'signal': {},
'validation': None
}
# 步骤 1: 获取价格数据 (Yahoo - 实时)
try:
result['price_data'] = self.yahoo.get_quote(ticker)
print(f" ✓ 价格数据获取成功: ${result['price_data'].get('price', 'N/A')}")
except Exception as e:
result['price_data'] = {'error': str(e)}
print(f" ✗ 价格数据获取失败: {e}")
# 步骤 2: 获取技术指标
for indicator in indicators:
indicator_upper = indicator.upper()
if indicator_upper == 'MACD':
# MACD 使用 Yahoo + 本地计算
try:
result['indicators']['MACD'] = self.yahoo.get_indicator(
ticker, 'MACD',
period=period,
fast_period=self.config['indicators']['MACD']['fast_period'],
slow_period=self.config['indicators']['MACD']['slow_period'],
signal_period=self.config['indicators']['MACD']['signal_period']
)
result['data_sources']['indicators']['MACD'] = 'Yahoo Finance (本地计算)'
print(f" ✓ MACD 计算成功")
except Exception as e:
result['indicators']['MACD'] = {'error': str(e)}
print(f" ✗ MACD 计算失败: {e}")
elif indicator_upper in ['RSI', 'BOLLINGER', 'BBANDS']:
# RSI 和 Bollinger 使用 Alpha Vantage MCP
indicator_name = 'BBANDS' if indicator_upper in ['BOLLINGER', 'BBANDS'] else indicator_upper
if indicator_name == 'RSI':
mcp_config = self.alpha_vantage.get_indicator(
ticker, 'RSI',
time_period=self.config['indicators']['RSI']['period']
)
else:
mcp_config = self.alpha_vantage.get_indicator(
ticker, 'BBANDS',
time_period=self.config['indicators']['Bollinger']['period'],
nbdevup=self.config['indicators']['Bollinger']['std_dev'],
nbdevdn=self.config['indicators']['Bollinger']['std_dev']
)
result['indicators'][indicator_name] = mcp_config
result['data_sources']['indicators'][indicator_name] = 'Alpha Vantage MCP'
print(f" → {indicator_name} 需要调用 MCP 工具: {mcp_config.get('mcp_tool')}")
else:
# 其他指标尝试 Alpha Vantage
mcp_config = self.alpha_vantage.get_indicator(ticker, indicator)
result['indicators'][indicator] = mcp_config
result['data_sources']['indicators'][indicator] = 'Alpha Vantage MCP'
# 步骤 3: 生成交易信号
result['signal'] = self._generate_signal(ticker, result['price_data'], result['indicators'])
# 步骤 4: 数据验证(可选)
if validate_data:
result['validation'] = {
'enabled': True,
'note': '数据验证需要同时调用 Alpha Vantage GLOBAL_QUOTE 进行对比',
'av_quote_config': self.alpha_vantage.get_quote(ticker)
}
print(f"\n[股票分析器] 分析完成")
print(f" → 信号: {result['signal'].get('action', 'N/A')} ({result['signal'].get('confidence', 'N/A')})")
return result
def compare(
self,
tickers: List[str],
rank_by: str = "momentum",
indicators: Optional[List[str]] = None
) -> Dict[str, Any]:
"""
对比多只股票并按技术强度排名
参数:
tickers: 股票代码列表
rank_by: 排名方式 ("momentum", "rsi", "composite")
indicators: 用于对比的指标
返回:
包含排名结果的字典
"""
indicators = indicators or ["RSI", "MACD"]
print(f"\n[股票分析器] 对比 {len(tickers)} 只股票...")
print(f" - 股票: {', '.join(tickers)}")
print(f" - 排名方式: {rank_by}")
comparisons = []
for ticker in tickers:
analysis = self.analyze(ticker, indicators, validate_data=False)
score = self._calculate_ranking_score(analysis, rank_by)
comparisons.append({
'ticker': ticker.upper(),
'analysis': analysis,
'score': score,
'rank': 0
})
# 按分数排序
comparisons.sort(key=lambda x: x['score'], reverse=True)
# 分配排名
for idx, comp in enumerate(comparisons, 1):
comp['rank'] = idx
result = {
'ranked_stocks': comparisons,
'ranking_method': rank_by,
'total_analyzed': len(tickers),
'timestamp': datetime.now().isoformat()
}
print(f"\n[股票分析器] 对比完成")
print(" 排名结果:")
for comp in comparisons:
print(f" #{comp['rank']}: {comp['ticker']} (分数: {comp['score']:.2f})")
return result
def validate_data_sources(self, ticker: str) -> Dict[str, Any]:
"""
验证两个数据源的数据一致性
这个方法会同时从 Yahoo Finance 和 Alpha Vantage 获取数据,
并对比关键字段。如果发现不一致会返回警告。
返回:
包含对比结果和任何差异警告的字典
"""
print(f"\n[数据验证] 正在验证 {ticker} 的数据源一致性...")
# 获取 Yahoo 数据
yahoo_quote = self.yahoo.get_quote(ticker)
# Alpha Vantage 需要通过 MCP 调用
av_config = self.alpha_vantage.get_quote(ticker)
return {
'ticker': ticker.upper(),
'timestamp': datetime.now().isoformat(),
'yahoo_quote': yahoo_quote,
'alpha_vantage_config': av_config,
'instruction': '请使用 MCP 工具获取 Alpha Vantage 数据后调用 compare_quotes() 进行对比',
'note': '如果价格差异超过 1%,将会触发警告'
}
def _default_config(self) -> Dict:
"""默认配置"""
return {
'data_sources': {
'price': 'yahoo_finance',
'rsi': 'alpha_vantage',
'macd': 'yahoo_finance',
'bollinger': 'alpha_vantage'
},
'indicators': {
'RSI': {
'period': 14,
'overbought': 70,
'oversold': 30
},
'MACD': {
'fast_period': 12,
'slow_period': 26,
'signal_period': 9
},
'Bollinger': {
'period': 20,
'std_dev': 2
}
},
'validation': {
'enabled': True,
'price_tolerance': 0.01 # 1%
}
}
def _generate_signal(
self,
ticker: str,
price_data: Dict,
indicators: Dict
) -> Dict[str, Any]:
"""
根据技术指标生成交易信号
策略: RSI + MACD 组合判断
- 买入: RSI 超卖 + MACD 金叉
- 卖出: RSI 超买 + MACD 死叉
- 持有: 其他情况
"""
reasoning = []
base_signal = "HOLD"
confidence = "low"
# RSI 分析
rsi_data = indicators.get('RSI', {})
if 'mcp_args' not in rsi_data: # 如果已经有实际数据
rsi_value = rsi_data.get('value')
if rsi_value:
if rsi_value < 30:
reasoning.append(f"RSI 超卖 ({rsi_value:.1f} < 30) - 潜在买入机会")
base_signal = "BUY"
confidence = "moderate"
elif rsi_value > 70:
reasoning.append(f"RSI 超买 ({rsi_value:.1f} > 70) - 潜在卖出信号")
base_signal = "SELL"
confidence = "moderate"
else:
reasoning.append(f"RSI 中性区间 ({rsi_value:.1f})")
else:
reasoning.append("RSI 需要通过 MCP 获取")
# MACD 分析
macd_data = indicators.get('MACD', {})
macd_signal = macd_data.get('signal')
if macd_signal:
if macd_signal == 'bullish_crossover':
reasoning.append("MACD 金叉 - 看涨信号")
if base_signal == "BUY":
confidence = "high"
elif base_signal == "HOLD":
base_signal = "BUY"
confidence = "moderate"
elif macd_signal == 'bearish_crossover':
reasoning.append("MACD 死叉 - 看跌信号")
if base_signal == "SELL":
confidence = "high"
elif base_signal == "HOLD":
base_signal = "SELL"
confidence = "moderate"
elif macd_signal == 'bullish':
reasoning.append("MACD 多头趋势")
else:
reasoning.append("MACD 空头趋势")
# Bollinger Bands 分析
bb_data = indicators.get('BBANDS', {})
if 'mcp_args' not in bb_data:
# 如果有实际数据可以分析
pass
return {
'action': base_signal,
'confidence': confidence,
'reasoning': reasoning,
'price': price_data.get('price'),
'timestamp': datetime.now().isoformat()
}
def _calculate_ranking_score(self, analysis: Dict, method: str) -> float:
"""计算排名分数"""
indicators = analysis.get('indicators', {})
if method == "rsi":
rsi_data = indicators.get('RSI', {})
rsi = rsi_data.get('value', 50)
return min(rsi, 70) if rsi else 50
elif method == "momentum":
score = 50
# RSI 贡献
rsi_data = indicators.get('RSI', {})
if 'value' in rsi_data:
score = rsi_data['value']
# MACD 贡献
macd_data = indicators.get('MACD', {})
macd_signal = macd_data.get('signal')
if macd_signal == 'bullish_crossover':
score += 15
elif macd_signal == 'bullish':
score += 5
elif macd_signal == 'bearish_crossover':
score -= 15
elif macd_signal == 'bearish':
score -= 5
return score
else: # composite
rsi_data = indicators.get('RSI', {})
rsi = rsi_data.get('value', 50)
macd_data = indicators.get('MACD', {})
macd_hist = macd_data.get('histogram', 0)
return (rsi * 0.6) + (macd_hist * 20 * 0.4)
def format_av_response(av_response: Dict, indicator: str) -> Dict[str, Any]:
"""
格式化 Alpha Vantage MCP 响应
用于处理从 MCP 工具返回的原始数据
"""
if 'error' in av_response or 'Information' in av_response:
return {'error': av_response.get('error') or av_response.get('Information')}
if indicator == 'RSI':
meta = av_response.get('Meta Data', {})
data = av_response.get('Technical Analysis: RSI', {})
if data:
latest_date = list(data.keys())[0]
latest_value = float(data[latest_date]['RSI'])
# 判断信号
if latest_value < 30:
signal = 'oversold'
interpretation = f'RSI 超卖 ({latest_value:.1f}) - 潜在买入机会'
elif latest_value > 70:
signal = 'overbought'
interpretation = f'RSI 超买 ({latest_value:.1f}) - 潜在卖出信号'
else:
signal = 'neutral'
interpretation = f'RSI 中性 ({latest_value:.1f})'
return {
'source': 'Alpha Vantage',
'indicator': 'RSI',
'value': latest_value,
'signal': signal,
'interpretation': interpretation,
'timestamp': latest_date
}
elif indicator == 'BBANDS':
meta = av_response.get('Meta Data', {})
data = av_response.get('Technical Analysis: BBANDS', {})
if data:
latest_date = list(data.keys())[0]
latest = data[latest_date]
return {
'source': 'Alpha Vantage',
'indicator': 'BBANDS',
'upper_band': float(latest['Real Upper Band']),
'middle_band': float(latest['Real Middle Band']),
'lower_band': float(latest['Real Lower Band']),
'timestamp': latest_date
}
elif indicator == 'GLOBAL_QUOTE':
quote = av_response.get('Global Quote', {})
if quote:
return {
'source': 'Alpha Vantage',
'ticker': quote.get('01. symbol'),
'price': float(quote.get('05. price', 0)),
'open': float(quote.get('02. open', 0)),
'high': float(quote.get('03. high', 0)),
'low': float(quote.get('04. low', 0)),
'volume': int(quote.get('06. volume', 0)),
'previous_close': float(quote.get('08. previous close', 0)),
'change': float(quote.get('09. change', 0)),
'change_percent': quote.get('10. change percent'),
'timestamp': quote.get('07. latest trading day'),
'realtime': False
}
return av_response
def main():
"""演示混合数据源股票分析"""
print("=" * 60)
print("股票分析器 - 混合数据源演示")
print("=" * 60)
analyzer = StockAnalyzer()
# 示例 1: 单股分析
print("\n--- 示例 1: 分析 AAPL ---")
result = analyzer.analyze("AAPL", ["RSI", "MACD", "Bollinger"])
print("\n分析结果摘要:")
print(f" 股票: {result['ticker']}")
print(f" 价格: ${result['price_data'].get('price', 'N/A')}")
print(f" 信号: {result['signal']['action']}")
print(f" 置信度: {result['signal']['confidence']}")
print(f" 理由: {', '.join(result['signal']['reasoning'])}")
print("\n" + "=" * 60)
print("演示完成!")
print("=" * 60)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,424 @@
"""
股票持仓管理模块
功能:
- 创建 Excel 持仓模板
- 读取持仓数据
- 计算实时盈亏
- 记录交易历史
配置说明:
通过环境变量 PORTFOLIO_PATH 或 config.json 配置持仓文件路径
"""
from typing import Dict, List, Optional, Any
from datetime import datetime
from pathlib import Path
import json
import os
def get_portfolio_path(output_path: str = None) -> Path:
"""获取持仓文件路径"""
if output_path:
return Path(output_path)
# 优先使用环境变量
if os.environ.get('PORTFOLIO_PATH'):
return Path(os.environ['PORTFOLIO_PATH'])
# 其次使用配置文件
config_path = Path('./config.json')
if config_path.exists():
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
if config.get('portfolio_path'):
return Path(config['portfolio_path'])
# 默认使用当前目录
return Path('./my_portfolio.xlsx')
def create_portfolio_template(output_path: str = None) -> str:
"""
创建 Excel 持仓模板
参数:
output_path: 输出路径,默认为当前目录的 my_portfolio.xlsx
返回:
创建的文件路径
"""
try:
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
from openpyxl.utils import get_column_letter
except ImportError:
raise ImportError("请安装 openpyxl: pip install openpyxl")
output_path = get_portfolio_path(output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
wb = Workbook()
# ===== Sheet 1: 持仓 =====
ws_holdings = wb.active
ws_holdings.title = "持仓"
# 表头样式
header_font = Font(bold=True, color="FFFFFF")
header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
header_alignment = Alignment(horizontal="center", vertical="center")
thin_border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# 持仓表头
holdings_headers = [
"股票代码", "股票名称", "持仓数量", "买入均价",
"当前价格", "市值", "盈亏金额", "盈亏比例", "买入日期", "备注"
]
for col, header in enumerate(holdings_headers, 1):
cell = ws_holdings.cell(row=1, column=col, value=header)
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_alignment
cell.border = thin_border
# 示例数据
sample_holdings = [
["AAPL", "苹果公司", 10, 180.00, "", "", "", "", "2024-01-15", "长期持有"],
["TSLA", "特斯拉", 5, 400.00, "", "", "", "", "2024-06-01", ""],
["GOOGL", "谷歌", 3, 150.00, "", "", "", "", "2024-03-20", ""],
]
for row_idx, row_data in enumerate(sample_holdings, 2):
for col_idx, value in enumerate(row_data, 1):
cell = ws_holdings.cell(row=row_idx, column=col_idx, value=value)
cell.border = thin_border
if col_idx >= 5: # 当前价格及之后的列
cell.alignment = Alignment(horizontal="right")
# 添加公式说明
ws_holdings.cell(row=6, column=1, value="说明:").font = Font(bold=True, color="FF0000")
ws_holdings.cell(row=7, column=1, value="• 股票代码:美股用代码如 AAPL,港股用 0700.HK,A股用 600519.SS")
ws_holdings.cell(row=8, column=1, value="• 当前价格、市值、盈亏:由 Claude 自动更新填写")
ws_holdings.cell(row=9, column=1, value="• 只需填写:股票代码、持仓数量、买入均价、买入日期")
# 调整列宽
column_widths = [12, 15, 12, 12, 12, 15, 15, 12, 12, 20]
for idx, width in enumerate(column_widths, 1):
ws_holdings.column_dimensions[get_column_letter(idx)].width = width
# ===== Sheet 2: 交易记录 =====
ws_transactions = wb.create_sheet("交易记录")
transaction_headers = [
"日期", "股票代码", "操作", "数量", "价格", "金额", "手续费", "备注"
]
for col, header in enumerate(transaction_headers, 1):
cell = ws_transactions.cell(row=1, column=col, value=header)
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_alignment
cell.border = thin_border
# 示例交易
sample_transactions = [
["2024-01-15", "AAPL", "买入", 10, 180.00, 1800.00, 0, "首次建仓"],
["2024-03-20", "GOOGL", "买入", 3, 150.00, 450.00, 0, ""],
["2024-06-01", "TSLA", "买入", 5, 400.00, 2000.00, 0, ""],
]
for row_idx, row_data in enumerate(sample_transactions, 2):
for col_idx, value in enumerate(row_data, 1):
cell = ws_transactions.cell(row=row_idx, column=col_idx, value=value)
cell.border = thin_border
# 调整列宽
trans_widths = [12, 12, 8, 10, 12, 12, 10, 25]
for idx, width in enumerate(trans_widths, 1):
ws_transactions.column_dimensions[get_column_letter(idx)].width = width
# ===== Sheet 3: 账户汇总 =====
ws_summary = wb.create_sheet("账户汇总")
summary_data = [
["账户汇总", ""],
["", ""],
["总投入", "=SUM(交易记录!F:F)"],
["当前市值", "(由 Claude 更新)"],
["总盈亏", "(由 Claude 更新)"],
["收益率", "(由 Claude 更新)"],
["", ""],
["最后更新时间", datetime.now().strftime("%Y-%m-%d %H:%M")],
]
ws_summary.cell(row=1, column=1, value="账户汇总").font = Font(bold=True, size=14)
for row_idx, (label, value) in enumerate(summary_data[2:], 3):
ws_summary.cell(row=row_idx, column=1, value=label).font = Font(bold=True)
ws_summary.cell(row=row_idx, column=2, value=value)
ws_summary.column_dimensions['A'].width = 15
ws_summary.column_dimensions['B'].width = 20
# ===== Sheet 4: 使用说明 =====
ws_help = wb.create_sheet("使用说明")
help_text = [
["股票持仓管理表 - 使用说明"],
[""],
["一、持仓表(必填项)"],
[" • 股票代码:填写股票代码"],
[" - 美股:直接填代码,如 AAPL, TSLA, GOOGL"],
[" - 港股:代码后加 .HK,如 0700.HK, 9988.HK"],
[" - A股:代码后加 .SS(上海) 或 .SZ(深圳),如 600519.SS"],
[" • 持仓数量:当前实际持有的股数"],
[" • 买入均价:当前持仓的平均成本价(做T后的实际均价)"],
[" • 买入日期:首次建仓日期 或 最近一次加仓日期"],
[""],
["二、做T操作说明"],
[" • 持仓表只记录「结果」,不需要记录每次操作"],
[" • 例如:TSLA 做了几轮T,最终持仓100股,均价$380"],
[" → 直接填:数量=100,均价=380"],
[" • 均价可从券商App查看,或自己计算"],
[" • 想追踪每次操作盈亏,可在「交易记录」表记录(可选)"],
[""],
["三、自动更新项"],
[" • 当前价格:Claude 会自动获取实时价格填入"],
[" • 市值 = 持仓数量 × 当前价格"],
[" • 盈亏金额 = 市值 - (持仓数量 × 买入均价)"],
[" • 盈亏比例 = 盈亏金额 / (持仓数量 × 买入均价) × 100%"],
[""],
["四、交易记录(可选)"],
[" • 每次买入/卖出后,在交易记录表添加一行"],
[" • 操作类型:买入 / 卖出"],
[" • 不记录也不影响持仓分析"],
[""],
["五、使用 Claude 分析"],
[" • 告诉 Claude:'分析我的持仓'"],
[" • Claude 会读取此表格,更新价格并给出建议"],
]
for row_idx, row in enumerate(help_text, 1):
ws_help.cell(row=row_idx, column=1, value=row[0] if row else "")
ws_help.cell(row=1, column=1).font = Font(bold=True, size=14)
ws_help.column_dimensions['A'].width = 60
# 保存文件
wb.save(output_path)
print(f"✅ 持仓模板已创建: {output_path}")
return str(output_path)
def read_portfolio(file_path: str = None) -> Dict[str, Any]:
"""
读取 Excel 持仓文件
参数:
file_path: Excel 文件路径
返回:
包含持仓数据的字典
"""
try:
from openpyxl import load_workbook
except ImportError:
raise ImportError("请安装 openpyxl: pip install openpyxl")
file_path = get_portfolio_path(file_path)
wb = load_workbook(file_path, data_only=True)
# 读取持仓表
ws_holdings = wb["持仓"]
holdings = []
for row in ws_holdings.iter_rows(min_row=2, max_row=ws_holdings.max_row):
# 跳过空行或说明行
ticker = row[0].value
if not ticker or ticker.startswith("说明") or ticker.startswith("•"):
continue
holding = {
'ticker': str(ticker).upper(),
'name': row[1].value or "",
'shares': float(row[2].value or 0),
'avg_cost': float(row[3].value or 0),
'current_price': row[4].value, # 可能为空
'market_value': row[5].value,
'profit_loss': row[6].value,
'profit_loss_pct': row[7].value,
'buy_date': str(row[8].value) if row[8].value else "",
'notes': row[9].value or ""
}
if holding['shares'] > 0:
holdings.append(holding)
# 读取交易记录
ws_trans = wb["交易记录"]
transactions = []
for row in ws_trans.iter_rows(min_row=2, max_row=ws_trans.max_row):
date = row[0].value
if not date:
continue
transaction = {
'date': str(date),
'ticker': str(row[1].value or "").upper(),
'action': row[2].value or "",
'shares': float(row[3].value or 0),
'price': float(row[4].value or 0),
'amount': float(row[5].value or 0),
'fee': float(row[6].value or 0),
'notes': row[7].value or ""
}
transactions.append(transaction)
wb.close()
return {
'holdings': holdings,
'transactions': transactions,
'file_path': str(file_path),
'read_time': datetime.now().isoformat()
}
def update_portfolio_prices(file_path: str, prices: Dict[str, float]) -> Dict[str, Any]:
"""
更新持仓表中的当前价格和盈亏
参数:
file_path: Excel 文件路径
prices: 股票代码 -> 当前价格 的字典
返回:
更新后的持仓汇总
"""
try:
from openpyxl import load_workbook
except ImportError:
raise ImportError("请安装 openpyxl: pip install openpyxl")
file_path = get_portfolio_path(file_path)
wb = load_workbook(file_path)
ws_holdings = wb["持仓"]
total_cost = 0
total_value = 0
updated_holdings = []
for row_idx in range(2, ws_holdings.max_row + 1):
ticker = ws_holdings.cell(row=row_idx, column=1).value
if not ticker or ticker.startswith("说明") or ticker.startswith("•"):
continue
ticker = str(ticker).upper()
shares = float(ws_holdings.cell(row=row_idx, column=3).value or 0)
avg_cost = float(ws_holdings.cell(row=row_idx, column=4).value or 0)
if shares <= 0:
continue
# 获取当前价格
current_price = prices.get(ticker)
if current_price:
# 计算市值和盈亏
cost_basis = shares * avg_cost
market_value = shares * current_price
profit_loss = market_value - cost_basis
profit_loss_pct = (profit_loss / cost_basis * 100) if cost_basis > 0 else 0
# 更新单元格
ws_holdings.cell(row=row_idx, column=5, value=round(current_price, 2))
ws_holdings.cell(row=row_idx, column=6, value=round(market_value, 2))
ws_holdings.cell(row=row_idx, column=7, value=round(profit_loss, 2))
ws_holdings.cell(row=row_idx, column=8, value=f"{profit_loss_pct:.2f}%")
total_cost += cost_basis
total_value += market_value
updated_holdings.append({
'ticker': ticker,
'shares': shares,
'avg_cost': avg_cost,
'current_price': current_price,
'market_value': market_value,
'profit_loss': profit_loss,
'profit_loss_pct': profit_loss_pct
})
# 更新汇总表
ws_summary = wb["账户汇总"]
ws_summary.cell(row=4, column=2, value=round(total_value, 2))
ws_summary.cell(row=5, column=2, value=round(total_value - total_cost, 2))
total_return = ((total_value - total_cost) / total_cost * 100) if total_cost > 0 else 0
ws_summary.cell(row=6, column=2, value=f"{total_return:.2f}%")
ws_summary.cell(row=8, column=2, value=datetime.now().strftime("%Y-%m-%d %H:%M"))
wb.save(file_path)
wb.close()
return {
'total_cost': total_cost,
'total_value': total_value,
'total_profit_loss': total_value - total_cost,
'total_return_pct': total_return,
'holdings': updated_holdings,
'update_time': datetime.now().isoformat()
}
def format_portfolio_summary(portfolio_data: Dict[str, Any]) -> str:
"""
格式化持仓汇总为易读文本
"""
holdings = portfolio_data.get('holdings', [])
if not holdings:
return "持仓为空,请先添加股票"
lines = [
"**持仓汇总**",
"",
"| 股票 | 数量 | 成本价 | 现价 | 盈亏 |",
"|------|------|--------|------|------|"
]
for h in holdings:
profit_emoji = "+" if h.get('profit_loss', 0) >= 0 else ""
lines.append(
f"| {h['ticker']} | {h['shares']:.0f} | ${h['avg_cost']:.2f} | "
f"${h.get('current_price', 0):.2f} | {profit_emoji}{h.get('profit_loss_pct', 0):.1f}% |"
)
total = portfolio_data.get('total_profit_loss', 0)
total_pct = portfolio_data.get('total_return_pct', 0)
total_sign = "+" if total >= 0 else ""
lines.extend([
"",
f"**总投入**: ${portfolio_data.get('total_cost', 0):,.2f}",
f"**当前市值**: ${portfolio_data.get('total_value', 0):,.2f}",
f"**总盈亏**: {total_sign}${total:,.2f} ({total_sign}{total_pct:.2f}%)"
])
return "\n".join(lines)
if __name__ == "__main__":
# 测试创建模板
path = create_portfolio_template()
print(f"\n测试读取: {path}")
data = read_portfolio(path)
print(f"读取到 {len(data['holdings'])} 条持仓记录")