Files
coin/internal/logic/spot_binance_close.go
2026-05-07 09:56:21 +08:00

96 lines
2.4 KiB
Go
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.
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
}