224 lines
7.0 KiB
Go
224 lines
7.0 KiB
Go
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)
|
||
}
|
||
}
|