deving
This commit is contained in:
@@ -2,74 +2,13 @@ package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.apinb.com/quant/coin/internal/impl"
|
||||
"git.apinb.com/quant/coin/internal/models"
|
||||
"github.com/adshao/go-binance/v2"
|
||||
)
|
||||
|
||||
// logSpotWatchConfigOnly 在 exchangeInfo 拉取失败时仍打印 yaml 中的标的与 OrderQty。
|
||||
func logSpotWatchConfigOnly() {
|
||||
for _, w := range spotWatchesFromConfig() {
|
||||
log.Printf("logic: 现货标的(仅配置,未取到交易所规则) %s 每笔OrderQty=%.8f", w.Symbol, w.OrderQty)
|
||||
}
|
||||
}
|
||||
|
||||
// refreshStepSizes 从 exchangeInfo 拉取各 symbol 的 LOT_SIZE.stepSize,供卖出数量格式化;
|
||||
// 成功后按标的打印配置 OrderQty 与交易所最小量、步长、最小名义等。
|
||||
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
|
||||
}
|
||||
bySymbol := make(map[string]binance.Symbol, len(info.Symbols))
|
||||
for i := range info.Symbols {
|
||||
s := info.Symbols[i]
|
||||
bySymbol[s.Symbol] = s
|
||||
lot := s.LotSizeFilter()
|
||||
if lot != nil && lot.StepSize != "" {
|
||||
stepSizes[s.Symbol] = lot.StepSize
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("logic: 现货 SpotWatchList 与交易所最小交易量(exchangeInfo)")
|
||||
for _, w := range watch {
|
||||
minQty, step := "-", "-"
|
||||
marketMin := "-"
|
||||
minNotional := "-"
|
||||
s, ok := bySymbol[w.Symbol]
|
||||
if !ok {
|
||||
log.Printf("logic: %s 配置OrderQty=%.8f | 交易所未返回该交易对规则", w.Symbol, w.OrderQty)
|
||||
continue
|
||||
}
|
||||
if lot := s.LotSizeFilter(); lot != nil {
|
||||
if lot.MinQuantity != "" {
|
||||
minQty = lot.MinQuantity
|
||||
}
|
||||
if lot.StepSize != "" {
|
||||
step = lot.StepSize
|
||||
}
|
||||
}
|
||||
if mls := s.MarketLotSizeFilter(); mls != nil && mls.MinQuantity != "" {
|
||||
marketMin = mls.MinQuantity
|
||||
}
|
||||
if nf := s.NotionalFilter(); nf != nil && nf.MinNotional != "" {
|
||||
minNotional = nf.MinNotional
|
||||
}
|
||||
log.Printf("logic: %s 配置OrderQty=%.8f | LOT最小量=%s 步长=%s | 市价单最小基础量=%s | minNotional=%s",
|
||||
w.Symbol, w.OrderQty, minQty, step, marketMin, minNotional)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadPortfolio 从 GORM 读入全表 spot_positions,填充内存 map(键为 BaseAsset)。
|
||||
func loadPortfolio() error {
|
||||
portfolioMu.Lock()
|
||||
@@ -91,119 +30,37 @@ func loadPortfolio() error {
|
||||
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
|
||||
func GetPortfolio() map[string]*models.SpotPosition {
|
||||
portfolioMu.Lock()
|
||||
defer portfolioMu.Unlock()
|
||||
return portfolio.Positions
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// balanceLocked 解析某资产下单冻结数量(Locked)。
|
||||
func balanceLocked(balances []binance.Balance, asset string) float64 {
|
||||
for _, b := range balances {
|
||||
if b.Asset == asset {
|
||||
v, err := strconv.ParseFloat(b.Locked, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return v
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// startupSyncSpotHoldingFromBinance 启动时用已拉取的账户余额与 ListPrices 现价,按 SpotWatchList 将实际可用持仓与空仓/有仓状态对齐到内存并写库(与 processOne 一致以 Free 为可交易数量)。
|
||||
func startupSyncSpotHoldingFromBinance(ctx context.Context, client *binance.Client, balances []binance.Balance) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 45*time.Second)
|
||||
func RefreshAccount() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
watch := spotWatchesFromConfig()
|
||||
if len(watch) == 0 {
|
||||
return nil
|
||||
}
|
||||
symbols := make([]string, len(watch))
|
||||
for i, w := range watch {
|
||||
symbols[i] = w.Symbol
|
||||
}
|
||||
prices, err := client.NewListPricesService().Symbols(symbols).Do(ctx)
|
||||
acct, err := BinanceClient.NewGetAccountService().Do(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
priceBySymbol := make(map[string]float64, len(prices))
|
||||
for _, p := range prices {
|
||||
v, err := strconv.ParseFloat(p.Price, 64)
|
||||
accountMu.Lock()
|
||||
defer accountMu.Unlock()
|
||||
for _, b := range acct.Balances {
|
||||
free, err := strconv.ParseFloat(b.Free, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
priceBySymbol[p.Symbol] = v
|
||||
account[b.Asset] = free
|
||||
}
|
||||
|
||||
portfolioMu.Lock()
|
||||
defer portfolioMu.Unlock()
|
||||
|
||||
for _, w := range watch {
|
||||
px, ok := priceBySymbol[w.Symbol]
|
||||
if !ok {
|
||||
log.Printf("logic: 启动同步持仓 %s 未取到现价,跳过该标的", w.Symbol)
|
||||
continue
|
||||
}
|
||||
st := getOrCreateState(w.Base, w.Symbol)
|
||||
st.Symbol = w.Symbol
|
||||
|
||||
free, err := balanceFree(balances, w.Base)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
locked := balanceLocked(balances, w.Base)
|
||||
st.Quantity = free
|
||||
|
||||
positionUSDT := free * px
|
||||
holdTh := spotHoldingThresholdUSDT(w, px)
|
||||
if positionUSDT < holdTh {
|
||||
st.DipAddsDone = 0
|
||||
st.DipLegLocked = false
|
||||
st.RallyLegLocked = false
|
||||
resetSpotTrail(st)
|
||||
st.OpenReboundLow = 0
|
||||
st.DipReboundLow = 0
|
||||
st.AvgCostUSDT = 0
|
||||
log.Printf("logic: 启动持仓同步 %s 可用=%.8f 冻结=%.8f 名义≈%.4f USDT(<%.4f USDT 视为空仓)已对齐库", w.Symbol, free, locked, positionUSDT, holdTh)
|
||||
continue
|
||||
}
|
||||
|
||||
st.OpenReboundLow = 0
|
||||
if st.AvgCostUSDT <= 0 {
|
||||
st.AvgCostUSDT = px
|
||||
}
|
||||
log.Printf("logic: 启动持仓同步 %s 可用=%.8f 冻结=%.8f 名义≈%.4f USDT(≥%.4f USDT 有仓)数量与成本已对齐库", w.Symbol, free, locked, positionUSDT, holdTh)
|
||||
}
|
||||
|
||||
if impl.DBService == nil {
|
||||
log.Printf("logic: 启动持仓同步完成(未配置数据库,仅内存)")
|
||||
return nil
|
||||
}
|
||||
if err := savePortfolioLocked(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("logic: 启动持仓同步已写入数据库")
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAccount(asset string) float64 {
|
||||
accountMu.Lock()
|
||||
defer accountMu.Unlock()
|
||||
free, ok := account[asset]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return free
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user