Files
coin/trade/binance_order.go
2026-01-09 15:48:31 +08:00

275 lines
9.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
})
}