package logic import ( "context" "strconv" "git.apinb.com/quant/coin/internal/impl" "git.apinb.com/quant/coin/internal/models" "github.com/adshao/go-binance/v2" ) // refreshStepSizes 从 exchangeInfo 拉取各 symbol 的 LOT_SIZE.stepSize,供卖出数量格式化。 func refreshStepSizes(ctx context.Context, client *binance.Client) error { watch := spotWatchesFromConfig() syms := make([]string, 0, len(watch)) for _, w := range watch { syms = append(syms, w.Symbol) } info, err := client.NewExchangeInfoService().Symbols(syms...).Do(ctx) if err != nil { return err } for _, s := range info.Symbols { lot := s.LotSizeFilter() if lot == nil || lot.StepSize == "" { continue } stepSizes[s.Symbol] = lot.StepSize } return nil } // loadPortfolio 从 GORM 读入全表 spot_positions,填充内存 map(键为 BaseAsset)。 func loadPortfolio() error { portfolioMu.Lock() defer portfolioMu.Unlock() if impl.DBService == nil { portfolio = models.NewSpotPortfolioSnapshot() return nil } var rows []models.SpotPosition if err := impl.DBService.Find(&rows).Error; err != nil { return err } portfolio = models.NewSpotPortfolioSnapshot() for i := range rows { p := new(models.SpotPosition) *p = rows[i] portfolio.Positions[p.BaseAsset] = p } return nil } // savePortfolioLocked 将内存中各 SpotPosition 以 Save 写回数据库(有主键则更新,无则插入)。 // 调用方必须已持有 portfolioMu。 func savePortfolioLocked() error { if impl.DBService == nil { return nil } for _, st := range portfolio.Positions { if st == nil { continue } if err := impl.DBService.Save(st).Error; err != nil { return err } } return nil } // balanceFree 从账户余额列表里解析某资产的可用数量(Free 字段为字符串)。 func balanceFree(balances []binance.Balance, asset string) (float64, error) { for _, b := range balances { if b.Asset == asset { return strconv.ParseFloat(b.Free, 64) } } return 0, nil }