Files
stock_cursor_v0/功能实现/2_持仓成本可视化使用说明.md
2026-06-15 01:26:39 +08:00

529 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 持仓成本可视化增强使用说明
## 功能概述
持仓成本可视化增强功能提供精确的交易成本计算、持仓成本线标注、盈亏分布分析等功能,帮助用户更直观地了解持仓成本和盈亏情况。
### 核心特性
**精确成本计算** - 印花税、佣金、过户费精确计算符合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年
**功能状态**: ✅ 已完成并测试