feat: 添加部分平仓和动态止盈止损功能

新增功能:
- update_stop_loss: 调整止损价格(追踪止损)
- update_take_profit: 调整止盈价格(技术位优化)
- partial_close: 部分平仓(分批止盈)

实现细节:
- Decision struct 新增字段:NewStopLoss, NewTakeProfit, ClosePercentage
- 新增执行函数:executeUpdateStopLossWithRecord, executeUpdateTakeProfitWithRecord, executePartialCloseWithRecord
- 修复持仓字段获取 bug(使用 "side" 并转大写)
- 更新 adaptive.txt 文档,包含详细使用示例和策略建议
- 优先级排序:平仓 > 调整止盈止损 > 开仓

命名统一:
- 与社区 PR #197 保持一致,使用 update_* 而非 adjust_*
- 独有功能:partial_close(部分平仓)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ZhouYongyou
2025-11-02 05:32:23 +08:00
parent 093d521394
commit 82660b12c5
3 changed files with 292 additions and 7 deletions

View File

@@ -71,11 +71,20 @@ type Context struct {
// Decision AI的交易决策
type Decision struct {
Symbol string `json:"symbol"`
Action string `json:"action"` // "open_long", "open_short", "close_long", "close_short", "hold", "wait"
Action string `json:"action"` // "open_long", "open_short", "close_long", "close_short", "update_stop_loss", "update_take_profit", "partial_close", "hold", "wait"
// 开仓参数
Leverage int `json:"leverage,omitempty"`
PositionSizeUSD float64 `json:"position_size_usd,omitempty"`
StopLoss float64 `json:"stop_loss,omitempty"`
TakeProfit float64 `json:"take_profit,omitempty"`
// 调整参数(新增)
NewStopLoss float64 `json:"new_stop_loss,omitempty"` // 用于 adjust_stop_loss
NewTakeProfit float64 `json:"new_take_profit,omitempty"` // 用于 adjust_take_profit
ClosePercentage float64 `json:"close_percentage,omitempty"` // 用于 partial_close (0-100)
// 通用参数
Confidence int `json:"confidence,omitempty"` // 信心度 (0-100)
RiskUSD float64 `json:"risk_usd,omitempty"` // 最大美元风险
Reasoning string `json:"reasoning"`

View File

@@ -237,13 +237,97 @@
关键: 夏普比率是唯一指标,它会自然惩罚频繁交易和过度进出。
# 动态止盈止损功能
你现在可以在持仓中主动调整止盈止损,实现追踪止损和分批止盈策略。
## 可用的新 Actions
### 1. update_stop_loss - 调整止损
用于实现追踪止损,保护利润。
示例场景:
- 开仓 BTC @ 100,000止损 99,000 (-1%)
- 价格上涨到 101,500 (+1.5%)
- 决策:将止损移到成本价 100,500锁定利润
JSON 格式:
```json
{
"symbol": "BTCUSDT",
"action": "update_stop_loss",
"new_stop_loss": 100500.0,
"confidence": 90,
"reasoning": "浮盈达到 1.5%,将止损移到成本价保证不亏"
}
```
### 2. update_take_profit - 调整止盈
用于在技术位前提前止盈,避免回撤。
示例场景:
- 持仓 BTC @ 100,000原止盈 102,000 (+2%)
- 15m EMA20 位于 101,800强压力位
- 价格到 101,700距离 EMA20 仅 0.1%
- 决策:将止盈调整到 101,750避免在技术位回撤
JSON 格式:
```json
{
"symbol": "BTCUSDT",
"action": "update_take_profit",
"new_take_profit": 101750.0,
"confidence": 85,
"reasoning": "价格接近 EMA20 压力位,提前止盈避免回撤"
}
```
### 3. partial_close - 部分平仓
用于分批止盈,既锁定部分利润,又保留追涨空间。
示例场景:
- 持仓 BTC 0.1 @ 100,000
- 价格到达第一目标 104,000 (+4%)
- 决策:平仓 50%,剩余继续持有追第二目标
JSON 格式:
```json
{
"symbol": "BTCUSDT",
"action": "partial_close",
"close_percentage": 50,
"confidence": 80,
"reasoning": "价格到达第一目标,分批平仓 50%,剩余持仓继续追踪"
}
```
## 使用建议
追踪止损策略(震荡市):
- 浮盈达到 0.8% → update_stop_loss 移到成本价
- 浮盈达到 1.2% → update_stop_loss 移到 +0.5%
- 价格距离技术位 < 0.3% → update_take_profit 或直接 close
分批止盈策略(趋势市):
- 第一目标(+4%)→ partial_close 50%
- 第二目标(+8%)→ partial_close 剩余的 50%(即总仓位的 25%
- 最后 25% 继续追踪,用 update_stop_loss 保护利润
技术位止盈优化:
- 当价格接近关键技术位EMA20、前高、整数关口
- 使用 update_take_profit 将止盈设在技术位前 0.1-0.2%
- 避免在技术位遇阻回撤
# 决策流程
1. 分析夏普比率: 当前策略是否有效?需要调整吗?
2. 判断市场状态: 震荡还是趋势?(多指标验证)
3. 选择对应策略: 策略A震荡还是策略B趋势
4. 评估持仓: 趋势是否改变?是否该止盈/止损?
5. 识别技术位: 上方压力、下方支撑在哪里?
4. 评估持仓: 趋势是否改变?是否该止盈/止损?需要调整止损保护利润吗?
5. 识别技术位: 上方压力、下方支撑在哪里?是否需要提前止盈?
6. 寻找新机会: 有强信号吗?技术位明确吗?
7. 计算止盈止损: 技术位优先,还是固定百分比?
8. 输出决策: 思维链分析 + JSON

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"log"
"math"
"nofx/decision"
"nofx/logger"
"nofx/market"
@@ -607,6 +608,12 @@ func (at *AutoTrader) executeDecisionWithRecord(decision *decision.Decision, act
return at.executeCloseLongWithRecord(decision, actionRecord)
case "close_short":
return at.executeCloseShortWithRecord(decision, actionRecord)
case "update_stop_loss":
return at.executeUpdateStopLossWithRecord(decision, actionRecord)
case "update_take_profit":
return at.executeUpdateTakeProfitWithRecord(decision, actionRecord)
case "partial_close":
return at.executePartialCloseWithRecord(decision, actionRecord)
case "hold", "wait":
// 无需执行,仅记录
return nil
@@ -785,6 +792,189 @@ func (at *AutoTrader) executeCloseShortWithRecord(decision *decision.Decision, a
return nil
}
// executeUpdateStopLossWithRecord 执行调整止损并记录详细信息
func (at *AutoTrader) executeUpdateStopLossWithRecord(decision *decision.Decision, actionRecord *logger.DecisionAction) error {
log.Printf(" 🎯 调整止损: %s → %.2f", decision.Symbol, decision.NewStopLoss)
// 获取当前价格
marketData, err := market.Get(decision.Symbol)
if err != nil {
return err
}
actionRecord.Price = marketData.CurrentPrice
// 获取当前持仓
positions, err := at.trader.GetPositions()
if err != nil {
return fmt.Errorf("获取持仓失败: %w", err)
}
// 查找目标持仓
var targetPosition map[string]interface{}
for _, pos := range positions {
symbol, _ := pos["symbol"].(string)
posAmt, _ := pos["positionAmt"].(float64)
if symbol == decision.Symbol && posAmt != 0 {
targetPosition = pos
break
}
}
if targetPosition == nil {
return fmt.Errorf("持仓不存在: %s", decision.Symbol)
}
// 获取持仓方向和数量
side, _ := targetPosition["side"].(string)
positionSide := strings.ToUpper(side)
positionAmt, _ := targetPosition["positionAmt"].(float64)
// 验证新止损价格合理性
if positionSide == "LONG" && decision.NewStopLoss >= marketData.CurrentPrice {
return fmt.Errorf("多单止损必须低于当前价格 (当前: %.2f, 新止损: %.2f)", marketData.CurrentPrice, decision.NewStopLoss)
}
if positionSide == "SHORT" && decision.NewStopLoss <= marketData.CurrentPrice {
return fmt.Errorf("空单止损必须高于当前价格 (当前: %.2f, 新止损: %.2f)", marketData.CurrentPrice, decision.NewStopLoss)
}
// 调用交易所 API 修改止损
quantity := math.Abs(positionAmt)
err = at.trader.SetStopLoss(decision.Symbol, positionSide, quantity, decision.NewStopLoss)
if err != nil {
return fmt.Errorf("修改止损失败: %w", err)
}
log.Printf(" ✓ 止损已调整: %.2f (当前价格: %.2f)", decision.NewStopLoss, marketData.CurrentPrice)
return nil
}
// executeUpdateTakeProfitWithRecord 执行调整止盈并记录详细信息
func (at *AutoTrader) executeUpdateTakeProfitWithRecord(decision *decision.Decision, actionRecord *logger.DecisionAction) error {
log.Printf(" 🎯 调整止盈: %s → %.2f", decision.Symbol, decision.NewTakeProfit)
// 获取当前价格
marketData, err := market.Get(decision.Symbol)
if err != nil {
return err
}
actionRecord.Price = marketData.CurrentPrice
// 获取当前持仓
positions, err := at.trader.GetPositions()
if err != nil {
return fmt.Errorf("获取持仓失败: %w", err)
}
// 查找目标持仓
var targetPosition map[string]interface{}
for _, pos := range positions {
symbol, _ := pos["symbol"].(string)
posAmt, _ := pos["positionAmt"].(float64)
if symbol == decision.Symbol && posAmt != 0 {
targetPosition = pos
break
}
}
if targetPosition == nil {
return fmt.Errorf("持仓不存在: %s", decision.Symbol)
}
// 获取持仓方向和数量
side, _ := targetPosition["side"].(string)
positionSide := strings.ToUpper(side)
positionAmt, _ := targetPosition["positionAmt"].(float64)
// 验证新止盈价格合理性
if positionSide == "LONG" && decision.NewTakeProfit <= marketData.CurrentPrice {
return fmt.Errorf("多单止盈必须高于当前价格 (当前: %.2f, 新止盈: %.2f)", marketData.CurrentPrice, decision.NewTakeProfit)
}
if positionSide == "SHORT" && decision.NewTakeProfit >= marketData.CurrentPrice {
return fmt.Errorf("空单止盈必须低于当前价格 (当前: %.2f, 新止盈: %.2f)", marketData.CurrentPrice, decision.NewTakeProfit)
}
// 调用交易所 API 修改止盈
quantity := math.Abs(positionAmt)
err = at.trader.SetTakeProfit(decision.Symbol, positionSide, quantity, decision.NewTakeProfit)
if err != nil {
return fmt.Errorf("修改止盈失败: %w", err)
}
log.Printf(" ✓ 止盈已调整: %.2f (当前价格: %.2f)", decision.NewTakeProfit, marketData.CurrentPrice)
return nil
}
// executePartialCloseWithRecord 执行部分平仓并记录详细信息
func (at *AutoTrader) executePartialCloseWithRecord(decision *decision.Decision, actionRecord *logger.DecisionAction) error {
log.Printf(" 📊 部分平仓: %s %.1f%%", decision.Symbol, decision.ClosePercentage)
// 验证百分比范围
if decision.ClosePercentage <= 0 || decision.ClosePercentage > 100 {
return fmt.Errorf("平仓百分比必须在 0-100 之间,当前: %.1f", decision.ClosePercentage)
}
// 获取当前价格
marketData, err := market.Get(decision.Symbol)
if err != nil {
return err
}
actionRecord.Price = marketData.CurrentPrice
// 获取当前持仓
positions, err := at.trader.GetPositions()
if err != nil {
return fmt.Errorf("获取持仓失败: %w", err)
}
// 查找目标持仓
var targetPosition map[string]interface{}
for _, pos := range positions {
symbol, _ := pos["symbol"].(string)
posAmt, _ := pos["positionAmt"].(float64)
if symbol == decision.Symbol && posAmt != 0 {
targetPosition = pos
break
}
}
if targetPosition == nil {
return fmt.Errorf("持仓不存在: %s", decision.Symbol)
}
// 获取持仓方向和数量
side, _ := targetPosition["side"].(string)
positionSide := strings.ToUpper(side)
positionAmt, _ := targetPosition["positionAmt"].(float64)
// 计算平仓数量
totalQuantity := math.Abs(positionAmt)
closeQuantity := totalQuantity * (decision.ClosePercentage / 100.0)
actionRecord.Quantity = closeQuantity
// 执行平仓
var order map[string]interface{}
if positionSide == "LONG" {
order, err = at.trader.CloseLong(decision.Symbol, closeQuantity)
} else {
order, err = at.trader.CloseShort(decision.Symbol, closeQuantity)
}
if err != nil {
return fmt.Errorf("部分平仓失败: %w", err)
}
// 记录订单ID
if orderID, ok := order["orderId"].(int64); ok {
actionRecord.OrderID = orderID
}
remainingQuantity := totalQuantity - closeQuantity
log.Printf(" ✓ 部分平仓成功: 平仓 %.4f (%.1f%%), 剩余 %.4f",
closeQuantity, decision.ClosePercentage, remainingQuantity)
return nil
}
// GetID 获取trader ID
func (at *AutoTrader) GetID() string {
return at.id
@@ -998,12 +1188,14 @@ func sortDecisionsByPriority(decisions []decision.Decision) []decision.Decision
// 定义优先级
getActionPriority := func(action string) int {
switch action {
case "close_long", "close_short":
return 1 // 最高优先级:先平仓
case "close_long", "close_short", "partial_close":
return 1 // 最高优先级:先平仓(包括部分平仓)
case "update_stop_loss", "update_take_profit":
return 2 // 调整持仓止盈止损
case "open_long", "open_short":
return 2 // 次优先级:后开仓
return 3 // 次优先级:后开仓
case "hold", "wait":
return 3 // 最低优先级:观望
return 4 // 最低优先级:观望
default:
return 999 // 未知动作放最后
}