exchange/okex/okex_ws.go
2024-06-26 00:59:56 +08:00

360 lines
9.3 KiB
Go

package okex
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"github.com/bitly/go-simplejson"
"strconv"
"time"
"git.qtrade.icu/coin-quant/exchange/ws"
. "git.qtrade.icu/coin-quant/trademodel"
"github.com/mitchellh/mapstructure"
log "github.com/sirupsen/logrus"
)
func (b *OkxTrader) runPrivate() (err error) {
b.wsUser, err = ws.NewWSConn(WSOkexPrivate, func(ws *ws.WSConn) error {
login := OPParam{
OP: "login",
Args: []interface{}{NewLoginArg(b.cfg.ApiKey, b.cfg.Passphrase, b.cfg.SecretKey)},
}
return ws.WriteMsg(login)
}, b.parseUserMsg)
return
}
type LoginArg struct {
ApiKey string `json:"apiKey"`
Passphrase string `json:"passphrase"`
Timestamp int64 `json:"timestamp"`
Sign string `json:"sign"`
}
func NewLoginArg(apiKey, pass, secret string) *LoginArg {
a := new(LoginArg)
a.ApiKey = apiKey
a.Passphrase = pass
t := time.Now()
a.Timestamp = t.Unix()
src := fmt.Sprintf("%dGET/users/self/verify", a.Timestamp)
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(src))
ret := h.Sum(nil)
n := base64.StdEncoding.EncodedLen(len(ret))
dst := make([]byte, n)
base64.StdEncoding.Encode(dst, ret)
a.Sign = string(dst)
return a
}
type OPArg struct {
Channel string `json:"channel"`
InstType string `json:"instType"`
Uly string `json:"uly,omitempty"`
InstID string `json:"instId,omitempty"`
}
type OPParam struct {
OP string `json:"op"`
Args []interface{} `json:"args"`
}
func (b *OkxTrader) getSymbolSub() (p OPParam) {
p.OP = "subscribe"
p.Args = append(p.Args, OPArg{Channel: "orders", InstType: "ANY"})
p.Args = append(p.Args, OPArg{Channel: "orders-algo", InstType: "ANY"})
p.Args = append(p.Args, OPArg{Channel: "algo-advance", InstType: "ANY"})
p.Args = append(p.Args, OPArg{Channel: "positions", InstType: "ANY"})
return
}
// {"arg":{"channel":"trades","instId":"BSV-USD-210924"},"data":[{"instId":"BSV-USD-210924","tradeId":"1957771","px":"166.5","sz":"22","side":"sell","ts":"1621862533713"}]}
// {"arg":{"channel":"books","instId":"BSV-USD-210924"},"action":"update","data":[{"asks":[["167.4","54","0","1"]],"bids":[["164.81","0","0","0"],["164.69","79","0","1"]],"ts":"1621862579059","checksum":71082836}]}
// books5 {"arg":{"channel":"books5","instId":"BSV-USD-210924"},"data":[{"asks":[["166.35","20","0","1"],["166.4","135","0","2"],["166.42","86","0","1"],["166.45","310","0","1"],["166.46","61","0","2"]],"bids":[["166.14","33","0","1"],["166.07","106","0","1"],["166.05","2","0","1"],["166.04","97","0","1"],["165.98","20","0","1"]],"instId":"BSV-USD-210924","ts":"1621862688397"}]}
func (b *OkxTrader) parseUserMsg(message []byte) (err error) {
var sj *simplejson.Json
var evt string
var channel string
sj, err = simplejson.NewJson(message)
if err != nil {
log.Warnf("parse json error:%s", string(message))
return
}
evtValue, ok := sj.CheckGet("event")
if ok {
evt, err = evtValue.String()
if err != nil {
log.Warnf("login error:%s %s", string(message), err.Error())
return
}
switch evt {
case "error":
log.Errorf("recv error: %s", string(message))
return
case "login":
var code string
code, err = sj.Get("code").String()
if code != "0" {
log.Warnf("login error:%s %s", string(message), err.Error())
return
}
param := b.getSymbolSub()
err = b.wsUser.WriteMsg(param)
if err != nil {
return
}
default:
}
return
}
arg, ok := sj.CheckGet("arg")
if !ok {
return
}
channelValue, ok := arg.CheckGet("channel")
if !ok {
return
}
channel, err = channelValue.String()
if err != nil {
log.Warnf("channelValue %v is not string error:%s", channelValue, err.Error())
return
}
switch channel {
case "orders":
var orders []OrderNormal
orders, err = parseOkexOrder(sj.Get("data"))
if err != nil {
log.Warnf("parseOkexOrder error:%s, %s", orders, err.Error())
return
}
for _, v := range orders {
o := v.GetOrder()
if o == nil {
continue
}
o.Status = OrderStatusFilled
b.ordersCache.Delete(v.OrdID)
b.stopOrdersCache.Delete(v.OrdID)
if b.tradeCb != nil {
b.tradeCb(o)
}
}
case "orders-algo":
// 算法单最终还是会生成一个普通单子
var algoOrders []AlgoOrder
algoOrders, err = parseOkexAlgoOrder(sj.Get("data"))
if err != nil {
log.Warnf("parseOkexAlgoOrder error:%s, %s", algoOrders, err.Error())
return
}
for _, v := range algoOrders {
if v.State == "filled" {
b.stopOrdersCache.Delete(v.AlgoID)
}
}
case "algo-advance":
case "positions":
var pos []OKEXPos
pos, err = parseOkexPos(sj.Get("data"))
if err != nil {
log.Warnf("parseOkexPos error:%s, %s", pos, err.Error())
return
}
for _, v := range pos {
t := v.GetPos()
if t == nil {
continue
}
if b.positionCb != nil {
b.positionCb(t)
}
}
default:
}
return
}
func (o *OrderNormal) GetOrder() (ret *Order) {
if o.State != "filled" {
return
}
var side = transSide(o.Side)
ret = &Order{
OrderID: o.OrdID,
Symbol: o.InstID,
// Currency:o.
Amount: parseFloat(o.Sz),
Price: parseFloat(o.AvgPx),
Status: o.State,
Side: side,
Time: parseTime(o.FillTime),
}
return
}
func transSide(oSide string) (side string) {
switch oSide {
case "buy":
side = "long"
case "sell":
side = "short"
default:
side = oSide
}
return
}
func parseTime(v string) time.Time {
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
log.Errorf("parseTime failed: %s", err.Error())
return time.Now()
}
return time.Unix(n/1000, (n%1000)*int64(time.Millisecond))
}
// {"accFillSz":"0","amendResult":"","avgPx":"0","cTime":"1639669567849","category":"normal","ccy":"","clOrdId":"","code":"0","execType":"","fee":"0","feeCcy":"USDT","fillFee":"0","fillFeeCcy":"","fillNotionalUsd":"","fillPx":"","fillSz":"0","fillTime":"","instId":"SOL-USDT-SWAP","instType":"SWAP","lever":"10","msg":"","notionalUsd":"185.666","ordId":"391737792421838866","ordType":"limit","pnl":"0","posSide":"short","px":"200","rebate":"0","rebateCcy":"USDT","reduceOnly":"false","reqId":"","side":"sell","slOrdPx":"","slTriggerPx":"","slTriggerPxType":"last","source":"","state":"live","sz":"1","tag":"","tdMode":"isolated","tgtCcy":"","tpOrdPx":"","tpTriggerPx":"","tpTriggerPxType":"last","tradeId":"","uTime":"1639669567849"}
func parseOkexOrder(sj *simplejson.Json) (orders []OrderNormal, err error) {
arr, err := sj.Array()
if err != nil {
return
}
var ret map[string]interface{}
var ok bool
for _, v := range arr {
ret, ok = v.(map[string]interface{})
if !ok {
err = errors.New("parseOrderBook error")
return
}
var o OrderNormal
err = mapstructure.Decode(ret, &o)
if err != nil {
return
}
orders = append(orders, o)
}
return
}
func parseOkexPos(sj *simplejson.Json) (trades []OKEXPos, err error) {
arr, err := sj.Array()
if err != nil {
return
}
var ret map[string]interface{}
var ok bool
for _, v := range arr {
ret, ok = v.(map[string]interface{})
if !ok {
err = errors.New("parseOkexTrades error")
return
}
var t OKEXPos
err = mapstructure.Decode(ret, &t)
if err != nil {
return
}
trades = append(trades, t)
}
return
}
type OKEXPos struct {
Adl string `json:"adl"`
AvailPos string `json:"availPos"`
AvgPx string `json:"avgPx"`
CTime string `json:"cTime"`
Ccy string `json:"ccy"`
DeltaBS string `json:"deltaBS"`
DeltaPA string `json:"deltaPA"`
GammaBS string `json:"gammaBS"`
GammaPA string `json:"gammaPA"`
Imr string `json:"imr"`
InstID string `json:"instId"`
InstType string `json:"instType"`
Interest string `json:"interest"`
Last string `json:"last"`
Lever string `json:"lever"`
Liab string `json:"liab"`
LiabCcy string `json:"liabCcy"`
LiqPx string `json:"liqPx"`
MarkPx string `json:"markPx"`
Margin string `json:"margin"`
MgnMode string `json:"mgnMode"`
MgnRatio string `json:"mgnRatio"`
Mmr string `json:"mmr"`
NotionalUsd string `json:"notionalUsd"`
OptVal string `json:"optVal"`
PTime string `json:"pTime"`
Pos string `json:"pos"`
PosCcy string `json:"posCcy"`
PosID string `json:"posId"`
PosSide string `json:"posSide"`
ThetaBS string `json:"thetaBS"`
ThetaPA string `json:"thetaPA"`
TradeID string `json:"tradeId"`
UTime string `json:"uTime"`
Upl string `json:"upl"`
UplRatio string `json:"uplRatio"`
VegaBS string `json:"vegaBS"`
VegaPA string `json:"vegaPA"`
}
func (ot *OKEXPos) GetPos() (pos *Position) {
var typ int
hold := parseFloat(ot.Pos)
if ot.PosSide == "long" {
typ = Long
} else if ot.PosSide == "short" {
typ = Short
hold = 0 - hold
} else if ot.PosSide == "net" {
if hold > 0 {
typ = Long
} else {
typ = Short
}
}
price := parseFloat(ot.AvgPx)
pos = &Position{
Symbol: ot.InstID,
Type: typ,
Hold: hold,
Price: price,
ProfitRatio: parseFloat(ot.Upl),
}
return
}
func parseOkexAlgoOrder(sj *simplejson.Json) (orders []AlgoOrder, err error) {
arr, err := sj.Array()
if err != nil {
return
}
var ret map[string]interface{}
var ok bool
for _, v := range arr {
ret, ok = v.(map[string]interface{})
if !ok {
err = errors.New("parseOrderBook error")
return
}
var o AlgoOrder
err = mapstructure.Decode(ret, &o)
if err != nil {
return
}
orders = append(orders, o)
}
return
}