# 持仓成本可视化增强使用说明 ## 功能概述 持仓成本可视化增强功能提供精确的交易成本计算、持仓成本线标注、盈亏分布分析等功能,帮助用户更直观地了解持仓成本和盈亏情况。 ### 核心特性 ✅ **精确成本计算** - 印花税、佣金、过户费精确计算(符合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年 **功能状态**: ✅ 已完成并测试