feat
This commit is contained in:
95
internal/logic/spot_binance_close.go
Normal file
95
internal/logic/spot_binance_close.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user