This commit is contained in:
2026-05-11 16:24:13 +08:00
parent e8e97c7047
commit b9ca145bbc
4 changed files with 26 additions and 16 deletions

View File

@@ -19,6 +19,14 @@ BinanceApiSecret: "YqTpRybnBWllS0fA1yk0T1MEx0RxRazc2bH2iZuPEI8QJKesUueq3saCDdDj7
SpotWatchList:
- Symbol: BTCUSDT
OrderQtyUsdt: 10
- Symbol: ETHUSDT
OrderQtyUsdt: 10
- Symbol: SOLUSDT
OrderQtyUsdt: 10
- Symbol: ADAUSDT
OrderQtyUsdt: 10
- Symbol: XRPUSDT
OrderQtyUsdt: 10
# 日志配置
Log:

View File

@@ -7,6 +7,7 @@ import (
"git.apinb.com/quant/coin/internal/impl"
"git.apinb.com/quant/coin/internal/models"
"github.com/adshao/go-binance/v2"
"github.com/shopspring/decimal"
)
var (
@@ -15,7 +16,7 @@ var (
// trySpotRallySell 跟踪止盈浮盈≥profitArmPct 后记录阶段峰值 PnL继续上涨则刷新峰值
// 当未实现盈亏较峰值回落≥gridStartPct 时市价卖出(数量来自 RefreshAccount 写入的 account[sym])。
func trySpotRallySell(pos *models.SpotPosition, pnlPct float64) error {
func trySpotRallySell(pos *models.SpotPosition, pnlPct float64, price float64) error {
if val, ok := CloseCache[pos.Symbol]; !ok || val == 0 {
CloseCache[pos.Symbol] = pnlPct
return nil
@@ -45,9 +46,9 @@ func trySpotRallySell(pos *models.SpotPosition, pnlPct float64) error {
if err != nil {
return err
}
orderPrice, err := strconv.ParseFloat(order.Price, 64)
pos.SellQuantity += parseFloat(order.ExecutedQuantity)
pos.SellPrice = (pos.SellQuantity*pos.SellPrice + parseFloat(order.ExecutedQuantity)*orderPrice) / pos.SellQuantity
pos.SellPrice = decimal.NewFromFloat(price).Round(2).InexactFloat64()
pos.Status = 1
impl.DBService.Save(pos)

View File

@@ -10,6 +10,7 @@ import (
"git.apinb.com/quant/coin/internal/impl"
"git.apinb.com/quant/coin/internal/models"
binance "github.com/adshao/go-binance/v2"
"github.com/shopspring/decimal"
)
var (
@@ -31,7 +32,7 @@ func CreateNewSpotPosition(symbol string) error {
}
}
qty, err := CalculateQty(symbol)
qty, price, err := CalculateQty(symbol)
if err != nil {
return err
}
@@ -50,7 +51,7 @@ func CreateNewSpotPosition(symbol string) error {
position := &models.SpotPosition{
Symbol: symbol,
BuyQuantity: parseFloat(order.ExecutedQuantity),
BuyCostPrice: parseFloat(order.Price),
BuyCostPrice: decimal.NewFromFloat(price).Round(2).InexactFloat64(),
}
if err := impl.DBService.Create(position).Error; err != nil {
@@ -95,9 +96,9 @@ func AddSpotPosition(pos *models.SpotPosition, pnlPct, price float64) error {
}
// 更新数据库
orderPrice, err := strconv.ParseFloat(order.Price, 64)
price = decimal.NewFromFloat(price).Round(2).InexactFloat64()
pos.BuyQuantity += parseFloat(order.ExecutedQuantity)
pos.BuyCostPrice = (pos.BuyQuantity*pos.BuyCostPrice + parseFloat(order.ExecutedQuantity)*orderPrice) / pos.BuyQuantity
pos.BuyCostPrice = decimal.NewFromFloat((pos.BuyCostPrice + price) / 2).Round(2).InexactFloat64()
pos.DipAddsDone++
impl.DBService.Save(pos)
@@ -109,34 +110,34 @@ func AddSpotPosition(pos *models.SpotPosition, pnlPct, price float64) error {
return nil
}
func CalculateQty(symbol string) (string, error) {
func CalculateQty(symbol string) (string, float64, error) {
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel()
prices, err := BinanceClient.NewListPricesService().Symbol(symbol).Do(ctx)
if err != nil || len(prices) == 0 {
return "", err
return "", 0, err
}
price, err := strconv.ParseFloat(prices[0].Price, 64)
if err != nil || price <= 0 {
return "", errors.New("现价无效")
return "", 0, errors.New("现价无效")
}
cfg := getSymbolInfo(symbol)
if cfg == nil {
return "", errors.New("交易对不存在")
return "", 0, errors.New("交易对不存在")
}
want := InvestUsdt[cfg.Symbol] / price
lot := cfg.LotSizeFilter()
if lot == nil {
return "", errors.New("交易对无 LOT_SIZE 规则")
return "", 0, errors.New("交易对无 LOT_SIZE 规则")
}
qtyStr, ok, err := formatQtyToLotStep(want, lot.StepSize)
if err != nil {
return "", err
return "", 0, err
}
if !ok {
return "", errors.New("数量不足")
return "", 0, errors.New("数量不足")
}
return qtyStr, nil
return qtyStr, price, nil
}
func CalculateQtyByPrice(symbol string, price float64) string {

View File

@@ -61,7 +61,7 @@ func Run(ctx context.Context) error {
pnl := spotUnrealizedPnLPct(pos, price)
if pnl >= profitArmPct {
trySpotRallySell(pos, pnl)
trySpotRallySell(pos, pnl, price)
continue
}
if pos.DipAddsDone >= maxDipAdds {