Files
nofx/docs/pnl.md
2025-11-15 22:20:06 -05:00

300 lines
8.8 KiB
Markdown
Raw 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.

# PNL计算重构方案 - 最终设计
## 📋 核心问题与答案
### 1. **Initial Balance初始余额**
**定义:** 创建trader时的账户净值Total Equity作为所有PNL计算的基准
**设置时机:**
-**创建trader时自动获取** - 从交易所API获取当前的Total Equity
-**允许用户手动更新** - 充值/提现后可通过前端主动同步
**存储位置:**
- 数据库:`traders.initial_balance` 字段
**计算公式:**
```
Initial Balance = Total Wallet Balance + Total Unrealized Profit
= 当前账户净值(创建时快照)
```
---
### 2. **Equity账户净值**
**定义:** 账户的实时总价值
**计算公式:**
```
Total Equity = Total Wallet Balance + Total Unrealized Profit
```
**数据来源:** 实时从交易所API获取
**说明:**
- `Total Wallet Balance`: 账户中的实际USDT余额包括已实现盈亏
- `Total Unrealized Profit`: 所有持仓的未实现盈亏总和
- Equity会随着市场价格波动和持仓变化实时变化
---
### 3. **PNL盈亏**
#### 3.1 Total PNL总盈亏
**计算公式:**
```
Total PNL = Current Equity - Initial Balance
Total PNL % = (Total PNL / Initial Balance) × 100%
```
**示例:**
```
Initial Balance: 10,000 USDT (创建时)
Current Equity: 11,500 USDT (实时)
-----------------------------------
Total PNL: +1,500 USDT
Total PNL %: +15%
```
#### 3.2 Unrealized PNL未实现盈亏
**定义:** 当前所有持仓的未实现盈亏总和
**来源:** 直接从交易所API获取 `totalUnrealizedProfit`
#### 3.3 单个持仓的PNL%
**计算公式:**
```
Position PNL % = (Unrealized PnL / Margin Used) × 100%
```
其中:`Margin Used = Position Value / Leverage`
---
## 🎯 最终实现方案
### 核心原则
| 原则 | 说明 |
|-----|------|
| ❌ **禁用自动同步** | 系统**不会**自动修改Initial Balance |
| ✅ **创建时自动获取** | 创建trader时从交易所获取真实equity |
| ✅ **允许手动更新** | 用户可通过前端主动同步(充值/提现后) |
| 🔒 **常规更新保护** | UpdateTrader方法**不允许**修改Initial Balance |
---
## 🔧 实现细节
### 1. 创建Trader时自动获取Initial Balance
**文件:** `api/server.go:handleCreateTrader()`
**逻辑:**
```go
// 查询交易所余额
balanceInfo, _ := tempTrader.GetBalance()
// 提取钱包余额和未实现盈亏
totalWalletBalance := balanceInfo["totalWalletBalance"].(float64)
totalUnrealizedProfit := balanceInfo["totalUnrealizedProfit"].(float64)
// 计算Total Equity作为Initial Balance
initialEquity := totalWalletBalance + totalUnrealizedProfit
// 存入数据库
trader := &config.TraderRecord{
InitialBalance: initialEquity, // 自动设置
// ... 其他字段
}
```
---
### 2. 禁用自动同步机制
**修改:** `trader/auto_trader.go:autoSyncBalanceIfNeeded()`
**操作:**
- 函数重命名为 `autoSyncBalanceIfNeeded_DEPRECATED()`
-`runCycle()` 中注释掉调用
**效果:** 系统运行过程中**不会**自动修改Initial Balance
---
### 3. 保护UpdateTrader方法
**文件:** `config/database.go:UpdateTrader()`
**修改:** 从SQL UPDATE语句中移除 `initial_balance` 字段
**效果:** 常规的配置更新操作**无法**修改Initial Balance
---
### 4. 提供手动更新API
**端点:** `POST /traders/:id`
**实现:** `api/server.go:handleUpdateTrader()`
**用途:** update trader, 包括Initial Balance基准值
**请求体:**
```json
{
"initial_balance": 10000.0
}
```
**流程:**
```
1. 用户输入新的initial_balance值
2. 更新数据库的initial_balance字段
3. 重新加载trader到内存
4. 返回更新前后的对比信息
```
**特点:**
- ✅ 用户可以输入**任意值**,不限于交易所当前余额
- ✅ 适用于充值/提现后重置基准
- ✅ 也可用于手动校正或调整统计基准
---
## 📊 数据流设计
```
┌─────────────────────────────────────────┐
│ 1. 创建Trader │
│ - 用户配置AI模型、交易所 │
│ - 系统自动获取当前equity │
│ → initial_balance = Total Equity │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ 2. 运行期间 │
│ - 系统不会自动修改initial_balance │
│ - 实时计算: │
│ current_equity = API获取 │
│ total_pnl = current - initial │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ 3. 充值/提现后 │
│ - 用户点击"更新初始余额"按钮 │
│ - 更新initial_balance │
│ - PNL计算重新基于新的基准 │
└─────────────────────────────────────────┘
```
---
## 📝 字段定义总结
| 字段 | 定义 | 计算方式 | 存储位置 | 更新频率 |
|-----|------|---------|---------|---------|
| **Initial Balance** | 基准余额 | 创建/手动同步时获取equity | DB: traders.initial_balance | 创建时+手动 |
| **Current Equity** | 当前净值 | wallet + unrealized | 不存储(实时计算) | 实时 |
| **Total PNL** | 总盈亏 | current_equity - initial_balance | 不存储(实时计算) | 实时 |
| **Total PNL %** | 盈亏百分比 | (total_pnl / initial_balance) × 100 | 不存储(实时计算) | 实时 |
---
## 🎮 用户操作场景
### 场景1创建新的Trader
```
用户操作:填写基本配置(不需要输入余额)
系统行为自动从交易所获取当前equity设置为initial_balance
结果initial_balance = 当前账户净值
```
### 场景2正常交易运行
```
用户操作:无
系统行为实时计算PNL不修改initial_balance
结果PNL = 当前equity - initial_balance
```
### 场景3充值后重新校准
```
用户操作:充值 → 输入新的Initial Balance10000 + 5000 = 15000
系统行为更新initial_balance为15000
结果PNL统计基于新的基准15000计算
```
### 场景4提现后重新校准
```
用户操作:提现 → 输入新的Initial Balance10000 - 2000 = 8000
系统行为更新initial_balance为8000
结果PNL统计基于新的基准8000计算
```
### 场景5手动调整统计基准
```
用户操作想重新开始统计PNL → 输入当前账户净值作为新基准
系统行为更新initial_balance为用户输入的值
结果PNL统计重置从新基准开始计算
```
---
## ✅ 优势分析
1. **稳定性**PNL基准不会自动变化统计更可靠
2. **灵活性**:用户可以在需要时主动校准
3. **准确性**Initial Balance基于真实equity不是手动输入
4. **可控性**:充值/提现后用户可以重置PNL统计
---
## 🚀 前端需要做的改动
### 1. 创建Trader页面
- ✅ 移除"初始资金"输入框
- ✅ 添加说明:系统将自动获取您的账户净值
### 2. Trader详情页面
- ✅ 添加"更新初始余额"按钮/表单
- ✅ 弹窗/输入框让用户输入新的Initial Balance值
- ✅ 提示文案:
```
当前初始余额: 10,000 USDT
请输入新的初始余额用于重新校准PNL统计
```
### 4. 用户体验建议
- 💡 可以在输入框旁边显示当前账户净值作为参考
- 💡 充值/提现后提示用户是否需要更新Initial Balance
- 💡 显示更新前后的对比信息,让用户确认
---
## 📖 关键代码位置
| 功能 | 文件 | 行号/函数 |
|-----|------|----------|
| 创建时自动获取equity | api/server.go | handleCreateTrader:540-625 |
| 禁用自动同步 | trader/auto_trader.go | autoSyncBalanceIfNeeded_DEPRECATED:291 |
| 保护UpdateTrader | config/database.go | UpdateTrader:954-969 |
| 手动同步API | api/server.go | handleSyncBalance:937-1050 |
| 手动同步数据库方法 | config/database.go | UpdateTraderInitialBalance:977-982 |
---
## 🎯 总结
这个设计平衡了**稳定性**和**灵活性**
- Initial Balance不会被系统自动修改确保PNL统计的一致性
- 用户拥有主动权,可以在充值/提现后重新校准
- 创建时自动获取真实equity避免手动输入错误