This commit is contained in:
2026-05-07 09:56:21 +08:00
parent 12ffcbef6a
commit 38c579145f
12 changed files with 560 additions and 222 deletions

View File

@@ -0,0 +1,95 @@
package logic
import (
"context"
"log"
"strconv"
"git.apinb.com/quant/coin/internal/models"
"github.com/adshao/go-binance/v2"
"github.com/shopspring/decimal"
)
// trySpotRallySell 跟踪止盈浮盈≥profitArmPct 后记录阶段最高价,不在该涨幅直接平仓;
// 仅当现价从本轮高点回撤≥trailPullbackPct 时市价卖出全部可用基础资产。
func trySpotRallySell(ctx context.Context, client *binance.Client, w spotSymbol, price float64, st *models.SpotPosition, free float64) error {
cost := st.AvgCostUSDT
if cost <= 0 {
return nil
}
armLine := cost * (1 + profitArmPct)
if !st.TrailArmed {
if price >= armLine {
st.TrailArmed = true
st.TrailPeakUSDT = price
}
return nil
}
if price > st.TrailPeakUSDT {
st.TrailPeakUSDT = price
}
sellLine := st.TrailPeakUSDT * (1 - trailPullbackPct)
if price >= sellLine {
return nil
}
qtyStr, ok, err := formatQtyToLotStep(free, spotLotStep(w.Symbol))
if err != nil {
return err
}
if !ok || parseFloat(qtyStr)*price < minSellNotional {
return nil
}
order, err := client.NewCreateOrderService().
Symbol(w.Symbol).
Side(binance.SideTypeSell).
Type(binance.OrderTypeMarket).
Quantity(qtyStr).
NewOrderRespType(binance.NewOrderRespTypeFULL).
Do(ctx)
if err != nil {
return err
}
sold, _ := strconv.ParseFloat(order.ExecutedQuantity, 64)
st.Quantity = free - sold
if st.Quantity < 0 {
st.Quantity = 0
}
peak := st.TrailPeakUSDT
resetSpotTrail(st)
log.Printf("logic: %s 从跟踪高点 %.6f 回撤≥%.1f%% 全平, 卖出数量 %s", w.Symbol, peak, trailPullbackPct*100, qtyStr)
return nil
}
// spotLotStep 返回交易对 LOT_SIZE 步长;未加载 exchangeInfo 时用保守默认。
func spotLotStep(symbol string) string {
if s := stepSizes[symbol]; s != "" {
return s
}
return "0.00001"
}
// formatQtyToLotStep 将数量按 stepSize 向下取整,满足 Binance LOT_SIZE买卖共用过小则返回 ok=false。
func formatQtyToLotStep(qty float64, stepSize string) (string, bool, error) {
if qty <= 0 {
return "", false, nil
}
step, err := decimal.NewFromString(stepSize)
if err != nil {
return "", false, err
}
q := decimal.NewFromFloat(qty)
n := q.Div(step).Floor()
out := n.Mul(step)
if out.LessThanOrEqual(decimal.Zero) {
return "", false, nil
}
return out.String(), true, nil
}
func parseFloat(s string) float64 {
v, _ := strconv.ParseFloat(s, 64)
return v
}