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