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

220 lines
7.4 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 (
"errors"
"fmt"
"log"
"math"
"git.apinb.com/bsm-sdk/core/utils"
"git.apinb.com/quant/strategy/internal/models"
)
func (bg *BitgetClient) OpenOrder_market(symbol, side, size string) error {
// 上涨计算
// 判断:开仓锁,开仓信号
if IsLock(symbol, "OPEN."+side) {
log.Println("103", "判断:开仓锁,开仓信号")
return errors.New("开仓锁")
}
args := map[string]string{
"productType": "USDT-FUTURES",
"symbol": symbol,
"marginMode": "crossed",
"marginCoin": "USDT",
"orderType": "market", // 订单类型,limit: 限价单market: 市价单
"size": size,
"side": GetSide(side), // 下单方向: buy,long,多仓sell,short,卖
"tradeSide": "open", // 开仓
"force": "fok", // ioc: 无法立即成交的部分就撤销,fok: 无法全部立即成交就撤销,gtc: 普通订单, 订单会一直有效,直到被成交或者取消,限价单limit时必填若省略则默认为gtc
"clientOid": GenClientId(),
}
resp, err := bg.OrderClient.PlaceOrder(args)
if err != nil {
log.Println("[ERROR] #001 OpenOrder", err)
return err
}
log.Println("[INFO] OpenOrder", resp)
// 加锁
SetLock(symbol, "OPEN."+side, 5)
return nil
}
func (bg *BitgetClient) OpenOrder_limit(symbol, side, size, price string) error {
// 上涨计算
// 判断:开仓锁,开仓信号
if IsLock(symbol, "OPEN."+side) {
log.Println("103", "判断:开仓锁,开仓信号")
return errors.New("开仓锁")
}
args := map[string]string{
"productType": "USDT-FUTURES",
"symbol": symbol,
"marginMode": "crossed",
"marginCoin": "USDT",
"orderType": "limit", // 订单类型,limit: 限价单market: 市价单
"size": size,
"side": GetSide(side), // 下单方向: buy,long,多仓sell,short,卖
"tradeSide": "open", // 开仓
"force": "fok", // ioc: 无法立即成交的部分就撤销,fok: 无法全部立即成交就撤销,gtc: 普通订单, 订单会一直有效,直到被成交或者取消,限价单limit时必填若省略则默认为gtc
"price": price,
"clientOid": GenClientId(),
}
resp, err := bg.OrderClient.PlaceOrder(args)
if err != nil {
log.Println("[ERROR] #001 OpenOrder", err)
return err
}
log.Println("[INFO] OpenOrder", resp)
// 加锁
SetLock(symbol, "OPEN."+side, 5)
return nil
}
func (bg *BitgetClient) CloseOrder(symbol, side, size string) error {
args := map[string]string{
"productType": "USDT-FUTURES",
"symbol": symbol,
"size": size,
"side": GetSide(side), // 下单方向: buy,long,多仓sell,short,卖
"tradeSide": "close", // 平仓
"marginMode": "crossed",
"marginCoin": "USDT",
"orderType": "market",
}
_, err := bg.OrderClient.PlaceOrder(args)
if err != nil {
log.Println("[ERROR] #004 CloseOrder", err)
return err
}
return nil
}
// 网格动态平仓
func (bg *BitgetClient) Dynamic(scfg *models.StrategyConf, symbol string, qty, currentPrice float64, orders []*models.QuantOrders, closeingProfitRate float64) {
// 利润率 <= 0时亏损并没有达到加仓的百分比时或是超过最大仓位的投入时,返回不做处理
newMaxMargin := AccountsAssets.Get("USDT").AccountEquity * 0.015
newMaxMargin = utils.FloatRound(newMaxMargin, 2)
// 设置止盈率
for _, row := range orders {
// 计算利润,计算回报率
profit, profitRate := bg.calProfitRate_V2(symbol, row.Side, float64(scfg.Leverage), row.OpenPrice, currentPrice, row.Volume)
// 得到持仓与开仓数量的倍数
multiple := math.Round(row.Volume / qty)
if multiple < 1 {
multiple = 1
}
// 根据倍数计算补仓百分比。
maxAddThreshold := scfg.AddThreshold * multiple * 1.2
if multiple > 3 {
maxAddThreshold = scfg.AddThreshold * multiple * 1.6
}
// cache key.
CacheKey := fmt.Sprintf("%s_%s", row.Symbol, row.Side)
// 网格计算
NewProfitCell := profitRate / scfg.DefCloseingGridPrice
NewProfitCell = math.Ceil(NewProfitCell)
// 打印日志
log.Println("Dynamic", row.Symbol, row.Side, "Volume:", row.Volume, "OpenPrice:", row.OpenPrice, "Profit:", profit, "ProfitRate", profitRate, "NewProfitCell", NewProfitCell, "closeingProfitRate", closeingProfitRate, "newMaxMargin", newMaxMargin, "maxAddThreshold", maxAddThreshold)
// 止损处理
if scfg.StopLossOn && profitRate <= scfg.StopThreshold {
log.Println("!!! Stop", CacheKey, "StopThreshold", scfg.StopThreshold, "profitRate", profitRate)
// 平仓
bg.CloseOrder(row.Symbol, row.Side, utils.Float64ToString(row.Volume))
continue
}
if scfg.AddPositionOn && profitRate <= maxAddThreshold && row.MarginSize < newMaxMargin {
if !IsLock(symbol, "OPEN."+row.Side) {
qtyStr := utils.Float64ToString(qty)
bg.OpenOrder_market(symbol, row.Side, qtyStr)
SetLock(symbol, "OPEN."+row.Side, 5)
continue
}
}
// 读取缓存
lastProfitCell, exists := bg.LastUPL[CacheKey]
if !exists {
// 缓存处理: 缓存不存在
// 收益处理: 收益率达到默认收益率
if profitRate >= closeingProfitRate {
log.Println("###", CacheKey, "profit", profit, "profitRate", profitRate, ">=", "closeingProfitRate", closeingProfitRate, "NewProfitCell", NewProfitCell, "newMaxMargin", newMaxMargin)
bg.LastUPL[CacheKey] = NewProfitCell
}
} else {
log.Println("$$$", CacheKey, "profit", profit, "profitRate", profitRate, ">", "closeingProfitRate", closeingProfitRate, "NewProfitCell", NewProfitCell, "lastProfitCell", lastProfitCell, "newMaxMargin", newMaxMargin)
if profitRate >= closeingProfitRate {
// 缓存处理: 存在缓存
// 判断: NewProfitCell < lastProfitCell 当前最新网格值 < 缓存的上一次网格值
if NewProfitCell < lastProfitCell {
// 缓存的收益低于当前的CELL则平仓。
log.Println("~~~", CacheKey, "profit:", profit, "ProfitRate", profitRate, " > CloseProfitRate", closeingProfitRate, "LastProfitCell", lastProfitCell, "< NewProfitCell", NewProfitCell)
// 平仓
bg.CloseOrder(symbol, row.Side, utils.Float64ToString(row.Volume))
delete(bg.LastUPL, CacheKey)
// 更新帐号资金
_, ac, err := bg.GetFuturesAccountBalance()
if err != nil {
log.Println("bg.GetFuturesAccountBalance", err.Error())
} else {
AccountsAssets.SetData(ac)
}
} else {
// 高于上次盈利CELL更新缓存
log.Println("^^^ Up", CacheKey, "profit:", profit, "ProfitRate", profitRate, " > CloseProfitRate", closeingProfitRate, "ProfitCell", NewProfitCell)
// 更新缓存
bg.LastUPL[CacheKey] = NewProfitCell
}
}
}
}
}
func (bg *BitgetClient) calProfitRate_V2(symbol, side string, leverage, avgPrice, currentPrice, volume float64) (profit, profitRate float64) {
// 计算利润
switch side {
case "LONG":
profit = (currentPrice - avgPrice) * volume
//Debug("CalculateProfit_Long", symbol, side, "AvgPrice:", avgPrice.String(), "CurrentPrice:", currentPrice.String(), "volume:", volume.String())
case "SHORT":
//Debug("CalculateProfit_Short", symbol, side, "AvgPrice:", avgPrice.String(), "CurrentPrice:", currentPrice.String(), "volume:", volume.String())
profit = (avgPrice - currentPrice) * volume
}
// 计算回报率
if profit == 0 {
return profit, profitRate
}
cost := avgPrice * volume
actualInvestment := cost / leverage
profitRate = profit / actualInvestment
profitRate = utils.FloatRound(profitRate, 3)
return profit, profitRate
}