first commit
This commit is contained in:
commit
b9699ebe72
28
binance/binance.go
Normal file
28
binance/binance.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package binance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.qtrade.icu/coin-quant/exchange"
|
||||||
|
"git.qtrade.icu/coin-quant/exchange/binance/common"
|
||||||
|
mfutures "git.qtrade.icu/coin-quant/exchange/binance/features"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
exchange.RegisterExchange("binance", NewBinance)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBinance(cfg exchange.Config, cltName string) (e exchange.Exchange, err error) {
|
||||||
|
var eCfg common.BinanceConfig
|
||||||
|
err = cfg.UnmarshalKey(fmt.Sprintf("exchanges.%s", cltName), &eCfg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientProxy := cfg.GetString("proxy")
|
||||||
|
switch eCfg.Kind {
|
||||||
|
case "futures":
|
||||||
|
e, err = mfutures.NewBinanceTrader(eCfg, cltName, clientProxy)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("binance unsupport kind %s", &eCfg.Kind)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
11
binance/common/config.go
Normal file
11
binance/common/config.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
type BinanceConfig struct {
|
||||||
|
Name string
|
||||||
|
ApiKey string
|
||||||
|
SecretKey string
|
||||||
|
Passphrase string
|
||||||
|
IsTest bool
|
||||||
|
Kind string
|
||||||
|
Currency string
|
||||||
|
}
|
547
binance/features/binance.go
Normal file
547
binance/features/binance.go
Normal file
|
@ -0,0 +1,547 @@
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"git.qtrade.icu/coin-quant/exchange"
|
||||||
|
bcommon "git.qtrade.icu/coin-quant/exchange/binance/common"
|
||||||
|
. "git.qtrade.icu/coin-quant/trademodel"
|
||||||
|
gobinance "github.com/adshao/go-binance/v2"
|
||||||
|
bfutures "github.com/adshao/go-binance/v2/futures"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
background = context.Background()
|
||||||
|
newLock sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ exchange.Exchange = &BinanceTrade{}
|
||||||
|
|
||||||
|
type BinanceTrade struct {
|
||||||
|
name string
|
||||||
|
api *bfutures.Client
|
||||||
|
|
||||||
|
tradeCb exchange.WatchFn
|
||||||
|
positionCb exchange.WatchFn
|
||||||
|
balanceCb exchange.WatchFn
|
||||||
|
closeCh chan bool
|
||||||
|
|
||||||
|
cancelService *bfutures.CancelAllOpenOrdersService
|
||||||
|
cancelOneService *bfutures.CancelOrderService
|
||||||
|
timeService *bfutures.ServerTimeService
|
||||||
|
|
||||||
|
klineLimit int
|
||||||
|
timeout time.Duration
|
||||||
|
|
||||||
|
wsUserListenKey string
|
||||||
|
|
||||||
|
baseCurrency string
|
||||||
|
symbols map[string]Symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBinanceTrader(cfg bcommon.BinanceConfig, cltName, clientProxy string) (b *BinanceTrade, err error) {
|
||||||
|
b = new(BinanceTrade)
|
||||||
|
b.name = "binance"
|
||||||
|
if cltName == "" {
|
||||||
|
cltName = "binance"
|
||||||
|
}
|
||||||
|
b.klineLimit = 1500
|
||||||
|
b.baseCurrency = "USDT"
|
||||||
|
if cfg.Currency != "" {
|
||||||
|
b.baseCurrency = cfg.Currency
|
||||||
|
}
|
||||||
|
|
||||||
|
b.timeout = time.Second * 5
|
||||||
|
b.closeCh = make(chan bool)
|
||||||
|
|
||||||
|
newLock.Lock()
|
||||||
|
defer func() {
|
||||||
|
bfutures.UseTestnet = false
|
||||||
|
newLock.Unlock()
|
||||||
|
}()
|
||||||
|
if cfg.IsTest {
|
||||||
|
bfutures.UseTestnet = true
|
||||||
|
log.Warnf("binance trade connecting to testnet")
|
||||||
|
}
|
||||||
|
bfutures.WebsocketKeepalive = true
|
||||||
|
b.api = gobinance.NewFuturesClient(cfg.ApiKey, cfg.SecretKey)
|
||||||
|
if clientProxy != "" {
|
||||||
|
var proxyURL *url.URL
|
||||||
|
proxyURL, err = url.Parse(clientProxy)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.api.HTTPClient = &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}}
|
||||||
|
|
||||||
|
websocket.DefaultDialer.Proxy = http.ProxyURL(proxyURL)
|
||||||
|
websocket.DefaultDialer.HandshakeTimeout = time.Second * 60
|
||||||
|
}
|
||||||
|
b.cancelService = b.api.NewCancelAllOpenOrdersService()
|
||||||
|
b.cancelOneService = b.api.NewCancelOrderService()
|
||||||
|
b.timeService = b.api.NewServerTimeService()
|
||||||
|
_, err = b.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// err = b.Start()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchBalance different with spot
|
||||||
|
func (b *BinanceTrade) fetchBalanceAndPosition() (err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
account, err := b.api.NewGetAccountService().Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var balance Balance
|
||||||
|
balance.Balance = parseFloat(account.TotalWalletBalance)
|
||||||
|
balance.Available = parseFloat(account.TotalCrossWalletBalance)
|
||||||
|
// balance.Frozen =
|
||||||
|
if b.balanceCb != nil {
|
||||||
|
b.balanceCb(&balance)
|
||||||
|
}
|
||||||
|
if b.positionCb != nil {
|
||||||
|
var amount, profit, initMargin float64
|
||||||
|
for _, v := range account.Positions {
|
||||||
|
amount = parseFloat(v.PositionAmt)
|
||||||
|
if amount == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var position Position
|
||||||
|
position.Symbol = v.Symbol
|
||||||
|
position.Hold = amount
|
||||||
|
position.Price = parseFloat(v.EntryPrice)
|
||||||
|
profit = parseFloat(v.UnrealizedProfit)
|
||||||
|
initMargin = parseFloat(v.PositionInitialMargin)
|
||||||
|
position.ProfitRatio = profit / initMargin
|
||||||
|
if position.Hold > 0 {
|
||||||
|
position.Type = Long
|
||||||
|
} else {
|
||||||
|
position.Type = Short
|
||||||
|
}
|
||||||
|
b.positionCb(&position)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BinanceTrade) Info() (info exchange.ExchangeInfo) {
|
||||||
|
info = exchange.ExchangeInfo{
|
||||||
|
Name: "binance_futures",
|
||||||
|
Value: "binance_futures",
|
||||||
|
Desc: "binance futures api",
|
||||||
|
KLineLimit: exchange.FetchLimit{
|
||||||
|
Limit: b.klineLimit,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BinanceTrade) Symbols() (symbols []Symbol, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
resp, err := b.api.NewExchangeInfoService().Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
symbols = make([]Symbol, len(resp.Symbols))
|
||||||
|
for i, v := range resp.Symbols {
|
||||||
|
value := Symbol{
|
||||||
|
Name: v.Symbol,
|
||||||
|
Exchange: "binance",
|
||||||
|
Symbol: v.Symbol,
|
||||||
|
Resolutions: "1m,5m,15m,30m,1h,4h,1d,1w",
|
||||||
|
Precision: v.PricePrecision,
|
||||||
|
AmountPrecision: v.QuantityPrecision,
|
||||||
|
PriceStep: 0,
|
||||||
|
AmountStep: 0,
|
||||||
|
}
|
||||||
|
for _, f := range v.Filters {
|
||||||
|
switch f["filterType"] {
|
||||||
|
case "PRICE_FILTER":
|
||||||
|
value.PriceStep = parseFloat(f["tickSize"].(string))
|
||||||
|
case "LOT_SIZE":
|
||||||
|
value.AmountStep = parseFloat(f["stepSize"].(string))
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
symbols[i] = value
|
||||||
|
}
|
||||||
|
if len(symbols) > 0 {
|
||||||
|
symbolMap := make(map[string]Symbol)
|
||||||
|
for _, v := range symbols {
|
||||||
|
symbolMap[v.Symbol] = v
|
||||||
|
}
|
||||||
|
b.symbols = symbolMap
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BinanceTrade) Start() (err error) {
|
||||||
|
err = b.fetchBalanceAndPosition()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// watch position and order changed
|
||||||
|
err = b.startUserWS()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (b *BinanceTrade) Stop() (err error) {
|
||||||
|
close(b.closeCh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// KlineChan get klines
|
||||||
|
func (b *BinanceTrade) GetKline(symbol, bSize string, start, end time.Time) (data []*Candle, err error) {
|
||||||
|
var temp *Candle
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
defer func() {
|
||||||
|
if err != nil && strings.Contains(err.Error(), "Too many requests") {
|
||||||
|
err = fmt.Errorf("%w, retry: %s", exchange.ErrRetry, err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// get server time
|
||||||
|
nTime, err := b.timeService.Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nStart := start.Unix() * 1000
|
||||||
|
nEnd := end.Unix() * 1000
|
||||||
|
|
||||||
|
klines, err := b.api.NewKlinesService().Interval(bSize).Symbol(symbol).StartTime(nStart).EndTime(nEnd).Limit(b.klineLimit).Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sort.Slice(klines, func(i, j int) bool {
|
||||||
|
return klines[i].OpenTime < klines[j].OpenTime
|
||||||
|
})
|
||||||
|
if len(klines) == 0 {
|
||||||
|
log.Warnf("GetKline once, param: [%s]-[%s] no data", start, end)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Infof("GetKline once, param: [%s]-[%s], total: %d, first: %s, last: %s", start, end, len(klines), time.UnixMilli(klines[0].OpenTime), time.UnixMilli(klines[len(klines)-1].OpenTime))
|
||||||
|
data = []*Candle{}
|
||||||
|
for k, v := range klines {
|
||||||
|
temp = transCandle(v)
|
||||||
|
if k == len(klines)-1 {
|
||||||
|
// check if candle is unfinished
|
||||||
|
if v.CloseTime > nTime {
|
||||||
|
log.Infof("skip unfinished candle: %##v\n", *v)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = append(data, temp)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BinanceTrade) handleError(typ string, cb func() error) func(error) {
|
||||||
|
return func(err error) {
|
||||||
|
log.Errorf("binance %s error:%s, call callback", typ, err.Error())
|
||||||
|
if cb != nil {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BinanceTrade) handleAggTradeEvent(fn exchange.WatchFn) func(evt *bfutures.WsAggTradeEvent) {
|
||||||
|
return func(evt *bfutures.WsAggTradeEvent) {
|
||||||
|
var err error
|
||||||
|
var trade Trade
|
||||||
|
trade.ID = fmt.Sprintf("%d", evt.AggregateTradeID)
|
||||||
|
trade.Amount, err = strconv.ParseFloat(evt.Quantity, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("AggTradeEvent parse amount failed: %s", evt.Quantity)
|
||||||
|
}
|
||||||
|
trade.Price, err = strconv.ParseFloat(evt.Price, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("AggTradeEvent parse amount failed: %s", evt.Quantity)
|
||||||
|
}
|
||||||
|
trade.Time = time.Unix(evt.Time/1000, (evt.Time%1000)*int64(time.Millisecond))
|
||||||
|
if fn != nil {
|
||||||
|
fn(&trade)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BinanceTrade) handleDepth(fn exchange.WatchFn) func(evt *bfutures.WsDepthEvent) {
|
||||||
|
return func(evt *bfutures.WsDepthEvent) {
|
||||||
|
var depth Depth
|
||||||
|
var err error
|
||||||
|
var price, amount float64
|
||||||
|
depth.UpdateTime = time.Unix(evt.TransactionTime/1000, (evt.TransactionTime%1000)*int64(time.Millisecond))
|
||||||
|
for _, v := range evt.Asks {
|
||||||
|
// depth.Sells
|
||||||
|
price, err = strconv.ParseFloat(v.Price, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("handleDepth parse price failed: %s", v.Price)
|
||||||
|
}
|
||||||
|
amount, err = strconv.ParseFloat(v.Quantity, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("handleDepth parse amount failed: %s", v.Quantity)
|
||||||
|
}
|
||||||
|
depth.Sells = append(depth.Sells, DepthInfo{Price: price, Amount: amount})
|
||||||
|
}
|
||||||
|
for _, v := range evt.Bids {
|
||||||
|
// depth.Sells
|
||||||
|
price, err = strconv.ParseFloat(v.Price, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("handleDepth parse price failed: %s", v.Price)
|
||||||
|
}
|
||||||
|
amount, err = strconv.ParseFloat(v.Quantity, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("handleDepth parse amount failed: %s", v.Quantity)
|
||||||
|
}
|
||||||
|
depth.Buys = append(depth.Buys, DepthInfo{Price: price, Amount: amount})
|
||||||
|
}
|
||||||
|
if fn != nil {
|
||||||
|
fn(&depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BinanceTrade) retry(param exchange.WatchParam, fn exchange.WatchFn) func() error {
|
||||||
|
return func() error {
|
||||||
|
// retry when error cause
|
||||||
|
select {
|
||||||
|
case <-b.closeCh:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return b.Watch(param, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BinanceTrade) Watch(param exchange.WatchParam, fn exchange.WatchFn) (err error) {
|
||||||
|
symbol := param.Param["symbol"]
|
||||||
|
var stopC chan struct{}
|
||||||
|
switch param.Type {
|
||||||
|
case exchange.WatchTypeCandle:
|
||||||
|
binSize := param.Param["bin"]
|
||||||
|
if binSize == "" {
|
||||||
|
binSize = "1m"
|
||||||
|
}
|
||||||
|
var doneC chan struct{}
|
||||||
|
finishC := make(chan struct{})
|
||||||
|
doneC, stopC, err = bfutures.WsKlineServe(symbol, binSize, processWsCandle(finishC, fn), b.handleError("watchKline", b.retry(param, fn)))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("exchange emitCandle error:", err.Error())
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
<-doneC
|
||||||
|
close(finishC)
|
||||||
|
}()
|
||||||
|
case exchange.WatchTypeDepth:
|
||||||
|
_, stopC, err = bfutures.WsPartialDepthServeWithRate(symbol, 10, 100*time.Millisecond, b.handleDepth(fn), b.handleError("depth", b.retry(param, fn)))
|
||||||
|
case exchange.WatchTypeTradeMarket:
|
||||||
|
_, stopC, err = bfutures.WsAggTradeServe(symbol, b.handleAggTradeEvent(fn), b.handleError("aggTrade", b.retry(param, fn)))
|
||||||
|
case exchange.WatchTypeTrade:
|
||||||
|
b.tradeCb = fn
|
||||||
|
case exchange.WatchTypePosition:
|
||||||
|
b.positionCb = fn
|
||||||
|
err = b.fetchBalanceAndPosition()
|
||||||
|
case exchange.WatchTypeBalance:
|
||||||
|
b.balanceCb = fn
|
||||||
|
err = b.fetchBalanceAndPosition()
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unknown wathc param: %s", param.Type)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if stopC != nil {
|
||||||
|
go func() {
|
||||||
|
<-b.closeCh
|
||||||
|
close(stopC)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BinanceTrade) CancelOrder(old *Order) (order *Order, err error) {
|
||||||
|
orderID, err := strconv.ParseInt(old.OrderID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := b.cancelOneService.Symbol(old.Symbol).OrderID(orderID).Do(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
price, err := strconv.ParseFloat(resp.Price, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("CancelOrder parse price %s error: %s", resp.Price, err.Error()))
|
||||||
|
}
|
||||||
|
amount, err := strconv.ParseFloat(resp.OrigQuantity, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("CancelOrder parse damount %s error: %s", resp.OrigQuantity, err.Error()))
|
||||||
|
}
|
||||||
|
order = &Order{
|
||||||
|
OrderID: strconv.FormatInt(resp.OrderID, 10),
|
||||||
|
Symbol: resp.Symbol,
|
||||||
|
Currency: resp.Symbol,
|
||||||
|
Amount: amount,
|
||||||
|
Price: price,
|
||||||
|
Status: strings.ToUpper(string(resp.Status)),
|
||||||
|
Side: strings.ToLower(string(resp.Side)),
|
||||||
|
Time: time.Unix(resp.UpdateTime/1000, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BinanceTrade) ProcessOrder(act TradeAction) (ret *Order, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
orderType := bfutures.OrderTypeLimit
|
||||||
|
if act.Action.IsStop() {
|
||||||
|
orderType = bfutures.OrderTypeStopMarket
|
||||||
|
}
|
||||||
|
var side bfutures.SideType
|
||||||
|
if act.Action.IsLong() {
|
||||||
|
side = bfutures.SideTypeBuy
|
||||||
|
} else {
|
||||||
|
side = bfutures.SideTypeSell
|
||||||
|
}
|
||||||
|
symbol, ok := b.symbols[act.Symbol]
|
||||||
|
if ok {
|
||||||
|
price := symbol.FixPrice(act.Price)
|
||||||
|
if price != act.Price {
|
||||||
|
log.Infof("binance change order price form %f to %f", act.Price, price)
|
||||||
|
act.Price = price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sent := b.api.NewCreateOrderService().Symbol(act.Symbol)
|
||||||
|
if act.Action.IsStop() {
|
||||||
|
sent = sent.StopPrice(fmt.Sprintf("%f", act.Price))
|
||||||
|
} else {
|
||||||
|
sent = sent.Price(fmt.Sprintf("%f", act.Price))
|
||||||
|
}
|
||||||
|
if !act.Action.IsOpen() {
|
||||||
|
sent = sent.ReduceOnly(true)
|
||||||
|
}
|
||||||
|
resp, err := sent.
|
||||||
|
Quantity(fmt.Sprintf("%f", act.Amount)).
|
||||||
|
TimeInForce(bfutures.TimeInForceTypeGTC).
|
||||||
|
Type(orderType).
|
||||||
|
Side(side).
|
||||||
|
Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret = transCreateOrder(resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: cancel stop order
|
||||||
|
func (b *BinanceTrade) CancelAllOrders() (orders []*Order, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
ret, err := b.api.NewListOrdersService().Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("CancelOrder failed with list: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
symbolMap := make(map[string]bool)
|
||||||
|
var st string
|
||||||
|
var ok bool
|
||||||
|
for _, v := range ret {
|
||||||
|
st = string(v.Status)
|
||||||
|
if st == OrderStatusFilled || st == OrderStatusCanceled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
od := transOrder(v)
|
||||||
|
orders = append(orders, od)
|
||||||
|
_, ok = symbolMap[od.Symbol]
|
||||||
|
if ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
symbolMap[od.Symbol] = true
|
||||||
|
err = b.cancelService.Symbol(od.Symbol).Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func transOrder(fo *bfutures.Order) (o *Order) {
|
||||||
|
price, err := strconv.ParseFloat(fo.Price, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("parse price %s error: %s", fo.Price, err.Error()))
|
||||||
|
}
|
||||||
|
amount, err := strconv.ParseFloat(fo.OrigQuantity, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("parse damount %s error: %s", fo.OrigQuantity, err.Error()))
|
||||||
|
}
|
||||||
|
o = &Order{
|
||||||
|
OrderID: strconv.FormatInt(fo.OrderID, 10),
|
||||||
|
Symbol: fo.Symbol,
|
||||||
|
Currency: fo.Symbol,
|
||||||
|
Amount: amount,
|
||||||
|
Price: price,
|
||||||
|
Status: strings.ToUpper(string(fo.Status)),
|
||||||
|
Side: strings.ToLower(string(fo.Side)),
|
||||||
|
Time: time.Unix(fo.Time/1000, 0),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func transCreateOrder(fo *bfutures.CreateOrderResponse) (o *Order) {
|
||||||
|
price, err := strconv.ParseFloat(fo.Price, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("parse price %s error: %s", fo.Price, err.Error()))
|
||||||
|
}
|
||||||
|
amount, err := strconv.ParseFloat(fo.OrigQuantity, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("parse damount %s error: %s", fo.OrigQuantity, err.Error()))
|
||||||
|
}
|
||||||
|
o = &Order{
|
||||||
|
OrderID: strconv.FormatInt(fo.OrderID, 10),
|
||||||
|
Symbol: fo.Symbol,
|
||||||
|
Currency: fo.Symbol,
|
||||||
|
Amount: amount,
|
||||||
|
Price: price,
|
||||||
|
Status: strings.ToUpper(string(fo.Status)),
|
||||||
|
Side: strings.ToLower(string(fo.Side)),
|
||||||
|
Time: time.Unix(fo.UpdateTime/1000, 0),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func transCandle(candle *bfutures.Kline) (ret *Candle) {
|
||||||
|
ret = &Candle{
|
||||||
|
ID: 0,
|
||||||
|
Start: candle.OpenTime / 1000,
|
||||||
|
Open: parseFloat(candle.Open),
|
||||||
|
High: parseFloat(candle.High),
|
||||||
|
Low: parseFloat(candle.Low),
|
||||||
|
Close: parseFloat(candle.Close),
|
||||||
|
Turnover: parseFloat(candle.QuoteAssetVolume),
|
||||||
|
Volume: parseFloat(candle.Volume),
|
||||||
|
Trades: candle.TradeNum,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFloat(str string) float64 {
|
||||||
|
f, err := strconv.ParseFloat(str, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic("binance parseFloat error:" + err.Error())
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
31
binance/features/kline.go
Normal file
31
binance/features/kline.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.qtrade.icu/coin-quant/exchange"
|
||||||
|
. "git.qtrade.icu/coin-quant/trademodel"
|
||||||
|
bfutures "github.com/adshao/go-binance/v2/futures"
|
||||||
|
)
|
||||||
|
|
||||||
|
func processWsCandle(doneC chan struct{}, cb exchange.WatchFn) func(event *bfutures.WsKlineEvent) {
|
||||||
|
return func(event *bfutures.WsKlineEvent) {
|
||||||
|
select {
|
||||||
|
case <-doneC:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if event.Kline.IsFinal {
|
||||||
|
candle := Candle{
|
||||||
|
ID: 0,
|
||||||
|
Start: event.Kline.StartTime / 1000,
|
||||||
|
Open: parseFloat(event.Kline.Open),
|
||||||
|
High: parseFloat(event.Kline.High),
|
||||||
|
Low: parseFloat(event.Kline.Low),
|
||||||
|
Close: parseFloat(event.Kline.Close),
|
||||||
|
Turnover: parseFloat(event.Kline.QuoteVolume),
|
||||||
|
Volume: parseFloat(event.Kline.Volume),
|
||||||
|
Trades: event.Kline.TradeNum,
|
||||||
|
}
|
||||||
|
cb(&candle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
137
binance/features/user_ws.go
Normal file
137
binance/features/user_ws.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package features
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
. "git.qtrade.icu/coin-quant/trademodel"
|
||||||
|
bfutures "github.com/adshao/go-binance/v2/futures"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *BinanceTrade) updateUserListenKey() {
|
||||||
|
var err error
|
||||||
|
ticker := time.NewTicker(time.Minute * 30)
|
||||||
|
Out:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-b.closeCh:
|
||||||
|
break Out
|
||||||
|
case <-ticker.C:
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
err = b.api.NewKeepaliveUserStreamService().ListenKey(b.wsUserListenKey).Do(ctx)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("update listen key failed:", err.Error())
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Error("update listen key failed 10 times,just exist::", err.Error())
|
||||||
|
break Out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BinanceTrade) startUserWS() (err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
listenKey, err := b.api.NewStartUserStreamService().Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.wsUserListenKey = listenKey
|
||||||
|
|
||||||
|
doneC, stopC, err := bfutures.WsUserDataServe(listenKey, b.handleUserData, b.handleUserDataError)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-doneC:
|
||||||
|
case <-b.closeCh:
|
||||||
|
}
|
||||||
|
close(stopC)
|
||||||
|
}()
|
||||||
|
go b.updateUserListenKey()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BinanceTrade) handleUserData(event *bfutures.WsUserDataEvent) {
|
||||||
|
switch event.Event {
|
||||||
|
case bfutures.UserDataEventTypeAccountUpdate:
|
||||||
|
var profit, total float64
|
||||||
|
var balance Balance
|
||||||
|
for _, v := range event.AccountUpdate.Balances {
|
||||||
|
if v.Asset == b.baseCurrency {
|
||||||
|
balance.Balance = parseFloat(v.Balance)
|
||||||
|
balance.Available = parseFloat(v.CrossWalletBalance)
|
||||||
|
if b.balanceCb != nil {
|
||||||
|
b.balanceCb(&balance)
|
||||||
|
}
|
||||||
|
total = balance.Balance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var pos Position
|
||||||
|
for _, v := range event.AccountUpdate.Positions {
|
||||||
|
pos.Symbol = v.Symbol
|
||||||
|
profit = parseFloat(v.UnrealizedPnL)
|
||||||
|
if v.IsolatedWallet != "" {
|
||||||
|
total = parseFloat(v.IsolatedWallet)
|
||||||
|
}
|
||||||
|
if total > 0 {
|
||||||
|
pos.ProfitRatio = profit / total
|
||||||
|
}
|
||||||
|
pos.Price = parseFloat(v.EntryPrice)
|
||||||
|
pos.Hold = parseFloat(v.Amount)
|
||||||
|
if pos.Hold > 0 {
|
||||||
|
pos.Type = Long
|
||||||
|
} else if pos.Hold < 0 {
|
||||||
|
pos.Type = Short
|
||||||
|
}
|
||||||
|
if b.positionCb != nil {
|
||||||
|
b.positionCb(&pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case bfutures.UserDataEventTypeOrderTradeUpdate:
|
||||||
|
var order Order
|
||||||
|
order.OrderID = strconv.FormatInt(event.OrderTradeUpdate.ID, 10)
|
||||||
|
order.Symbol = event.OrderTradeUpdate.Symbol
|
||||||
|
order.Currency = event.OrderTradeUpdate.Symbol
|
||||||
|
|
||||||
|
order.Amount = parseFloat(event.OrderTradeUpdate.OriginalQty)
|
||||||
|
order.Filled = parseFloat(event.OrderTradeUpdate.AccumulatedFilledQty)
|
||||||
|
if order.Filled == order.Amount {
|
||||||
|
order.Price = parseFloat(event.OrderTradeUpdate.AveragePrice)
|
||||||
|
} else {
|
||||||
|
order.Price = parseFloat(event.OrderTradeUpdate.OriginalPrice)
|
||||||
|
}
|
||||||
|
order.Status = string(event.OrderTradeUpdate.Status)
|
||||||
|
order.Side = string(event.OrderTradeUpdate.Side)
|
||||||
|
order.Time = time.UnixMilli(event.OrderTradeUpdate.TradeTime)
|
||||||
|
if b.tradeCb != nil {
|
||||||
|
b.tradeCb(&order)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BinanceTrade) handleUserDataError(err error) {
|
||||||
|
log.Errorf("binance userdata error: %s,reconnect", err.Error())
|
||||||
|
doneC, stopC, err := bfutures.WsUserDataServe(b.wsUserListenKey, b.handleUserData, b.handleUserDataError)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-doneC:
|
||||||
|
case <-b.closeCh:
|
||||||
|
}
|
||||||
|
close(stopC)
|
||||||
|
}()
|
||||||
|
}
|
31
config.go
Normal file
31
config.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package exchange
|
||||||
|
|
||||||
|
import "github.com/spf13/viper"
|
||||||
|
|
||||||
|
type Config interface {
|
||||||
|
Get(string) interface{}
|
||||||
|
GetBool(string) bool
|
||||||
|
GetInt(string) int
|
||||||
|
GetString(string) string
|
||||||
|
UnmarshalKey(string, interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExchangeConfig struct {
|
||||||
|
Name string
|
||||||
|
ApiKey string
|
||||||
|
SecretKey string
|
||||||
|
Passphrase string
|
||||||
|
IsTest bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapViper(cfg *viper.Viper) Config {
|
||||||
|
return &ViperCfg{Viper: cfg}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ViperCfg struct {
|
||||||
|
*viper.Viper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ViperCfg) UnmarshalKey(key string, rawVal interface{}) error {
|
||||||
|
return c.Viper.UnmarshalKey(key, rawVal)
|
||||||
|
}
|
111
exchange.go
Normal file
111
exchange.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package exchange
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "git.qtrade.icu/coin-quant/trademodel"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
WatchTypeCandle = "candle"
|
||||||
|
WatchTypeDepth = "depth"
|
||||||
|
WatchTypeTradeMarket = "trade_market"
|
||||||
|
WatchTypeTrade = "trade"
|
||||||
|
WatchTypePosition = "position"
|
||||||
|
WatchTypeBalance = "balance"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrRetry = errors.New("need retry")
|
||||||
|
)
|
||||||
|
|
||||||
|
type WatchParam struct {
|
||||||
|
Type string
|
||||||
|
Param map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func WatchCandle(symbol, binSize string) WatchParam {
|
||||||
|
return WatchParam{
|
||||||
|
Type: WatchTypeCandle,
|
||||||
|
Param: map[string]string{"symbol": symbol, "bin": binSize},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type WatchFn func(interface{})
|
||||||
|
|
||||||
|
type FetchLimit struct {
|
||||||
|
Duration string
|
||||||
|
Limit int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExchangeInfo exchange info
|
||||||
|
type ExchangeInfo struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
Desc string
|
||||||
|
KLineLimit FetchLimit
|
||||||
|
OrderLimit FetchLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
type Exchange interface {
|
||||||
|
Info() ExchangeInfo
|
||||||
|
Symbols() ([]Symbol, error)
|
||||||
|
|
||||||
|
Start() error
|
||||||
|
Stop() error
|
||||||
|
|
||||||
|
// Watch exchange event, call multiple times for different event
|
||||||
|
Watch(WatchParam, WatchFn) error
|
||||||
|
|
||||||
|
// Kline get klines
|
||||||
|
GetKline(symbol, bSize string, start, end time.Time) (data []*Candle, err error)
|
||||||
|
|
||||||
|
// for trade
|
||||||
|
ProcessOrder(act TradeAction) (ret *Order, err error)
|
||||||
|
CancelAllOrders() (orders []*Order, err error)
|
||||||
|
CancelOrder(old *Order) (orders *Order, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func KlineChan(e Exchange, symbol, bSize string, start, end time.Time) (dataCh chan *Candle, errCh chan error) {
|
||||||
|
dataCh = make(chan *Candle, 1024*10)
|
||||||
|
errCh = make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
close(dataCh)
|
||||||
|
close(errCh)
|
||||||
|
}()
|
||||||
|
tStart := start
|
||||||
|
tEnd := end
|
||||||
|
var nPrevStart int64
|
||||||
|
for {
|
||||||
|
klines, err := e.GetKline(symbol, bSize, tStart, tEnd)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrRetry) {
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sort.Slice(klines, func(i, j int) bool {
|
||||||
|
return klines[i].Start < klines[j].Start
|
||||||
|
})
|
||||||
|
for _, v := range klines {
|
||||||
|
if v.Start <= nPrevStart {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dataCh <- v
|
||||||
|
tStart = v.Time()
|
||||||
|
}
|
||||||
|
if tStart.Sub(tEnd) >= 0 || tStart.Unix() <= nPrevStart || len(klines) == 0 {
|
||||||
|
logrus.Info("KlineChan finished: [%s]-[%s], last datatime: %s", start, end, tStart)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nPrevStart = tStart.Unix()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return
|
||||||
|
}
|
42
go.mod
Normal file
42
go.mod
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
module git.qtrade.icu/coin-quant/exchange
|
||||||
|
|
||||||
|
go 1.22.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.qtrade.icu/coin-quant/trademodel v0.0.0-20240625151548-cef4b6fc28b9
|
||||||
|
github.com/adshao/go-binance/v2 v2.4.5
|
||||||
|
github.com/deepmap/oapi-codegen v1.10.1
|
||||||
|
github.com/getkin/kin-openapi v0.94.0
|
||||||
|
github.com/gorilla/websocket v1.5.0
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
|
github.com/sirupsen/logrus v1.9.0
|
||||||
|
github.com/spf13/viper v1.11.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bitly/go-simplejson v0.5.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||||
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
github.com/go-openapi/swag v0.21.1 // indirect
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect
|
||||||
|
github.com/spf13/afero v1.8.2 // indirect
|
||||||
|
github.com/spf13/cast v1.4.1 // indirect
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/subosito/gotenv v1.2.0 // indirect
|
||||||
|
golang.org/x/sys v0.2.0 // indirect
|
||||||
|
golang.org/x/text v0.4.0 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
610
go.sum
Normal file
610
go.sum
Normal file
|
@ -0,0 +1,610 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||||
|
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||||
|
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||||
|
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||||
|
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||||
|
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||||
|
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||||
|
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||||
|
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||||
|
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||||
|
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||||
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||||
|
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||||
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
|
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||||
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
|
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
|
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
git.qtrade.icu/coin-quant/trademodel v0.0.0-20240625151548-cef4b6fc28b9 h1:9T1u+MzfbG9jZU1wzDtmBoOwN1m/fRX0iX7NbLwAHgU=
|
||||||
|
git.qtrade.icu/coin-quant/trademodel v0.0.0-20240625151548-cef4b6fc28b9/go.mod h1:SZnI+IqcRlKVcDSS++NIgthZX4GG1OU4UG+RDrSOD34=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/adshao/go-binance/v2 v2.4.5 h1:V3KpolmS9a7TLVECSrl2gYm+GGBSxhVk9ILaxvOTOVw=
|
||||||
|
github.com/adshao/go-binance/v2 v2.4.5/go.mod h1:41Up2dG4NfMXpCldrDPETEtiOq+pHoGsFZ73xGgaumo=
|
||||||
|
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
|
||||||
|
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||||
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||||
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||||
|
github.com/deepmap/oapi-codegen v1.10.1 h1:xybuJUR6D8l7P+LAuxOm5SD7nTlFKHWvOPl31q+DDVs=
|
||||||
|
github.com/deepmap/oapi-codegen v1.10.1/go.mod h1:TvVmDQlUkFli9gFij/gtW1o+tFBr4qCHyv2zG+R0YZY=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||||
|
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||||
|
github.com/getkin/kin-openapi v0.94.0 h1:bAxg2vxgnHHHoeefVdmGbR+oxtJlcv5HsJJa3qmAHuo=
|
||||||
|
github.com/getkin/kin-openapi v0.94.0/go.mod h1:LWZfzOd7PRy8GJ1dJ6mCU6tNdSfOwRac1BUPam4aw6Q=
|
||||||
|
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
|
github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
|
||||||
|
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
|
github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||||
|
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
|
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||||
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
|
||||||
|
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||||
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
|
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||||
|
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
|
||||||
|
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||||
|
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||||
|
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||||
|
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
||||||
|
github.com/lestrrat-go/jwx v1.2.23/go.mod h1:sAXjRwzSvCN6soO4RLoWWm1bVPpb8iOuv0IYfH8OWd8=
|
||||||
|
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||||
|
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||||
|
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk=
|
||||||
|
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 h1:dy81yyLYJDwMTifq24Oi/IslOslRrDSb3jwDggjz3Z0=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
|
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||||
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
|
||||||
|
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
|
||||||
|
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||||
|
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44=
|
||||||
|
github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||||
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
|
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||||
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
|
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||||
|
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||||
|
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||||
|
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||||
|
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||||
|
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||||
|
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||||
|
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
|
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||||
|
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
|
||||||
|
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
6
include/all.go
Normal file
6
include/all.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "git.qtrade.icu/coin-quant/exchange/binance"
|
||||||
|
_ "git.qtrade.icu/coin-quant/exchange/okex"
|
||||||
|
)
|
3324
okex/api/account/account.gen.go
Normal file
3324
okex/api/account/account.gen.go
Normal file
File diff suppressed because it is too large
Load Diff
2086
okex/api/market/market.gen.go
Normal file
2086
okex/api/market/market.gen.go
Normal file
File diff suppressed because it is too large
Load Diff
25
okex/api/market/market_test.go
Normal file
25
okex/api/market/market_test.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package market
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCandles(t *testing.T) {
|
||||||
|
ApiAddr := "https://www.okex.com/"
|
||||||
|
api, err := NewClientWithResponses(ApiAddr)
|
||||||
|
tEnd := time.Now().Add(-time.Hour * 24 * 365)
|
||||||
|
tStart := tEnd.Add(-time.Hour)
|
||||||
|
startStr := strconv.FormatInt((tStart.Unix() * 1000), 10)
|
||||||
|
endStr := strconv.FormatInt((tEnd.Unix() * 1000), 10)
|
||||||
|
bSize := "1m"
|
||||||
|
var params = GetApiV5MarketHistoryCandlesParams{InstId: "ETH-USDT-SWAP", Bar: &bSize, After: &endStr, Before: &startStr}
|
||||||
|
resp, err := api.GetApiV5MarketHistoryCandlesWithResponse(context.Background(), ¶ms)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
|
||||||
|
}
|
||||||
|
t.Log(string(resp.Body), resp.JSON200)
|
||||||
|
}
|
2857
okex/api/public/public.gen.go
Normal file
2857
okex/api/public/public.gen.go
Normal file
File diff suppressed because it is too large
Load Diff
3877
okex/api/trade/trade.gen.go
Normal file
3877
okex/api/trade/trade.gen.go
Normal file
File diff suppressed because it is too large
Load Diff
290
okex/def.go
Normal file
290
okex/def.go
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
package okex
|
||||||
|
|
||||||
|
type OKEXConfig struct {
|
||||||
|
Name string
|
||||||
|
ApiKey string
|
||||||
|
SecretKey string
|
||||||
|
Passphrase string
|
||||||
|
TdMode string
|
||||||
|
IsTest bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlgoOrder struct {
|
||||||
|
ActualPx string `json:"actualPx"`
|
||||||
|
ActualSide string `json:"actualSide"`
|
||||||
|
ActualSz string `json:"actualSz"`
|
||||||
|
AlgoID string `json:"algoId"`
|
||||||
|
CTime string `json:"cTime"`
|
||||||
|
Ccy string `json:"ccy"`
|
||||||
|
InstID string `json:"instId"`
|
||||||
|
InstType string `json:"instType"`
|
||||||
|
Lever string `json:"lever"`
|
||||||
|
NotionalUsd string `json:"notionalUsd"`
|
||||||
|
OrdID string `json:"ordId"`
|
||||||
|
OrdPx string `json:"ordPx"`
|
||||||
|
OrdType string `json:"ordType"`
|
||||||
|
PosSide string `json:"posSide"`
|
||||||
|
ReduceOnly string `json:"reduceOnly"`
|
||||||
|
Side string `json:"side"`
|
||||||
|
SlOrdPx string `json:"slOrdPx"`
|
||||||
|
SlTriggerPx string `json:"slTriggerPx"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Sz string `json:"sz"`
|
||||||
|
TdMode string `json:"tdMode"`
|
||||||
|
TgtCcy string `json:"tgtCcy"`
|
||||||
|
TpOrdPx string `json:"tpOrdPx"`
|
||||||
|
TpTriggerPx string `json:"tpTriggerPx"`
|
||||||
|
TriggerPx string `json:"triggerPx"`
|
||||||
|
TriggerTime string `json:"triggerTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderNormal struct {
|
||||||
|
AccFillSz string `json:"accFillSz"`
|
||||||
|
AmendResult string `json:"amendResult"`
|
||||||
|
AvgPx string `json:"avgPx"`
|
||||||
|
CTime string `json:"cTime"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Ccy string `json:"ccy"`
|
||||||
|
ClOrdID string `json:"clOrdId"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
ExecType string `json:"execType"`
|
||||||
|
Fee string `json:"fee"`
|
||||||
|
FeeCcy string `json:"feeCcy"`
|
||||||
|
FillFee string `json:"fillFee"`
|
||||||
|
FillFeeCcy string `json:"fillFeeCcy"`
|
||||||
|
FillNotionalUsd string `json:"fillNotionalUsd"`
|
||||||
|
FillPx string `json:"fillPx"`
|
||||||
|
FillSz string `json:"fillSz"`
|
||||||
|
FillTime string `json:"fillTime"`
|
||||||
|
InstID string `json:"instId"`
|
||||||
|
InstType string `json:"instType"`
|
||||||
|
Lever string `json:"lever"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
NotionalUsd string `json:"notionalUsd"`
|
||||||
|
OrdID string `json:"ordId"`
|
||||||
|
OrdType string `json:"ordType"`
|
||||||
|
Pnl string `json:"pnl"`
|
||||||
|
PosSide string `json:"posSide"`
|
||||||
|
Px string `json:"px"`
|
||||||
|
Rebate string `json:"rebate"`
|
||||||
|
RebateCcy string `json:"rebateCcy"`
|
||||||
|
ReduceOnly string `json:"reduceOnly"`
|
||||||
|
ReqID string `json:"reqId"`
|
||||||
|
Side string `json:"side"`
|
||||||
|
SlOrdPx string `json:"slOrdPx"`
|
||||||
|
SlTriggerPx string `json:"slTriggerPx"`
|
||||||
|
SlTriggerPxType string `json:"slTriggerPxType"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Sz string `json:"sz"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
TdMode string `json:"tdMode"`
|
||||||
|
TgtCcy string `json:"tgtCcy"`
|
||||||
|
TpOrdPx string `json:"tpOrdPx"`
|
||||||
|
TpTriggerPx string `json:"tpTriggerPx"`
|
||||||
|
TpTriggerPxType string `json:"tpTriggerPxType"`
|
||||||
|
TradeID string `json:"tradeId"`
|
||||||
|
UTime string `json:"uTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CandleResp struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data [][9]string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OKEXOrder struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data []struct {
|
||||||
|
ClOrdID string `json:"clOrdId"`
|
||||||
|
OrdID string `json:"ordId"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
SCode string `json:"sCode"`
|
||||||
|
SMsg string `json:"sMsg"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OKEXAlgoOrder struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data []struct {
|
||||||
|
AlgoID string `json:"algoId"`
|
||||||
|
SCode string `json:"sCode"`
|
||||||
|
SMsg string `json:"sMsg"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstrumentResp struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data []Instrument `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Instrument struct {
|
||||||
|
InstType string `json:"instType"` // 产品类型
|
||||||
|
InstID string `json:"instId"` // 产品id, 如 BTC-USD-SWAP
|
||||||
|
Uly string `json:"uly"` // 标的指数,如 BTC-USD,仅适用于交割/永续/期权
|
||||||
|
Category string `json:"category"` // 手续费档位,每个交易产品属于哪个档位手续费
|
||||||
|
BaseCcy string `json:"baseCcy"` // 交易货币币种,如 BTC-USDT 中的 BTC ,仅适用于币币
|
||||||
|
QuoteCcy string `json:"quoteCcy"` // 计价货币币种,如 BTC-USDT 中的USDT ,仅适用于币币
|
||||||
|
SettleCcy string `json:"settleCcy"` // 盈亏结算和保证金币种,如 BTC 仅适用于交割/永续/期权
|
||||||
|
CtVal string `json:"ctVal"` // 合约面值,仅适用于交割/永续/期权
|
||||||
|
CtMult string `json:"ctMult"` // 合约乘数,仅适用于交割/永续/期权
|
||||||
|
CtValCcy string `json:"ctValCcy"` // 合约面值计价币种,仅适用于交割/永续/期权
|
||||||
|
OptType string `json:"optType"` // 期权类型,C或P 仅适用于期权
|
||||||
|
Stk string `json:"stk"` // 行权价格,仅适用于期权
|
||||||
|
ListTime string `json:"listTime"` // 上线日期 Unix时间戳的毫秒数格式,如 1597026383085
|
||||||
|
ExpTime string `json:"expTime"` // 交割/行权日期,仅适用于交割 和 期权 Unix时间戳的毫秒数格式,如 1597026383085
|
||||||
|
Lever string `json:"lever"` // 该instId支持的最大杠杆倍数,不适用于币币、期权
|
||||||
|
TickSz string `json:"tickSz"` // 下单价格精度,如 0.0001
|
||||||
|
LotSz string `json:"lotSz"` // 下单数量精度,如 BTC-USDT-SWAP:1
|
||||||
|
MinSz string `json:"minSz"` // 最小下单数量
|
||||||
|
CtType string `json:"ctType"` // linear:正向合约 inverse:反向合约 仅适用于交割/永续
|
||||||
|
Alias string `json:"alias"` // 合约日期别名 this_week:本周 next_week:次周 quarter:季度 next_quarter:次季度 仅适用于交割
|
||||||
|
State string `json:"state"` // 产品状态 live:交易中 suspend:暂停中 preopen:预上线settlement:资金费结算
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountConfig struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Data []struct {
|
||||||
|
AcctLv string `json:"acctLv"`
|
||||||
|
AutoLoan bool `json:"autoLoan"`
|
||||||
|
CtIsoMode string `json:"ctIsoMode"`
|
||||||
|
GreeksType string `json:"greeksType"`
|
||||||
|
Level string `json:"level"`
|
||||||
|
LevelTmp string `json:"levelTmp"`
|
||||||
|
MgnIsoMode string `json:"mgnIsoMode"`
|
||||||
|
PosMode string `json:"posMode"`
|
||||||
|
SpotOffsetType string `json:"spotOffsetType"`
|
||||||
|
UID string `json:"uid"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
RoleType string `json:"roleType"`
|
||||||
|
TraderInsts []any `json:"traderInsts"`
|
||||||
|
OpAuth string `json:"opAuth"`
|
||||||
|
IP string `json:"ip"`
|
||||||
|
} `json:"data"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountPosition 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"`
|
||||||
|
UsdPx string `json:"usdPx"`
|
||||||
|
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"`
|
||||||
|
SpotInUseAmt string `json:"spotInUseAmt"`
|
||||||
|
SpotInUseCcy string `json:"spotInUseCcy"`
|
||||||
|
ThetaBS string `json:"thetaBS"`
|
||||||
|
ThetaPA string `json:"thetaPA"`
|
||||||
|
TradeID string `json:"tradeId"`
|
||||||
|
BizRefID string `json:"bizRefId"`
|
||||||
|
BizRefType string `json:"bizRefType"`
|
||||||
|
QuoteBal string `json:"quoteBal"`
|
||||||
|
BaseBal string `json:"baseBal"`
|
||||||
|
BaseBorrowed string `json:"baseBorrowed"`
|
||||||
|
BaseInterest string `json:"baseInterest"`
|
||||||
|
QuoteBorrowed string `json:"quoteBorrowed"`
|
||||||
|
QuoteInterest string `json:"quoteInterest"`
|
||||||
|
UTime string `json:"uTime"`
|
||||||
|
Upl string `json:"upl"`
|
||||||
|
UplRatio string `json:"uplRatio"`
|
||||||
|
VegaBS string `json:"vegaBS"`
|
||||||
|
VegaPA string `json:"vegaPA"`
|
||||||
|
CloseOrderAlgo []struct {
|
||||||
|
AlgoID string `json:"algoId"`
|
||||||
|
SlTriggerPx string `json:"slTriggerPx"`
|
||||||
|
SlTriggerPxType string `json:"slTriggerPxType"`
|
||||||
|
TpTriggerPx string `json:"tpTriggerPx"`
|
||||||
|
TpTriggerPxType string `json:"tpTriggerPxType"`
|
||||||
|
CloseFraction string `json:"closeFraction"`
|
||||||
|
} `json:"closeOrderAlgo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountPositionResp struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data []AccountPosition `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountBalance struct {
|
||||||
|
AdjEq string `json:"adjEq"`
|
||||||
|
Details []struct {
|
||||||
|
AvailBal string `json:"availBal"`
|
||||||
|
AvailEq string `json:"availEq"`
|
||||||
|
CashBal string `json:"cashBal"`
|
||||||
|
Ccy string `json:"ccy"`
|
||||||
|
CrossLiab string `json:"crossLiab"`
|
||||||
|
DisEq string `json:"disEq"`
|
||||||
|
Eq string `json:"eq"`
|
||||||
|
EqUsd string `json:"eqUsd"`
|
||||||
|
FrozenBal string `json:"frozenBal"`
|
||||||
|
Interest string `json:"interest"`
|
||||||
|
IsoEq string `json:"isoEq"`
|
||||||
|
IsoLiab string `json:"isoLiab"`
|
||||||
|
IsoUpl string `json:"isoUpl"`
|
||||||
|
Liab string `json:"liab"`
|
||||||
|
MaxLoan string `json:"maxLoan"`
|
||||||
|
MgnRatio string `json:"mgnRatio"`
|
||||||
|
NotionalLever string `json:"notionalLever"`
|
||||||
|
OrdFrozen string `json:"ordFrozen"`
|
||||||
|
Twap string `json:"twap"`
|
||||||
|
UTime string `json:"uTime"`
|
||||||
|
Upl string `json:"upl"`
|
||||||
|
UplLiab string `json:"uplLiab"`
|
||||||
|
StgyEq string `json:"stgyEq"`
|
||||||
|
SpotInUseAmt string `json:"spotInUseAmt"`
|
||||||
|
} `json:"details"`
|
||||||
|
Imr string `json:"imr"`
|
||||||
|
IsoEq string `json:"isoEq"`
|
||||||
|
MgnRatio string `json:"mgnRatio"`
|
||||||
|
Mmr string `json:"mmr"`
|
||||||
|
NotionalUsd string `json:"notionalUsd"`
|
||||||
|
OrdFroz string `json:"ordFroz"`
|
||||||
|
TotalEq string `json:"totalEq"`
|
||||||
|
UTime string `json:"uTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountBalanceResp struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Data []AccountBalance `json:"data"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CancelNormalResp struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data []OrderNormal `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CancelAlgoResp struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data []AlgoOrder `json:"data"`
|
||||||
|
}
|
1390
okex/docs/account.json
Normal file
1390
okex/docs/account.json
Normal file
File diff suppressed because it is too large
Load Diff
804
okex/docs/market.json
Normal file
804
okex/docs/market.json
Normal file
|
@ -0,0 +1,804 @@
|
||||||
|
{
|
||||||
|
"openapi": "3.0.1",
|
||||||
|
"info": {
|
||||||
|
"title": "REST API",
|
||||||
|
"version": "v5",
|
||||||
|
"description": "# 使用说明 \n <b>接口只会请求模拟环境</b><br><br>*Parameters* 面板中点击`Try it out`按钮,编辑请求参数,点击`Execute`按钮发送请求。*Responses* 面板中查看请求结果。<br>"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "Market Data",
|
||||||
|
"description": "行情数据"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/api/v5/market/tickers": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Market Data"
|
||||||
|
],
|
||||||
|
"summary": "获取所有产品行情信息",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "instType",
|
||||||
|
"in": "query",
|
||||||
|
"description": "产品类型<br>`SPOT`:币币<br>`SWAP`:永续合约<br>`FUTURES`:交割合约<br>`OPTION`:期权",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"example": "SPOT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "uly",
|
||||||
|
"in": "query",
|
||||||
|
"description": "合约标的指数<br>仅适用于`交割/永续/期权`,如 `BTC-USD`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"code": "0",
|
||||||
|
"msg": "",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"instType": "SWAP",
|
||||||
|
"instId": "LTC-USD-SWAP",
|
||||||
|
"last": "9999.99",
|
||||||
|
"lastSz": "0.1",
|
||||||
|
"askPx": "9999.99",
|
||||||
|
"askSz": "11",
|
||||||
|
"bidPx": "8888.88",
|
||||||
|
"bidSz": "5",
|
||||||
|
"open24h": "9000",
|
||||||
|
"high24h": "10000",
|
||||||
|
"low24h": "8888.88",
|
||||||
|
"volCcy24h": "2222",
|
||||||
|
"vol24h": "2222",
|
||||||
|
"sodUtc0": "0.1",
|
||||||
|
"sodUtc8": "0.1",
|
||||||
|
"ts": "1597026383085"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v5/market/ticker": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Market Data"
|
||||||
|
],
|
||||||
|
"summary": "获取单个产品行情信息",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "instId",
|
||||||
|
"in": "query",
|
||||||
|
"description": "产品ID,如:`BTC-USD-SWAP`",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"example": "BTC-USD-SWAP"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"code": "0",
|
||||||
|
"msg": "",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"instType": "SWAP",
|
||||||
|
"instId": "BTC-USD-SWAP",
|
||||||
|
"last": "56956.1",
|
||||||
|
"lastSz": "3",
|
||||||
|
"askPx": "56959.1",
|
||||||
|
"askSz": "10582",
|
||||||
|
"bidPx": "56959",
|
||||||
|
"bidSz": "4552",
|
||||||
|
"open24h": "55926",
|
||||||
|
"high24h": "57641.1",
|
||||||
|
"low24h": "54570.1",
|
||||||
|
"volCcy24h": "81137.755",
|
||||||
|
"vol24h": "46258703",
|
||||||
|
"ts": "1620289117764",
|
||||||
|
"sodUtc0": "55926",
|
||||||
|
"sodUtc8": "55926"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v5/market/index-tickers": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Market Data"
|
||||||
|
],
|
||||||
|
"summary": "获取指数行情",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "instId",
|
||||||
|
"in": "query",
|
||||||
|
"description": "指数,如:`BTC-USD`<br>`instId`和`quoteCcy`必须填写一个",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"example": "BTC-USD"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "quoteCcy",
|
||||||
|
"in": "query",
|
||||||
|
"description": "指数计价单位<br>目前只有`USD`/`USDT`/`BTC`为计价单位的指数",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"code": "0",
|
||||||
|
"msg": "",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"instId": "BTC-USDT",
|
||||||
|
"idxPx": "0.1",
|
||||||
|
"high24h": "0.5",
|
||||||
|
"low24h": "0.1",
|
||||||
|
"open24h": "0.1",
|
||||||
|
"sodUtc0": "0.1",
|
||||||
|
"sodUtc8": "0.1",
|
||||||
|
"ts": "1597026383085"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v5/market/books": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Market Data"
|
||||||
|
],
|
||||||
|
"summary": "获取产品深度",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "instId",
|
||||||
|
"in": "query",
|
||||||
|
"description": "产品ID,如:`BTC-USDT`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"example": "BTC-USDT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sz",
|
||||||
|
"in": "query",
|
||||||
|
"description": "深度档位数量<br>最大值可传400,即买卖深度共800条<br>不填写此参数,默认返回1档深度数据",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"code": "0",
|
||||||
|
"msg": "",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"asks": [
|
||||||
|
[
|
||||||
|
"41006.8",
|
||||||
|
"0.60038921",
|
||||||
|
"0",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"bids": [
|
||||||
|
[
|
||||||
|
"41006.3",
|
||||||
|
"0.30178218",
|
||||||
|
"0",
|
||||||
|
"2"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"ts": "1629966436396"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v5/market/candles": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Market Data"
|
||||||
|
],
|
||||||
|
"summary": "获取所有交易产品K线数据",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "instId",
|
||||||
|
"in": "query",
|
||||||
|
"description": "产品ID,如:`BTC-USDT`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"example": "BTC-USDT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bar",
|
||||||
|
"in": "query",
|
||||||
|
"description": "时间粒度,默认值`1m`<br>如 [1m/3m/5m/15m/30m/1H/2H/4H/6H/12H/1D/1W/1M/3M/6M/1Y]",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "after",
|
||||||
|
"in": "query",
|
||||||
|
"description": "请求此时间戳之前(更旧的数据)的分页内容,传的值为对应接口的`ts`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "before",
|
||||||
|
"in": "query",
|
||||||
|
"description": "请求此时间戳之后(更新的数据)的分页内容,传的值为对应接口的`ts`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query",
|
||||||
|
"description": "分页返回的结果集数量,最大为100,不填默认返回100条",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"code": "0",
|
||||||
|
"msg": "",
|
||||||
|
"data": [
|
||||||
|
[
|
||||||
|
"1597026383085",
|
||||||
|
"3.721",
|
||||||
|
"3.743",
|
||||||
|
"3.677",
|
||||||
|
"3.708",
|
||||||
|
"8422410",
|
||||||
|
"22698348.04828491"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"1597026383085",
|
||||||
|
"3.731",
|
||||||
|
"3.799",
|
||||||
|
"3.494",
|
||||||
|
"3.72",
|
||||||
|
"24912403",
|
||||||
|
"67632347.24399722"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v5/market/history-candles": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Market Data"
|
||||||
|
],
|
||||||
|
"summary": "获取交易产品历史K线数据(仅主流币)",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "instId",
|
||||||
|
"in": "query",
|
||||||
|
"description": "产品ID,如:`BTC-USDT`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"example": "BTC-USDT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bar",
|
||||||
|
"in": "query",
|
||||||
|
"description": "时间粒度,默认值`1m`<br>如 [1m/3m/5m/15m/30m/1H/2H/4H/6H/12H/1D/1W/1M/3M/6M/1Y]",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "after",
|
||||||
|
"in": "query",
|
||||||
|
"description": "请求此时间戳之前(更旧的数据)的分页内容,传的值为对应接口的`ts`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "before",
|
||||||
|
"in": "query",
|
||||||
|
"description": "请求此时间戳之后(更新的数据)的分页内容,传的值为对应接口的`ts`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query",
|
||||||
|
"description": "分页返回的结果集数量,最大为100,不填默认返回100条",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"code": "0",
|
||||||
|
"msg": "",
|
||||||
|
"data": [
|
||||||
|
[
|
||||||
|
"1597026383085",
|
||||||
|
"3.721",
|
||||||
|
"3.743",
|
||||||
|
"3.677",
|
||||||
|
"3.708",
|
||||||
|
"8422410",
|
||||||
|
"22698348.04828491"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"1597026383085",
|
||||||
|
"3.731",
|
||||||
|
"3.799",
|
||||||
|
"3.494",
|
||||||
|
"3.72",
|
||||||
|
"24912403",
|
||||||
|
"67632347.24399722"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v5/market/index-candles": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Market Data"
|
||||||
|
],
|
||||||
|
"summary": "获取指数K线数据",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "instId",
|
||||||
|
"in": "query",
|
||||||
|
"description": "现货指数,如:`BTC-USD`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"example": "BTC-USDT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bar",
|
||||||
|
"in": "query",
|
||||||
|
"description": "时间粒度,默认值`1m`<br>如 [1m/3m/5m/15m/30m/1H/2H/4H/6H/12H/1D/1W/1M/3M/6M/1Y]",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "after",
|
||||||
|
"in": "query",
|
||||||
|
"description": "请求此时间戳之前(更旧的数据)的分页内容,传的值为对应接口的`ts`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "before",
|
||||||
|
"in": "query",
|
||||||
|
"description": "请求此时间戳之后(更新的数据)的分页内容,传的值为对应接口的`ts`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query",
|
||||||
|
"description": "分页返回的结果集数量,最大为100,不填默认返回100条",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"code": "0",
|
||||||
|
"msg": "",
|
||||||
|
"data": [
|
||||||
|
[
|
||||||
|
"1597026383085",
|
||||||
|
"3.721",
|
||||||
|
"3.743",
|
||||||
|
"3.677",
|
||||||
|
"3.708",
|
||||||
|
"8422410",
|
||||||
|
"22698348.04828491"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"1597026383085",
|
||||||
|
"3.731",
|
||||||
|
"3.799",
|
||||||
|
"3.494",
|
||||||
|
"3.72",
|
||||||
|
"24912403",
|
||||||
|
"67632347.24399722"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v5/market/mark-price-candles": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Market Data"
|
||||||
|
],
|
||||||
|
"summary": "获取标记价格K线数据",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "instId",
|
||||||
|
"in": "query",
|
||||||
|
"description": "现货指数,如:`BTC-USD-SWAP`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"example": "BTC-USD-SWAP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bar",
|
||||||
|
"in": "query",
|
||||||
|
"description": "时间粒度,默认值`1m`<br>如 [1m/3m/5m/15m/30m/1H/2H/4H/6H/12H/1D/1W/1M/3M/6M/1Y]",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "after",
|
||||||
|
"in": "query",
|
||||||
|
"description": "请求此时间戳之前(更旧的数据)的分页内容,传的值为对应接口的`ts`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "before",
|
||||||
|
"in": "query",
|
||||||
|
"description": "请求此时间戳之后(更新的数据)的分页内容,传的值为对应接口的`ts`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query",
|
||||||
|
"description": "分页返回的结果集数量,最大为100,不填默认返回100条",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"code": "0",
|
||||||
|
"msg": "",
|
||||||
|
"data": [
|
||||||
|
[
|
||||||
|
"1597026383085",
|
||||||
|
"3.721",
|
||||||
|
"3.743",
|
||||||
|
"3.677",
|
||||||
|
"3.708"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"1597026383085",
|
||||||
|
"3.731",
|
||||||
|
"3.799",
|
||||||
|
"3.494",
|
||||||
|
"3.72"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v5/market/trades": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Market Data"
|
||||||
|
],
|
||||||
|
"summary": "获取交易产品公共成交数据",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "instId",
|
||||||
|
"in": "query",
|
||||||
|
"description": "产品ID,如:`BTC-USDT`",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"example": "BTC-USDT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query",
|
||||||
|
"description": "分页返回的结果集数量,最大为500,不填默认返回100条",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"code": "0",
|
||||||
|
"msg": "",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"instId": "BTC-USDT",
|
||||||
|
"tradeId": "9",
|
||||||
|
"px": "0.016",
|
||||||
|
"sz": "50",
|
||||||
|
"side": "buy",
|
||||||
|
"ts": "1597026383085"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"instId": "BTC-USDT",
|
||||||
|
"tradeId": "9",
|
||||||
|
"px": "0.016",
|
||||||
|
"sz": "50",
|
||||||
|
"side": "buy",
|
||||||
|
"ts": "1597026383085"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v5/market/platform-24-volume": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Market Data"
|
||||||
|
],
|
||||||
|
"summary": "获取平台24小时总成交量",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"code": "0",
|
||||||
|
"msg": "",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"volUsd": "2222",
|
||||||
|
"volCny": "14220.8"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v5/market/open-oracle": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Market Data"
|
||||||
|
],
|
||||||
|
"summary": "Oracle上链交易数据",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"code": "0",
|
||||||
|
"msg": "",
|
||||||
|
"data": {
|
||||||
|
"messages": [
|
||||||
|
"0x00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000060d98cc000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000081a06ed800000000000000000000000000000000000000000000000000000000000000006707269636573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034254430000000000000000000000000000000000000000000000000000000000"
|
||||||
|
],
|
||||||
|
"prices": {
|
||||||
|
"BTC": "34796.4"
|
||||||
|
},
|
||||||
|
"signatures": [
|
||||||
|
"0xa8124d0dd7a6cd46aafc752272d2e67b09f0abb0f759c55712cf0c100e5ed6ad25853d97fd691c47539eac08d7e7b0ce3f6d1e8f6fa15850d8099718d37af376000000000000000000000000000000000000000000000000000000000000001c"
|
||||||
|
],
|
||||||
|
"timestamp": "1624870080"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v5/market/index-components": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Market Data"
|
||||||
|
],
|
||||||
|
"summary": "获取指数成分数据",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "index",
|
||||||
|
"in": "query",
|
||||||
|
"description": "指数,如:`BTC-USDT`",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"example": "BTC-USDT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"example": {
|
||||||
|
"code": "0",
|
||||||
|
"msg": "",
|
||||||
|
"data": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"symbol": "BTC/USDT",
|
||||||
|
"symPx": "52733.2",
|
||||||
|
"wgt": "0.250",
|
||||||
|
"cnvPx": "52733.2",
|
||||||
|
"exch": "OKEx"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"symbol": "BTC/USDT",
|
||||||
|
"symPx": "52739.87000000",
|
||||||
|
"wgt": "0.250",
|
||||||
|
"cnvPx": "52739.87000000",
|
||||||
|
"exch": "Binance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"symbol": "BTC/USDT",
|
||||||
|
"symPx": "52729.1",
|
||||||
|
"wgt": "0.250",
|
||||||
|
"cnvPx": "52729.1",
|
||||||
|
"exch": "Huobi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"symbol": "BTC/USDT",
|
||||||
|
"symPx": "52739.47929397",
|
||||||
|
"wgt": "0.250",
|
||||||
|
"cnvPx": "52739.47929397",
|
||||||
|
"exch": "Poloniex"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"last": "52735.4123234925",
|
||||||
|
"index": "BTC-USDT",
|
||||||
|
"ts": "1630985335599"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {}
|
||||||
|
}
|
1125
okex/docs/public.json
Normal file
1125
okex/docs/public.json
Normal file
File diff suppressed because it is too large
Load Diff
2015
okex/docs/trade.json
Normal file
2015
okex/docs/trade.json
Normal file
File diff suppressed because it is too large
Load Diff
4
okex/gen.sh
Normal file
4
okex/gen.sh
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
oapi-codegen -package trade -alias-types -generate types,client,spec docs/trade.json > api/trade/trade.gen.go
|
||||||
|
oapi-codegen -package account -alias-types -generate types,client,spec docs/account.json > api/account/account.gen.go
|
||||||
|
oapi-codegen -package market -alias-types -generate types,client,spec docs/market.json > api/market/market.gen.go
|
||||||
|
oapi-codegen -package public -alias-types -generate types,client,spec docs/public.json > api/public/public.gen.go
|
849
okex/okex.go
Normal file
849
okex/okex.go
Normal file
|
@ -0,0 +1,849 @@
|
||||||
|
package okex
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.qtrade.icu/coin-quant/exchange"
|
||||||
|
"git.qtrade.icu/coin-quant/exchange/okex/api/account"
|
||||||
|
"git.qtrade.icu/coin-quant/exchange/okex/api/market"
|
||||||
|
"git.qtrade.icu/coin-quant/exchange/okex/api/public"
|
||||||
|
"git.qtrade.icu/coin-quant/exchange/okex/api/trade"
|
||||||
|
"git.qtrade.icu/coin-quant/exchange/ws"
|
||||||
|
. "git.qtrade.icu/coin-quant/trademodel"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
background = context.Background()
|
||||||
|
|
||||||
|
ApiAddr = "https://www.okx.com/"
|
||||||
|
WSOkexPUbilc = "wss://wsaws.okx.com:8443/ws/v5/public"
|
||||||
|
WSOkexPrivate = "wss://wsaws.okx.com:8443/ws/v5/private"
|
||||||
|
|
||||||
|
TypeSPOT = "SPOT" //币币
|
||||||
|
TypeMARGIN = "MARGIN" // 币币杠杆
|
||||||
|
TypeSWAP = "SWAP" //永续合约
|
||||||
|
TypeFUTURES = "FUTURES" //交割合约
|
||||||
|
TypeOption = "OPTION" //期权
|
||||||
|
|
||||||
|
PosNetMode = "net_mode"
|
||||||
|
PosLongShortMode = "long_short_mode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ exchange.Exchange = &OkxTrader{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
exchange.RegisterExchange("okx", NewOkexExchange)
|
||||||
|
}
|
||||||
|
|
||||||
|
type OkxTrader struct {
|
||||||
|
Name string
|
||||||
|
tradeApi *trade.ClientWithResponses
|
||||||
|
marketApi *market.ClientWithResponses
|
||||||
|
publicApi *public.ClientWithResponses
|
||||||
|
accountApi *account.ClientWithResponses
|
||||||
|
|
||||||
|
tradeCb exchange.WatchFn
|
||||||
|
positionCb exchange.WatchFn
|
||||||
|
balanceCb exchange.WatchFn
|
||||||
|
depthCb exchange.WatchFn
|
||||||
|
tradeMarketCb exchange.WatchFn
|
||||||
|
klineCb exchange.WatchFn
|
||||||
|
|
||||||
|
closeCh chan bool
|
||||||
|
|
||||||
|
cfg *OKEXConfig
|
||||||
|
|
||||||
|
klineLimit int
|
||||||
|
wsUser *ws.WSConn
|
||||||
|
wsPublic *ws.WSConn
|
||||||
|
|
||||||
|
ordersCache sync.Map
|
||||||
|
stopOrdersCache sync.Map
|
||||||
|
|
||||||
|
posMode string
|
||||||
|
|
||||||
|
watchPublics []OPParam
|
||||||
|
|
||||||
|
instType string
|
||||||
|
timeout time.Duration
|
||||||
|
symbols map[string]Symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOkexExchange(cfg exchange.Config, cltName string) (e exchange.Exchange, err error) {
|
||||||
|
b, err := NewOkxTrader(cfg, cltName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e = b
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOkxTrader(cfg exchange.Config, cltName string) (b *OkxTrader, err error) {
|
||||||
|
b = new(OkxTrader)
|
||||||
|
b.Name = "okx"
|
||||||
|
b.instType = "SWAP"
|
||||||
|
if cltName == "" {
|
||||||
|
cltName = "okx"
|
||||||
|
}
|
||||||
|
b.symbols = make(map[string]Symbol)
|
||||||
|
b.klineLimit = 100
|
||||||
|
b.timeout = time.Second * 10
|
||||||
|
var okxCfg OKEXConfig
|
||||||
|
err = cfg.UnmarshalKey(fmt.Sprintf("exchanges.%s", cltName), &okxCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.cfg = &okxCfg
|
||||||
|
|
||||||
|
// isDebug := cfg.GetBool(fmt.Sprintf("exchanges.%s.debug", cltName))
|
||||||
|
if b.cfg.TdMode == "" {
|
||||||
|
b.cfg.TdMode = "isolated"
|
||||||
|
}
|
||||||
|
log.Infof("okex %s, tdMode: %s, isTest: %t", cltName, b.cfg.TdMode, b.cfg.IsTest)
|
||||||
|
|
||||||
|
b.closeCh = make(chan bool)
|
||||||
|
|
||||||
|
b.tradeApi, err = trade.NewClientWithResponses(ApiAddr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.marketApi, err = market.NewClientWithResponses(ApiAddr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.publicApi, err = public.NewClientWithResponses(ApiAddr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.accountApi, err = account.NewClientWithResponses(ApiAddr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientProxy := cfg.GetString("proxy")
|
||||||
|
if clientProxy != "" {
|
||||||
|
var proxyURL *url.URL
|
||||||
|
proxyURL, err = url.Parse(clientProxy)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clt := b.tradeApi.ClientInterface.(*trade.Client).Client.(*http.Client)
|
||||||
|
*clt = http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}}
|
||||||
|
clt = b.marketApi.ClientInterface.(*market.Client).Client.(*http.Client)
|
||||||
|
*clt = http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}}
|
||||||
|
clt = b.publicApi.ClientInterface.(*public.Client).Client.(*http.Client)
|
||||||
|
*clt = http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}}
|
||||||
|
clt = b.accountApi.ClientInterface.(*account.Client).Client.(*http.Client)
|
||||||
|
*clt = http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}}
|
||||||
|
websocket.DefaultDialer.Proxy = http.ProxyURL(proxyURL)
|
||||||
|
websocket.DefaultDialer.HandshakeTimeout = time.Second * 60
|
||||||
|
}
|
||||||
|
_, err = b.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if b.cfg.ApiKey != "" {
|
||||||
|
err = b.getAccountConfig()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) Info() exchange.ExchangeInfo {
|
||||||
|
info := exchange.ExchangeInfo{
|
||||||
|
Name: "okx",
|
||||||
|
Value: "okx",
|
||||||
|
Desc: "okx api",
|
||||||
|
KLineLimit: exchange.FetchLimit{
|
||||||
|
Limit: b.klineLimit,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) SetInstType(instType string) {
|
||||||
|
b.instType = instType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) auth(ctx context.Context, req *http.Request) (err error) {
|
||||||
|
var temp []byte
|
||||||
|
if req.Method != "GET" {
|
||||||
|
temp, err = io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Body.Close()
|
||||||
|
buf := bytes.NewBuffer(temp)
|
||||||
|
req.Body = io.NopCloser(buf)
|
||||||
|
} else {
|
||||||
|
if req.URL.RawQuery != "" {
|
||||||
|
temp = []byte(fmt.Sprintf("?%s", req.URL.RawQuery))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var signStr string
|
||||||
|
tmStr := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
|
||||||
|
signStr = fmt.Sprintf("%s%s%s%s", tmStr, req.Method, req.URL.Path, string(temp))
|
||||||
|
h := hmac.New(sha256.New, []byte(b.cfg.SecretKey))
|
||||||
|
h.Write([]byte(signStr))
|
||||||
|
ret := h.Sum(nil)
|
||||||
|
n := base64.StdEncoding.EncodedLen(len(ret))
|
||||||
|
dst := make([]byte, n)
|
||||||
|
base64.StdEncoding.Encode(dst, ret)
|
||||||
|
sign := string(dst)
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("OK-ACCESS-KEY", b.cfg.ApiKey)
|
||||||
|
req.Header.Set("OK-ACCESS-SIGN", sign)
|
||||||
|
|
||||||
|
req.Header.Set("OK-ACCESS-TIMESTAMP", tmStr)
|
||||||
|
req.Header.Set("OK-ACCESS-PASSPHRASE", b.cfg.Passphrase)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) Start() (err error) {
|
||||||
|
fmt.Println("start okx")
|
||||||
|
err = b.runPublic()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = b.runPrivate()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("start okx finished")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (b *OkxTrader) Stop() (err error) {
|
||||||
|
b.wsPublic.Close()
|
||||||
|
b.wsUser.Close()
|
||||||
|
close(b.closeCh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) customReq(ctx context.Context, req *http.Request) error {
|
||||||
|
if b.cfg.IsTest {
|
||||||
|
req.Header.Set("x-simulated-trading", "1")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (b *OkxTrader) getAccountConfig() (err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
resp, err := b.accountApi.GetApiV5AccountConfigWithResponse(ctx, b.auth, b.customReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var accountCfg AccountConfig
|
||||||
|
err = json.Unmarshal(resp.Body, &accountCfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if accountCfg.Code != "0" {
|
||||||
|
err = fmt.Errorf("[%s]%s", accountCfg.Code, accountCfg.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// long_short_mode:双向持仓 net_mode:单向持仓
|
||||||
|
// 仅适用交割/永续
|
||||||
|
b.posMode = accountCfg.Data[0].PosMode
|
||||||
|
log.Infof("posMode: %s", b.posMode)
|
||||||
|
if b.posMode != PosNetMode {
|
||||||
|
log.Warnf("account posmode is %s, stop order will failed if no position", b.posMode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KlineChan get klines
|
||||||
|
func (b *OkxTrader) GetKline(symbol, bSize string, start, end time.Time) (data []*Candle, err error) {
|
||||||
|
// fmt.Println("GetKline:", symbol, bSize, start, end)
|
||||||
|
|
||||||
|
nStart := start.Unix() * 1000
|
||||||
|
nEnd := end.UnixMilli()
|
||||||
|
tempEnd := nEnd
|
||||||
|
var resp *market.GetApiV5MarketHistoryCandlesResponse
|
||||||
|
var startStr, endStr string
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(background, time.Second*3)
|
||||||
|
startStr = strconv.FormatInt(nStart, 10)
|
||||||
|
tempEnd = nStart + 100*60*1000
|
||||||
|
if tempEnd > nEnd {
|
||||||
|
tempEnd = nEnd
|
||||||
|
}
|
||||||
|
endStr = strconv.FormatInt(tempEnd, 10)
|
||||||
|
var params = market.GetApiV5MarketHistoryCandlesParams{InstId: symbol, Bar: &bSize, Before: &startStr, After: &endStr}
|
||||||
|
resp, err = b.marketApi.GetApiV5MarketHistoryCandlesWithResponse(ctx, ¶ms, b.customReq)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err = parseCandles(resp)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "Requests too frequent.") {
|
||||||
|
err = fmt.Errorf("requests too frequent %w", exchange.ErrRetry)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sort.Slice(data, func(i, j int) bool {
|
||||||
|
return data[i].Start < data[j].Start
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) Watch(param exchange.WatchParam, fn exchange.WatchFn) (err error) {
|
||||||
|
symbol := param.Param["symbol"]
|
||||||
|
log.Info("okex watch:", param)
|
||||||
|
switch param.Type {
|
||||||
|
case exchange.WatchTypeCandle:
|
||||||
|
var p = OPParam{
|
||||||
|
OP: "subscribe",
|
||||||
|
Args: []interface{}{
|
||||||
|
OPArg{Channel: "candle1m", InstType: b.instType, InstID: symbol},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b.klineCb = fn
|
||||||
|
b.watchPublics = append(b.watchPublics, p)
|
||||||
|
err = b.wsPublic.WriteMsg(p)
|
||||||
|
case exchange.WatchTypeDepth:
|
||||||
|
var p = OPParam{
|
||||||
|
OP: "subscribe",
|
||||||
|
Args: []interface{}{
|
||||||
|
OPArg{Channel: "books5", InstType: b.instType, InstID: symbol},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b.depthCb = fn
|
||||||
|
b.watchPublics = append(b.watchPublics, p)
|
||||||
|
err = b.wsPublic.WriteMsg(p)
|
||||||
|
case exchange.WatchTypeTradeMarket:
|
||||||
|
var p = OPParam{
|
||||||
|
OP: "subscribe",
|
||||||
|
Args: []interface{}{
|
||||||
|
OPArg{Channel: "trades", InstType: b.instType, InstID: symbol},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b.tradeMarketCb = fn
|
||||||
|
b.watchPublics = append(b.watchPublics, p)
|
||||||
|
err = b.wsPublic.WriteMsg(p)
|
||||||
|
case exchange.WatchTypeTrade:
|
||||||
|
b.tradeCb = fn
|
||||||
|
case exchange.WatchTypePosition:
|
||||||
|
b.positionCb = fn
|
||||||
|
err = b.fetchBalanceAndPosition()
|
||||||
|
case exchange.WatchTypeBalance:
|
||||||
|
b.balanceCb = fn
|
||||||
|
err = b.fetchBalanceAndPosition()
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unknown wath param: %s", param.Type)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (b *OkxTrader) fetchBalanceAndPosition() (err error) {
|
||||||
|
err = b.fetchBalance()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = b.fetchPosition()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) fetchPosition() (err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
var params = account.GetApiV5AccountPositionsParams{InstType: &b.instType}
|
||||||
|
resp, err := b.accountApi.GetApiV5AccountPositionsWithResponse(ctx, ¶ms, b.auth, b.customReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if b.positionCb == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var data AccountPositionResp
|
||||||
|
err = json.Unmarshal(resp.Body, &data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, v := range data.Data {
|
||||||
|
var pos Position
|
||||||
|
pos.Hold = parseFloat(v.Pos)
|
||||||
|
pos.Price = parseFloat(v.AvgPx)
|
||||||
|
pos.Symbol = v.InstID
|
||||||
|
if pos.Hold > 0 {
|
||||||
|
pos.Type = Long
|
||||||
|
} else {
|
||||||
|
pos.Type = Short
|
||||||
|
}
|
||||||
|
pos.ProfitRatio = parseFloat(v.UplRatio)
|
||||||
|
if pos.Hold == 0 {
|
||||||
|
log.Warnf("fetch position return 0: %s", string(resp.Body))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b.positionCb(&pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) fetchBalance() (err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
var ccy = "USDT"
|
||||||
|
var param = account.GetApiV5AccountBalanceParams{Ccy: &ccy}
|
||||||
|
resp, err := b.accountApi.GetApiV5AccountBalanceWithResponse(ctx, ¶m, b.auth, b.customReq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if b.balanceCb == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var balance AccountBalanceResp
|
||||||
|
err = json.Unmarshal(resp.Body, &balance)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, v := range balance.Data {
|
||||||
|
for _, d := range v.Details {
|
||||||
|
if d.Ccy == ccy {
|
||||||
|
var bal Balance
|
||||||
|
bal.Available = parseFloat(d.AvailBal)
|
||||||
|
bal.Balance = parseFloat(d.CashBal)
|
||||||
|
bal.Currency = ccy
|
||||||
|
bal.Frozen = parseFloat(d.OrdFrozen)
|
||||||
|
b.balanceCb(&bal)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) processStopOrder(act TradeAction) (ret *Order, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
var side, posSide string
|
||||||
|
// open: side = posSide, close: side!=posSide
|
||||||
|
if act.Action.IsLong() {
|
||||||
|
side = "buy"
|
||||||
|
posSide = "short"
|
||||||
|
} else {
|
||||||
|
side = "sell"
|
||||||
|
posSide = "long"
|
||||||
|
}
|
||||||
|
reduceOnly := true
|
||||||
|
var orderPx = "-1"
|
||||||
|
triggerPx := fmt.Sprintf("%f", act.Price)
|
||||||
|
// PostApiV5TradeOrderAlgoJSONBody defines parameters for PostApiV5TradeOrderAlgo.
|
||||||
|
params := trade.PostApiV5TradeOrderAlgoJSONBody{
|
||||||
|
// 非必填<br>保证金币种,如:USDT<br>仅适用于单币种保证金模式下的全仓杠杆订单
|
||||||
|
// Ccy *string `json:"ccy,omitempty"`
|
||||||
|
|
||||||
|
// 必填<br>产品ID,如:`BTC-USDT`
|
||||||
|
InstId: act.Symbol,
|
||||||
|
|
||||||
|
// 必填<br>订单类型。<br>`conditional`:单向止盈止损<br>`oco`:双向止盈止损<br>`trigger`:计划委托<br>`iceberg`:冰山委托<br>`twap`:时间加权委托
|
||||||
|
OrdType: "conditional",
|
||||||
|
|
||||||
|
// 非必填<br>委托价格<br>委托价格为-1时,执行市价委托<br>适用于`计划委托`
|
||||||
|
OrderPx: &orderPx,
|
||||||
|
|
||||||
|
// 可选<br>持仓方向<br>在双向持仓模式下必填,且仅可选择 `long` 或 `short`
|
||||||
|
PosSide: &posSide,
|
||||||
|
|
||||||
|
// 非必填<br>挂单限制价<br>适用于`冰山委托`和`时间加权委托`
|
||||||
|
// PxLimit *string `json:"pxLimit,omitempty"`
|
||||||
|
|
||||||
|
// 非必填<br>距离盘口的比例价距<br>适用于`冰山委托`和`时间加权委托`
|
||||||
|
// PxSpread *string `json:"pxSpread,omitempty"`
|
||||||
|
|
||||||
|
// 非必填<br>距离盘口的比例<br>pxVar和pxSpread只能传入一个<br>适用于`冰山委托`和`时间加权委托`
|
||||||
|
// PxVar *string `json:"pxVar,omitempty"`
|
||||||
|
|
||||||
|
// 非必填<br>是否只减仓,`true` 或 `false`,默认`false`
|
||||||
|
// 仅适用于币币杠杆,以及买卖模式下的交割/永续
|
||||||
|
ReduceOnly: &reduceOnly,
|
||||||
|
|
||||||
|
// 必填<br>订单方向。买:`buy` 卖:`sell`
|
||||||
|
Side: side,
|
||||||
|
|
||||||
|
// 非必填<br>止损委托价,如果填写此参数,必须填写止损触发价<br>委托价格为-1时,执行市价止损<br>适用于`止盈止损委托`
|
||||||
|
SlOrdPx: &orderPx,
|
||||||
|
|
||||||
|
// 非必填<br>止损触发价,如果填写此参数,必须填写止损委托价<br>适用于`止盈止损委托`
|
||||||
|
SlTriggerPx: &triggerPx,
|
||||||
|
|
||||||
|
// 必填<br>委托数量
|
||||||
|
Sz: fmt.Sprintf("%d", int(act.Amount)),
|
||||||
|
|
||||||
|
// 非必填<br>单笔数量<br>适用于`冰山委托`和`时间加权委托`
|
||||||
|
// SzLimit *string `json:"szLimit,omitempty"`
|
||||||
|
|
||||||
|
// 必填<br>交易模式<br>保证金模式:`isolated`:逐仓 ;`cross`<br>全仓非保证金模式:`cash`:非保证金
|
||||||
|
TdMode: b.cfg.TdMode,
|
||||||
|
|
||||||
|
// 非必填<br>市价单委托数量的类型<br>交易货币:`base_ccy`<br>计价货币:`quote_ccy`<br>仅适用于币币订单
|
||||||
|
// TgtCcy *string `json:"tgtCcy,omitempty"`
|
||||||
|
|
||||||
|
// 非必填<br>挂单限制价<br>适用于`时间加权委托`
|
||||||
|
// TimeInterval *string `json:"timeInterval,omitempty"`
|
||||||
|
|
||||||
|
// 非必填<br>止盈委托价,如果填写此参数,必须填写止盈触发价<br>委托价格为-1时,执行市价止盈<br>适用于`止盈止损委托`
|
||||||
|
// TpOrdPx ,
|
||||||
|
|
||||||
|
// 非必填<br>止盈触发价,如果填写此参数,必须填写止盈委托价<br>适用于`止盈止损委托`
|
||||||
|
// TpTriggerPx *string `json:"tpTriggerPx,omitempty"`
|
||||||
|
|
||||||
|
// 非必填<br>计划委托触发价格<br>适用于`计划委托`
|
||||||
|
// TriggerPx *string `json:"triggerPx,omitempty"`
|
||||||
|
}
|
||||||
|
if b.posMode == PosNetMode {
|
||||||
|
params.PosSide = nil
|
||||||
|
}
|
||||||
|
resp, err := b.tradeApi.PostApiV5TradeOrderAlgoWithResponse(ctx, params, b.auth, b.customReq)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orders, err := parsePostAlgoOrders(act.Symbol, "open", side, act.Price, act.Amount, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(orders) != 1 {
|
||||||
|
err = fmt.Errorf("orders len not match: %#v", orders)
|
||||||
|
log.Warnf(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret = orders[0]
|
||||||
|
ret.Remark = "stop"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (b *OkxTrader) CancelOrder(old *Order) (order *Order, err error) {
|
||||||
|
_, ok := b.ordersCache.Load(old.OrderID)
|
||||||
|
if ok {
|
||||||
|
order, err = b.cancelNormalOrder(old)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.ordersCache.Delete(old.OrderID)
|
||||||
|
}
|
||||||
|
_, ok = b.stopOrdersCache.Load(old.OrderID)
|
||||||
|
if ok {
|
||||||
|
order, err = b.cancelAlgoOrder(old)
|
||||||
|
b.stopOrdersCache.Delete(old.OrderID)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) cancelNormalOrder(old *Order) (order *Order, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var body trade.PostApiV5TradeCancelOrderJSONRequestBody
|
||||||
|
body.InstId = old.Symbol
|
||||||
|
body.OrdId = &old.OrderID
|
||||||
|
|
||||||
|
cancelResp, err := b.tradeApi.PostApiV5TradeCancelOrderWithResponse(ctx, body, b.auth, b.customReq)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
temp := OKEXOrder{}
|
||||||
|
err = json.Unmarshal(cancelResp.Body, &temp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if temp.Code != "0" {
|
||||||
|
err = errors.New(string(cancelResp.Body))
|
||||||
|
}
|
||||||
|
order = old
|
||||||
|
if len(temp.Data) > 0 {
|
||||||
|
order.OrderID = temp.Data[0].OrdID
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) cancelAlgoOrder(old *Order) (order *Order, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, time.Second*2)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var body = make(trade.PostApiV5TradeCancelAlgosJSONBody, 1)
|
||||||
|
body[0] = trade.CancelAlgoOrder{AlgoId: old.OrderID, InstId: old.Symbol}
|
||||||
|
|
||||||
|
cancelResp, err := b.tradeApi.PostApiV5TradeCancelAlgosWithResponse(ctx, body, b.auth, b.customReq)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
temp := OKEXAlgoOrder{}
|
||||||
|
err = json.Unmarshal(cancelResp.Body, &temp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if temp.Code != "0" {
|
||||||
|
err = errors.New(string(cancelResp.Body))
|
||||||
|
}
|
||||||
|
order = old
|
||||||
|
if len(temp.Data) > 0 {
|
||||||
|
order.OrderID = temp.Data[0].AlgoID
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) ProcessOrder(act TradeAction) (ret *Order, err error) {
|
||||||
|
symbol, ok := b.symbols[act.Symbol]
|
||||||
|
if ok {
|
||||||
|
price := symbol.FixPrice(act.Price)
|
||||||
|
if price != act.Price {
|
||||||
|
log.Infof("okx change order price form %f to %f", act.Price, price)
|
||||||
|
act.Price = price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if act.Action.IsStop() {
|
||||||
|
// if no position:
|
||||||
|
// stopOrder will fail when posMode = long_short_mode
|
||||||
|
// stopOrder will success when posMode =net_mode
|
||||||
|
ret, err = b.processStopOrder(act)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.stopOrdersCache.Store(ret.OrderID, ret)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
var side, posSide, px string
|
||||||
|
if act.Action.IsLong() {
|
||||||
|
side = "buy"
|
||||||
|
if act.Action.IsOpen() {
|
||||||
|
posSide = "long"
|
||||||
|
} else {
|
||||||
|
posSide = "short"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
side = "sell"
|
||||||
|
if act.Action.IsOpen() {
|
||||||
|
posSide = "short"
|
||||||
|
} else {
|
||||||
|
posSide = "long"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ordType := "limit"
|
||||||
|
tag := "trade"
|
||||||
|
px = fmt.Sprintf("%f", act.Price)
|
||||||
|
params := trade.PostApiV5TradeOrderJSONRequestBody{
|
||||||
|
//ClOrdId *string `json:"clOrdId,omitempty"`
|
||||||
|
// 必填<br>产品ID,如:`BTC-USDT`
|
||||||
|
InstId: act.Symbol,
|
||||||
|
// 必填<br>订单类型。<br>市价单:`market`<br>限价单:`limit`<br>只做maker单:`post_only`<br>全部成交或立即取消:`fok`<br>立即成交并取消剩余:`ioc`<br>市价委托立即成交并取消剩余:`optimal_limit_ioc`(仅适用交割、永续)
|
||||||
|
OrdType: ordType,
|
||||||
|
|
||||||
|
// 可选<br>持仓方向<br>在双向持仓模式下必填,且仅可选择 `long` 或 `short`
|
||||||
|
PosSide: &posSide,
|
||||||
|
|
||||||
|
// 可选<br>委托价格<br>仅适用于`limit`、`post_only`、`fok`、`ioc`类型的订单
|
||||||
|
Px: &px,
|
||||||
|
|
||||||
|
// 非必填<br>是否只减仓,`true` 或 `false`,默认`false`<br>仅适用于币币杠杆订单
|
||||||
|
// ReduceOnly: &reduceOnly,
|
||||||
|
// 必填<br>订单方向。买:`buy` 卖:`sell`
|
||||||
|
Side: side,
|
||||||
|
// 必填<br>委托数量
|
||||||
|
Sz: fmt.Sprintf("%d", int(act.Amount)),
|
||||||
|
// 非必填<br>订单标签<br>字母(区分大小写)与数字的组合,可以是纯字母、纯数字,且长度在1-8位之间。
|
||||||
|
Tag: &tag,
|
||||||
|
// 必填<br>交易模式<br>保证金模式:`isolated`:逐仓 ;`cross`<br>全仓非保证金模式:`cash`:非保证金
|
||||||
|
TdMode: b.cfg.TdMode,
|
||||||
|
// 非必填<br>市价单委托数量的类型<br>交易货币:`base_ccy`<br>计价货币:`quote_ccy`<br>仅适用于币币订单
|
||||||
|
// TgtCcy *string `json:"tgtCcy,omitempty"`
|
||||||
|
}
|
||||||
|
if b.posMode == PosNetMode {
|
||||||
|
params.PosSide = nil
|
||||||
|
}
|
||||||
|
resp, err := b.tradeApi.PostApiV5TradeOrderWithResponse(ctx, params, b.auth, b.customReq)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
orders, err := parsePostOrders(act.Symbol, "open", side, act.Price, act.Amount, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(orders) != 1 {
|
||||||
|
err = fmt.Errorf("orders len not match: %#v", orders)
|
||||||
|
log.Warnf(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret = orders[0]
|
||||||
|
b.ordersCache.Store(ret.OrderID, ret)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) cancelAllNormal() (orders []*Order, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
instType := b.instType
|
||||||
|
var params = trade.GetApiV5TradeOrdersPendingParams{
|
||||||
|
// InstId: &b.symbol,
|
||||||
|
InstType: &instType,
|
||||||
|
}
|
||||||
|
resp, err := b.tradeApi.GetApiV5TradeOrdersPendingWithResponse(ctx, ¶ms, b.auth, b.customReq)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var orderResp CancelNormalResp
|
||||||
|
err = json.Unmarshal(resp.Body, &orderResp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if orderResp.Code != "0" {
|
||||||
|
err = errors.New(string(resp.Body))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(orderResp.Data) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var body trade.PostApiV5TradeCancelBatchOrdersJSONRequestBody
|
||||||
|
for _, v := range orderResp.Data {
|
||||||
|
temp := v.OrdID
|
||||||
|
body = append(body, trade.CancelBatchOrder{
|
||||||
|
InstId: v.InstID,
|
||||||
|
OrdId: &temp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelResp, err := b.tradeApi.PostApiV5TradeCancelBatchOrdersWithResponse(ctx, body, b.auth, b.customReq)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
temp := OKEXOrder{}
|
||||||
|
err = json.Unmarshal(cancelResp.Body, &temp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if temp.Code != "0" {
|
||||||
|
err = errors.New(string(cancelResp.Body))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) cancelAllAlgo() (orders []*Order, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
instType := b.instType
|
||||||
|
var params = trade.GetApiV5TradeOrdersAlgoPendingParams{
|
||||||
|
OrdType: "conditional",
|
||||||
|
// InstId: &b.symbol,
|
||||||
|
InstType: &instType,
|
||||||
|
}
|
||||||
|
resp, err := b.tradeApi.GetApiV5TradeOrdersAlgoPendingWithResponse(ctx, ¶ms, b.auth, b.customReq)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var orderResp CancelAlgoResp
|
||||||
|
err = json.Unmarshal(resp.Body, &orderResp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if orderResp.Code != "0" {
|
||||||
|
err = errors.New(string(resp.Body))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(orderResp.Data) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var body trade.PostApiV5TradeCancelAlgosJSONRequestBody
|
||||||
|
for _, v := range orderResp.Data {
|
||||||
|
body = append(body, trade.CancelAlgoOrder{
|
||||||
|
InstId: v.InstID,
|
||||||
|
AlgoId: v.AlgoID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelResp, err := b.tradeApi.PostApiV5TradeCancelAlgosWithResponse(ctx, body, b.auth, b.customReq)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
temp := OKEXAlgoOrder{}
|
||||||
|
err = json.Unmarshal(cancelResp.Body, &temp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if temp.Code != "0" {
|
||||||
|
err = errors.New(string(cancelResp.Body))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (b *OkxTrader) CancelAllOrders() (orders []*Order, err error) {
|
||||||
|
temp, err := b.cancelAllNormal()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
orders, err = b.cancelAllAlgo()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
orders = append(temp, orders...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *OkxTrader) Symbols() (symbols []Symbol, err error) {
|
||||||
|
ctx, cancel := context.WithTimeout(background, b.timeout)
|
||||||
|
defer cancel()
|
||||||
|
resp, err := b.publicApi.GetApiV5PublicInstrumentsWithResponse(ctx, &public.GetApiV5PublicInstrumentsParams{InstType: b.instType}, b.customReq)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var instruments InstrumentResp
|
||||||
|
err = json.Unmarshal(resp.Body, &instruments)
|
||||||
|
if instruments.Code != "0" {
|
||||||
|
err = errors.New(string(resp.Body))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var value, amountPrecision float64
|
||||||
|
symbols = make([]Symbol, len(instruments.Data))
|
||||||
|
for i, v := range instruments.Data {
|
||||||
|
value, err = strconv.ParseFloat(v.TickSz, 64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
amountPrecision, err = strconv.ParseFloat(v.LotSz, 64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
symbolInfo := Symbol{
|
||||||
|
Name: v.InstID,
|
||||||
|
Exchange: "okx",
|
||||||
|
Symbol: v.InstID,
|
||||||
|
Resolutions: "1m,5m,15m,30m,1h,4h,1d,1w",
|
||||||
|
Precision: int(float64(1) / value),
|
||||||
|
AmountPrecision: int(float64(1) / amountPrecision),
|
||||||
|
PriceStep: value,
|
||||||
|
AmountStep: 0,
|
||||||
|
}
|
||||||
|
value, err = strconv.ParseFloat(v.MinSz, 64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
symbolInfo.AmountStep = value
|
||||||
|
symbols[i] = symbolInfo
|
||||||
|
}
|
||||||
|
if len(symbols) > 0 {
|
||||||
|
symbolMap := make(map[string]Symbol)
|
||||||
|
for _, v := range symbols {
|
||||||
|
symbolMap[v.Symbol] = v
|
||||||
|
}
|
||||||
|
b.symbols = symbolMap
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
156
okex/okex_test.go
Normal file
156
okex/okex_test.go
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package okex
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.qtrade.icu/coin-quant/exchange"
|
||||||
|
"git.qtrade.icu/coin-quant/trademodel"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testClt *OkxTrader
|
||||||
|
)
|
||||||
|
|
||||||
|
func getTestClt() *OkxTrader {
|
||||||
|
cfgPath := "../test/test.yaml"
|
||||||
|
cfg := viper.New()
|
||||||
|
cfg.SetConfigFile(cfgPath)
|
||||||
|
err := cfg.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("ReadInConfig failed:" + err.Error())
|
||||||
|
}
|
||||||
|
testClt, err = NewOkxTrader(exchange.WrapViper(cfg), "okx")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("create client failed:" + err.Error())
|
||||||
|
}
|
||||||
|
testClt.Start()
|
||||||
|
return testClt
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
testClt = getTestClt()
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSymbols(t *testing.T) {
|
||||||
|
symbols, err := testClt.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
for _, v := range symbols {
|
||||||
|
t.Log(v.Symbol, v.Precision, v.PriceStep, v.AmountPrecision, v.AmountStep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKline(t *testing.T) {
|
||||||
|
// start, _ := time.Parse("2006-01-02 15:04:05", "2023-01-01 00:00:00")
|
||||||
|
start := time.Now().Add(time.Minute * -5)
|
||||||
|
end := start.Add(time.Hour + time.Second)
|
||||||
|
datas, err := testClt.GetKline("BTC-USDT-SWAP", "1m", start, end)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, v := range datas {
|
||||||
|
t.Log(v)
|
||||||
|
}
|
||||||
|
if len(datas) != 60 {
|
||||||
|
t.Fatal("Kline resp not match:", len(datas))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrder(t *testing.T) {
|
||||||
|
order, err := testClt.ProcessOrder(trademodel.TradeAction{
|
||||||
|
Symbol: "APT-USDT-SWAP",
|
||||||
|
Action: trademodel.OpenShort,
|
||||||
|
Amount: 1,
|
||||||
|
Price: 20,
|
||||||
|
Time: time.Now(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
t.Log(*order)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
_, err = testClt.CancelAllOrders()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStopOrder(t *testing.T) {
|
||||||
|
order, err := testClt.ProcessOrder(trademodel.TradeAction{
|
||||||
|
Symbol: "APT-USDT-SWAP",
|
||||||
|
Action: trademodel.StopShort,
|
||||||
|
Amount: 1,
|
||||||
|
Price: 17,
|
||||||
|
Time: time.Now(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
t.Log(*order)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
_, err = testClt.CancelAllOrders()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCancelAllOrder(t *testing.T) {
|
||||||
|
_, err := testClt.CancelAllOrders()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCancelOrder(t *testing.T) {
|
||||||
|
act := trademodel.TradeAction{
|
||||||
|
Action: trademodel.OpenLong,
|
||||||
|
Amount: 1,
|
||||||
|
Price: 2000,
|
||||||
|
Time: time.Now(),
|
||||||
|
}
|
||||||
|
order, err := testClt.ProcessOrder(act)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
t.Log(*order)
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
ret, err := testClt.CancelOrder(order)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
t.Log("ret:", *ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCancelStopOrder(t *testing.T) {
|
||||||
|
act := trademodel.TradeAction{
|
||||||
|
Action: trademodel.StopLong,
|
||||||
|
Amount: 1,
|
||||||
|
Price: 2000,
|
||||||
|
Time: time.Now(),
|
||||||
|
}
|
||||||
|
order, err := testClt.ProcessOrder(act)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
t.Log(*order)
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
ret, err := testClt.CancelOrder(order)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
t.Log("ret:", *ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDepth(t *testing.T) {
|
||||||
|
param := exchange.WatchParam{Type: exchange.WatchTypeDepth, Param: map[string]string{"symbol": "ETH-USDT-SWAP", "name": "depth"}}
|
||||||
|
testClt.Watch(param, func(data interface{}) {
|
||||||
|
fmt.Println(data)
|
||||||
|
})
|
||||||
|
time.Sleep(time.Second * 10)
|
||||||
|
}
|
359
okex/okex_ws.go
Normal file
359
okex/okex_ws.go
Normal file
|
@ -0,0 +1,359 @@
|
||||||
|
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
|
||||||
|
}
|
312
okex/okex_ws_public.go
Normal file
312
okex/okex_ws_public.go
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
package okex
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/bitly/go-simplejson"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.qtrade.icu/coin-quant/exchange/ws"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
. "git.qtrade.icu/coin-quant/trademodel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *OkxTrader) runPublic() (err error) {
|
||||||
|
b.wsPublic, err = ws.NewWSConn(WSOkexPUbilc, func(ws *ws.WSConn) error {
|
||||||
|
// watch when reconnect
|
||||||
|
for _, v := range b.watchPublics {
|
||||||
|
err1 := ws.WriteMsg(v)
|
||||||
|
if err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, b.parsePublicMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (b *OkxTrader) parsePublicMsg(message []byte) (err error) {
|
||||||
|
sj, err := simplejson.NewJson(message)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("parse json error:%s", string(message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, ok := sj.CheckGet("event")
|
||||||
|
if ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
arg, ok := sj.CheckGet("arg")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
channelValue, ok := arg.CheckGet("channel")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
symbol, err := arg.Get("instId").String()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("okex public ws message instId not found %s, %s", string(message), err.Error())
|
||||||
|
}
|
||||||
|
_ = symbol
|
||||||
|
channel, err := channelValue.String()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("channelValue %v is not string error:%s", channelValue, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch channel {
|
||||||
|
case "books5":
|
||||||
|
value, ok := sj.CheckGet("data")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
if b.depthCb == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var depths5 []*Depth
|
||||||
|
depths5, err = parseOkexBooks5(value)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("parseOkexBooks5 failed:%s, %s", string(message), err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, v := range depths5 {
|
||||||
|
b.depthCb(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "trades":
|
||||||
|
value, ok := sj.CheckGet("data")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if b.tradeMarketCb == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var trades []*Trade
|
||||||
|
trades, err = parseOkexTrade(value)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("parseOkexTrade failed:%s, %s", string(message), err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, v := range trades {
|
||||||
|
b.tradeMarketCb(v)
|
||||||
|
}
|
||||||
|
case "candle1m":
|
||||||
|
value, ok := sj.CheckGet("data")
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// fmt.Println("candle1m:", string(message))
|
||||||
|
var candles []*Candle
|
||||||
|
candles, err = parseWsCandle(value)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("parseWsCandle failed:%s, %s", string(message), err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(candles) == 0 {
|
||||||
|
return
|
||||||
|
} else if len(candles) != 1 {
|
||||||
|
log.Warnf("parseWsCandle candles len warn :%d, %v", len(candles), err)
|
||||||
|
}
|
||||||
|
if b.klineCb == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.klineCb(candles[0])
|
||||||
|
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 parseOkexBooks5(sj *simplejson.Json) (depths []*Depth, err error) {
|
||||||
|
arr, err := sj.Array()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("parseOkexBooks5 not array: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var obj, asks, bids *simplejson.Json
|
||||||
|
var askInfo, bidInfo []DepthInfo
|
||||||
|
var tsStr string
|
||||||
|
var nTs int64
|
||||||
|
for k := range arr {
|
||||||
|
obj = sj.GetIndex(k)
|
||||||
|
asks = obj.Get("asks")
|
||||||
|
bids = obj.Get("bids")
|
||||||
|
if asks == nil || bids == nil {
|
||||||
|
err = errors.New("data error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
askInfo, err = parseOrderBook(asks)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bidInfo, err = parseOrderBook(bids)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tsStr, err = obj.Get("ts").String()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dp := Depth{Buys: bidInfo, Sells: askInfo}
|
||||||
|
nTs, err = strconv.ParseInt(tsStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dp.UpdateTime = time.Unix(nTs/1000, (nTs%1000)*int64(time.Millisecond))
|
||||||
|
depths = append(depths, &dp)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOrderBook(sj *simplejson.Json) (datas []DepthInfo, err error) {
|
||||||
|
arr, err := sj.Array()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var ret []interface{}
|
||||||
|
var ok bool
|
||||||
|
for _, v := range arr {
|
||||||
|
ret, ok = v.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("parseOrderBook error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(ret) != 4 {
|
||||||
|
err = errors.New("parseOrderBook len error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
di := DepthInfo{Price: getFloat(ret[0]), Amount: getFloat(ret[1])}
|
||||||
|
datas = append(datas, di)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOkexTrade(sj *simplejson.Json) (trades []*Trade, err error) {
|
||||||
|
arr, err := sj.Array()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// {"instId":"BSV-USD-210924","tradeId":"1957771","px":"166.5","sz":"22","side":"sell","ts":"1621862533713"}
|
||||||
|
var temp map[string]interface{}
|
||||||
|
var ok bool
|
||||||
|
for _, v := range arr {
|
||||||
|
temp, ok = v.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
log.Warnf("data error:%#v", temp)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var t Trade
|
||||||
|
t.Remark = getStr(temp, "instId")
|
||||||
|
t.ID = getStr(temp, "tradeId")
|
||||||
|
t.Side = getStr(temp, "side")
|
||||||
|
t.Price = getStrFloat(temp, "px")
|
||||||
|
t.Amount = getStrFloat(temp, "sz")
|
||||||
|
nTs := getStrTs(temp, "ts")
|
||||||
|
t.Time = time.Unix(nTs/1000, int64(time.Millisecond)*(nTs%1000))
|
||||||
|
trades = append(trades, &t)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStrTs(dict map[string]interface{}, key string) (nTs int64) {
|
||||||
|
str := getStr(dict, key)
|
||||||
|
if str == "" {
|
||||||
|
log.Warnf("getStrTs of %s empty", key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nTs, err := strconv.ParseInt(str, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("getStrTs of %s parse int err: %s", key, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return nTs
|
||||||
|
// t = time.Unix(nTs/1000, (nTs%1000)*int64(time.Millisecond))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStrFloat(dict map[string]interface{}, key string) float64 {
|
||||||
|
str := getStr(dict, key)
|
||||||
|
if str == "" {
|
||||||
|
log.Warnf("getStrFloat of %s empty", key)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
f, err := strconv.ParseFloat(str, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("getStrFloat of %s failed: %s", key, err.Error())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStr(dict map[string]interface{}, key string) string {
|
||||||
|
v, ok := dict[key]
|
||||||
|
if !ok {
|
||||||
|
log.Warnf("getStr of %s empty", key)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
str, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
log.Warnf("getStr of %s type error: %#v", key, v)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFloat(v interface{}) float64 {
|
||||||
|
str, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
f, err := strconv.ParseFloat(str, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("getFloat %#v failed: %s", v, err.Error())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseWsCandle(sj *simplejson.Json) (ret []*Candle, err error) {
|
||||||
|
arr, err := sj.Array()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// {"instId":"BSV-USD-210924","tradeId":"1957771","px":"166.5","sz":"22","side":"sell","ts":"1621862533713"}
|
||||||
|
var values []interface{}
|
||||||
|
var ok bool
|
||||||
|
var nTs int64
|
||||||
|
for _, v := range arr {
|
||||||
|
values, ok = v.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
log.Warnf("transWsCandle data error:%#v", values)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(values) != 9 {
|
||||||
|
log.Warnf("transWsCandle data len error:%#v", values)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nTs, err = strconv.ParseInt(values[0].(string), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("trans candle error: %#v", values))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// unfinished kline
|
||||||
|
if values[8].(string) == "0" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
temp := Candle{
|
||||||
|
ID: 0,
|
||||||
|
Start: nTs / 1000,
|
||||||
|
Open: parseFloat(values[1].(string)),
|
||||||
|
High: parseFloat(values[2].(string)),
|
||||||
|
Low: parseFloat(values[3].(string)),
|
||||||
|
Close: parseFloat(values[4].(string)),
|
||||||
|
Volume: parseFloat(values[5].(string)),
|
||||||
|
Turnover: parseFloat(values[7].(string)),
|
||||||
|
}
|
||||||
|
ret = append(ret, &temp)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
124
okex/tool.go
Normal file
124
okex/tool.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package okex
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"git.qtrade.icu/coin-quant/exchange/okex/api/market"
|
||||||
|
. "git.qtrade.icu/coin-quant/trademodel"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func transCandle(values [9]string) (ret *Candle) {
|
||||||
|
nTs, err := strconv.ParseInt(values[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("trans candle error: %#v", values))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ret = &Candle{
|
||||||
|
ID: 0,
|
||||||
|
Start: nTs / 1000,
|
||||||
|
Open: parseFloat(values[1]),
|
||||||
|
High: parseFloat(values[2]),
|
||||||
|
Low: parseFloat(values[3]),
|
||||||
|
Close: parseFloat(values[4]),
|
||||||
|
Volume: parseFloat(values[5]),
|
||||||
|
Turnover: parseFloat(values[7]),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFloat(str string) float64 {
|
||||||
|
if str == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
f, err := strconv.ParseFloat(str, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic("okex parseFloat error:" + err.Error())
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCandles(resp *market.GetApiV5MarketHistoryCandlesResponse) (candles []*Candle, err error) {
|
||||||
|
var candleResp CandleResp
|
||||||
|
err = json.Unmarshal(resp.Body, &candleResp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if candleResp.Code != "0" {
|
||||||
|
err = errors.New(string(resp.Body))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, v := range candleResp.Data {
|
||||||
|
// unfinished candle
|
||||||
|
if v[8] == "0" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
temp := transCandle(v)
|
||||||
|
candles = append(candles, temp)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePostOrders(symbol, status, side string, amount, price float64, body []byte) (ret []*Order, err error) {
|
||||||
|
temp := OKEXOrder{}
|
||||||
|
err = json.Unmarshal(body, &temp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if temp.Code != "0" {
|
||||||
|
err = fmt.Errorf("error resp: %s", string(body))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, v := range temp.Data {
|
||||||
|
if v.SCode != "0" {
|
||||||
|
err = fmt.Errorf("%s %s", v.SCode, v.SMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
temp := &Order{
|
||||||
|
OrderID: v.OrdID,
|
||||||
|
Symbol: symbol,
|
||||||
|
// Currency
|
||||||
|
Side: side,
|
||||||
|
Status: status,
|
||||||
|
Price: price,
|
||||||
|
Amount: amount,
|
||||||
|
Time: time.Now(),
|
||||||
|
}
|
||||||
|
ret = append(ret, temp)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePostAlgoOrders(symbol, status, side string, amount, price float64, body []byte) (ret []*Order, err error) {
|
||||||
|
temp := OKEXAlgoOrder{}
|
||||||
|
err = json.Unmarshal(body, &temp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if temp.Code != "0" {
|
||||||
|
err = fmt.Errorf("error resp: %s", string(body))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, v := range temp.Data {
|
||||||
|
if v.SCode != "0" {
|
||||||
|
err = fmt.Errorf("%s %s", v.SCode, v.SMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
temp := &Order{
|
||||||
|
OrderID: v.AlgoID,
|
||||||
|
Symbol: symbol,
|
||||||
|
// Currency
|
||||||
|
Side: side,
|
||||||
|
Status: status,
|
||||||
|
Price: price,
|
||||||
|
Amount: amount,
|
||||||
|
Time: time.Now(),
|
||||||
|
}
|
||||||
|
ret = append(ret, temp)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
49
pool.go
Normal file
49
pool.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package exchange
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
exchangeFactory = map[string]NewExchangeFn{}
|
||||||
|
|
||||||
|
exchangeMutex sync.Mutex
|
||||||
|
exchanges = map[string]Exchange{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type NewExchangeFn func(cfg Config, cltName string) (t Exchange, err error)
|
||||||
|
|
||||||
|
func RegisterExchange(name string, fn NewExchangeFn) {
|
||||||
|
exchangeFactory[name] = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExchange(name string, cfg Config, cltName string) (ex Exchange, err error) {
|
||||||
|
exchangeMutex.Lock()
|
||||||
|
defer exchangeMutex.Unlock()
|
||||||
|
if cfg.GetBool("share_exchange") {
|
||||||
|
v, ok := exchanges[cltName]
|
||||||
|
if ok {
|
||||||
|
ex = v
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
exchanges[cltName] = ex
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
fn, ok := exchangeFactory[name]
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("no such exchange %s", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ex, err = fn(cfg, cltName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExchangeViper(name, cltName string) (ex Exchange, err error) {
|
||||||
|
cfg := WrapViper(viper.GetViper())
|
||||||
|
return NewExchange(name, cfg, cltName)
|
||||||
|
}
|
204
ws/ws.go
Normal file
204
ws/ws.go
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
package ws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
pongMsg = []byte("pong")
|
||||||
|
)
|
||||||
|
|
||||||
|
type WSInitFn func(ws *WSConn) error
|
||||||
|
type MessageFn func(message []byte) error
|
||||||
|
type PingFn func(ws *WSConn) error
|
||||||
|
type PongFn func(message []byte) bool
|
||||||
|
|
||||||
|
func defaultPingFn(ws *WSConn) error {
|
||||||
|
return ws.WriteText("ping")
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultPongFn(message []byte) bool {
|
||||||
|
return bytes.Equal(pongMsg, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
type WSConn struct {
|
||||||
|
addr string
|
||||||
|
ws *websocket.Conn
|
||||||
|
initFn WSInitFn
|
||||||
|
messageFn MessageFn
|
||||||
|
pingFn PingFn
|
||||||
|
pongFn PongFn
|
||||||
|
closeCh chan int
|
||||||
|
writeMuetx sync.Mutex
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWSConnWithPingPong(addr string, initFn WSInitFn, messageFn MessageFn, ping PingFn, pong PongFn) (conn *WSConn, err error) {
|
||||||
|
conn = new(WSConn)
|
||||||
|
conn.addr = addr
|
||||||
|
conn.initFn = initFn
|
||||||
|
conn.messageFn = messageFn
|
||||||
|
conn.closeCh = make(chan int, 1)
|
||||||
|
conn.pingFn = ping
|
||||||
|
conn.pongFn = pong
|
||||||
|
err = conn.connect()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWSConn(addr string, initFn WSInitFn, messageFn MessageFn) (conn *WSConn, err error) {
|
||||||
|
conn = new(WSConn)
|
||||||
|
conn.addr = addr
|
||||||
|
conn.initFn = initFn
|
||||||
|
conn.pingFn = defaultPingFn
|
||||||
|
conn.pongFn = defaultPongFn
|
||||||
|
conn.messageFn = messageFn
|
||||||
|
conn.closeCh = make(chan int, 1)
|
||||||
|
err = conn.connect()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *WSConn) SetPingPongFn(ping PingFn, pong PongFn) {
|
||||||
|
conn.pingFn = ping
|
||||||
|
conn.pongFn = pong
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *WSConn) Close() (err error) {
|
||||||
|
close(conn.closeCh)
|
||||||
|
conn.wg.Wait()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *WSConn) WriteText(value string) (err error) {
|
||||||
|
conn.writeMuetx.Lock()
|
||||||
|
if conn.ws != nil {
|
||||||
|
err = conn.ws.WriteMessage(websocket.TextMessage, []byte(value))
|
||||||
|
} else {
|
||||||
|
log.Warnf("WriteText ignore conn of %s not init", conn.addr)
|
||||||
|
}
|
||||||
|
conn.writeMuetx.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *WSConn) WriteMsg(value interface{}) (err error) {
|
||||||
|
conn.writeMuetx.Lock()
|
||||||
|
if conn.ws != nil {
|
||||||
|
err = conn.ws.WriteJSON(value)
|
||||||
|
} else {
|
||||||
|
log.Warnf("WriteMsg ignore conn of %s not init", conn.addr)
|
||||||
|
}
|
||||||
|
conn.writeMuetx.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *WSConn) connect() (err error) {
|
||||||
|
u, err := url.Parse(conn.addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.ws, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("connect to %s failed: %w", conn.addr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if conn.initFn != nil {
|
||||||
|
err = conn.initFn(conn)
|
||||||
|
if err != nil {
|
||||||
|
conn.ws.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go conn.loop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *WSConn) loop() {
|
||||||
|
ws := conn.ws
|
||||||
|
ch := make(chan []byte, 1024)
|
||||||
|
needReconn := make(chan bool, 1)
|
||||||
|
go conn.readLoop(ws, ch, needReconn)
|
||||||
|
var msg []byte
|
||||||
|
var err error
|
||||||
|
var lastMsgTime time.Time
|
||||||
|
ticker := time.NewTicker(time.Second * 5)
|
||||||
|
|
||||||
|
conn.wg.Add(1)
|
||||||
|
defer conn.wg.Done()
|
||||||
|
var ok bool
|
||||||
|
defer ticker.Stop()
|
||||||
|
Out:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg, ok = <-ch:
|
||||||
|
if !ok {
|
||||||
|
break Out
|
||||||
|
}
|
||||||
|
lastMsgTime = time.Now()
|
||||||
|
|
||||||
|
if conn.pongFn != nil && conn.pongFn(msg) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = conn.messageFn(msg)
|
||||||
|
if err != nil {
|
||||||
|
break Out
|
||||||
|
}
|
||||||
|
case <-ticker.C:
|
||||||
|
dur := time.Since(lastMsgTime)
|
||||||
|
if dur > time.Second*5 {
|
||||||
|
if conn.pingFn != nil {
|
||||||
|
err1 := conn.pingFn(conn)
|
||||||
|
if err1 != nil {
|
||||||
|
log.Errorf("ws pingFn failed: %s", err1.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-conn.closeCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reConn := <-needReconn
|
||||||
|
if reConn {
|
||||||
|
for i := 0; i != 100; i++ {
|
||||||
|
err = conn.connect()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Errorf("ws reconnect %d to failed: %s", i, err.Error())
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *WSConn) readLoop(ws *websocket.Conn, ch chan []byte, needConn chan bool) {
|
||||||
|
defer func() {
|
||||||
|
ws.Close()
|
||||||
|
close(ch)
|
||||||
|
close(needConn)
|
||||||
|
}()
|
||||||
|
var message []byte
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-conn.closeCh:
|
||||||
|
needConn <- false
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
_, message, err = ws.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s ws read error: %s", conn.addr, err.Error())
|
||||||
|
needConn <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- message
|
||||||
|
}
|
||||||
|
}
|
53
ws/ws_test.go
Normal file
53
ws/ws_test.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package ws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWsConn(t *testing.T) {
|
||||||
|
initFn := func(ws *WSConn) error {
|
||||||
|
var p = map[string]interface{}{
|
||||||
|
"op": "subscribe",
|
||||||
|
"args": []interface{}{
|
||||||
|
map[string]interface{}{"channel": "trades", "instType": "SWAP", "instId": "BTC-USDT-SWAP"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return ws.WriteMsg(p)
|
||||||
|
}
|
||||||
|
messageFn := func(msg []byte) error {
|
||||||
|
fmt.Println("msg:", string(msg))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
conn, err := NewWSConn("wss://wsaws.okx.com:8443/ws/v5/public", initFn, messageFn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWsPing(t *testing.T) {
|
||||||
|
initFn := func(ws *WSConn) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
messageFn := func(msg []byte) error {
|
||||||
|
fmt.Println("msg:", string(msg))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pongFn := func(msg []byte) bool {
|
||||||
|
fmt.Println("recv pong msg:", string(msg))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
conn, err := NewWSConn("wss://wsaws.okx.com:8443/ws/v5/public", initFn, messageFn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
conn.SetPingPongFn(defaultPingFn, pongFn)
|
||||||
|
conn.ws.WriteMessage(websocket.TextMessage, []byte("ping"))
|
||||||
|
time.Sleep(time.Second * 15)
|
||||||
|
conn.Close()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user