fix: 修復5個關鍵交易bug(保證金計算、部分平倉統計、止損止盈分離、雙向持倉)

## 修復內容

### 1. 保證金計算錯誤(Critical)
- 修正提示詞中的保證金公式(adaptive.txt, nof1.txt, default.txt)
- 新增代碼級保證金驗證(auto_trader.go)
- 防止開倉時保證金不足錯誤(code=-2019)

### 2. 部分平倉統計錯誤(Medium)
- 修改統計邏輯:多次 partial_close 聚合為一筆交易
- 新增追蹤字段:remainingQuantity, accumulatedPnL
- 只在完全平倉時計入 TotalTrades++

### 3. 前端配置覆蓋問題(Medium)
- 修正 TraderConfigModal.tsx 條件判斷
- 防止空字符串覆蓋用戶選擇的提示詞

### 4/5. 動態止損/止盈刪除配對訂單(Critical)
- 新增接口:CancelStopLossOrders, CancelTakeProfitOrders
- 分離訂單取消邏輯(Binance, Hyperliquid, Aster)
- 調整止損時不刪除止盈,反之亦然

### 7. 雙向持倉模式初始化(Critical)
- 新增 setDualSidePosition() 函數
- 在 NewFuturesTrader() 中初始化 Hedge Mode
- 防止 code=-4061 錯誤(PositionSide 參數錯誤)

## 影響範圍
- 修改文件:10個
- 新增代碼:+480行
- 刪除代碼:-71行

## 測試狀態
-  編譯通過(go build ./...)
-  語法檢查通過
- ⚠️ 需要在測試環境運行驗證實際交易效果
This commit is contained in:
ZhouYongyou
2025-11-04 18:36:39 +08:00
parent eda6685af0
commit df2d6533de
10 changed files with 480 additions and 71 deletions

View File

@@ -1036,6 +1036,106 @@ func (t *AsterTrader) CancelStopOrders(symbol string) error {
return nil
}
// CancelStopLossOrders 仅取消止损单(不影响止盈单)
func (t *AsterTrader) CancelStopLossOrders(symbol string) error {
// 获取该币种的所有未完成订单
params := map[string]interface{}{
"symbol": symbol,
}
body, err := t.request("GET", "/fapi/v3/openOrders", params)
if err != nil {
return fmt.Errorf("获取未完成订单失败: %w", err)
}
var orders []map[string]interface{}
if err := json.Unmarshal(body, &orders); err != nil {
return fmt.Errorf("解析订单数据失败: %w", err)
}
// 过滤出止损单并取消
canceledCount := 0
for _, order := range orders {
orderType, _ := order["type"].(string)
// 只取消止损订单(不取消止盈订单)
if orderType == "STOP_MARKET" || orderType == "STOP" {
orderID, _ := order["orderId"].(float64)
cancelParams := map[string]interface{}{
"symbol": symbol,
"orderId": int64(orderID),
}
_, err := t.request("DELETE", "/fapi/v1/order", cancelParams)
if err != nil {
log.Printf(" ⚠ 取消止损单 %d 失败: %v", int64(orderID), err)
continue
}
canceledCount++
log.Printf(" ✓ 已取消止损单 (订单ID: %d, 类型: %s)", int64(orderID), orderType)
}
}
if canceledCount == 0 {
log.Printf(" %s 没有止损单需要取消", symbol)
} else {
log.Printf(" ✓ 已取消 %s 的 %d 个止损单", symbol, canceledCount)
}
return nil
}
// CancelTakeProfitOrders 仅取消止盈单(不影响止损单)
func (t *AsterTrader) CancelTakeProfitOrders(symbol string) error {
// 获取该币种的所有未完成订单
params := map[string]interface{}{
"symbol": symbol,
}
body, err := t.request("GET", "/fapi/v3/openOrders", params)
if err != nil {
return fmt.Errorf("获取未完成订单失败: %w", err)
}
var orders []map[string]interface{}
if err := json.Unmarshal(body, &orders); err != nil {
return fmt.Errorf("解析订单数据失败: %w", err)
}
// 过滤出止盈单并取消
canceledCount := 0
for _, order := range orders {
orderType, _ := order["type"].(string)
// 只取消止盈订单(不取消止损订单)
if orderType == "TAKE_PROFIT_MARKET" || orderType == "TAKE_PROFIT" {
orderID, _ := order["orderId"].(float64)
cancelParams := map[string]interface{}{
"symbol": symbol,
"orderId": int64(orderID),
}
_, err := t.request("DELETE", "/fapi/v1/order", cancelParams)
if err != nil {
log.Printf(" ⚠ 取消止盈单 %d 失败: %v", int64(orderID), err)
continue
}
canceledCount++
log.Printf(" ✓ 已取消止盈单 (订单ID: %d, 类型: %s)", int64(orderID), orderType)
}
}
if canceledCount == 0 {
log.Printf(" %s 没有止盈单需要取消", symbol)
} else {
log.Printf(" ✓ 已取消 %s 的 %d 个止盈单", symbol, canceledCount)
}
return nil
}
// FormatQuantity 格式化数量实现Trader接口
func (t *AsterTrader) FormatQuantity(symbol string, quantity float64) (string, error) {
formatted, err := t.formatQuantity(symbol, quantity)