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" ) // 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, 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{ BaseAsset: spotBaseFromSymbol(symbol), Symbol: symbol, Quantity: parseFloat(order.ExecutedQuantity), AvgCostUSDT: parseFloat(order.Price), } 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 CalculateQty(symbol string) (string, 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 } price, err := strconv.ParseFloat(prices[0].Price, 64) if err != nil || price <= 0 { return "", errors.New("现价无效") } cfg := getSymbolInfo(symbol) if cfg == nil { return "", errors.New("交易对不存在") } want := InvestUsdt[cfg.Symbol] / price step := cfg.LotSizeFilter().StepSize qtyStr, ok, err := formatQtyToLotStep(want, step) if err != nil { return "", err } if !ok { return "", errors.New("数量不足") } return qtyStr, nil }