功能细节优化
This commit is contained in:
435
功能实现/1_自选股分组管理使用说明.md
Normal file
435
功能实现/1_自选股分组管理使用说明.md
Normal file
@@ -0,0 +1,435 @@
|
||||
# 自选股分组管理使用说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
自选股分组管理功能允许用户将股票分类到不同的分组中,实现更精细的股票管理。
|
||||
|
||||
### 核心特性
|
||||
|
||||
✅ **多分组支持** - 预设 4 个分组(核心自选、观察池、持仓股、概念股),支持自定义创建
|
||||
✅ **分组快速切换** - 快速查看不同分组的股票
|
||||
✅ **拖拽排序** - 分组和股票都支持拖拽排序
|
||||
✅ **批量操作** - 批量添加、移动股票
|
||||
✅ **股票备注** - 每只股票可添加个人备注
|
||||
✅ **跨分组搜索** - 快速查找股票所在分组
|
||||
✅ **颜色标识** - 每个分组有独立颜色标识
|
||||
|
||||
---
|
||||
|
||||
## 数据结构
|
||||
|
||||
### 分组表 (watchlist_groups)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int | 分组 ID |
|
||||
| name | string | 分组名称 |
|
||||
| description | string | 分组描述 |
|
||||
| color | string | 颜色标识 (red/blue/green/purple) |
|
||||
| sort_order | int | 排序号 |
|
||||
| is_default | bool | 是否默认分组(不可删除) |
|
||||
| created_at | datetime | 创建时间 |
|
||||
|
||||
### 股票项表 (watchlist_items)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int | 项目 ID |
|
||||
| group_id | int | 所属分组 ID |
|
||||
| code | string | 股票代码 |
|
||||
| name | string | 股票名称 |
|
||||
| sort_order | int | 排序号 |
|
||||
| note | string | 个人备注 |
|
||||
| added_at | datetime | 添加时间 |
|
||||
|
||||
---
|
||||
|
||||
## API 接口
|
||||
|
||||
### 1. 获取所有分组
|
||||
|
||||
```bash
|
||||
GET /api/watchlist/groups
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "核心自选",
|
||||
"description": "重点关注的核心股票",
|
||||
"color": "red",
|
||||
"count": 5,
|
||||
"is_default": true,
|
||||
"sort_order": 0
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "观察池",
|
||||
"description": "待观察的潜力股",
|
||||
"color": "blue",
|
||||
"count": 3,
|
||||
"is_default": false,
|
||||
"sort_order": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 创建新分组
|
||||
|
||||
```bash
|
||||
POST /api/watchlist/groups
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "科技股",
|
||||
"description": "科技板块相关",
|
||||
"color": "purple"
|
||||
}
|
||||
```
|
||||
|
||||
**可选颜色**: `red`, `blue`, `green`, `purple`, `orange`, `yellow`
|
||||
|
||||
### 3. 更新分组信息
|
||||
|
||||
```bash
|
||||
PUT /api/watchlist/groups/{group_id}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "新名称",
|
||||
"description": "新描述",
|
||||
"color": "green"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 删除分组
|
||||
|
||||
```bash
|
||||
DELETE /api/watchlist/groups/{group_id}
|
||||
```
|
||||
|
||||
**注意**: 默认分组不能删除,删除分组会同时删除分组内的所有股票。
|
||||
|
||||
### 5. 重新排序分组
|
||||
|
||||
```bash
|
||||
POST /api/watchlist/groups/reorder
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"group_ids": [2, 1, 3, 4]
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 获取分组内的股票
|
||||
|
||||
```bash
|
||||
GET /api/watchlist/groups/{group_id}/stocks?with_quotes=true
|
||||
```
|
||||
|
||||
**参数**:
|
||||
- `with_quotes` (bool): 是否包含实时行情,默认 true
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"group": {
|
||||
"id": 1,
|
||||
"name": "核心自选",
|
||||
"description": "重点关注的核心股票",
|
||||
"color": "red"
|
||||
},
|
||||
"stocks": [
|
||||
{
|
||||
"id": 1,
|
||||
"code": "600519",
|
||||
"name": "贵州茅台",
|
||||
"price": 1680.5,
|
||||
"pct": 2.3,
|
||||
"change": 37.8,
|
||||
"amount": 125.6,
|
||||
"note": "长期持有",
|
||||
"added_at": "2024-01-15"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 添加股票到分组
|
||||
|
||||
```bash
|
||||
POST /api/watchlist/groups/{group_id}/stocks
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"code": "600519",
|
||||
"note": "优质白马股"
|
||||
}
|
||||
```
|
||||
|
||||
### 8. 批量添加股票
|
||||
|
||||
```bash
|
||||
POST /api/watchlist/groups/{group_id}/stocks/batch
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"codes": ["600519", "300750", "002594"]
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"added": 3,
|
||||
"skipped": 0
|
||||
}
|
||||
```
|
||||
|
||||
### 9. 从分组中移除股票
|
||||
|
||||
```bash
|
||||
DELETE /api/watchlist/stocks/{item_id}
|
||||
```
|
||||
|
||||
### 10. 移动股票到另一个分组
|
||||
|
||||
```bash
|
||||
POST /api/watchlist/stocks/{item_id}/move
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"target_group_id": 2
|
||||
}
|
||||
```
|
||||
|
||||
### 11. 更新股票备注
|
||||
|
||||
```bash
|
||||
PUT /api/watchlist/stocks/{item_id}/note
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"note": "待突破压力位"
|
||||
}
|
||||
```
|
||||
|
||||
### 12. 重新排序股票
|
||||
|
||||
```bash
|
||||
POST /api/watchlist/stocks/reorder
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"item_ids": [3, 1, 2, 5, 4]
|
||||
}
|
||||
```
|
||||
|
||||
### 13. 跨分组搜索股票
|
||||
|
||||
```bash
|
||||
GET /api/watchlist/search?keyword=茅台
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"code": "600519",
|
||||
"name": "贵州茅台",
|
||||
"group_id": 1,
|
||||
"group_name": "核心自选",
|
||||
"group_color": "red",
|
||||
"note": "长期持有"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例 1: 创建分组并添加股票
|
||||
|
||||
```bash
|
||||
# 1. 创建分组
|
||||
curl -X POST http://localhost:8000/api/watchlist/groups \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"科技股","description":"科技板块","color":"purple"}'
|
||||
|
||||
# 假设返回的 group_id 是 5
|
||||
|
||||
# 2. 批量添加股票
|
||||
curl -X POST http://localhost:8000/api/watchlist/groups/5/stocks/batch \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"codes":["300750","688981","002594"]}'
|
||||
|
||||
# 3. 查看分组内股票
|
||||
curl http://localhost:8000/api/watchlist/groups/5/stocks
|
||||
```
|
||||
|
||||
### 示例 2: 移动股票到不同分组
|
||||
|
||||
```bash
|
||||
# 1. 搜索股票
|
||||
curl "http://localhost:8000/api/watchlist/search?keyword=宁德"
|
||||
|
||||
# 假设找到 item_id 是 3
|
||||
|
||||
# 2. 移动到观察池(假设 group_id 是 2)
|
||||
curl -X POST http://localhost:8000/api/watchlist/stocks/3/move \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"target_group_id":2}'
|
||||
```
|
||||
|
||||
### 示例 3: 添加备注并排序
|
||||
|
||||
```bash
|
||||
# 1. 给股票添加备注
|
||||
curl -X PUT http://localhost:8000/api/watchlist/stocks/3/note \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"note":"关注突破1000元压力位"}'
|
||||
|
||||
# 2. 重新排序(把最重要的放前面)
|
||||
curl -X POST http://localhost:8000/api/watchlist/stocks/reorder \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"item_ids":[3,1,5,2,4]}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 初始化
|
||||
|
||||
### 首次使用
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
source .venv/bin/activate
|
||||
python cli.py init
|
||||
```
|
||||
|
||||
这会自动创建 4 个默认分组:
|
||||
1. **核心自选** (红色) - 重点关注的核心股票
|
||||
2. **观察池** (蓝色) - 待观察的潜力股
|
||||
3. **持仓股** (绿色) - 当前持仓的股票
|
||||
4. **概念股** (紫色) - 热门概念板块
|
||||
|
||||
---
|
||||
|
||||
## 数据迁移
|
||||
|
||||
如果你之前使用的是旧版 `watchlist.json` 文件,可以通过以下方式迁移:
|
||||
|
||||
```python
|
||||
import json
|
||||
import requests
|
||||
|
||||
# 读取旧数据
|
||||
with open('backend/watchlist.json') as f:
|
||||
old_codes = json.load(f)
|
||||
|
||||
# 添加到默认分组(核心自选,假设 id 是 1)
|
||||
requests.post(
|
||||
'http://localhost:8000/api/watchlist/groups/1/stocks/batch',
|
||||
json={'codes': old_codes}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 分组建议
|
||||
|
||||
1. **核心自选** - 重点关注、深度研究的 10-20 只股票
|
||||
2. **观察池** - 符合选股条件但尚未买入的 20-50 只
|
||||
3. **持仓股** - 当前实际持仓的股票
|
||||
4. **概念股** - 按热点题材分类(新能源、半导体、医药等)
|
||||
|
||||
### 使用技巧
|
||||
|
||||
1. **定期清理**: 每周清理观察池中不再关注的股票
|
||||
2. **备注记录**: 记录买入理由、目标价、止损位等关键信息
|
||||
3. **排序管理**: 按重要性或涨跌幅排序,优先级高的放前面
|
||||
4. **分组配色**: 用颜色快速识别分组性质(红=重点,蓝=观察,绿=持仓)
|
||||
|
||||
---
|
||||
|
||||
## 与其他功能集成
|
||||
|
||||
### 与预警系统集成
|
||||
|
||||
为分组内的股票批量设置预警:
|
||||
|
||||
```bash
|
||||
# 1. 获取分组内股票
|
||||
curl http://localhost:8000/api/watchlist/groups/1/stocks?with_quotes=false
|
||||
|
||||
# 2. 为每只股票设置预警
|
||||
for code in codes:
|
||||
curl -X POST http://localhost:8000/api/alerts \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"code":"'$code'","kind":"price_above","threshold":100}'
|
||||
```
|
||||
|
||||
### 与回测系统集成
|
||||
|
||||
批量回测分组内的股票:
|
||||
|
||||
```bash
|
||||
# 获取分组内股票列表,逐个回测
|
||||
curl http://localhost:8000/api/watchlist/groups/1/stocks?with_quotes=false
|
||||
|
||||
for code in codes:
|
||||
curl "http://localhost:8000/api/backtest?symbol=$code&fast=5&slow=20"
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 可以创建多少个分组?
|
||||
A: 没有硬性限制,但建议控制在 10 个以内,便于管理。
|
||||
|
||||
### Q: 一只股票可以在多个分组中吗?
|
||||
A: 可以。同一只股票可以添加到多个分组,互不影响。
|
||||
|
||||
### Q: 删除分组会删除股票的历史数据吗?
|
||||
A: 不会。只删除分组和分组关联关系,不影响股票的行情、交易等数据。
|
||||
|
||||
### Q: 如何快速找到某只股票在哪些分组?
|
||||
A: 使用跨分组搜索接口:`/api/watchlist/search?keyword=股票名称或代码`
|
||||
|
||||
### Q: 分组排序和股票排序如何保存?
|
||||
A: 调用对应的 reorder 接口后立即保存到数据库,重启服务不会丢失。
|
||||
|
||||
---
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 核心模块
|
||||
- `backend/watchlist_manager.py` - 分组管理核心逻辑
|
||||
- `backend/models.py` - 数据模型(WatchlistGroup、WatchlistItem)
|
||||
- `backend/main.py` - API 接口定义
|
||||
|
||||
### 数据库索引
|
||||
- `group_id` - 加速分组查询
|
||||
- `code` - 加速股票代码查询
|
||||
- `(group_id, code)` - 唯一约束,防止重复添加
|
||||
|
||||
---
|
||||
|
||||
**实现完成时间**: 2024年
|
||||
**功能状态**: ✅ 已完成并测试
|
||||
528
功能实现/2_持仓成本可视化使用说明.md
Normal file
528
功能实现/2_持仓成本可视化使用说明.md
Normal file
@@ -0,0 +1,528 @@
|
||||
# 持仓成本可视化增强使用说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
持仓成本可视化增强功能提供精确的交易成本计算、持仓成本线标注、盈亏分布分析等功能,帮助用户更直观地了解持仓成本和盈亏情况。
|
||||
|
||||
### 核心特性
|
||||
|
||||
✅ **精确成本计算** - 印花税、佣金、过户费精确计算(符合A股规则)
|
||||
✅ **成本线标注** - K线图上标注持仓成本线
|
||||
✅ **成本历史追踪** - 记录每次买入/卖出后的成本变化
|
||||
✅ **盈亏分布图** - 可视化展示盈利/亏损持仓分布
|
||||
✅ **交易成本预估** - 下单前估算实际成本
|
||||
✅ **成本明细拆解** - 详细展示每笔交易的成本构成
|
||||
|
||||
---
|
||||
|
||||
## 交易成本规则(A股)
|
||||
|
||||
### 成本构成
|
||||
|
||||
| 费用类型 | 费率 | 适用范围 | 说明 |
|
||||
|---------|------|---------|------|
|
||||
| **印花税** | 0.1% | 仅卖出 | 固定费率 |
|
||||
| **佣金** | 0.03% | 买入+卖出 | 最低5元 |
|
||||
| **过户费** | 0.001% | 买入+卖出 | 仅沪市 |
|
||||
|
||||
### 计算示例
|
||||
|
||||
**买入示例**(沪市):
|
||||
```
|
||||
股票代码: 600519(贵州茅台)
|
||||
成交价格: 1680元
|
||||
成交数量: 100股
|
||||
|
||||
成交金额 = 1680 × 100 = 168,000元
|
||||
佣金 = 168,000 × 0.0003 = 50.4元
|
||||
过户费 = 168,000 × 0.00001 = 1.68元
|
||||
印花税 = 0元(买入无印花税)
|
||||
|
||||
总成本 = 168,000 + 50.4 + 1.68 = 168,052.08元
|
||||
成本率 = 0.031%
|
||||
```
|
||||
|
||||
**卖出示例**(沪市):
|
||||
```
|
||||
成交金额 = 168,000元
|
||||
佣金 = 50.4元
|
||||
过户费 = 1.68元
|
||||
印花税 = 168,000 × 0.001 = 168元
|
||||
|
||||
总成本 = 220.08元
|
||||
实际到账 = 168,000 - 220.08 = 167,779.92元
|
||||
成本率 = 0.131%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API 接口
|
||||
|
||||
### 1. 获取持仓成本线
|
||||
|
||||
```bash
|
||||
GET /api/portfolio/cost_line/{code}
|
||||
```
|
||||
|
||||
**用途**: K线图上标注成本线
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"code": "600519",
|
||||
"name": "贵州茅台",
|
||||
"current_position": {
|
||||
"qty": 100,
|
||||
"avg_cost": 1680.5,
|
||||
"total_cost": 168050.0,
|
||||
"current_price": 1720.0,
|
||||
"market_value": 172000.0,
|
||||
"unrealized_pnl": 3950.0,
|
||||
"unrealized_pct": 2.35,
|
||||
"trades_count": 2
|
||||
},
|
||||
"cost_history": [
|
||||
{
|
||||
"date": "2024-01-15",
|
||||
"cost": 1650.0,
|
||||
"qty": 100,
|
||||
"action": "买入",
|
||||
"trade_price": 1650.0,
|
||||
"trade_qty": 100
|
||||
},
|
||||
{
|
||||
"date": "2024-02-10",
|
||||
"cost": 1680.5,
|
||||
"qty": 100,
|
||||
"action": "补仓",
|
||||
"trade_price": 1710.0,
|
||||
"trade_qty": 50
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**前端使用**(ECharts 标注成本线):
|
||||
```javascript
|
||||
// 在 K 线图上添加成本线
|
||||
const costLineData = await fetch(`/api/portfolio/cost_line/600519`).then(r => r.json());
|
||||
|
||||
if (costLineData.ok && costLineData.current_position) {
|
||||
const avgCost = costLineData.current_position.avg_cost;
|
||||
|
||||
option.series.push({
|
||||
type: 'line',
|
||||
name: '持仓成本',
|
||||
data: Array(dates.length).fill(avgCost),
|
||||
lineStyle: { color: '#FFA500', width: 2, type: 'dashed' },
|
||||
z: 10
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 获取持仓成本分布
|
||||
|
||||
```bash
|
||||
GET /api/portfolio/cost_distribution
|
||||
```
|
||||
|
||||
**用途**: 盈亏分布可视化
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"profitable": [
|
||||
{
|
||||
"code": "600519",
|
||||
"name": "贵州茅台",
|
||||
"qty": 100,
|
||||
"avg_cost": 1680.5,
|
||||
"current_price": 1720.0,
|
||||
"market_value": 172000.0,
|
||||
"cost_value": 168050.0,
|
||||
"unrealized": 3950.0,
|
||||
"unrealized_pct": 2.35
|
||||
}
|
||||
],
|
||||
"unprofitable": [
|
||||
{
|
||||
"code": "300750",
|
||||
"name": "宁德时代",
|
||||
"qty": 50,
|
||||
"avg_cost": 220.0,
|
||||
"current_price": 210.0,
|
||||
"market_value": 10500.0,
|
||||
"cost_value": 11000.0,
|
||||
"unrealized": -500.0,
|
||||
"unrealized_pct": -4.55
|
||||
}
|
||||
],
|
||||
"breakeven": [],
|
||||
"summary": {
|
||||
"total_positions": 2,
|
||||
"profitable_count": 1,
|
||||
"unprofitable_count": 1,
|
||||
"breakeven_count": 0,
|
||||
"win_rate": 50.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 估算交易成本
|
||||
|
||||
```bash
|
||||
POST /api/portfolio/estimate_cost
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"code": "600519",
|
||||
"price": 1680.0,
|
||||
"qty": 100,
|
||||
"side": "buy"
|
||||
}
|
||||
```
|
||||
|
||||
**用途**: 下单前预估实际成本
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"code": "600519",
|
||||
"price": 1680.0,
|
||||
"qty": 100,
|
||||
"side": "buy",
|
||||
"cost_detail": {
|
||||
"amount": 168000.0,
|
||||
"commission": 50.4,
|
||||
"stamp_tax": 0.0,
|
||||
"transfer_fee": 1.68,
|
||||
"total_cost": 52.08,
|
||||
"cost_rate": 0.031
|
||||
},
|
||||
"net_amount": 168052.08,
|
||||
"message": "买入需支付: 168052.08 元(含交易成本 52.08 元)"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 获取持仓成本明细
|
||||
|
||||
```bash
|
||||
GET /api/portfolio/cost_breakdown/{code}
|
||||
```
|
||||
|
||||
**用途**: 查看累计交易成本拆解
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"code": "600519",
|
||||
"name": "贵州茅台",
|
||||
"total_cost": 168052.08,
|
||||
"purchase_amount": 168000.0,
|
||||
"commission": 50.4,
|
||||
"stamp_tax": 0.0,
|
||||
"transfer_fee": 1.68,
|
||||
"cost_rate": 0.031,
|
||||
"trades": [
|
||||
{
|
||||
"date": "2024-01-15",
|
||||
"price": 1680.0,
|
||||
"qty": 100,
|
||||
"amount": 168000.0,
|
||||
"cost_detail": {
|
||||
"amount": 168000.0,
|
||||
"commission": 50.4,
|
||||
"stamp_tax": 0.0,
|
||||
"transfer_fee": 1.68,
|
||||
"total_cost": 52.08,
|
||||
"cost_rate": 0.031
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 场景 1: K线图上标注成本线
|
||||
|
||||
**前端实现示例**:
|
||||
```javascript
|
||||
// 获取K线数据
|
||||
const klineData = await fetch(`/api/kline?symbol=600519&days=60`).then(r => r.json());
|
||||
|
||||
// 获取成本线数据
|
||||
const costData = await fetch(`/api/portfolio/cost_line/600519`).then(r => r.json());
|
||||
|
||||
if (costData.ok && costData.current_position) {
|
||||
const avgCost = costData.current_position.avg_cost;
|
||||
const currentPrice = costData.current_position.current_price;
|
||||
|
||||
// ECharts 配置
|
||||
const option = {
|
||||
title: {
|
||||
text: `${costData.name} - 成本: ${avgCost} 当前: ${currentPrice}`
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'candlestick',
|
||||
data: klineData.ohlc
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
name: '持仓成本',
|
||||
data: Array(klineData.dates.length).fill(avgCost),
|
||||
lineStyle: {
|
||||
color: '#FFA500',
|
||||
width: 2,
|
||||
type: 'dashed'
|
||||
},
|
||||
markLine: {
|
||||
data: [{ yAxis: avgCost, name: `成本 ${avgCost}` }]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 场景 2: 盈亏分布饼图
|
||||
|
||||
```javascript
|
||||
const distData = await fetch('/api/portfolio/cost_distribution').then(r => r.json());
|
||||
|
||||
if (distData.ok) {
|
||||
const option = {
|
||||
title: { text: '持仓盈亏分布' },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
data: [
|
||||
{
|
||||
value: distData.profitable.length,
|
||||
name: `盈利 ${distData.profitable.length}`,
|
||||
itemStyle: { color: '#26a69a' }
|
||||
},
|
||||
{
|
||||
value: distData.unprofitable.length,
|
||||
name: `亏损 ${distData.unprofitable.length}`,
|
||||
itemStyle: { color: '#ef5350' }
|
||||
}
|
||||
]
|
||||
}]
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 场景 3: 下单前成本计算
|
||||
|
||||
```javascript
|
||||
// 用户输入买入价格和数量
|
||||
const buyPrice = 1680;
|
||||
const buyQty = 100;
|
||||
|
||||
const estimate = await fetch('/api/portfolio/estimate_cost', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
code: '600519',
|
||||
price: buyPrice,
|
||||
qty: buyQty,
|
||||
side: 'buy'
|
||||
})
|
||||
}).then(r => r.json());
|
||||
|
||||
if (estimate.ok) {
|
||||
alert(`${estimate.message}\n\n成本明细:\n` +
|
||||
`佣金: ${estimate.cost_detail.commission}元\n` +
|
||||
`过户费: ${estimate.cost_detail.transfer_fee}元\n` +
|
||||
`印花税: ${estimate.cost_detail.stamp_tax}元`);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完整示例
|
||||
|
||||
### Python 脚本示例
|
||||
|
||||
```python
|
||||
import requests
|
||||
import json
|
||||
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
# 1. 查看持仓成本线
|
||||
def show_cost_line(code):
|
||||
resp = requests.get(f"{BASE_URL}/api/portfolio/cost_line/{code}")
|
||||
data = resp.json()
|
||||
|
||||
if data["ok"]:
|
||||
pos = data["current_position"]
|
||||
print(f"\n{data['name']} ({code})")
|
||||
print(f"持仓数量: {pos['qty']}股")
|
||||
print(f"平均成本: {pos['avg_cost']}元")
|
||||
print(f"当前价格: {pos['current_price']}元")
|
||||
print(f"浮动盈亏: {pos['unrealized_pnl']}元 ({pos['unrealized_pct']}%)")
|
||||
|
||||
print("\n成本变化历史:")
|
||||
for h in data["cost_history"]:
|
||||
print(f"{h['date']} {h['action']}: 成本={h['cost']}元, 持仓={h['qty']}股")
|
||||
|
||||
# 2. 查看盈亏分布
|
||||
def show_distribution():
|
||||
resp = requests.get(f"{BASE_URL}/api/portfolio/cost_distribution")
|
||||
data = resp.json()
|
||||
|
||||
if data["ok"]:
|
||||
print(f"\n持仓总数: {data['summary']['total_positions']}")
|
||||
print(f"盈利: {data['summary']['profitable_count']}")
|
||||
print(f"亏损: {data['summary']['unprofitable_count']}")
|
||||
print(f"胜率: {data['summary']['win_rate']}%")
|
||||
|
||||
print("\n盈利股票:")
|
||||
for s in data["profitable"]:
|
||||
print(f"{s['name']}: +{s['unrealized']}元 (+{s['unrealized_pct']}%)")
|
||||
|
||||
print("\n亏损股票:")
|
||||
for s in data["unprofitable"]:
|
||||
print(f"{s['name']}: {s['unrealized']}元 ({s['unrealized_pct']}%)")
|
||||
|
||||
# 3. 估算交易成本
|
||||
def estimate_cost(code, price, qty, side="buy"):
|
||||
resp = requests.post(f"{BASE_URL}/api/portfolio/estimate_cost", json={
|
||||
"code": code,
|
||||
"price": price,
|
||||
"qty": qty,
|
||||
"side": side
|
||||
})
|
||||
data = resp.json()
|
||||
|
||||
if data["ok"]:
|
||||
print(f"\n{data['message']}")
|
||||
cost = data["cost_detail"]
|
||||
print(f"\n成本明细:")
|
||||
print(f" 成交金额: {cost['amount']}元")
|
||||
print(f" 佣金: {cost['commission']}元")
|
||||
print(f" 印花税: {cost['stamp_tax']}元")
|
||||
print(f" 过户费: {cost['transfer_fee']}元")
|
||||
print(f" 总成本: {cost['total_cost']}元 ({cost['cost_rate']}%)")
|
||||
|
||||
# 运行示例
|
||||
if __name__ == "__main__":
|
||||
show_cost_line("600519")
|
||||
show_distribution()
|
||||
estimate_cost("600519", 1680, 100, "buy")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 成本线使用建议
|
||||
|
||||
- **买入参考**: 当价格接近成本线时考虑加仓
|
||||
- **止损参考**: 设置成本线下方一定比例作为止损位
|
||||
- **目标价参考**: 成本线上方设置分批止盈位
|
||||
|
||||
### 2. 成本计算注意事项
|
||||
|
||||
- **沪深区别**: 深市(000、002、300开头)无过户费
|
||||
- **最低佣金**: 单笔佣金不足5元按5元收取
|
||||
- **印花税**: 只在卖出时收取,买入无需支付
|
||||
|
||||
### 3. 盈亏分析建议
|
||||
|
||||
- **定期检查**: 每周查看盈亏分布,调整持仓结构
|
||||
- **及时止损**: 亏损超过-8%的持仓需要重新评估
|
||||
- **落袋为安**: 盈利超过+20%考虑分批止盈
|
||||
|
||||
---
|
||||
|
||||
## 与其他功能集成
|
||||
|
||||
### 与自选股分组集成
|
||||
|
||||
```bash
|
||||
# 将持仓股添加到"持仓股"分组
|
||||
curl -X POST http://localhost:8000/api/watchlist/groups/3/stocks/batch \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"codes":["600519","300750"]}'
|
||||
```
|
||||
|
||||
### 与预警系统集成
|
||||
|
||||
```bash
|
||||
# 为持仓股设置成本价预警(跌破成本-5%)
|
||||
AVG_COST=1680
|
||||
STOP_LOSS=$(echo "$AVG_COST * 0.95" | bc)
|
||||
|
||||
curl -X POST http://localhost:8000/api/alerts \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"code":"600519","kind":"price_below","threshold":'$STOP_LOSS',"note":"跌破成本-5%"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 为什么计算的成本和实际有差异?
|
||||
A: 可能原因:
|
||||
1. 交易记录未完整录入
|
||||
2. 券商佣金费率不同(本系统默认万三)
|
||||
3. 分红、配股等特殊情况未计入
|
||||
|
||||
### Q: 如何修改佣金费率?
|
||||
A: 编辑 `backend/position_cost.py` 中的 `COST_CONFIG` 配置:
|
||||
```python
|
||||
COST_CONFIG = {
|
||||
"commission_rate": 0.0003, # 改为实际佣金率
|
||||
"commission_min": 5.0, # 改为实际最低佣金
|
||||
}
|
||||
```
|
||||
|
||||
### Q: 成本线为什么不显示?
|
||||
A: 检查:
|
||||
1. 该股票是否有交易记录
|
||||
2. 当前是否有持仓(已清仓则无成本线)
|
||||
3. 接口返回是否成功
|
||||
|
||||
### Q: 补仓后成本如何计算?
|
||||
A: 使用加权平均法:
|
||||
```
|
||||
新成本 = (原持仓成本 + 补仓金额 + 补仓费用) / 新持仓数量
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 核心模块
|
||||
- `backend/position_cost.py` - 成本计算核心逻辑
|
||||
- `backend/portfolio.py` - 原有持仓管理
|
||||
- `backend/main.py` - API 接口定义
|
||||
|
||||
### 算法说明
|
||||
|
||||
**移动加权平均成本法**:
|
||||
```python
|
||||
if side == "buy":
|
||||
total_cost += price * qty + fee
|
||||
total_qty += qty
|
||||
avg_cost = total_cost / total_qty
|
||||
else: # sell
|
||||
avg_cost = total_cost / total_qty
|
||||
pnl = (price - avg_cost) * qty - fee
|
||||
total_cost -= avg_cost * qty
|
||||
total_qty -= qty
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**实现完成时间**: 2024年
|
||||
**功能状态**: ✅ 已完成并测试
|
||||
Reference in New Issue
Block a user