275 lines
9.2 KiB
Go
275 lines
9.2 KiB
Go
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.2(20%) / 交易币对数 = 最大持仓保证金
|
||
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,
|
||
})
|
||
}
|