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

224 lines
7.0 KiB
Go
Raw 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 (
"encoding/json"
"log"
"strings"
"time"
"git.apinb.com/bsm-sdk/core/utils"
"git.apinb.com/quant/strategy/internal/impl"
"git.apinb.com/quant/strategy/internal/models"
"github.com/bitget-golang/sdk-api/pkg/client/ws"
"github.com/bitget-golang/sdk-api/types"
"github.com/robfig/cron/v3"
"github.com/vmihailenco/msgpack/v5"
)
type WebsocketPositionMessage struct {
Action string `json:"action"`
Arg struct {
Channel string `json:"channel"`
InstID string `json:"instId"`
InstType string `json:"instType"`
} `json:"arg"`
Data []struct {
AchievedProfits string `json:"achievedProfits"`
AssetMode string `json:"assetMode"`
AutoMargin string `json:"autoMargin"`
Available string `json:"available"`
BreakEvenPrice string `json:"breakEvenPrice"`
CTime string `json:"cTime"`
DeductedFee string `json:"deductedFee"`
Frozen string `json:"frozen"`
HoldSide string `json:"holdSide"`
InstID string `json:"instId"` // symbol
KeepMarginRate string `json:"keepMarginRate"`
Leverage int64 `json:"leverage"`
LiquidationPrice string `json:"liquidationPrice"`
MarginCoin string `json:"marginCoin"`
MarginMode string `json:"marginMode"`
MarginRate string `json:"marginRate"`
MarginSize string `json:"marginSize"`
MarkPrice string `json:"markPrice"`
OpenPriceAvg string `json:"openPriceAvg"`
PosID string `json:"posId"`
PosMode string `json:"posMode"`
Total string `json:"total"`
TotalFee string `json:"totalFee"`
UTime string `json:"uTime"`
UnrealizedPL string `json:"unrealizedPL"`
UnrealizedPLR string `json:"unrealizedPLR"`
} `json:"data"`
Ts int64 `json:"ts"`
}
var PlanKeyName string
func (bg *BitgetClient) RefreshByApi(planKeyName string) {
PlanKeyName = planKeyName
// 根据基本币监控帐号可用资金变动仓位以及最近7天的交易情况
c := cron.New(cron.WithSeconds())
c.AddFunc("@every 1s", func() {
bg.PositionsByApi(planKeyName)
})
c.Start()
}
func (bg *BitgetClient) RefreshByWebSocket(planKeyName string) {
// 初始获取持仓
PlanKeyName = planKeyName
bg.PositionsByApi(planKeyName)
// 根据WebSocket推送实时更新持仓添加重连机制
go func() {
for {
var channels []types.SubscribeReq
positions := types.SubscribeReq{
InstType: "USDT-FUTURES",
Channel: "positions",
InstId: "default",
}
channels = append(channels, positions)
wsClient := new(ws.BitgetWsClient).Init(true, receiveHandler, errorHandler)
wsClient.SubscribeDef(channels)
log.Println("Bitget Websocket Connect...")
// Connect() 是阻塞调用,当连接断开时会返回
wsClient.Connect()
// 连接断开后等待5秒后重连
log.Println("Bitget Websocket disconnected, will reconnect in 5 seconds...")
time.Sleep(5 * time.Second)
// 重新获取持仓数据
bg.PositionsByApi(planKeyName)
}
}()
}
func receiveHandler(message string) {
var reply WebsocketPositionMessage
err := json.Unmarshal([]byte(message), &reply)
if err != nil {
log.Println("WatchPositions JSON Unmarshal Error:", err)
return
}
if len(reply.Data) == 0 {
impl.RedisService.Client.Del(impl.RedisService.Ctx, PlanKeyName+".PosSummary").Result()
impl.RedisService.Client.Del(impl.RedisService.Ctx, PlanKeyName+".PosOrders").Result()
log.Println("WatchPositions:", "No Positions")
return
}
orders := make(map[string][]*models.QuantOrders, 0)
var summary []string
for _, v := range reply.Data {
side := strings.ToUpper(v.HoldSide)
// 假设 v.CTime 是一个表示毫秒时间戳的字符串
t := time.UnixMilli(utils.String2Int64(v.CTime))
record := &models.QuantOrders{
Exchange: "BITGET",
Symbol: v.InstID,
Side: side,
Fee: utils.String2Float64(v.TotalFee),
OpenPrice: utils.String2Float64(v.OpenPriceAvg), // 开仓均价
Volume: utils.String2Float64(v.Total), // 交易币成交数量
MarginSize: utils.String2Float64(v.MarginSize), // 计价币成交数量
Leverage: int(v.Leverage),
UnrealizedPL: utils.String2Float64(v.UnrealizedPL),
CreatedAt: t, // 毫秒转time.Time
}
orders[v.InstID] = append(orders[v.InstID], record)
log.Println("Record", record)
summary = append(summary, v.InstID+"."+side)
}
_, err = impl.RedisService.Client.Set(impl.RedisService.Ctx, PlanKeyName+".PosSummary", strings.Join(summary, ","), 0).Result()
if err != nil {
log.Println("WatchPositions:", err)
}
// 序列化为 MessagePack
ordersPack, _ := msgpack.Marshal(orders)
_, err = impl.RedisService.Client.Set(impl.RedisService.Ctx, PlanKeyName+".PosOrders", ordersPack, 0).Result()
if err != nil {
log.Println("WatchPositions:", err)
}
}
func errorHandler(message string) {
log.Println("WebSocket Error:", message)
}
func (bg *BitgetClient) PositionsByApi(planKeyName string) {
args := map[string]string{
"productType": "USDT-FUTURES",
"marginCoin": "USDT",
}
log.Println("===", "RefreshPositions:", planKeyName, "===")
resp, err := bg.AccountClient.AllPosition(args)
if err != nil {
log.Println("WatchPositions:", err)
return
}
var reply AllPositionResp
json.Unmarshal([]byte(resp), &reply)
if err != nil {
log.Println("WatchPositions:", err)
return
}
if reply.Code != "00000" {
log.Println("WatchPositions:", reply.Code+" "+reply.Msg)
return
}
if len(reply.Data) == 0 {
impl.RedisService.Client.Del(impl.RedisService.Ctx, planKeyName+".PosSummary").Result()
impl.RedisService.Client.Del(impl.RedisService.Ctx, planKeyName+".PosOrders").Result()
log.Println("WatchPositions:", "No Positions")
return
}
orders := make(map[string][]*models.QuantOrders, 0)
var summary []string
for _, v := range reply.Data {
side := strings.ToUpper(v.HoldSide)
// 假设 v.CTime 是一个表示毫秒时间戳的字符串
t := time.UnixMilli(utils.String2Int64(v.CTime))
record := &models.QuantOrders{
Exchange: "BITGET",
Symbol: v.Symbol,
Side: side,
Fee: utils.String2Float64(v.TotalFee),
OpenPrice: utils.String2Float64(v.OpenPriceAvg), // 开仓均价
Volume: utils.String2Float64(v.Total), // 交易币成交数量
MarginSize: utils.String2Float64(v.MarginSize), // 计价币成交数量
Leverage: utils.String2Int(v.Leverage),
UnrealizedPL: utils.String2Float64(v.UnrealizedPL),
CreatedAt: t, // 毫秒转time.Time
}
orders[v.Symbol] = append(orders[v.Symbol], record)
log.Println("Record", record)
summary = append(summary, v.Symbol+"."+side)
}
_, err = impl.RedisService.Client.Set(impl.RedisService.Ctx, planKeyName+".PosSummary", strings.Join(summary, ","), 0).Result()
if err != nil {
log.Println("WatchPositions:", err)
}
// 序列化为 MessagePack
ordersPack, _ := msgpack.Marshal(orders)
_, err = impl.RedisService.Client.Set(impl.RedisService.Ctx, planKeyName+".PosOrders", ordersPack, 0).Result()
if err != nil {
log.Println("WatchPositions:", err)
}
}