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: SpotWatchList:
- Symbol: BTCUSDT - Symbol: BTCUSDT
OrderQtyUsdt: 10 OrderQtyUsdt: 10
- Symbol: ETHUSDT
OrderQtyUsdt: 10
- Symbol: SOLUSDT
OrderQtyUsdt: 10
- Symbol: ADAUSDT
OrderQtyUsdt: 10
- Symbol: XRPUSDT
OrderQtyUsdt: 10
# 日志配置 # 日志配置
Log: Log:

View File

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

View File

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

View File

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