159 lines
3.8 KiB
Go
159 lines
3.8 KiB
Go
package logic
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log"
|
|
"strconv"
|
|
"time"
|
|
|
|
"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 (
|
|
AddCache = make(map[string]float64)
|
|
)
|
|
|
|
// OpenPosition 按交易对用配置的 OrderQtyUsdt 市价买入首笔;更新内存、写入数据库并 loadPortfolio 刷新持仓缓存。
|
|
// 若链上可用基础币名义已达「有仓」阈值则只同步数量与成本写库,不再重复下单。
|
|
func CreateNewSpotPosition(symbol string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
|
|
defer cancel()
|
|
|
|
if err := RefreshAccount(); err != nil {
|
|
return err
|
|
} else {
|
|
account := GetAccount("USDT")
|
|
if account <= 10 {
|
|
return errors.New("USDT 余额不足")
|
|
}
|
|
}
|
|
|
|
qty, price, err := CalculateQty(symbol)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
order, err := BinanceClient.NewCreateOrderService().
|
|
Symbol(symbol).
|
|
Side(binance.SideTypeBuy).
|
|
Type(binance.OrderTypeMarket).
|
|
Quantity(qty).
|
|
NewOrderRespType(binance.NewOrderRespTypeRESULT).
|
|
Do(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
position := &models.SpotPosition{
|
|
Symbol: symbol,
|
|
BuyQuantity: parseFloat(order.ExecutedQuantity),
|
|
BuyCostPrice: decimal.NewFromFloat(price).Round(2).InexactFloat64(),
|
|
}
|
|
|
|
if err := impl.DBService.Create(position).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := loadPortfolio(); err != nil {
|
|
log.Printf("logic: OpenPosition 刷新持仓缓存失败: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func AddSpotPosition(pos *models.SpotPosition, pnlPct, price float64) error {
|
|
if val, ok := AddCache[pos.Symbol]; !ok || val == 0 {
|
|
AddCache[pos.Symbol] = pnlPct
|
|
return nil
|
|
}
|
|
|
|
if pnlPct >= AddCache[pos.Symbol] {
|
|
AddCache[pos.Symbol] = pnlPct
|
|
return nil
|
|
}
|
|
if AddCache[pos.Symbol]-pnlPct < gridStartPct {
|
|
return nil
|
|
}
|
|
// 加仓
|
|
qty := CalculateQtyByPrice(pos.Symbol, price)
|
|
if qty == "" {
|
|
return errors.New("数量不足")
|
|
}
|
|
ctx := context.Background()
|
|
order, err := BinanceClient.NewCreateOrderService().
|
|
Symbol(pos.Symbol).
|
|
Side(binance.SideTypeBuy).
|
|
Type(binance.OrderTypeMarket).
|
|
Quantity(qty).
|
|
NewOrderRespType(binance.NewOrderRespTypeRESULT).
|
|
Do(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 更新数据库
|
|
price = decimal.NewFromFloat(price).Round(2).InexactFloat64()
|
|
pos.BuyQuantity += parseFloat(order.ExecutedQuantity)
|
|
pos.BuyCostPrice = decimal.NewFromFloat((pos.BuyCostPrice + price) / 2).Round(2).InexactFloat64()
|
|
pos.DipAddsDone++
|
|
impl.DBService.Save(pos)
|
|
|
|
// 更新缓存
|
|
loadPortfolio()
|
|
|
|
AddCache[pos.Symbol] = 0
|
|
|
|
return nil
|
|
}
|
|
|
|
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 "", 0, err
|
|
}
|
|
price, err := strconv.ParseFloat(prices[0].Price, 64)
|
|
if err != nil || price <= 0 {
|
|
return "", 0, errors.New("现价无效")
|
|
}
|
|
cfg := getSymbolInfo(symbol)
|
|
if cfg == nil {
|
|
return "", 0, errors.New("交易对不存在")
|
|
}
|
|
want := InvestUsdt[cfg.Symbol] / price
|
|
lot := cfg.LotSizeFilter()
|
|
if lot == nil {
|
|
return "", 0, errors.New("交易对无 LOT_SIZE 规则")
|
|
}
|
|
qtyStr, ok, err := formatQtyToLotStep(want, lot.StepSize)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
if !ok {
|
|
return "", 0, errors.New("数量不足")
|
|
}
|
|
return qtyStr, price, nil
|
|
}
|
|
|
|
func CalculateQtyByPrice(symbol string, price float64) string {
|
|
cfg := getSymbolInfo(symbol)
|
|
if cfg == nil || price <= 0 {
|
|
return ""
|
|
}
|
|
want := InvestUsdt[symbol] / price
|
|
lot := cfg.LotSizeFilter()
|
|
if lot == nil {
|
|
return ""
|
|
}
|
|
qtyStr, ok, err := formatQtyToLotStep(want, lot.StepSize)
|
|
if err != nil || !ok {
|
|
return ""
|
|
}
|
|
return qtyStr
|
|
}
|