This commit is contained in:
2026-01-09 15:48:31 +08:00
parent e32eabbf95
commit c8e189e9c7
28 changed files with 2795 additions and 0 deletions

274
trade/binance_order.go Normal file
View File

@@ -0,0 +1,274 @@
package trade
import (
"context"
"fmt"
"log"
"math"
"git.apinb.com/bsm-sdk/core/utils"
"git.apinb.com/quant/strategy/internal/impl"
"git.apinb.com/quant/strategy/internal/models"
"github.com/adshao/go-binance/v2/futures"
)
const (
OrderSide_Long string = "BUY"
OrderSide_Short string = "SELL"
)
func (bn *BinanceClient) AppendOrder(symbol string, side string, quantity string) {
// 判断:开仓锁,开仓信号
if IsLock(symbol, "OPEN."+side) {
Error("103", "判断:开仓锁,开仓信号")
return
}
var res *futures.CreateOrderResponse
var err error
// 做多
if side == "LONG" {
bid, _, err := bn.BookTicker(symbol)
if err != nil {
Error("113", "BookTicker", err.Error())
return
}
res, err = bn.CreateFuturesLimitOrder(
symbol,
quantity,
"BUY",
bid,
futures.PositionSideTypeLong,
)
}
if side == "SHORT" {
_, ask, err := bn.BookTicker(symbol)
if err != nil {
Error("113", "BookTicker", err.Error())
return
}
res, err = bn.CreateFuturesLimitOrder(
symbol,
quantity,
"SELL",
ask,
futures.PositionSideTypeShort,
)
}
if err != nil {
Error("104", "CreateFuturesOrder_Limit_Binance", err.Error())
return
}
if res != nil {
Debug("Order Result", utils.Int642String(res.OrderID))
SetLock(symbol, "OPEN."+side, 3)
}
}
// CreateOrder 创建订单
// symbol: 交易对
// side: 买卖方向: BUY 做多 / SELL 做空
// positionSide: 持仓方向: LONG 多仓 / SHORT 空仓
// quantity: 数量
// orderType: 订单类型:LIMIT, MARKET
func (bn *BinanceClient) CreateFuturesMarketOrder(symbol, quantity, side string, positionSide futures.PositionSideType) (res *futures.CreateOrderResponse, err error) {
if IsLock(symbol, "OPEN."+side) {
return nil, fmt.Errorf("symbol %s side %s is lock", symbol, side)
}
srv := bn.Futures.NewCreateOrderService().Symbol(symbol).Side(futures.SideType(side)).PositionSide(positionSide).Quantity(quantity).Type(futures.OrderTypeMarket)
result, err := srv.Do(context.Background())
if err != nil {
Error("120", "CreateOrder:", err.Error())
return nil, err
}
SetLock(symbol, "OPEN."+side, 3)
Info("[SUCCESS] Create Order, OrderID:", utils.Int642String(result.OrderID))
return result, nil
}
// CreateOrder 创建订单
// symbol: 交易对
// side: 买卖方向: BUY 做多 / SELL 做空
// positionSide: 持仓方向: LONG 多仓 / SHORT 空仓
// quantity: 数量
// orderType: 订单类型:LIMIT, MARKET
func (bn *BinanceClient) CreateFuturesLimitOrder(symbol, quantity, side, price string, positionSide futures.PositionSideType) (res *futures.CreateOrderResponse, err error) {
if IsLock(symbol, "OPEN."+side) {
return nil, fmt.Errorf("symbol %s side %s is lock", symbol, side)
}
srv := bn.Futures.NewCreateOrderService().Symbol(symbol).Side(futures.SideType(side)).PositionSide(positionSide).Quantity(quantity).Type(futures.OrderTypeLimit).Price(price).TimeInForce(futures.TimeInForceTypeGTX)
extra := map[string]any{
// "priceMatch": "OPPONENT_5",
}
result, err := srv.Do(context.Background(), futures.WithExtraForm(extra))
if err != nil {
Error("120", "CreateOrder:", err.Error())
return nil, err
}
SetLock(symbol, "OPEN."+side, 3)
Info("[SUCCESS] Create Order, OrderID:", utils.Int642String(result.OrderID))
return result, nil
}
func (bn *BinanceClient) CloseFuturesMarketOrder(symbol, orderType, quantity string, positionSide futures.PositionSideType) (res *futures.CreateOrderResponse, err error) {
Debug("CloseFuturesOrder_Binance", symbol, orderType, quantity, string(positionSide))
var side string
if positionSide == futures.PositionSideTypeLong {
side = OrderSide_Short
} else {
side = OrderSide_Long
}
result, err := bn.Futures.NewCreateOrderService().Symbol(symbol).Side(futures.SideType(side)).PositionSide(positionSide).Type(futures.OrderType(orderType)).Quantity(quantity).Do(context.Background())
if err != nil {
Error("121", "CloseOrder:", err.Error())
return nil, err
}
// 锁定仓位
SetLock(symbol, "CLOSE."+string(positionSide), 3)
Info("[SUCCESS] Close Order, OrderID:", utils.Int642String(result.OrderID))
return res, nil
}
func (bn *BinanceClient) CloseFuturesLimitOrder(symbol, quantity, price string, positionSide futures.PositionSideType) (res *futures.CreateOrderResponse, err error) {
Debug("CloseFuturesOrder_Limit_Binance", symbol, string(positionSide), price, quantity)
var side string
if positionSide == futures.PositionSideTypeLong {
side = OrderSide_Short
} else {
side = OrderSide_Long
}
result, err := bn.Futures.NewCreateOrderService().Symbol(symbol).Side(futures.SideType(side)).PositionSide(positionSide).Quantity(quantity).Type(futures.OrderTypeLimit).Price(price).TimeInForce(futures.TimeInForceTypeGTX).Do(context.Background())
if err != nil {
Error("121", "CloseOrder:", err.Error())
return nil, err
}
Info("[SUCCESS] Close Order, OrderID:", utils.Int642String(result.OrderID))
return res, nil
}
func (bn *BinanceClient) QuickMarketClose(PlanKeyName, symbol string, side string) {
Debug(PlanKeyName, "Quick closeing")
orders, _ := GetPositions(PlanKeyName, symbol)
if len(orders) == 0 {
return
}
volume := utils.Float64ToString(orders[0].Volume)
bn.CloseFuturesMarketOrder(symbol, "MARKET", volume, futures.PositionSideType(side))
SetLock(symbol, "CLOSE."+side, 5)
//content := fmt.Sprintf("快速平仓 %s %s Volume:%s", symbol, side, volume)
//models.WriteSimpleLog(models.LogType_Close, p.Strategy.KeyName, content)
}
// 网格动态平仓
func (bn *BinanceClient) Dynamic(p *Spec, symbol string, currentPrice float64, orders []*models.QuantOrders, closeingProfitRate, qty float64) {
// 最小持仓保证金
equity := AccountsAssets.Get("USDT").AccountEquity
newMargin := utils.FloatRound(equity*p.StrategyConf.MarginMultipleByBalance, 1)
if newMargin < p.StrategyConf.Margin {
newMargin = p.StrategyConf.Margin
}
// 最大保证金:账户保证金 * 0.220% / 交易币对数 = 最大持仓保证金
newMaxMargin := math.Ceil(equity * 0.2 / float64(len(p.AllowSymbols)))
// 设置止盈率
for _, row := range orders {
// 计算利润,计算回报率
profit, profitRate := calProfitRate_V2(symbol, row.Side, float64(p.StrategyConf.Leverage), row.OpenPrice, currentPrice, row.Volume)
profit = utils.FloatRound(profit, 3)
profitRate = utils.FloatRound(profitRate, 3)
// 开仓保证金计算
if qty <= 0 {
qty = QtyBalByFloat(
currentPrice,
newMargin,
p.StrategyConf.Leverage,
p.SymbolsSetting[symbol],
)
}
quantity := utils.Float64ToString(qty)
// cache key.
CacheKey := fmt.Sprintf("%s_%s", row.Symbol, row.Side)
// 网格计算
NewProfitCell := profitRate / p.StrategyConf.DefCloseingGridPrice
NewProfitCell = math.Ceil(NewProfitCell)
log.Println("Dynamic", row.Symbol, row.Side, "Profit:", profit, "ProfitRate", profitRate, "NewProfitCell", NewProfitCell, "closeingProfitRate", closeingProfitRate)
// 增持处理,持仓的利润达到设定的增持仓位百分比
if p.StrategyConf.AddPositionOn && profitRate <= p.StrategyConf.AddThreshold && row.MarginSize < newMaxMargin {
// 增持处理,持仓的利润达到设定的增持仓位百分比
newAddThreshold := p.StrategyConf.AddThreshold
if row.Volume > (qty * 1.95) {
newAddThreshold = newAddThreshold * 2
}
if profitRate <= newAddThreshold {
if !IsLock(symbol, "OPEN."+row.Side) {
bn.AppendOrder(symbol, row.Side, quantity)
SetLock(symbol, "OPEN."+row.Side, 5)
continue
}
}
}
// 收益处理: 收益率没达到默认收益率,跳过
// 以下是收益率》=默认收益率的逻辑的业务逻辑
// 读取缓存
lastProfitCell, exists := bn.LastUPL[CacheKey]
if !exists {
// 缓存处理: 缓存不存在
// 收益处理: 收益率达到默认收益率
if profitRate >= closeingProfitRate {
log.Println("###", CacheKey, "profit", profit, "profitRate", profitRate, ">=", "closeingProfitRate", closeingProfitRate, "NewProfitCell", NewProfitCell)
bn.LastUPL[CacheKey] = NewProfitCell
}
} else {
log.Println("$$$", CacheKey, "profit", profit, "profitRate", profitRate, ">", "closeingProfitRate", closeingProfitRate, "NewProfitCell", NewProfitCell, "lastProfitCell", lastProfitCell)
if profitRate >= closeingProfitRate {
// 缓存处理: 存在缓存
// 判断: NewProfitCell < lastProfitCell 当前最新网格值 < 缓存的上一次网格值
if NewProfitCell < lastProfitCell {
// 缓存的收益低于当前的CELL则平仓。
log.Println("~~~", CacheKey, "profit:", profit, "ProfitRate", profitRate, " > CloseProfitRate", closeingProfitRate, "LastProfitCell", lastProfitCell, "< NewProfitCell", NewProfitCell)
// 平仓
bn.CloseFuturesMarketOrder(symbol, "MARKET", utils.Float64ToString(row.Volume), futures.PositionSideType(row.Side))
delete(bn.LastUPL, CacheKey)
} else {
// 高于上次盈利CELL更新缓存
log.Println("^^^ Up", CacheKey, "profit:", profit, "ProfitRate", profitRate, " > CloseProfitRate", closeingProfitRate, "ProfitCell", NewProfitCell)
// 更新缓存
bn.LastUPL[CacheKey] = NewProfitCell
}
}
}
}
}
func CreateSignal(identity, signalHex, orderId string) {
impl.DBService.Create(&models.QuantSignal{
Identity: identity,
Md5Hex: signalHex,
OrderId: orderId,
})
}