package logic import ( "math" "sync" "big-qmt/go-client/config" "big-qmt/go-client/libs" "big-qmt/go-client/sdk" ) const ( legBase = "base" legAdded = "add" ) var ( peakMu sync.Mutex peakGrids = make(map[string]int) ) func managePositions(client *sdk.Client, ticks map[string]sdk.Tick, positions []sdk.Position, marketOK bool) { for _, pos := range positions { item, err := QuantState.Get(code) if err != nil || item.BaseStatus == StatusIng || item.AddedStatus == StatusIng || position.Volume != item.BaseQty+item.AddedQty { continue } price := ticks[code].LastPrice if price <= 0 { continue } if item.AddedQty > 0 { pnl := (price - item.AddedCost) / item.AddedCost * 100 if shouldSell(code, legAdded, pnl) { sell(client, item, position.CanUseVolume, item.AddedQty, legAdded, pnl) } continue } pnl := (price - item.BaseCost) / item.BaseCost * 100 if shouldSell(code, legBase, pnl) { sell(client, item, position.CanUseVolume, item.BaseQty, legBase, pnl) } else if pnl <= config.Account.LossTriggerPct { buyAdded(client, item, price, marketOK, budget) } } } func syncAdded(item *StateItem, position sdk.Position) { addedQty := position.Volume - item.BaseQty if addedQty <= 0 { item.BaseQty = position.Volume item.BaseCost = position.OpenPrice item.AddedQty = 0 item.AddedCost = 0 item.AddedStatus = StatusNone peakMu.Lock() delete(peakGrids, item.Code+"|"+legAdded) peakMu.Unlock() return } item.AddedQty = addedQty totalCost := position.OpenPrice * float64(position.Volume) baseCost := item.BaseCost * float64(item.BaseQty) item.AddedCost = math.Max(0, (totalCost-baseCost)/float64(addedQty)) item.AddedStatus = StatusOk } func buyAdded(client *sdk.Client, item *StateItem, price float64, marketOK bool, budget *float64) { if !marketOK || !PosbuyWatch.Triggered("补仓", item.Code, price) { return } volume := libs.CalcBuyVolume(price, config.Account.BuyValue) amount := price * float64(volume) if amount > *budget || orderBusy(item.Code, "BUY") { return } orderID := NewOrderID(legAdded) if !OrderBook.Place(client, sdk.OpBuy, item.Code, volume, orderID) { return } item.AddedOrderId = orderID item.AddedNum++ item.AddedQty = volume item.AddedCost = price item.AddedStatus = StatusIng QuantState.Set(item) *budget -= amount } func sell(client *sdk.Client, item *StateItem, usable, volume int, leg string, pnl float64) { volume -= volume % 100 if volume <= 0 || usable < volume || orderBusy(item.Code, "SELL") { return } orderID := NewOrderID(leg) if !OrderBook.Place(client, sdk.OpSell, item.Code, volume, orderID) { return } if leg == legAdded { item.AddedOrderId = orderID item.AddedStatus = StatusIng } else { item.BaseOrderId = orderID item.BaseStatus = StatusIng } QuantState.Set(item) logf("INFO", "[止盈] %s 卖出%d股,盈利=%.2f%%", item.Code, volume, pnl) } func orderBusy(code, side string) bool { OrderBook.mu.Lock() defer OrderBook.mu.Unlock() order := OrderBook.Data[side+"-"+code] if order == nil { return false } switch order.Status { case "48", "49", "50", "51", "52", "55": return true default: return false } } func shouldSell(code, leg string, pnl float64) bool { if pnl < config.Account.MinProfitPct { return false } grid := int(math.Floor(pnl / config.Account.GridStepPct)) key := code + "|" + leg peakMu.Lock() defer peakMu.Unlock() peak, tracked := peakGrids[key] if !tracked || grid > peak { peakGrids[key] = grid return false } return grid < peak } func forget(code string) { OpenWatch.mu.Lock() delete(OpenWatch.Data, code) OpenWatch.mu.Unlock() PosbuyWatch.mu.Lock() delete(PosbuyWatch.Data, code) PosbuyWatch.mu.Unlock() peakMu.Lock() delete(peakGrids, code+"|"+legBase) delete(peakGrids, code+"|"+legAdded) peakMu.Unlock() }