exchanges: Add ByBit support (#887)

* few fixes and add ratelimiter

* adds test

* revert configtest.json changes

* configtest updated

* WIP: adds public endpoint support

* WIP: adds public endpoint support

* adds public endpoint support

* WIP: adds auth. endpoint support

* adds test for auth. endpoint

* fixes

* adds auth. endpoint support

* WIP: ws support

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* Testing

* Complete WS spot testing

* adds support for ws events

* minor change

* WIP: adds REST support for CoinMarginedFutures

* Fixes

* WIP: adds REST support for CoinMarginedFutures

* Fixes

* improvement in SPOT REST

* Typo fix

* WIP: add REST support for CMF Account API

* minor fixes

* WIP: add support for CMF conditional orders and few minor fixes

* complete support for CMF conditional orders

* adds support for public CMF endpoint

* adds support for CMF position API

* Complete REST CMF support

* WIP

* Testing REST CMF support

* Testing REST CMF support

* Testing REST CMF support completed

* WIP: add support for UMF

* completed non-auth UMF

* WIP: add support for REST Auth. UMF

* WIP: add support for REST Auth. UMF and some improvements

* WIP

* WIP

* WIP

* completed REST UMF

* renaming

* adds REST support for futures

* add testcases for UMF and some optimizations

* add testcases for futures

* Testing UMF, futures and its changes

* Fixes

* Fixes after testing

* WIP

* WIP

* WIP

* completed ws USDT futures support

* WIP: ws support for futures

* fixes in WS futures

* fixes in WS support

* roll back changes made for WS CMF, USDT and Futures

* fixes

* WIP

* WIP

* fixes

* Steps for new PR

* WIP

* WIP

* WIP

* WIP

* complete PR setup

* fixes for successfully running tests

* update in symbol for futures pair in test file

* WIP

* Fixes in test file and other minor fix

* fix testdata/configtest.json

* reset CONTRIBUTORS file

* review changes

* remove unwanted file

* remove redundant code

* improvisation

* adds comment for exported functions

* remove unwanted TODO and commented code

* fix

* improvisation

* fix

* defined errors

* improvisation

* improvisation

* improvisation

* updates test

* adds comment for exported types

* review changes

* review changes

* fix

* fixes

* Changes for making BYBIT compatible with existing code base

* Test file changes

* Changes for making BYBIT compatible with existing code base

* Changes for making BYBIT compatible with existing code base

* fix lint issues

* fix

* review changes

* review changes

* review changes

* review changes

* review changes

* review changes

* review changes

* review changes

* review changes

* review changes

* WIP

* add test cases for new API's

* minor improvements

* add missing API and their tests

* minor fixes

* add bybitTime

* add bybitTimeSec, bybitTimeMilliSec, bybitTimeNanoSec and necessary support

* fix GetTradeHistory function

* error handling

* test fixes

* add GetServerTime API

* adds GetHistoricCandlesExtended and review changes

* test fixes

* minor fix

* integrating CMF Bybit recent change log

* minor fixes

* adds extractCurrencyPair

* minor fixes

* minor fix

* review changes

* adds variable declaration of error

* review commit

* adds embeddable type in API response for all API and integrate it

* fixes

* adds authentication WS connection

* review changes

* review changes

* compatible changes

* adds asset to GetWithdrawalsHistory

* adds asset_type in rpc.proto

* adds asset argument in gctcli withdrawal request command

* improve error handling in exchange API error

* web socket fix

* review changes

* improvements

* improvements

* minor fix

* review changes

* fixing wrapper issues

* fixes

* fixes

* review changes

* add test cases

* fix for GetActiveOrders

* lint fixes

* fixes in websocket

* adds wrapper testcases

* adds wrapper testcases

* adds wrapper testcases

* fixes

* fix issue with GetHistoricCandlesExtended

* fix merge issues

* improving error reporting

* adds wrapper testcases and a minor fix

* gctrpc changes

* adds test cases
fixes in websocket

* review changes for ws

* review changes in WS

* fix gctrpc

* merge fixes

* review changes

* WIP

* updates pair in configs

* adds new asset USDCMarginedFutures

* adds URL const for USDCMarginedFutures

* adds API support

* minor fixes

* adds kline API

* minor fix

* adds API

* adds API

* adds API

* WIP

* WIP

* WIP

* adds support for USDC auth requests to SendAuthHTTPRequest

* adds SendUSDCAuthHTTPRequest

* run test and fix them

* rollback support added for Auth. USDC request inside SendAuthHTTPRequest

* adds API and test cases

* adds API and test cases

* adds APIs and test cases

* adds APIs

* adds rate limit for USDC

* adds USDCMarginedFutures to wrapper

* adds USDC testcases in wrapper and fix few issues

* minor test fixes

* minor test fixes

* fix lint issues

* WIP

* Merge changes

* minor fixes

* remove "else" and optimize

* review changes

* review changes

* review changes

* fix lint issue

* merge fix

* fix test

* fix templates and run them

* changes after merge

* review changes and improvements

* code improvement

* fixes with respect to changes in API response in documentation

* fixed review change in test

* adds check in CancelExistingOrder

* update exchange template

* review changes

* adds GetDepositAddress API

* WIP: adds GetOrderHistory

* complete GetOrderHistory

* fixes

* adds test case

* fixes and add WithdrawFund API

* WIP

* WIP

* updating all SendAuthHTTPRequest call

* adds WithdrawCryptocurrencyFunds

* update test cases

* fix lint issues

* fixes after merge

* adds GetAvailableTransferChains and few fixes

* minor fix in GetDepositAddress

* minor fix with WS ping/pong handling

* add ping handler for WS Auth.

* fix typo mistake

* update doc
This commit is contained in:
Jaydeep Rajpurohit
2022-08-08 06:59:43 +05:30
committed by GitHub
parent 67c512e92d
commit 247da918a8
70 changed files with 17927 additions and 1773 deletions

View File

@@ -236,7 +236,7 @@ func (a *Alphapoint) GetFundingHistory(ctx context.Context) ([]exchange.FundHist
}
// GetWithdrawalsHistory returns previous withdrawals data
func (a *Alphapoint) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (a *Alphapoint) GetWithdrawalsHistory(ctx context.Context, c currency.Code, as asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -33,9 +33,10 @@ const (
DownsideProfitContract
CoinMarginedFutures
USDTMarginedFutures
USDCMarginedFutures
futuresFlag = PerpetualContract | PerpetualSwap | Futures | UpsideProfitContract | DownsideProfitContract | CoinMarginedFutures | USDTMarginedFutures
supportedFlag = Spot | Margin | MarginFunding | Index | Binary | PerpetualContract | PerpetualSwap | Futures | UpsideProfitContract | DownsideProfitContract | CoinMarginedFutures | USDTMarginedFutures
futuresFlag = PerpetualContract | PerpetualSwap | Futures | UpsideProfitContract | DownsideProfitContract | CoinMarginedFutures | USDTMarginedFutures | USDCMarginedFutures
supportedFlag = Spot | Margin | MarginFunding | Index | Binary | PerpetualContract | PerpetualSwap | Futures | UpsideProfitContract | DownsideProfitContract | CoinMarginedFutures | USDTMarginedFutures | USDCMarginedFutures
spot = "spot"
margin = "margin"
@@ -49,10 +50,11 @@ const (
downsideProfitContract = "downsideprofitcontract"
coinMarginedFutures = "coinmarginedfutures"
usdtMarginedFutures = "usdtmarginedfutures"
usdcMarginedFutures = "usdcmarginedfutures"
)
var (
supportedList = Items{Spot, Margin, MarginFunding, Index, Binary, PerpetualContract, PerpetualSwap, Futures, UpsideProfitContract, DownsideProfitContract, CoinMarginedFutures, USDTMarginedFutures}
supportedList = Items{Spot, Margin, MarginFunding, Index, Binary, PerpetualContract, PerpetualSwap, Futures, UpsideProfitContract, DownsideProfitContract, CoinMarginedFutures, USDTMarginedFutures, USDCMarginedFutures}
)
// Supported returns a list of supported asset types
@@ -87,6 +89,8 @@ func (a Item) String() string {
return coinMarginedFutures
case USDTMarginedFutures:
return usdtMarginedFutures
case USDCMarginedFutures:
return usdcMarginedFutures
default:
return ""
}
@@ -180,6 +184,8 @@ func New(input string) (Item, error) {
return CoinMarginedFutures, nil
case usdtMarginedFutures:
return USDTMarginedFutures, nil
case usdcMarginedFutures:
return USDCMarginedFutures, nil
default:
return 0, fmt.Errorf("%w '%v', only supports %s",
ErrNotSupported,

View File

@@ -92,6 +92,7 @@ func TestNew(t *testing.T) {
{Input: "DownsideProfitContract", Expected: DownsideProfitContract},
{Input: "CoinMarginedFutures", Expected: CoinMarginedFutures},
{Input: "USDTMarginedFutures", Expected: USDTMarginedFutures},
{Input: "USDCMarginedFutures", Expected: USDCMarginedFutures},
}
for x := range cases {
@@ -177,6 +178,10 @@ func TestIsFutures(t *testing.T) {
item: USDTMarginedFutures,
isFutures: true,
},
{
item: USDCMarginedFutures,
isFutures: true,
},
}
for _, s := range scenarios {
testScenario := s

View File

@@ -2073,7 +2073,7 @@ func TestWithdrawHistory(t *testing.T) {
if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests {
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
}
_, err := b.GetWithdrawalsHistory(context.Background(), currency.ETH)
_, err := b.GetWithdrawalsHistory(context.Background(), currency.ETH, asset.Spot)
switch {
case areTestAPIKeysSet() && err != nil:
t.Error("GetWithdrawalsHistory() error", err)

View File

@@ -818,7 +818,7 @@ func (b *Binance) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory
}
// GetWithdrawalsHistory returns previous withdrawals data
func (b *Binance) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (b *Binance) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
w, err := b.WithdrawHistory(ctx, c, "", time.Time{}, time.Time{}, 0, 10000)
if err != nil {
return nil, err

View File

@@ -551,7 +551,7 @@ func (b *Bitfinex) GetFundingHistory(ctx context.Context) ([]exchange.FundHistor
}
// GetWithdrawalsHistory returns previous withdrawals data
func (b *Bitfinex) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (b *Bitfinex) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -338,7 +338,7 @@ func (b *Bitflyer) GetFundingHistory(_ context.Context) ([]exchange.FundHistory,
}
// GetWithdrawalsHistory returns previous withdrawals data
func (b *Bitflyer) GetWithdrawalsHistory(_ context.Context, _ currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (b *Bitflyer) GetWithdrawalsHistory(_ context.Context, _ currency.Code, _ asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -426,7 +426,7 @@ func (b *Bithumb) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory
}
// GetWithdrawalsHistory returns previous withdrawals data
func (b *Bithumb) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (b *Bithumb) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -488,7 +488,7 @@ func (b *Bitmex) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory,
}
// GetWithdrawalsHistory returns previous withdrawals data
func (b *Bitmex) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (b *Bitmex) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -484,7 +484,7 @@ func (b *Bitstamp) GetFundingHistory(ctx context.Context) ([]exchange.FundHistor
}
// GetWithdrawalsHistory returns previous withdrawals data
func (b *Bitstamp) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (b *Bitstamp) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -506,7 +506,7 @@ func (b *Bittrex) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory
}
// GetWithdrawalsHistory returns previous withdrawals data
func (b *Bittrex) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (b *Bittrex) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -490,7 +490,7 @@ func (b *BTCMarkets) GetFundingHistory(ctx context.Context) ([]exchange.FundHist
}
// GetWithdrawalsHistory returns previous withdrawals data
func (b *BTCMarkets) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (b *BTCMarkets) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -459,7 +459,7 @@ func (b *BTSE) withinLimits(pair currency.Pair, amount float64) bool {
}
// GetWithdrawalsHistory returns previous withdrawals data
func (b *BTSE) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (b *BTSE) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

140
exchanges/bybit/README.md Normal file
View File

@@ -0,0 +1,140 @@
# GoCryptoTrader package Bybit
<img src="/common/gctlogo.png?raw=true" width="350px" height="350px" hspace="70">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/bybit)
[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
This bybit package is part of the GoCryptoTrader codebase.
## This is still in active development
You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk)
## Bybit Exchange
### Current Features
+ REST Support
+ Websocket Support
### How to enable
+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example)
+ Individual package example below:
```go
// Exchanges will be abstracted out in further updates and examples will be
// supplied then
```
### How to do REST public/private calls
+ If enabled via "configuration".json file the exchange will be added to the
IBotExchange array in the ```go var bot Bot``` and you will only be able to use
the wrapper interface functions for accessing exchange data. View routines.go
for an example of integration usage with GoCryptoTrader. Rudimentary example
below:
main.go
```go
var b exchange.IBotExchange
for i := range bot.Exchanges {
if bot.Exchanges[i].GetName() == "Bybit" {
b = bot.Exchanges[i]
}
}
// Public calls - wrapper functions
// Fetches current ticker information
tick, err := b.FetchTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := b.FetchOrderbook()
if err != nil {
// Handle error
}
// Private calls - wrapper functions - make sure your APIKEY and APISECRET are
// set and AuthenticatedAPISupport is set to true
// Fetches current account information
accountInfo, err := b.GetAccountInfo()
if err != nil {
// Handle error
}
```
+ If enabled via individually importing package, rudimentary example below:
```go
// Public calls
// Fetches current ticker information
ticker, err := b.GetTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := b.GetOrderBook()
if err != nil {
// Handle error
}
// Private calls - make sure your APIKEY and APISECRET are set and
// AuthenticatedAPISupport is set to true
// GetUserInfo returns account info
accountInfo, err := b.GetUserInfo(...)
if err != nil {
// Handle error
}
// Submits an order and the exchange and returns its tradeID
tradeID, err := b.Trade(...)
if err != nil {
// Handle error
}
```
### How to do Websocket public/private calls
```go
// Exchanges will be abstracted out in further updates and examples will be
// supplied then
```
### Please click GoDocs chevron above to view current GoDoc information for this package
## Contribution
Please feel free to submit any pull requests or suggest any desired features to be added.
When submitting a PR, please abide by our coding guidelines:
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
+ Pull requests need to be based on and opened against the `master` branch.
## Donations
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***

998
exchanges/bybit/bybit.go Normal file
View File

@@ -0,0 +1,998 @@
package bybit
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
)
// Bybit is the overarching type across this package
type Bybit struct {
exchange.Base
}
const (
bybitAPIURL = "https://api.bybit.com"
defaultRecvWindow = "5000" // 5000 milli second
sideBuy = "Buy"
sideSell = "Sell"
// Public endpoints
bybitSpotGetSymbols = "/spot/v1/symbols"
bybitOrderBook = "/spot/quote/v1/depth"
bybitMergedOrderBook = "/spot/quote/v1/depth/merged"
bybitRecentTrades = "/spot/quote/v1/trades"
bybitCandlestickChart = "/spot/quote/v1/kline"
bybit24HrsChange = "/spot/quote/v1/ticker/24hr"
bybitLastTradedPrice = "/spot/quote/v1/ticker/price"
bybitBestBidAskPrice = "/spot/quote/v1/ticker/book_ticker"
// Authenticated endpoints
bybitSpotOrder = "/spot/v1/order" // create, query, cancel
bybitFastCancelSpotOrder = "/spot/v1/order/fast"
bybitBatchCancelSpotOrder = "/spot/order/batch-cancel"
bybitFastBatchCancelSpotOrder = "/spot/order/batch-fast-cancel"
bybitOpenOrder = "/spot/v1/open-orders"
bybitPastOrder = "/spot/v1/history-orders"
bybitTradeHistory = "/spot/v1/myTrades"
bybitWalletBalance = "/spot/v1/account"
bybitServerTime = "/spot/v1/time"
// Account asset endpoint
bybitGetDepositAddress = "/asset/v1/private/deposit/address"
bybitWithdrawFund = "/asset/v1/private/withdraw"
)
// GetAllSpotPairs gets all pairs on the exchange
func (by *Bybit) GetAllSpotPairs(ctx context.Context) ([]PairData, error) {
resp := struct {
Data []PairData `json:"result"`
Error
}{}
return resp.Data, by.SendHTTPRequest(ctx, exchange.RestSpot, bybitSpotGetSymbols, publicSpotRate, &resp)
}
func processOB(ob [][2]string) ([]orderbook.Item, error) {
o := make([]orderbook.Item, len(ob))
for x := range ob {
var price, amount float64
amount, err := strconv.ParseFloat(ob[x][1], 64)
if err != nil {
return nil, err
}
price, err = strconv.ParseFloat(ob[x][0], 64)
if err != nil {
return nil, err
}
o[x] = orderbook.Item{
Price: price,
Amount: amount,
}
}
return o, nil
}
func constructOrderbook(o *orderbookResponse) (*Orderbook, error) {
var (
s Orderbook
err error
)
s.Bids, err = processOB(o.Data.Bids)
if err != nil {
return nil, err
}
s.Asks, err = processOB(o.Data.Asks)
if err != nil {
return nil, err
}
s.Time = o.Data.Time.Time()
return &s, err
}
// GetOrderBook gets orderbook for a given market with a given depth (default depth 100)
func (by *Bybit) GetOrderBook(ctx context.Context, symbol string, depth int64) (*Orderbook, error) {
var o orderbookResponse
strDepth := "100" // default depth
if depth > 0 && depth < 100 {
strDepth = strconv.FormatInt(depth, 10)
}
params := url.Values{}
params.Set("symbol", symbol)
params.Set("limit", strDepth)
path := common.EncodeURLValues(bybitOrderBook, params)
err := by.SendHTTPRequest(ctx, exchange.RestSpot, path, publicSpotRate, &o)
if err != nil {
return nil, err
}
return constructOrderbook(&o)
}
// GetMergedOrderBook gets orderbook for a given market with a given depth (default depth 100)
func (by *Bybit) GetMergedOrderBook(ctx context.Context, symbol string, scale, depth int64) (*Orderbook, error) {
var o orderbookResponse
params := url.Values{}
if scale > 0 {
params.Set("scale", strconv.FormatInt(scale, 10))
}
strDepth := "100" // default depth
if depth > 0 && depth <= 200 {
strDepth = strconv.FormatInt(depth, 10)
}
params.Set("symbol", symbol)
params.Set("limit", strDepth)
path := common.EncodeURLValues(bybitMergedOrderBook, params)
err := by.SendHTTPRequest(ctx, exchange.RestSpot, path, publicSpotRate, &o)
if err != nil {
return nil, err
}
return constructOrderbook(&o)
}
// GetTrades gets recent trades from the exchange
func (by *Bybit) GetTrades(ctx context.Context, symbol string, limit int64) ([]TradeItem, error) {
resp := struct {
Data []struct {
Price float64 `json:"price,string"`
Time bybitTimeMilliSec `json:"time"`
Quantity float64 `json:"qty,string"`
IsBuyerMaker bool `json:"isBuyerMaker"`
} `json:"result"`
Error
}{}
params := url.Values{}
params.Set("symbol", symbol)
strLimit := "60" // default limit
if limit > 0 && limit < 60 {
strLimit = strconv.FormatInt(limit, 10)
}
params.Set("limit", strLimit)
path := common.EncodeURLValues(bybitRecentTrades, params)
err := by.SendHTTPRequest(ctx, exchange.RestSpot, path, publicSpotRate, &resp)
if err != nil {
return nil, err
}
trades := make([]TradeItem, len(resp.Data))
for x := range resp.Data {
var tradeSide string
if resp.Data[x].IsBuyerMaker {
tradeSide = order.Buy.String()
} else {
tradeSide = order.Sell.String()
}
trades[x] = TradeItem{
CurrencyPair: symbol,
Price: resp.Data[x].Price,
Side: tradeSide,
Volume: resp.Data[x].Quantity,
Time: resp.Data[x].Time.Time(),
}
}
return trades, nil
}
// GetKlines data returns the kline data for a specific symbol. Limitation: It only returns latest 3500 candles irrespective of interval passed
func (by *Bybit) GetKlines(ctx context.Context, symbol, period string, limit int64, start, end time.Time) ([]KlineItem, error) {
resp := struct {
Data [][]interface{} `json:"result"`
Error
}{}
v := url.Values{}
v.Add("symbol", symbol)
v.Add("interval", period)
if !start.IsZero() {
v.Add("startTime", strconv.FormatInt(start.UnixMilli(), 10))
}
if !end.IsZero() {
v.Add("endTime", strconv.FormatInt(end.UnixMilli(), 10))
}
if limit <= 0 || limit > 1000 {
limit = 1000
}
v.Add("limit", strconv.FormatInt(limit, 10))
path := common.EncodeURLValues(bybitCandlestickChart, v)
if err := by.SendHTTPRequest(ctx, exchange.RestSpot, path, publicSpotRate, &resp); err != nil {
return nil, err
}
klines := make([]KlineItem, len(resp.Data))
for x := range resp.Data {
if len(resp.Data[x]) != 11 {
return klines, fmt.Errorf("%v GetKlines: invalid response, array length not as expected, check api docs for updates", by.Name)
}
var kline KlineItem
var err error
startTime, ok := resp.Data[x][0].(float64)
if !ok {
return klines, fmt.Errorf("%v GetKlines: %w for StartTime", by.Name, errTypeAssert)
}
kline.StartTime = time.UnixMilli(int64(startTime))
open, ok := resp.Data[x][1].(string)
if !ok {
return klines, fmt.Errorf("%v GetKlines: %w for Open", by.Name, errTypeAssert)
}
kline.Open, err = strconv.ParseFloat(open, 64)
if err != nil {
return klines, fmt.Errorf("%v GetKlines: %w for Open", by.Name, errStrParsing)
}
high, ok := resp.Data[x][2].(string)
if !ok {
return klines, fmt.Errorf("%v GetKlines: %w for High", by.Name, errTypeAssert)
}
kline.High, err = strconv.ParseFloat(high, 64)
if err != nil {
return klines, fmt.Errorf("%v GetKlines: %w for High", by.Name, errStrParsing)
}
low, ok := resp.Data[x][3].(string)
if !ok {
return klines, fmt.Errorf("%v GetKlines: %w for Low", by.Name, errTypeAssert)
}
kline.Low, err = strconv.ParseFloat(low, 64)
if err != nil {
return klines, fmt.Errorf("%v GetKlines: %w for Low", by.Name, errStrParsing)
}
c, ok := resp.Data[x][4].(string)
if !ok {
return klines, fmt.Errorf("%v GetKlines: %w for Close", by.Name, errTypeAssert)
}
kline.Close, err = strconv.ParseFloat(c, 64)
if err != nil {
return klines, fmt.Errorf("%v GetKlines: %w for Close", by.Name, errStrParsing)
}
volume, ok := resp.Data[x][5].(string)
if !ok {
return klines, fmt.Errorf("%v GetKlines: %w for Volume", by.Name, errTypeAssert)
}
kline.Volume, err = strconv.ParseFloat(volume, 64)
if err != nil {
return klines, fmt.Errorf("%v GetKlines: %w for Volume", by.Name, errStrParsing)
}
endTime, ok := resp.Data[x][6].(float64)
if !ok {
return klines, fmt.Errorf("%v GetKlines: %w for EndTime", by.Name, errTypeAssert)
}
kline.EndTime = time.UnixMilli(int64(endTime))
quoteAssetVolume, ok := resp.Data[x][7].(string)
if !ok {
return klines, fmt.Errorf("%v GetKlines: %w for QuoteAssetVolume", by.Name, errTypeAssert)
}
kline.QuoteAssetVolume, err = strconv.ParseFloat(quoteAssetVolume, 64)
if err != nil {
return klines, fmt.Errorf("%v GetKlines: %w for QuoteAssetVolume", by.Name, errStrParsing)
}
tradesCount, ok := resp.Data[x][8].(float64)
if !ok {
return klines, fmt.Errorf("%v GetKlines: %w for TradesCount", by.Name, errTypeAssert)
}
kline.TradesCount = int64(tradesCount)
takerBaseVolume, ok := resp.Data[x][9].(string)
if !ok {
return klines, fmt.Errorf("%v GetKlines: %w for TakerBaseVolume", by.Name, errTypeAssert)
}
kline.TakerBaseVolume, err = strconv.ParseFloat(takerBaseVolume, 64)
if err != nil {
return klines, fmt.Errorf("%v GetKlines: %w for TakerBaseVolume", by.Name, errStrParsing)
}
takerQuoteVolume, ok := resp.Data[x][10].(string)
if !ok {
return klines, fmt.Errorf("%v GetKlines: %w for TakerQuoteVolume", by.Name, errTypeAssert)
}
kline.TakerQuoteVolume, err = strconv.ParseFloat(takerQuoteVolume, 64)
if err != nil {
return klines, fmt.Errorf("%v GetKlines: %w for TakerQuoteVolume", by.Name, errStrParsing)
}
klines[x] = kline
}
return klines, nil
}
// Get24HrsChange returns price change statistics for the last 24 hours
// If symbol not passed then it will return price change statistics for all pairs
func (by *Bybit) Get24HrsChange(ctx context.Context, symbol string) ([]PriceChangeStats, error) {
type priceChangeStats struct {
Time bybitTimeMilliSec `json:"time"`
Symbol string `json:"symbol"`
BestBidPrice float64 `json:"bestBidPrice,string"`
BestAskPrice float64 `json:"bestAskPrice,string"`
LastPrice float64 `json:"lastPrice,string"`
OpenPrice float64 `json:"openPrice,string"`
HighPrice float64 `json:"highPrice,string"`
LowPrice float64 `json:"lowPrice,string"`
Volume float64 `json:"volume,string"`
QuoteVolume float64 `json:"quoteVolume,string"`
}
var stats []PriceChangeStats
if symbol != "" {
resp := struct {
Data priceChangeStats `json:"result"`
Error
}{}
params := url.Values{}
params.Set("symbol", symbol)
path := common.EncodeURLValues(bybit24HrsChange, params)
err := by.SendHTTPRequest(ctx, exchange.RestSpot, path, publicSpotRate, &resp)
if err != nil {
return nil, err
}
stats = append(stats, PriceChangeStats{
resp.Data.Time.Time(),
resp.Data.Symbol,
resp.Data.BestAskPrice,
resp.Data.BestAskPrice,
resp.Data.LastPrice,
resp.Data.OpenPrice,
resp.Data.HighPrice,
resp.Data.LowPrice,
resp.Data.Volume,
resp.Data.QuoteVolume,
})
} else {
resp := struct {
Data []priceChangeStats `json:"result"`
Error
}{}
err := by.SendHTTPRequest(ctx, exchange.RestSpot, bybit24HrsChange, publicSpotRate, &resp)
if err != nil {
return nil, err
}
for x := range resp.Data {
stats = append(stats, PriceChangeStats{
resp.Data[x].Time.Time(),
resp.Data[x].Symbol,
resp.Data[x].BestAskPrice,
resp.Data[x].BestAskPrice,
resp.Data[x].LastPrice,
resp.Data[x].OpenPrice,
resp.Data[x].HighPrice,
resp.Data[x].LowPrice,
resp.Data[x].Volume,
resp.Data[x].QuoteVolume,
})
}
}
return stats, nil
}
// GetLastTradedPrice returns last trading price
// If symbol not passed then it will return last trading price for all pairs
func (by *Bybit) GetLastTradedPrice(ctx context.Context, symbol string) ([]LastTradePrice, error) {
var lastTradePrices []LastTradePrice
if symbol != "" {
resp := struct {
Data LastTradePrice `json:"result"`
Error
}{}
params := url.Values{}
params.Set("symbol", symbol)
path := common.EncodeURLValues(bybitLastTradedPrice, params)
err := by.SendHTTPRequest(ctx, exchange.RestSpot, path, publicSpotRate, &resp)
if err != nil {
return nil, err
}
lastTradePrices = append(lastTradePrices, LastTradePrice{
resp.Data.Symbol,
resp.Data.Price,
})
} else {
resp := struct {
Data []LastTradePrice `json:"result"`
Error
}{}
err := by.SendHTTPRequest(ctx, exchange.RestSpot, bybitLastTradedPrice, publicSpotRate, &resp)
if err != nil {
return nil, err
}
for x := range resp.Data {
lastTradePrices = append(lastTradePrices, LastTradePrice{
resp.Data[x].Symbol,
resp.Data[x].Price,
})
}
}
return lastTradePrices, nil
}
// GetBestBidAskPrice returns best BID and ASK price
// If symbol not passed then it will return best BID and ASK price for all pairs
func (by *Bybit) GetBestBidAskPrice(ctx context.Context, symbol string) ([]TickerData, error) {
type bestTicker struct {
Symbol string `json:"symbol"`
BidPrice float64 `json:"bidPrice,string"`
BidQuantity float64 `json:"bidQty,string"`
AskPrice float64 `json:"askPrice,string"`
AskQuantity float64 `json:"askQty,string"`
Time bybitTimeMilliSec `json:"time"`
}
var tickers []TickerData
if symbol != "" {
resp := struct {
Data bestTicker `json:"result"`
Error
}{}
params := url.Values{}
params.Set("symbol", symbol)
path := common.EncodeURLValues(bybitBestBidAskPrice, params)
err := by.SendHTTPRequest(ctx, exchange.RestSpot, path, publicSpotRate, &resp)
if err != nil {
return nil, err
}
tickers = append(tickers, TickerData{
resp.Data.Symbol,
resp.Data.BidPrice,
resp.Data.BidQuantity,
resp.Data.AskPrice,
resp.Data.AskQuantity,
resp.Data.Time.Time(),
})
} else {
resp := struct {
Data []bestTicker `json:"result"`
Error
}{}
err := by.SendHTTPRequest(ctx, exchange.RestSpot, bybitBestBidAskPrice, publicSpotRate, &resp)
if err != nil {
return nil, err
}
for x := range resp.Data {
tickers = append(tickers, TickerData{
resp.Data[x].Symbol,
resp.Data[x].BidPrice,
resp.Data[x].BidQuantity,
resp.Data[x].AskPrice,
resp.Data[x].AskQuantity,
resp.Data[x].Time.Time(),
})
}
}
return tickers, nil
}
// CreatePostOrder create and post order
func (by *Bybit) CreatePostOrder(ctx context.Context, o *PlaceOrderRequest) (*PlaceOrderResponse, error) {
if o == nil {
return nil, errInvalidOrderRequest
}
params := url.Values{}
params.Set("symbol", o.Symbol)
params.Set("qty", strconv.FormatFloat(o.Quantity, 'f', -1, 64))
params.Set("side", o.Side)
params.Set("type", o.TradeType)
if o.TimeInForce != "" {
params.Set("timeInForce", o.TimeInForce)
}
if (o.TradeType == BybitRequestParamsOrderLimit || o.TradeType == BybitRequestParamsOrderLimitMaker) && o.Price == 0 {
return nil, errMissingPrice
}
if o.Price != 0 {
params.Set("price", strconv.FormatFloat(o.Price, 'f', -1, 64))
}
if o.OrderLinkID != "" {
params.Set("orderLinkId", o.OrderLinkID)
}
resp := struct {
Data PlaceOrderResponse `json:"result"`
Error
}{}
return &resp.Data, by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, bybitSpotOrder, params, nil, &resp, privateSpotRate)
}
// QueryOrder returns order data based upon orderID or orderLinkID
func (by *Bybit) QueryOrder(ctx context.Context, orderID, orderLinkID string) (*QueryOrderResponse, error) {
if orderID == "" && orderLinkID == "" {
return nil, errOrderOrOrderLinkIDMissing
}
params := url.Values{}
if orderID != "" {
params.Set("orderId", orderID)
}
if orderLinkID != "" {
params.Set("orderLinkId", orderLinkID)
}
resp := struct {
Data QueryOrderResponse `json:"result"`
Error
}{}
return &resp.Data, by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, bybitSpotOrder, params, nil, &resp, privateSpotRate)
}
// CancelExistingOrder cancels existing order based upon orderID or orderLinkID
func (by *Bybit) CancelExistingOrder(ctx context.Context, orderID, orderLinkID string) (*CancelOrderResponse, error) {
if orderID == "" && orderLinkID == "" {
return nil, errOrderOrOrderLinkIDMissing
}
params := url.Values{}
if orderID != "" {
params.Set("orderId", orderID)
}
if orderLinkID != "" {
params.Set("orderLinkId", orderLinkID)
}
resp := struct {
Data CancelOrderResponse `json:"result"`
Error
}{}
err := by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, bybitSpotOrder, params, nil, &resp, privateSpotRate)
if err != nil {
return nil, err
}
// In case open order is cancelled, this endpoint return status as NEW whereas if we try to cancel a already cancelled order then it's status is returned as CANCELED without any error. So this check is added to prevent this obscurity.
if resp.Data.Status == "CANCELED" {
return nil, fmt.Errorf("%s order already cancelled", resp.Data.OrderID)
}
return &resp.Data, nil
}
// FastCancelExistingOrder cancels existing order based upon orderID or orderLinkID
func (by *Bybit) FastCancelExistingOrder(ctx context.Context, symbol, orderID, orderLinkID string) (bool, error) {
resp := struct {
Data struct {
IsCancelled bool `json:"isCancelled"`
} `json:"result"`
Error
}{}
if orderID == "" && orderLinkID == "" {
return resp.Data.IsCancelled, errOrderOrOrderLinkIDMissing
}
params := url.Values{}
if symbol == "" {
return resp.Data.IsCancelled, errSymbolMissing
}
params.Set("symbolId", symbol)
if orderID != "" {
params.Set("orderId", orderID)
}
if orderLinkID != "" {
params.Set("orderLinkId", orderLinkID)
}
return resp.Data.IsCancelled, by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, bybitFastCancelSpotOrder, params, nil, &resp, privateSpotRate)
}
// BatchCancelOrder cancels orders in batch based upon symbol, side or orderType
func (by *Bybit) BatchCancelOrder(ctx context.Context, symbol, side, orderTypes string) (bool, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if side != "" {
params.Set("side", side)
}
if orderTypes != "" {
params.Set("orderTypes", orderTypes)
}
resp := struct {
Result struct {
Success bool `json:"success"`
} `json:"result"`
Error
}{}
return resp.Result.Success, by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, bybitBatchCancelSpotOrder, params, nil, &resp, privateSpotRate)
}
// BatchFastCancelOrder cancels orders in batch based upon symbol, side or orderType
func (by *Bybit) BatchFastCancelOrder(ctx context.Context, symbol, side, orderTypes string) (bool, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if side != "" {
params.Set("side", side)
}
if orderTypes != "" {
params.Set("orderTypes", orderTypes)
}
resp := struct {
Result struct {
Success bool `json:"success"`
} `json:"result"`
Error
}{}
return resp.Result.Success, by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, bybitFastBatchCancelSpotOrder, params, nil, &resp, privateSpotRate)
}
// BatchCancelOrderByIDs cancels orders in batch based on comma separated order id's
func (by *Bybit) BatchCancelOrderByIDs(ctx context.Context, orderIDs []string) (bool, error) {
params := url.Values{}
if len(orderIDs) == 0 {
return false, errEmptyOrderIDs
}
params.Set("orderIds", strings.Join(orderIDs, ","))
resp := struct {
Result struct {
Success bool `json:"success"`
} `json:"result"`
Error
}{}
return resp.Result.Success, by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, bybitFastBatchCancelSpotOrder, params, nil, &resp, privateSpotRate)
}
// ListOpenOrders returns all open orders
func (by *Bybit) ListOpenOrders(ctx context.Context, symbol, orderID string, limit int64) ([]QueryOrderResponse, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if orderID != "" {
params.Set("orderId", orderID)
}
if limit != 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
resp := struct {
Data []QueryOrderResponse `json:"result"`
Error
}{}
return resp.Data, by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, bybitOpenOrder, params, nil, &resp, privateSpotRate)
}
// GetPastOrders returns all past orders from history
func (by *Bybit) GetPastOrders(ctx context.Context, symbol, orderID string, limit int64, startTime, endTime time.Time) ([]QueryOrderResponse, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if orderID != "" {
params.Set("orderId", orderID)
}
if limit != 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if !startTime.IsZero() {
params.Set("startTime", strconv.FormatInt(startTime.UnixMilli(), 10))
}
if !endTime.IsZero() {
params.Set("endTime", strconv.FormatInt(endTime.UnixMilli(), 10))
}
resp := struct {
Data []QueryOrderResponse `json:"result"`
Error
}{}
return resp.Data, by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, bybitPastOrder, params, nil, &resp, privateSpotRate)
}
// GetTradeHistory returns user trades
func (by *Bybit) GetTradeHistory(ctx context.Context, limit int64, symbol, fromID, toID, orderID string, startTime, endTime time.Time) ([]HistoricalTrade, error) {
params := url.Values{}
if symbol != "" {
params.Set("symbol", symbol)
}
if limit != 0 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if fromID != "" {
params.Set("fromTicketId", fromID)
}
if toID != "" {
params.Set("toTicketId", toID)
}
if orderID != "" {
params.Set("orderId", orderID)
}
if !startTime.IsZero() {
params.Set("startTime", strconv.FormatInt(startTime.UnixMilli(), 10))
}
if !endTime.IsZero() {
params.Set("endTime", strconv.FormatInt(endTime.UnixMilli(), 10))
}
resp := struct {
Data []HistoricalTrade `json:"result"`
Error
}{}
return resp.Data, by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, bybitTradeHistory, params, nil, &resp, privateSpotRate)
}
// GetWalletBalance returns user wallet balance
func (by *Bybit) GetWalletBalance(ctx context.Context) ([]Balance, error) {
resp := struct {
Data struct {
Balances []Balance `json:"balances"`
} `json:"result"`
Error
}{}
return resp.Data.Balances, by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, bybitWalletBalance, url.Values{}, nil, &resp, privateSpotRate)
}
// GetSpotServerTime returns server time
func (by *Bybit) GetSpotServerTime(ctx context.Context) (time.Time, error) {
resp := struct {
Result struct {
ServerTime int64 `json:"serverTime"`
} `json:"result"`
Error
}{}
err := by.SendHTTPRequest(ctx, exchange.RestSpot, bybitServerTime, publicSpotRate, &resp)
return time.UnixMilli(resp.Result.ServerTime), err
}
// GetDepositAddressForCurrency returns deposit wallet address based upon the coin.
func (by *Bybit) GetDepositAddressForCurrency(ctx context.Context, coin string) (DepositWalletInfo, error) {
resp := struct {
Result DepositWalletInfo `json:"result"`
Error
}{}
params := url.Values{}
if coin == "" {
return resp.Result, errInvalidCoin
}
params.Set("coin", strings.ToUpper(coin))
return resp.Result, by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, bybitGetDepositAddress, params, nil, &resp, publicSpotRate)
}
// WithdrawFund creates request for fund withdrawal.
func (by *Bybit) WithdrawFund(ctx context.Context, coin, chain, address, tag, amount string) (string, error) {
resp := struct {
Data struct {
ID string `json:"id"`
} `json:"result"`
Error
}{}
params := make(map[string]interface{})
params["coin"] = coin
params["chain"] = chain
params["address"] = address
params["amount"] = amount
if tag != "" {
params["tag"] = tag
}
return resp.Data.ID, by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, bybitWithdrawFund, nil, params, &resp, privateSpotRate)
}
// SendHTTPRequest sends an unauthenticated request
func (by *Bybit) SendHTTPRequest(ctx context.Context, ePath exchange.URL, path string, f request.EndpointLimit, result UnmarshalTo) error {
endpointPath, err := by.API.Endpoints.GetURL(ePath)
if err != nil {
return err
}
err = by.SendPayload(ctx, f, func() (*request.Item, error) {
return &request.Item{
Method: http.MethodGet,
Path: endpointPath + path,
Result: result,
Verbose: by.Verbose,
HTTPDebugging: by.HTTPDebugging,
HTTPRecording: by.HTTPRecording}, nil
})
if err != nil {
return err
}
return result.GetError()
}
// SendAuthHTTPRequest sends an authenticated HTTP request
// If payload is non-nil then request is considered to be JSON
func (by *Bybit) SendAuthHTTPRequest(ctx context.Context, ePath exchange.URL, method, path string, params url.Values, jsonPayload map[string]interface{}, result UnmarshalTo, f request.EndpointLimit) error {
creds, err := by.GetCredentials(ctx)
if err != nil {
return err
}
if result == nil {
result = &Error{}
}
endpointPath, err := by.API.Endpoints.GetURL(ePath)
if err != nil {
return err
}
if params == nil && jsonPayload == nil {
params = url.Values{}
}
if jsonPayload != nil {
jsonPayload["recvWindow"] = defaultRecvWindow
} else if params.Get("recvWindow") == "" {
params.Set("recvWindow", defaultRecvWindow)
}
err = by.SendPayload(ctx, f, func() (*request.Item, error) {
var (
payload []byte
hmacSignedStr string
)
headers := make(map[string]string)
if jsonPayload != nil {
headers["Content-Type"] = "application/json"
jsonPayload["timestamp"] = strconv.FormatInt(time.Now().UnixMilli(), 10)
jsonPayload["api_key"] = creds.Key
hmacSignedStr, err = getJSONRequestSignature(jsonPayload, creds.Secret)
if err != nil {
return nil, err
}
jsonPayload["sign"] = hmacSignedStr
payload, err = json.Marshal(jsonPayload)
if err != nil {
return nil, err
}
} else {
params.Set("timestamp", strconv.FormatInt(time.Now().UnixMilli(), 10))
params.Set("api_key", creds.Key)
hmacSignedStr, err = getSign(params.Encode(), creds.Secret)
if err != nil {
return nil, err
}
headers["Content-Type"] = "application/x-www-form-urlencoded"
switch method {
case http.MethodPost:
params.Set("sign", hmacSignedStr)
payload = []byte(params.Encode())
default:
path = common.EncodeURLValues(path, params)
path += "&sign=" + hmacSignedStr
}
}
return &request.Item{
Method: method,
Path: endpointPath + path,
Headers: headers,
Body: bytes.NewBuffer(payload),
Result: &result,
AuthRequest: true,
Verbose: by.Verbose,
HTTPDebugging: by.HTTPDebugging,
HTTPRecording: by.HTTPRecording}, nil
})
if err != nil {
return err
}
return result.GetError()
}
// Error defines all error information for each request
type Error struct {
ReturnCode int64 `json:"ret_code"`
ReturnMsg string `json:"ret_msg"`
ExtCode string `json:"ext_code"`
ExtMsg string `json:"ext_info"`
}
// GetError checks and returns an error if it is supplied.
func (e Error) GetError() error {
if e.ReturnCode != 0 && e.ReturnMsg != "" {
return errors.New(e.ReturnMsg)
}
if e.ExtCode != "" && e.ExtMsg != "" {
return errors.New(e.ExtMsg)
}
return nil
}
func getSide(side string) order.Side {
switch side {
case sideBuy:
return order.Buy
case sideSell:
return order.Sell
default:
return order.UnknownSide
}
}
func getTradeType(tradeType string) order.Type {
switch tradeType {
case BybitRequestParamsOrderLimit:
return order.Limit
case BybitRequestParamsOrderMarket:
return order.Market
case BybitRequestParamsOrderLimitMaker:
return order.Limit
default:
return order.UnknownType
}
}
func getOrderStatus(status string) order.Status {
switch status {
case "NEW":
return order.New
case "PARTIALLY_FILLED":
return order.PartiallyFilled
case "FILLED":
return order.Filled
case "CANCELED":
return order.Cancelled
case "PENDING_CANCEL":
return order.PendingCancel
case "PENDING_NEW":
return order.Pending
case "REJECTED":
return order.Rejected
default:
return order.UnknownStatus
}
}
func getJSONRequestSignature(payload map[string]interface{}, secret string) (string, error) {
payloadArr := make([]string, len(payload))
var i int
for p := range payload {
payloadArr[i] = p
i++
}
sort.Strings(payloadArr)
var signStr string
for _, key := range payloadArr {
if value, found := payload[key]; found {
if v, ok := value.(string); ok {
signStr += key + "=" + v + "&"
}
} else {
return "", errors.New("non-string payload parameter not expected")
}
}
return getSign(signStr[:len(signStr)-1], secret)
}
func getSign(sign, secret string) (string, error) {
hmacSigned, err := crypto.GetHMAC(crypto.HashSHA256, []byte(sign), []byte(secret))
if err != nil {
return "", err
}
return crypto.HexEncodeToString(hmacSigned), nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,757 @@
package bybit
import (
"context"
"net/http"
"net/url"
"strconv"
"time"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
const (
// auth endpoint
futuresCreateOrder = "/futures/private/order/create"
futuresGetActiveOrders = "/futures/private/order/list"
futuresCancelActiveOrder = "/futures/private/order/cancel"
futuresCancelAllActiveOrders = "/futures/private/order/cancelAll"
futuresReplaceActiveOrder = "/futures/private/order/replace"
futuresGetActiveRealtimeOrders = "/futures/private/order"
futuresCreateConditionalOrder = "/futures/private/stop-order/create"
futuresGetConditionalOrders = "/futures/private/stop-order/list"
futuresCancelConditionalOrder = "/futures/private/stop-order/cancel"
futuresCancelAllConditionalOrders = "/futures/private/stop-order/cancelAll"
futuresReplaceConditionalOrder = "/futures/private/stop-order/replace"
futuresGetConditionalRealtimeOrders = "/futures/private/stop-order"
futuresPosition = "/futures/private/position/list"
futuresUpdateMargin = "/futures/private/position/change-position-margin"
futuresSetTradingStop = "/futures/private/position/trading-stop"
futuresSetLeverage = "/futures/private/position/leverage/save"
futuresSwitchPositionMode = "/futures/private/position/switch-mode"
futuresSwitchPosition = "/futures/private/tpsl/switch-mode"
futuresSwitchMargin = "/futures/private/position/switch-isolated"
futuresGetTrades = "/futures/private/execution/list"
futuresGetClosedTrades = "/futures/private/trade/closed-pnl/list"
futuresSetRiskLimit = "/futures/private/position/risk-limit"
)
// CreateFuturesOrder sends a new futures order to the exchange
func (by *Bybit) CreateFuturesOrder(ctx context.Context, positionMode int64, symbol currency.Pair, side, orderType, timeInForce,
orderLinkID, takeProfitTriggerBy, stopLossTriggerBy string,
quantity, price, takeProfit, stopLoss float64, closeOnTrigger, reduceOnly bool) (FuturesOrderDataResp, error) {
resp := struct {
Result FuturesOrderDataResp `json:"result"`
Error
}{}
params := url.Values{}
if positionMode < 0 || positionMode > 2 {
return resp.Result, errInvalidPositionMode
}
params.Set("position_idx", strconv.FormatInt(positionMode, 10))
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Result, err
}
params.Set("symbol", symbolValue)
params.Set("side", side)
params.Set("order_type", orderType)
if quantity <= 0 {
return resp.Result, errInvalidQuantity
}
params.Set("qty", strconv.FormatFloat(quantity, 'f', -1, 64))
if price != 0 {
params.Set("price", strconv.FormatFloat(price, 'f', -1, 64))
}
if timeInForce == "" {
return resp.Result, errInvalidTimeInForce
}
params.Set("time_in_force", timeInForce)
if closeOnTrigger {
params.Set("close_on_trigger", "true")
}
if orderLinkID != "" {
params.Set("order_link_id", orderLinkID)
}
if takeProfit != 0 {
params.Set("take_profit", strconv.FormatFloat(takeProfit, 'f', -1, 64))
}
if stopLoss != 0 {
params.Set("stop_loss", strconv.FormatFloat(stopLoss, 'f', -1, 64))
}
if takeProfitTriggerBy != "" {
params.Set("tp_trigger_by", takeProfitTriggerBy)
}
if stopLossTriggerBy != "" {
params.Set("sl_trigger_by", stopLossTriggerBy)
}
if reduceOnly {
params.Set("reduce_only", "true")
}
return resp.Result, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresCreateOrder, params, nil, &resp, futuresCreateOrderRate)
}
// GetActiveFuturesOrders gets list of futures active orders
func (by *Bybit) GetActiveFuturesOrders(ctx context.Context, symbol currency.Pair, orderStatus, direction, cursor string, limit int64) ([]FuturesActiveOrder, error) {
resp := struct {
Result struct {
Data []FuturesActiveOrder `json:"data"`
Cursor string `json:"cursor"`
} `json:"result"`
Error
}{}
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Result.Data, err
}
params.Set("symbol", symbolValue)
if orderStatus != "" {
params.Set("order_status", orderStatus)
}
if direction != "" {
params.Set("direction", direction)
}
if limit > 0 && limit <= 50 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if cursor != "" {
params.Set("cursor", cursor)
}
return resp.Result.Data, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodGet, futuresGetActiveOrders, params, nil, &resp, futuresGetActiveOrderRate)
}
// CancelActiveFuturesOrders cancels futures unfilled or partially filled orders
func (by *Bybit) CancelActiveFuturesOrders(ctx context.Context, symbol currency.Pair, orderID, orderLinkID string) (FuturesOrderCancelResp, error) {
resp := struct {
Result FuturesOrderCancelResp `json:"result"`
Error
}{}
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Result, err
}
params.Set("symbol", symbolValue)
if orderID == "" && orderLinkID == "" {
return resp.Result, errOrderOrOrderLinkIDMissing
}
if orderID != "" {
params.Set("order_id", orderID)
}
if orderLinkID != "" {
params.Set("order_link_id", orderLinkID)
}
return resp.Result, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresCancelActiveOrder, params, nil, &resp, futuresCancelOrderRate)
}
// CancelAllActiveFuturesOrders cancels all futures unfilled or partially filled orders
func (by *Bybit) CancelAllActiveFuturesOrders(ctx context.Context, symbol currency.Pair) ([]FuturesCancelOrderData, error) {
resp := struct {
Result []FuturesCancelOrderData `json:"result"`
Error
}{}
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Result, err
}
params.Set("symbol", symbolValue)
return resp.Result, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresCancelAllActiveOrders, params, nil, &resp, futuresCancelAllOrderRate)
}
// ReplaceActiveFuturesOrders modify unfilled or partially filled orders
func (by *Bybit) ReplaceActiveFuturesOrders(ctx context.Context, symbol currency.Pair, orderID, orderLinkID, takeProfitTriggerBy, stopLossTriggerBy string,
updatedQty, updatedPrice, takeProfitPrice, stopLossPrice float64) (string, error) {
resp := struct {
Result struct {
OrderID string `json:"order_id"`
} `json:"result"`
Error
}{}
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return "", err
}
params.Set("symbol", symbolValue)
if orderID == "" && orderLinkID == "" {
return "", errOrderOrOrderLinkIDMissing
}
if orderID != "" {
params.Set("order_id", orderID)
}
if orderLinkID != "" {
params.Set("order_link_id", orderLinkID)
}
if updatedQty != 0 {
params.Set("p_r_qty", strconv.FormatFloat(updatedQty, 'f', -1, 64))
}
if updatedPrice != 0 {
params.Set("p_r_price", strconv.FormatFloat(updatedPrice, 'f', -1, 64))
}
if takeProfitPrice != 0 {
params.Set("take_profit", strconv.FormatFloat(takeProfitPrice, 'f', -1, 64))
}
if stopLossPrice != 0 {
params.Set("stop_loss", strconv.FormatFloat(stopLossPrice, 'f', -1, 64))
}
if takeProfitTriggerBy != "" {
params.Set("tp_trigger_by", takeProfitTriggerBy)
}
if stopLossTriggerBy != "" {
params.Set("sl_trigger_by", stopLossTriggerBy)
}
return resp.Result.OrderID, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresReplaceActiveOrder, params, nil, &resp, futuresReplaceOrderRate)
}
// GetActiveRealtimeOrders query real time order data
func (by *Bybit) GetActiveRealtimeOrders(ctx context.Context, symbol currency.Pair, orderID, orderLinkID string) ([]FuturesActiveRealtimeOrder, error) {
var data []FuturesActiveRealtimeOrder
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return data, err
}
params.Set("symbol", symbolValue)
if orderID != "" {
params.Set("order_id", orderID)
}
if orderLinkID != "" {
params.Set("order_link_id", orderLinkID)
}
if orderID == "" && orderLinkID == "" {
resp := struct {
Result []FuturesActiveRealtimeOrder `json:"result"`
Error
}{}
err = by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodGet, futuresGetActiveRealtimeOrders, params, nil, &resp, futuresGetActiveRealtimeOrderRate)
if err != nil {
return data, err
}
data = append(data, resp.Result...)
} else {
resp := struct {
Result FuturesActiveRealtimeOrder `json:"result"`
Error
}{}
err = by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodGet, futuresGetActiveRealtimeOrders, params, nil, &resp, futuresGetActiveRealtimeOrderRate)
if err != nil {
return data, err
}
data = append(data, resp.Result)
}
return data, nil
}
// CreateConditionalFuturesOrder sends a new conditional futures order to the exchange
func (by *Bybit) CreateConditionalFuturesOrder(ctx context.Context, positionMode int64, symbol currency.Pair, side, orderType, timeInForce,
orderLinkID, takeProfitTriggerBy, stopLossTriggerBy, triggerBy string,
quantity, price, takeProfit, stopLoss, basePrice, stopPrice float64, closeOnTrigger bool) (FuturesConditionalOrderResp, error) {
resp := struct {
Result FuturesConditionalOrderResp `json:"result"`
Error
}{}
params := url.Values{}
if positionMode < 0 || positionMode > 2 {
return resp.Result, errInvalidPositionMode
}
params.Set("position_idx", strconv.FormatInt(positionMode, 10))
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Result, err
}
params.Set("symbol", symbolValue)
params.Set("side", side)
params.Set("order_type", orderType)
if quantity <= 0 {
return resp.Result, errInvalidQuantity
}
params.Set("qty", strconv.FormatFloat(quantity, 'f', -1, 64))
if price != 0 {
params.Set("price", strconv.FormatFloat(price, 'f', -1, 64))
}
if basePrice <= 0 {
return resp.Result, errInvalidBasePrice
}
params.Set("base_price", strconv.FormatFloat(basePrice, 'f', -1, 64))
if stopPrice <= 0 {
return resp.Result, errInvalidStopPrice
}
params.Set("stop_px", strconv.FormatFloat(stopPrice, 'f', -1, 64))
if timeInForce == "" {
return resp.Result, errInvalidTimeInForce
}
params.Set("time_in_force", timeInForce)
if triggerBy != "" {
params.Set("trigger_by", triggerBy)
}
if closeOnTrigger {
params.Set("close_on_trigger", "true")
}
if orderLinkID != "" {
params.Set("order_link_id", orderLinkID)
}
if takeProfit != 0 {
params.Set("take_profit", strconv.FormatFloat(takeProfit, 'f', -1, 64))
}
if stopLoss != 0 {
params.Set("stop_loss", strconv.FormatFloat(stopLoss, 'f', -1, 64))
}
if takeProfitTriggerBy != "" {
params.Set("tp_trigger_by", takeProfitTriggerBy)
}
if stopLossTriggerBy != "" {
params.Set("sl_trigger_by", stopLossTriggerBy)
}
return resp.Result, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresCreateConditionalOrder, params, nil, &resp, futuresCreateConditionalOrderRate)
}
// GetConditionalFuturesOrders gets list of futures conditional orders
func (by *Bybit) GetConditionalFuturesOrders(ctx context.Context, symbol currency.Pair, stopOrderStatus, direction, cursor string, limit int64) ([]FuturesConditionalOrders, error) {
resp := struct {
Result struct {
Result []FuturesConditionalOrders `json:"data"`
Cursor string `json:"cursor"`
} `json:"result"`
Error
}{}
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Result.Result, err
}
params.Set("symbol", symbolValue)
if stopOrderStatus != "" {
params.Set("stop_order_status", stopOrderStatus)
}
if direction != "" {
params.Set("direction", direction)
}
if limit > 0 && limit <= 50 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
if cursor != "" {
params.Set("cursor", cursor)
}
return resp.Result.Result, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodGet, futuresGetConditionalOrders, params, nil, &resp, futuresGetConditionalOrderRate)
}
// CancelConditionalFuturesOrders cancels untriggered conditional orders
func (by *Bybit) CancelConditionalFuturesOrders(ctx context.Context, symbol currency.Pair, stopOrderID, orderLinkID string) (string, error) {
resp := struct {
Result struct {
StopOrderID string `json:"stop_order_id"`
} `json:"result"`
Error
}{}
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return "", err
}
params.Set("symbol", symbolValue)
if stopOrderID == "" && orderLinkID == "" {
return "", errStopOrderOrOrderLinkIDMissing
}
if stopOrderID != "" {
params.Set("stop_order_id", stopOrderID)
}
if orderLinkID != "" {
params.Set("order_link_id", orderLinkID)
}
return resp.Result.StopOrderID, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresCancelConditionalOrder, params, nil, &resp, futuresCancelConditionalOrderRate)
}
// CancelAllConditionalFuturesOrders cancels all untriggered conditional orders
func (by *Bybit) CancelAllConditionalFuturesOrders(ctx context.Context, symbol currency.Pair) ([]FuturesCancelOrderResp, error) {
resp := struct {
Result []FuturesCancelOrderResp `json:"result"`
Error
}{}
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Result, err
}
params.Set("symbol", symbolValue)
return resp.Result, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresCancelAllConditionalOrders, params, nil, &resp, futuresCancelAllConditionalOrderRate)
}
// ReplaceConditionalFuturesOrders modify unfilled or partially filled conditional orders
func (by *Bybit) ReplaceConditionalFuturesOrders(ctx context.Context, symbol currency.Pair, stopOrderID, orderLinkID, takeProfitTriggerBy, stopLossTriggerBy string,
updatedQty, updatedPrice, takeProfitPrice, stopLossPrice, orderTriggerPrice float64) (string, error) {
resp := struct {
Result struct {
OrderID string `json:"stop_order_id"`
} `json:"result"`
Error
}{}
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return "", err
}
params.Set("symbol", symbolValue)
if stopOrderID == "" && orderLinkID == "" {
return "", errStopOrderOrOrderLinkIDMissing
}
if stopOrderID != "" {
params.Set("stop_order_id", stopOrderID)
}
if orderLinkID != "" {
params.Set("order_link_id", orderLinkID)
}
if updatedQty != 0 {
params.Set("p_r_qty", strconv.FormatFloat(updatedQty, 'f', -1, 64))
}
if updatedPrice != 0 {
params.Set("p_r_price", strconv.FormatFloat(updatedPrice, 'f', -1, 64))
}
if orderTriggerPrice != 0 {
params.Set("p_r_trigger_price", strconv.FormatFloat(orderTriggerPrice, 'f', -1, 64))
}
if takeProfitPrice != 0 {
params.Set("take_profit", strconv.FormatFloat(takeProfitPrice, 'f', -1, 64))
}
if stopLossPrice != 0 {
params.Set("stop_loss", strconv.FormatFloat(stopLossPrice, 'f', -1, 64))
}
if takeProfitTriggerBy != "" {
params.Set("tp_trigger_by", takeProfitTriggerBy)
}
if stopLossTriggerBy != "" {
params.Set("sl_trigger_by", stopLossTriggerBy)
}
return resp.Result.OrderID, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresReplaceConditionalOrder, params, nil, &resp, futuresReplaceConditionalOrderRate)
}
// GetConditionalRealtimeOrders query real time conditional order data
func (by *Bybit) GetConditionalRealtimeOrders(ctx context.Context, symbol currency.Pair, stopOrderID, orderLinkID string) ([]FuturesConditionalRealtimeOrder, error) {
var data []FuturesConditionalRealtimeOrder
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return data, err
}
params.Set("symbol", symbolValue)
if stopOrderID != "" {
params.Set("stop_order_id", stopOrderID)
}
if orderLinkID != "" {
params.Set("order_link_id", orderLinkID)
}
if stopOrderID == "" && orderLinkID == "" {
resp := struct {
Result []FuturesConditionalRealtimeOrder `json:"result"`
Error
}{}
err = by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodGet, futuresGetConditionalRealtimeOrders, params, nil, &resp, futuresGetConditionalRealtimeOrderRate)
if err != nil {
return data, err
}
data = append(data, resp.Result...)
} else {
resp := struct {
Result FuturesConditionalRealtimeOrder `json:"result"`
Error
}{}
err = by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodGet, futuresGetConditionalRealtimeOrders, params, nil, &resp, futuresGetConditionalRealtimeOrderRate)
if err != nil {
return data, err
}
data = append(data, resp.Result)
}
return data, nil
}
// GetPositions returns list of user positions
func (by *Bybit) GetPositions(ctx context.Context, symbol currency.Pair) ([]PositionResp, error) {
params := url.Values{}
resp := struct {
Result []struct {
Data PositionResp `json:"data"`
IsValid bool `json:"is_valid"`
} `json:"result"`
Error
}{}
if !symbol.IsEmpty() {
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return nil, err
}
params.Set("symbol", symbolValue)
}
err := by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodGet, futuresPosition, params, nil, &resp, futuresPositionRate)
if err != nil {
return nil, err
}
data := make([]PositionResp, len(resp.Result))
for x := range resp.Result {
data[x] = resp.Result[x].Data
}
return data, nil
}
// SetMargin updates margin
func (by *Bybit) SetMargin(ctx context.Context, positionMode int64, symbol currency.Pair, margin string) (float64, error) {
resp := struct {
Result float64 `json:"result"`
Error
}{}
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Result, err
}
params.Set("symbol", symbolValue)
if positionMode < 0 || positionMode > 2 {
return resp.Result, errInvalidPositionMode
}
params.Set("position_idx", strconv.FormatInt(positionMode, 10))
if margin == "" {
return resp.Result, errInvalidMargin
}
params.Set("margin", margin)
return resp.Result, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresUpdateMargin, params, nil, &resp, futuresUpdateMarginRate)
}
// SetTradingAndStop sets take profit, stop loss, and trailing stop for your open position
func (by *Bybit) SetTradingAndStop(ctx context.Context, positionMode int64, symbol currency.Pair, takeProfit, stopLoss, trailingStop, newTrailingActive, stopLossQty, takeProfitQty float64, takeProfitTriggerBy, stopLossTriggerBy string) (SetTradingAndStopResp, error) {
resp := struct {
Result SetTradingAndStopResp `json:"result"`
Error
}{}
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Result, err
}
params.Set("symbol", symbolValue)
if positionMode < 0 || positionMode > 2 {
return resp.Result, errInvalidPositionMode
}
params.Set("position_idx", strconv.FormatInt(positionMode, 10))
if takeProfit >= 0 {
params.Set("take_profit", strconv.FormatFloat(takeProfit, 'f', -1, 64))
}
if stopLoss >= 0 {
params.Set("stop_loss", strconv.FormatFloat(stopLoss, 'f', -1, 64))
}
if trailingStop >= 0 {
params.Set("trailing_stop", strconv.FormatFloat(trailingStop, 'f', -1, 64))
}
if newTrailingActive != 0 {
params.Set("new_trailing_active", strconv.FormatFloat(newTrailingActive, 'f', -1, 64))
}
if stopLossQty != 0 {
params.Set("sl_size", strconv.FormatFloat(stopLossQty, 'f', -1, 64))
}
if takeProfitQty != 0 {
params.Set("tp_size", strconv.FormatFloat(takeProfitQty, 'f', -1, 64))
}
if takeProfitTriggerBy != "" {
params.Set("tp_trigger_by", takeProfitTriggerBy)
}
if stopLossTriggerBy != "" {
params.Set("sl_trigger_by", stopLossTriggerBy)
}
return resp.Result, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresSetTradingStop, params, nil, &resp, futuresSetTradingStopRate)
}
// SetLeverage sets leverage
func (by *Bybit) SetLeverage(ctx context.Context, symbol currency.Pair, buyLeverage, sellLeverage float64) (float64, error) {
resp := struct {
Result float64 `json:"result"`
Error
}{}
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Result, err
}
params.Set("symbol", symbolValue)
params.Set("buy_leverage", strconv.FormatFloat(buyLeverage, 'f', -1, 64))
params.Set("sell_leverage", strconv.FormatFloat(sellLeverage, 'f', -1, 64))
return resp.Result, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresSetLeverage, params, nil, &resp, futuresSetLeverageRate)
}
// ChangePositionMode switches mode between One-Way or Hedge Mode
func (by *Bybit) ChangePositionMode(ctx context.Context, symbol currency.Pair, mode int64) error {
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return err
}
params.Set("symbol", symbolValue)
params.Set("mode", strconv.FormatInt(mode, 10))
return by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresSwitchPositionMode, params, nil, nil, futuresSwitchPositionModeRate)
}
// ChangeMode switches mode between full or partial position
func (by *Bybit) ChangeMode(ctx context.Context, symbol currency.Pair, takeProfitStopLoss string) (string, error) {
resp := struct {
Result struct {
Mode string `json:"tp_sl_mode"`
} `json:"result"`
Error
}{}
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Result.Mode, err
}
params.Set("symbol", symbolValue)
if takeProfitStopLoss == "" {
return resp.Result.Mode, errInvalidTakeProfitStopLoss
}
params.Set("tp_sl_mode", takeProfitStopLoss)
return resp.Result.Mode, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresSwitchPosition, params, nil, &resp, futuresSwitchPositionRate)
}
// ChangeMargin switches margin between cross or isolated
func (by *Bybit) ChangeMargin(ctx context.Context, symbol currency.Pair, buyLeverage, sellLeverage float64, isIsolated bool) error {
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return err
}
params.Set("symbol", symbolValue)
params.Set("buy_leverage", strconv.FormatFloat(buyLeverage, 'f', -1, 64))
params.Set("sell_leverage", strconv.FormatFloat(sellLeverage, 'f', -1, 64))
if isIsolated {
params.Set("is_isolated", "true")
} else {
params.Set("is_isolated", "false")
}
return by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresSwitchMargin, params, nil, nil, futuresSwitchMarginRate)
}
// GetTradeRecords returns list of user trades
func (by *Bybit) GetTradeRecords(ctx context.Context, symbol currency.Pair, orderID, order string, startTime, page, limit int64) ([]TradeResp, error) {
params := url.Values{}
resp := struct {
Data struct {
OrderID string `json:"order_id"`
Trades []TradeResp `json:"trade_list"`
} `json:"result"`
Error
}{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Data.Trades, err
}
params.Set("symbol", symbolValue)
if orderID != "" {
params.Set("order_id", orderID)
}
if order != "" {
params.Set("order", order)
}
if startTime != 0 {
params.Set("start_time", strconv.FormatInt(startTime, 10))
}
if page != 0 {
params.Set("page", strconv.FormatInt(page, 10))
}
if limit > 0 && limit <= 200 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
return resp.Data.Trades, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodGet, futuresGetTrades, params, nil, &resp, futuresGetTradeRate)
}
// GetClosedTrades returns closed profit and loss records
func (by *Bybit) GetClosedTrades(ctx context.Context, symbol currency.Pair, executionType string, startTime, endTime time.Time, page, limit int64) ([]ClosedTrades, error) {
params := url.Values{}
resp := struct {
Data struct {
CurrentPage int64 `json:"current_page"`
Trades []ClosedTrades `json:"data"`
} `json:"result"`
Error
}{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Data.Trades, err
}
params.Set("symbol", symbolValue)
if executionType != "" {
params.Set("execution_type", executionType)
}
if !startTime.IsZero() {
params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10))
}
if !endTime.IsZero() {
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
}
if page > 0 && page <= 50 {
params.Set("page", strconv.FormatInt(page, 10))
}
if limit > 0 && limit <= 50 {
params.Set("limit", strconv.FormatInt(limit, 10))
}
return resp.Data.Trades, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodGet, futuresGetClosedTrades, params, nil, &resp, futuresDefaultRate)
}
// SetRiskLimit sets risk limit
func (by *Bybit) SetRiskLimit(ctx context.Context, symbol currency.Pair, riskID, positionMode int64) (int64, error) {
resp := struct {
Result struct {
RiskID int64 `json:"risk_id"`
} `json:"result"`
Error
}{}
params := url.Values{}
symbolValue, err := by.FormatSymbol(symbol, asset.Futures)
if err != nil {
return resp.Result.RiskID, err
}
params.Set("symbol", symbolValue)
if riskID <= 0 {
return resp.Result.RiskID, errInvalidRiskID
}
params.Set("risk_id", strconv.FormatInt(riskID, 10))
if positionMode < 0 || positionMode > 2 {
return resp.Result.RiskID, errInvalidPositionMode
}
params.Set("position_idx", strconv.FormatInt(positionMode, 10))
return resp.Result.RiskID, by.SendAuthHTTPRequest(ctx, exchange.RestFutures, http.MethodPost, futuresSetRiskLimit, params, nil, &resp, futuresDefaultRate)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,917 @@
package bybit
import (
"encoding/json"
"errors"
"strconv"
"time"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
)
var (
errTypeAssert = errors.New("type assertion failed")
errStrParsing = errors.New("parsing string failed")
errInvalidSide = errors.New("invalid side")
errInvalidInterval = errors.New("invalid interval")
errInvalidPeriod = errors.New("invalid period")
errInvalidStartTime = errors.New("startTime can't be zero or missing")
errInvalidQuantity = errors.New("quantity can't be zero or missing")
errInvalidBasePrice = errors.New("basePrice can't be empty or missing")
errInvalidStopPrice = errors.New("stopPrice can't be empty or missing")
errInvalidTimeInForce = errors.New("timeInForce can't be empty or missing")
errInvalidTakeProfitStopLoss = errors.New("takeProfitStopLoss can't be empty or missing")
errInvalidMargin = errors.New("margin can't be empty")
errInvalidLeverage = errors.New("leverage can't be zero or less then it")
errInvalidRiskID = errors.New("riskID can't be zero or lesser")
errInvalidPositionMode = errors.New("position mode is invalid")
errInvalidOrderType = errors.New("orderType can't be empty or missing")
errInvalidMode = errors.New("mode can't be empty or missing")
errInvalidBuyLeverage = errors.New("buyLeverage can't be zero or less then it")
errInvalidSellLeverage = errors.New("sellLeverage can't be zero or less then it")
errInvalidOrderRequest = errors.New("order request param can't be nil")
errInvalidOrderFilter = errors.New("orderFilter can't be empty or missing")
errInvalidCategory = errors.New("invalid category")
errInvalidCoin = errors.New("coin can't be empty")
errStopOrderOrOrderLinkIDMissing = errors.New("atleast one should be present among stopOrderID and orderLinkID")
errOrderOrOrderLinkIDMissing = errors.New("atleast one should be present among orderID and orderLinkID")
errSymbolMissing = errors.New("symbol missing")
errUnsupportedOrderType = errors.New("unsupported order type")
errEmptyOrderIDs = errors.New("orderIDs can't be empty")
errMissingPrice = errors.New("price should be present for Limit and LimitMaker orders")
errExpectedOneOrder = errors.New("expected one order")
)
// bybitTimeSec provides an internal conversion helper
type bybitTimeSec time.Time
// UnmarshalJSON is custom json unmarshaller for bybitTimeSec
func (b *bybitTimeSec) UnmarshalJSON(data []byte) error {
var timestamp int64
err := json.Unmarshal(data, &timestamp)
if err != nil {
return err
}
*b = bybitTimeSec(time.Unix(timestamp, 0))
return nil
}
// Time returns a time.Time object
func (b bybitTimeSec) Time() time.Time {
return time.Time(b)
}
// bybitTimeSecStr provides an internal conversion helper
type bybitTimeSecStr time.Time
// UnmarshalJSON is custom json unmarshaller for bybitTimeSec
func (b *bybitTimeSecStr) UnmarshalJSON(data []byte) error {
var timestamp string
err := json.Unmarshal(data, &timestamp)
if err != nil {
return err
}
t, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
return err
}
*b = bybitTimeSecStr(time.Unix(t, 0))
return nil
}
// Time returns a time.Time object
func (b bybitTimeSecStr) Time() time.Time {
return time.Time(b)
}
// bybitTimeMilliSec provides an internal conversion helper
type bybitTimeMilliSec time.Time
// UnmarshalJSON is custom type json unmarshaller for bybitTimeMilliSec
func (b *bybitTimeMilliSec) UnmarshalJSON(data []byte) error {
var timestamp int64
err := json.Unmarshal(data, &timestamp)
if err != nil {
return err
}
*b = bybitTimeMilliSec(time.UnixMilli(timestamp))
return nil
}
// Time returns a time.Time object
func (b bybitTimeMilliSec) Time() time.Time {
return time.Time(b)
}
// bybitTimeMilliSecStr provides an internal conversion helper
type bybitTimeMilliSecStr time.Time
// UnmarshalJSON is custom type json unmarshaller for bybitTimeMilliSec
func (b *bybitTimeMilliSecStr) UnmarshalJSON(data []byte) error {
var timestamp string
err := json.Unmarshal(data, &timestamp)
if err != nil {
return err
}
t, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
return err
}
*b = bybitTimeMilliSecStr(time.UnixMilli(t))
return nil
}
// Time returns a time.Time object
func (b bybitTimeMilliSecStr) Time() time.Time {
return time.Time(b)
}
// bybitTimeNanoSec provides an internal conversion helper
type bybitTimeNanoSec time.Time
// UnmarshalJSON is custom type json unmarshaller for bybitTimeNanoSec
func (b *bybitTimeNanoSec) UnmarshalJSON(data []byte) error {
var timestamp int64
err := json.Unmarshal(data, &timestamp)
if err != nil {
return err
}
*b = bybitTimeNanoSec(time.Unix(0, timestamp))
return nil
}
// Time returns a time.Time object
func (b bybitTimeNanoSec) Time() time.Time {
return time.Time(b)
}
// UnmarshalTo acts as interface to exchange API response
type UnmarshalTo interface {
GetError() error
}
// PairData stores pair data
type PairData struct {
Name string `json:"name"`
Alias string `json:"alias"`
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
BasePrecision float64 `json:"basePrecision,string"`
QuotePrecision float64 `json:"quotePrecision,string"`
MinTradeQuantity float64 `json:"minTradeQuantity,string"`
MinTradeAmount float64 `json:"minTradeAmount,string"`
MinPricePrecision float64 `json:"minPricePrecision,string"`
MaxTradeQuantity float64 `json:"maxTradeQuantity,string"`
MaxTradeAmount float64 `json:"maxTradeAmount,string"`
Category int64 `json:"category"`
ShowStatus bool `json:"showStatus"`
}
// Orderbook stores the orderbook data
type Orderbook struct {
Bids []orderbook.Item
Asks []orderbook.Item
Symbol string
Time time.Time
}
// TradeItem stores a single trade
type TradeItem struct {
CurrencyPair string
Price float64
Side string
Volume float64
Time time.Time
}
// KlineItem stores an individual kline data item
type KlineItem struct {
StartTime time.Time
EndTime time.Time
Open float64
Close float64
High float64
Low float64
Volume float64
QuoteAssetVolume float64
TakerBaseVolume float64
TakerQuoteVolume float64
TradesCount int64
}
// PriceChangeStats contains statistics for the last 24 hours trade
type PriceChangeStats struct {
Time time.Time
Symbol string
BestBidPrice float64
BestAskPrice float64
LastPrice float64
OpenPrice float64
HighPrice float64
LowPrice float64
Volume float64
QuoteVolume float64
}
// LastTradePrice contains price for last trade
type LastTradePrice struct {
Symbol string `json:"symbol"`
Price float64 `json:"price,string"`
}
// TickerData stores ticker data
type TickerData struct {
Symbol string
BidPrice float64
BidQuantity float64
AskPrice float64
AskQuantity float64
Time time.Time
}
var (
// BybitRequestParamsOrderLimit Limit order
BybitRequestParamsOrderLimit = "LIMIT"
// BybitRequestParamsOrderMarket Market order
BybitRequestParamsOrderMarket = "MARKET"
// BybitRequestParamsOrderLimitMaker Limit Maker
BybitRequestParamsOrderLimitMaker = "LIMIT_MAKER"
)
var (
// BybitRequestParamsTimeGTC Good Till Canceled
BybitRequestParamsTimeGTC = "GTC"
// BybitRequestParamsTimeFOK Fill or Kill
BybitRequestParamsTimeFOK = "FOK"
// BybitRequestParamsTimeIOC Immediate or Cancel
BybitRequestParamsTimeIOC = "IOC"
)
// PlaceOrderRequest store new order request type
type PlaceOrderRequest struct {
Symbol string
Quantity float64
Side string
TradeType string
TimeInForce string
Price float64
OrderLinkID string
}
// PlaceOrderResponse store new order response type
type PlaceOrderResponse struct {
OrderID string `json:"orderId"`
OrderLinkID string `json:"orderLinkId"`
Symbol string `json:"symbol"`
Time bybitTimeMilliSecStr `json:"transactTime"`
Price float64 `json:"price,string"`
Quantity float64 `json:"origQty,string"`
TradeType string `json:"type"`
Side string `json:"side"`
Status string `json:"status"`
TimeInForce string `json:"timeInForce"`
AccountID string `json:"accountId"`
SymbolName string `json:"symbolName"`
ExecutedQty float64 `json:"executedQty,string"`
}
// QueryOrderResponse holds query order data
type QueryOrderResponse struct {
AccountID string `json:"accountId"`
ExchangeID string `json:"exchangeId"`
Symbol string `json:"symbol"`
SymbolName string `json:"symbolName"`
OrderLinkID string `json:"orderLinkId"`
OrderID string `json:"orderId"`
Price float64 `json:"price,string"`
Quantity float64 `json:"origQty,string"`
ExecutedQty float64 `json:"executedQty,string"`
CummulativeQuoteQty float64 `json:"cummulativeQuoteQty,string"`
AveragePrice float64 `json:"avgPrice,string"`
Status string `json:"status"`
TimeInForce string `json:"timeInForce"`
TradeType string `json:"type"`
Side string `json:"side"`
StopPrice float64 `json:"stopPrice,string"`
IcebergQty float64 `json:"icebergQty,string"`
Time bybitTimeMilliSecStr `json:"time"`
UpdateTime bybitTimeMilliSecStr `json:"updateTime"`
IsWorking bool `json:"isWorking"`
}
// CancelOrderResponse is the return structured response from the exchange
type CancelOrderResponse struct {
OrderID string `json:"orderId"`
OrderLinkID string `json:"orderLinkId"`
Symbol string `json:"symbol"`
Status string `json:"status"`
AccountID string `json:"accountId"`
Time bybitTimeMilliSecStr `json:"transactTime"`
Price float64 `json:"price,string"`
Quantity float64 `json:"origQty,string"`
ExecutedQty float64 `json:"executedQty,string"`
TimeInForce string `json:"timeInForce"`
TradeType string `json:"type"`
Side string `json:"side"`
}
// HistoricalTrade holds recent trade data
type HistoricalTrade struct {
Symbol string `json:"symbol"`
ID string `json:"id"`
OrderID string `json:"orderId"`
TicketID string `json:"ticketId"`
Price float64 `json:"price,string"`
Quantity float64 `json:"qty,string"`
Commission float64 `json:"commission,string"`
CommissionAsset float64 `json:"commissionAsset,string"`
Time bybitTimeMilliSecStr `json:"time"`
IsBuyer bool `json:"isBuyer"`
IsMaker bool `json:"isMaker"`
SymbolName string `json:"symbolName"`
MatchOrderID string `json:"matchOrderId"`
Fee FeeData `json:"fee"`
FeeTokenID string `json:"feeTokenId"`
FeeAmount float64 `json:"feeAmount,string"`
MakerRebate float64 `json:"makerRebate,string"`
}
// FeeData store fees data
type FeeData struct {
FeeTokenID int64 `json:"feeTokenId"`
FeeTokenName string `json:"feeTokenName"`
Fee float64 `json:"fee,string"`
}
// Balance holds wallet balance
type Balance struct {
Coin string `json:"coin"`
CoinID string `json:"coinId"`
CoinName string `json:"coinName"`
Total float64 `json:"total,string"`
Free float64 `json:"free,string"`
Locked float64 `json:"locked,string"`
}
type orderbookResponse struct {
Data struct {
Asks [][2]string `json:"asks"`
Bids [][2]string `json:"bids"`
Time bybitTimeMilliSec `json:"time"`
} `json:"result"`
Error
}
type DepositWalletInfo struct {
Coin string `json:"coin"`
Chains []ChainInfo `json:"chains"`
}
type ChainInfo struct {
ChainType string `json:"chain_type"`
DepositAddress string `json:"address_deposit"`
DepositTag string `json:"tag_deposit"`
Chain string `json:"chain"`
}
// Websocket Structures
// Authenticate stores authentication variables required
type Authenticate struct {
Args []interface{} `json:"args"`
Operation string `json:"op"`
}
// WsReq has the data used for ws request
type WsReq struct {
Topic string `json:"topic"`
Event string `json:"event"`
Parameters interface{} `json:"params"`
}
// WsFuturesReq stores futures ws request
type WsFuturesReq struct {
Topic string `json:"op"`
Args []string `json:"args"`
}
// WsParams store ws parameters
type WsParams struct {
Symbol string `json:"symbol"`
IsBinary bool `json:"binary,string"`
SymbolName string `json:"symbolName,omitempty"`
KlineType string `json:"klineType,omitempty"` // only present in kline ws stream
}
// WsSpotTickerData stores ws ticker data
type WsSpotTickerData struct {
Symbol string `json:"symbol"`
Bid float64 `json:"bidPrice,string"`
Ask float64 `json:"askPrice,string"`
BidSize float64 `json:"bidQty,string"`
AskSize float64 `json:"askQty,string"`
Time bybitTimeMilliSec `json:"time"`
}
// WsSpotTicker stores ws ticker data
type WsSpotTicker struct {
Topic string `json:"topic"`
Parameters WsParams `json:"params"`
Ticker WsSpotTickerData `json:"data"`
}
// KlineStreamData stores ws kline stream data
type KlineStreamData struct {
StartTime bybitTimeMilliSec `json:"t"`
Symbol string `json:"s"`
ClosePrice float64 `json:"c,string"`
HighPrice float64 `json:"h,string"`
LowPrice float64 `json:"l,string"`
OpenPrice float64 `json:"o,string"`
Volume float64 `json:"v,string"`
}
// KlineStream holds the kline stream data
type KlineStream struct {
Topic string `json:"topic"`
Parameters WsParams `json:"params"`
Kline KlineStreamData `json:"data"`
}
// WsOrderbookData stores ws orderbook data
type WsOrderbookData struct {
Symbol string `json:"s"`
Time bybitTimeMilliSec `json:"t"`
Version string `json:"v"`
Bids [][2]string `json:"b"`
Asks [][2]string `json:"a"`
}
// WsOrderbook stores ws orderbook data
type WsOrderbook struct {
Topic string `json:"topic"`
Parameters WsParams `json:"params"`
OBData WsOrderbookData `json:"data"`
}
// WsTradeData stores ws trade data
type WsTradeData struct {
Time bybitTimeMilliSec `json:"t"`
ID string `json:"v"`
Price float64 `json:"p,string"`
Size float64 `json:"q,string"`
Side bool `json:"m"`
}
// WsTrade stores ws trades data
type WsTrade struct {
Topic string `json:"topic"`
Parameters WsParams `json:"params"`
TradeData WsTradeData `json:"data"`
}
// wsAccount defines websocket account info data
type wsAccount struct {
EventType string `json:"e"`
EventTime string `json:"E"`
CanTrade bool `json:"T"`
CanWithdraw bool `json:"W"`
CanDeposit bool `json:"D"`
Balance []Currencies `json:"B"`
}
// Currencies stores currencies data
type Currencies struct {
Asset string `json:"a"`
Available float64 `json:"f,string"`
Locked float64 `json:"l,string"`
}
// wsOrderUpdate defines websocket account order update data
type wsOrderUpdate struct {
EventType string `json:"e"`
EventTime string `json:"E"`
Symbol string `json:"s"`
ClientOrderID string `json:"c"`
Side string `json:"S"`
OrderType string `json:"o"`
TimeInForce string `json:"f"`
Quantity float64 `json:"q,string"`
Price float64 `json:"p,string"`
OrderStatus string `json:"X"`
OrderID string `json:"i"`
OpponentOrderID string `json:"M"`
LastExecutedQuantity float64 `json:"l,string"`
CumulativeFilledQuantity float64 `json:"z,string"`
LastExecutedPrice float64 `json:"L,string"`
Commission float64 `json:"n,string"`
CommissionAsset string `json:"N"`
IsNormal bool `json:"u"`
IsOnOrderBook bool `json:"w"`
IsLimitMaker bool `json:"m"`
OrderCreationTime bybitTimeMilliSecStr `json:"O"`
CumulativeQuoteTransactedQuantity float64 `json:"Z,string"`
AccountID string `json:"A"`
IsClose bool `json:"C"`
Leverage float64 `json:"v,string"`
}
// wsOrderFilled defines websocket account order filled data
type wsOrderFilled struct {
EventType string `json:"e"`
EventTime string `json:"E"`
Symbol string `json:"s"`
Quantity float64 `json:"q,string"`
Timestamp bybitTimeMilliSecStr `json:"t"`
Price float64 `json:"p,string"`
TradeID string `json:"T"`
OrderID string `json:"o"`
UserGenOrderID string `json:"c"`
OpponentOrderID string `json:"O"`
AccountID string `json:"a"`
OpponentAccountID string `json:"A"`
IsMaker bool `json:"m"`
Side string `json:"S"`
}
// WsFuturesOrderbookData stores ws futures orderbook data
type WsFuturesOrderbookData struct {
Price float64 `json:"price,string"`
Symbol string `json:"symbol"`
ID int64 `json:"id"`
Side string `json:"side"`
Size float64 `json:"size"`
}
// WsFuturesOrderbook stores ws futures orderbook
type WsFuturesOrderbook struct {
Topic string `json:"topic"`
Type string `json:"string"`
OBData []WsFuturesOrderbookData `json:"data"`
}
// WsUSDTOrderbook stores ws usdt orderbook
type WsUSDTOrderbook struct {
Topic string `json:"topic"`
Type string `json:"string"`
Data struct {
OBData []WsFuturesOrderbookData `json:"order_book"`
} `json:"data"`
}
// WsCoinDeltaOrderbook stores ws coinmargined orderbook
type WsCoinDeltaOrderbook struct {
Topic string `json:"topic"`
Type string `json:"string"`
OBData struct {
Delete []WsFuturesOrderbookData `json:"delete"`
Update []WsFuturesOrderbookData `json:"update"`
Insert []WsFuturesOrderbookData `json:"insert"`
} `json:"data"`
}
// WsFuturesTradeData stores ws future trade data
type WsFuturesTradeData struct {
Time time.Time `json:"timestamp"`
TimeInMilliseconds bybitTimeMilliSec `json:"trade_time_ms"`
Symbol string `json:"symbol"`
Side string `json:"side"`
Size float64 `json:"size"`
Price float64 `json:"price"`
Direction string `json:"tick_direction"`
ID string `json:"trade_id"`
}
// WsFuturesTrade stores ws future trade
type WsFuturesTrade struct {
Topic string `json:"topic"`
TradeData []WsFuturesTradeData `json:"data"`
}
// WsFuturesKlineData stores ws future kline data
type WsFuturesKlineData struct {
StartTime bybitTimeSec `json:"start"`
EndTime bybitTimeSec `json:"end"`
Close float64 `json:"close"`
Open float64 `json:"open"`
High float64 `json:"high"`
Low float64 `json:"low"`
Volume float64 `json:"volume"`
TurnOver float64 `json:"turnover"`
Confirm bool `json:"confirm"`
Timestamp bybitTimeMilliSec `json:"timestamp"`
}
// WsFuturesKline stores ws future kline
type WsFuturesKline struct {
Topic string `json:"topic"`
KlineData []WsFuturesKlineData `json:"data"`
}
// WsInsuranceData stores ws insurance data
type WsInsuranceData struct {
Currency string `json:"currency"`
Timestamp time.Time `json:"timestamp"`
WalletBalance float64 `json:"wallet_balance"`
}
// WsInsurance stores ws insurance
type WsInsurance struct {
Topic string `json:"topic"`
Data []WsInsuranceData `json:"data"`
}
// WsTickerData stores ws ticker data
type WsTickerData struct {
ID string `json:"id"`
Symbol string `json:"symbol"`
LastPrice float64 `json:"last_price,string"`
BidPrice float64 `json:"bid1_price"`
AskPrice float64 `json:"ask1_price"`
LastDirection string `json:"last_tick_direction"`
PrevPrice24h float64 `json:"prev_price_24h,string"`
Price24hPercentChange float64 `json:"price_24h_pcnt_e6"`
Price1hPercentChange float64 `json:"price_1h_pcnt_e6"`
HighPrice24h float64 `json:"high_price_24h,string"`
LowPrice24h float64 `json:"low_price_24h,string"`
PrevPrice1h float64 `json:"prev_price_1h,string"`
MarkPrice float64 `json:"mark_price,string"`
IndexPrice float64 `json:"index_price,string"`
OpenInterest float64 `json:"open_interest"`
OpenValue float64 `json:"open_value_e8"`
TotalTurnOver float64 `json:"total_turnover_e8"`
TurnOver24h float64 `json:"turnover_24h_e8"`
TotalVolume float64 `json:"total_volume"`
Volume24h float64 `json:"volume_24h"`
FundingRate int64 `json:"funding_rate_e6"`
PredictedFundingRate float64 `json:"predicted_funding_rate_e6"`
CreatedAt time.Time `json:"created_at"`
UpdateAt time.Time `json:"updated_at"`
NextFundingAt time.Time `json:"next_funding_time"`
CountDownHour int64 `json:"countdown_hour"`
}
// WsTicker stores ws ticker
type WsTicker struct {
Topic string `json:"topic"`
Ticker WsTickerData `json:"data"`
}
// WsDeltaTicker stores ws ticker
type WsDeltaTicker struct {
Topic string `json:"topic"`
Type string `json:"string"`
Data struct {
Delete []WsTickerData `json:"delete"`
Update []WsTickerData `json:"update"`
Insert []WsTickerData `json:"insert"`
} `json:"data"`
}
// WsFuturesTickerData stores ws future ticker data
type WsFuturesTickerData struct {
ID string `json:"id"`
Symbol string `json:"symbol"`
SymbolName string `json:"symbol_name"`
SymbolYear int64 `json:"symbol_year"`
ContractType string `json:"contract_type"`
Coin string `json:"coin"`
QuoteSymbol string `json:"quote_symbol"`
Mode string `json:"mode"`
IsUpBorrowable int64 `json:"is_up_borrowable"`
ImportTime bybitTimeNanoSec `json:"import_time_e9"`
StartTradingTime bybitTimeNanoSec `json:"start_trading_time_e9"`
TimeToSettle bybitTimeNanoSec `json:"settle_time_e9"`
SettleFeeRate int64 `json:"settle_fee_rate_e8"`
ContractStatus string `json:"contract_status"`
SystemSubsidy int64 `json:"system_subsidy_e8"`
LastPrice float64 `json:"last_price,string"`
BidPrice float64 `json:"bid1_price"`
AskPrice float64 `json:"ask1_price"`
LastDirection string `json:"last_tick_direction"`
PrevPrice24h float64 `json:"prev_price_24h,string"`
Price24hPercentChange float64 `json:"price_24h_pcnt_e6"`
Price1hPercentChange float64 `json:"price_1h_pcnt_e6"`
HighPrice24h float64 `json:"high_price_24h,string"`
LowPrice24h float64 `json:"low_price_24h,string"`
PrevPrice1h float64 `json:"prev_price_1h,string"`
MarkPrice float64 `json:"mark_price,string"`
IndexPrice float64 `json:"index_price,string"`
OpenInterest float64 `json:"open_interest"`
OpenValue float64 `json:"open_value_e8"`
TotalTurnOver float64 `json:"total_turnover_e8"`
TurnOver24h float64 `json:"turnover_24h_e8"`
TotalVolume float64 `json:"total_volume"`
Volume24h float64 `json:"volume_24h"`
FairBasis float64 `json:"fair_basis_e8"`
FairBasisRate float64 `json:"fair_basis_rate_e8"`
BasisInYear float64 `json:"basis_in_year_e8"`
ExpectPrice float64 `json:"expect_price,string"`
CreatedAt time.Time `json:"created_at"`
UpdateAt time.Time `json:"updated_at"`
}
// WsFuturesTicker stores ws future ticker
type WsFuturesTicker struct {
Topic string `json:"topic"`
Ticker WsFuturesTickerData `json:"data"`
}
// WsDeltaFuturesTicker stores ws delta future ticker
type WsDeltaFuturesTicker struct {
Topic string `json:"topic"`
Type string `json:"string"`
Data struct {
Delete []WsFuturesTickerData `json:"delete"`
Update []WsFuturesTickerData `json:"update"`
Insert []WsFuturesTickerData `json:"insert"`
} `json:"data"`
}
// WsLiquidationData stores ws liquidation data
type WsLiquidationData struct {
Symbol string `json:"symbol"`
Side string `json:"side"`
Price float64 `json:"price,string"`
Qty float64 `json:"qty"`
Timestamp bybitTimeMilliSec `json:"time"`
}
// WsFuturesLiquidation stores ws future liquidation
type WsFuturesLiquidation struct {
Topic string `json:"topic"`
Data WsLiquidationData `json:"data"`
}
// WsFuturesPositionData stores ws future position data
type WsFuturesPositionData struct {
UserID int64 `json:"user_id"`
Symbol string `json:"symbol"`
Side string `json:"side"`
Size float64 `json:"size"`
PositionID int64 `json:"position_idx"` // present in Futures position struct only
Mode int64 `json:"mode"` // present in Futures position struct only
Isolated bool `json:"isolated"` // present in Futures position struct only
PositionValue float64 `json:"position_value,string"`
EntryPrice float64 `json:"entry_price,string"`
LiquidPrice float64 `json:"liq_price,string"`
BustPrice float64 `json:"bust_price,string"`
Leverage float64 `json:"leverage,string"`
OrderMargin float64 `json:"order_margin,string"`
PositionMargin float64 `json:"position_margin,string"`
AvailableBalance float64 `json:"available_balance,string"`
TakeProfit float64 `json:"take_profit,string"`
TakeProfitTriggerBy string `json:"tp_trigger_by"`
StopLoss float64 `json:"stop_loss,string"`
StopLossTriggerBy string `json:"sl_trigger_by"`
RealisedPNL float64 `json:"realised_pnl,string"`
TrailingStop float64 `json:"trailing_stop,string"`
TrailingActive float64 `json:"trailing_active,string"`
WalletBalance float64 `json:"wallet_balance,string"`
RiskID int64 `json:"risk_id"`
ClosingFee float64 `json:"occ_closing_fee,string"`
FundingFee float64 `json:"occ_funding_fee,string"`
AutoAddMargin int64 `json:"auto_add_margin"`
TotalPNL float64 `json:"cum_realised_pnl,string"`
Status string `json:"position_status"`
Version int64 `json:"position_seq"`
}
// WsFuturesPosition stores ws future position
type WsFuturesPosition struct {
Topic string `json:"topic"`
Action string `json:"action"`
Data []WsFuturesPositionData `json:"data"`
}
// WsFuturesExecutionData stores ws future execution data
type WsFuturesExecutionData struct {
Symbol string `json:"symbol"`
Side string `json:"side"`
OrderID string `json:"order_id"`
ExecutionID string `json:"exec_id"`
OrderLinkID string `json:"order_link_id"`
Price float64 `json:"price,string"`
OrderQty float64 `json:"order_qty"`
ExecutionType string `json:"exec_type"`
ExecutionQty float64 `json:"exec_qty"`
ExecutionFee float64 `json:"exec_fee,string"`
LeavesQty float64 `json:"leaves_qty"`
IsMaker bool `json:"is_maker"`
Time time.Time `json:"trade_time"`
}
// WsFuturesExecution stores ws future execution
type WsFuturesExecution struct {
Topic string `json:"topic"`
Data []WsFuturesExecutionData `json:"data"`
}
// WsOrderData stores ws order data
type WsOrderData struct {
OrderID string `json:"order_id"`
OrderLinkID string `json:"order_link_id"`
Symbol string `json:"symbol"`
Side string `json:"side"`
OrderType string `json:"order_type"`
Price float64 `json:"price,string"`
OrderQty float64 `json:"qty"`
TimeInForce string `json:"time_in_force"`
CreateType string `json:"create_type"`
CancelType string `json:"cancel_type"`
OrderStatus string `json:"order_status"`
LeavesQty float64 `json:"leaves_qty"`
CummulativeExecQty float64 `json:"cum_exec_qty"`
CummulativeExecValue float64 `json:"cum_exec_value,string"`
CummulativeExecFee float64 `json:"cum_exec_fee,string"`
TakeProfit float64 `json:"take_profit,string"`
StopLoss float64 `json:"stop_loss,string"`
TrailingStop float64 `json:"trailing_stop,string"`
TrailingActive float64 `json:"trailing_active,string"`
LastExecPrice float64 `json:"last_exec_price,string"`
ReduceOnly bool `json:"reduce_only"`
CloseOnTrigger bool `json:"close_on_trigger"`
Time time.Time `json:"timestamp"` // present in CoinMarginedFutures and Futures only
CreateTime time.Time `json:"create_time"` // present in USDTMarginedFutures only
UpdateTime time.Time `json:"update_time"` // present in USDTMarginedFutures only
}
// WsOrder stores ws order
type WsOrder struct {
Topic string `json:"topic"`
Data []WsOrderData `json:"data"`
}
// WsStopOrderData stores ws stop order data
type WsStopOrderData struct {
OrderID string `json:"order_id"`
OrderLinkID string `json:"order_link_id"`
UserID int64 `json:"user_id"`
Symbol string `json:"symbol"`
Side string `json:"side"`
OrderType string `json:"order_type"`
Price float64 `json:"price,string"`
OrderQty float64 `json:"qty"`
TimeInForce string `json:"time_in_force"`
CreateType string `json:"create_type"`
CancelType string `json:"cancel_type"`
OrderStatus string `json:"order_status"`
StopOrderType string `json:"stop_order_type"`
TriggerBy string `json:"trigger_by"`
TriggerPrice float64 `json:"trigger_price,string"`
Time time.Time `json:"timestamp"`
CloseOnTrigger bool `json:"close_on_trigger"`
}
// WsFuturesStopOrder stores ws future stop order
type WsFuturesStopOrder struct {
Topic string `json:"topic"`
Data []WsStopOrderData `json:"data"`
}
// WsUSDTStopOrderData stores ws USDT stop order data
type WsUSDTStopOrderData struct {
OrderID string `json:"stop_order_id"`
OrderLinkID string `json:"order_link_id"`
UserID int64 `json:"user_id"`
Symbol string `json:"symbol"`
Side string `json:"side"`
OrderType string `json:"order_type"`
Price float64 `json:"price,string"`
OrderQty float64 `json:"qty"`
TimeInForce string `json:"time_in_force"`
OrderStatus string `json:"order_status"`
StopOrderType string `json:"stop_order_type"`
TriggerBy string `json:"trigger_by"`
TriggerPrice float64 `json:"trigger_price,string"`
ReduceOnly bool `json:"reduce_only"`
CloseOnTrigger bool `json:"close_on_trigger"`
CreateTime time.Time `json:"create_time"`
UpdateTime time.Time `json:"update_time"`
}
// WsUSDTFuturesStopOrder stores ws USDT stop order
type WsUSDTFuturesStopOrder struct {
Topic string `json:"topic"`
Data []WsUSDTStopOrderData `json:"data"`
}
// WsFuturesWalletData stores ws future wallet data
type WsFuturesWalletData struct {
WalletBalance float64 `json:"wallet_balance"`
AvailableBalance float64 `json:"available_balance"`
}
// WsFuturesWallet stores ws future wallet
type WsFuturesWallet struct {
Topic string `json:"topic"`
Data []WsFuturesWalletData `json:"data"`
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,555 @@
package bybit
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
)
const (
bybitWSBaseURL = "wss://stream.bybit.com/"
wsSpotPublicTopicV2 = "spot/quote/ws/v2"
wsSpotPrivate = "spot/ws"
bybitWebsocketTimer = 20 * time.Second
wsOrderbook = "depth"
wsTicker = "bookTicker"
wsTrades = "trade"
wsKlines = "kline"
wsAccountInfo = "outboundAccountInfo"
wsOrderExecution = "executionReport"
wsTickerInfo = "ticketInfo"
sub = "sub" // event for subscribe
cancel = "cancel" // event for unsubscribe
)
var comms = make(chan stream.Response)
// WsConnect connects to a websocket feed
func (by *Bybit) WsConnect() error {
if !by.Websocket.IsEnabled() || !by.IsEnabled() || !by.IsAssetWebsocketSupported(asset.Spot) {
return errors.New(stream.WebsocketNotEnabled)
}
var dialer websocket.Dialer
err := by.Websocket.Conn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
by.Websocket.Conn.SetupPingHandler(stream.PingHandler{
MessageType: websocket.TextMessage,
Message: []byte(`{"op":"ping"}`),
Delay: bybitWebsocketTimer,
})
by.Websocket.Wg.Add(1)
go by.wsReadData(by.Websocket.Conn)
if by.IsWebsocketAuthenticationSupported() {
err = by.WsAuth(context.TODO())
if err != nil {
by.Websocket.DataHandler <- err
by.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
}
by.Websocket.Wg.Add(1)
go by.WsDataHandler()
return nil
}
// WsAuth sends an authentication message to receive auth data
func (by *Bybit) WsAuth(ctx context.Context) error {
var dialer websocket.Dialer
err := by.Websocket.AuthConn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
by.Websocket.AuthConn.SetupPingHandler(stream.PingHandler{
MessageType: websocket.TextMessage,
Message: []byte(`{"op":"ping"}`),
Delay: bybitWebsocketTimer,
})
by.Websocket.Wg.Add(1)
go by.wsReadData(by.Websocket.AuthConn)
creds, err := by.GetCredentials(ctx)
if err != nil {
return err
}
intNonce := (time.Now().Unix() + 1) * 1000
strNonce := strconv.FormatInt(intNonce, 10)
hmac, err := crypto.GetHMAC(
crypto.HashSHA256,
[]byte("GET/realtime"+strNonce),
[]byte(creds.Secret),
)
if err != nil {
return err
}
sign := crypto.HexEncodeToString(hmac)
req := Authenticate{
Operation: "auth",
Args: []interface{}{creds.Key, intNonce, sign},
}
return by.Websocket.AuthConn.SendJSONMessage(req)
}
// Subscribe sends a websocket message to receive data from the channel
func (by *Bybit) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
var errs common.Errors
for i := range channelsToSubscribe {
var subReq WsReq
subReq.Topic = channelsToSubscribe[i].Channel
subReq.Event = sub
formattedPair, err := by.FormatExchangeCurrency(channelsToSubscribe[i].Currency, asset.Spot)
if err != nil {
errs = append(errs, err)
continue
}
if channelsToSubscribe[i].Channel == wsKlines {
subReq.Parameters = WsParams{
Symbol: formattedPair.String(),
IsBinary: true,
KlineType: "1m",
}
} else {
subReq.Parameters = WsParams{
Symbol: formattedPair.String(),
IsBinary: true,
}
}
err = by.Websocket.Conn.SendJSONMessage(subReq)
if err != nil {
errs = append(errs, err)
continue
}
by.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (by *Bybit) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
var errs common.Errors
for i := range channelsToUnsubscribe {
var unSub WsReq
unSub.Event = cancel
unSub.Topic = channelsToUnsubscribe[i].Channel
formattedPair, err := by.FormatExchangeCurrency(channelsToUnsubscribe[i].Currency, asset.Spot)
if err != nil {
errs = append(errs, err)
continue
}
unSub.Parameters = WsParams{
Symbol: formattedPair.String(),
}
err = by.Websocket.Conn.SendJSONMessage(unSub)
if err != nil {
errs = append(errs, err)
continue
}
by.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
// wsReadData receives and passes on websocket messages for processing
func (by *Bybit) wsReadData(ws stream.Connection) {
defer by.Websocket.Wg.Done()
for {
resp := ws.ReadMessage()
if resp.Raw == nil {
return
}
comms <- resp
}
}
// GenerateDefaultSubscriptions generates default subscription
func (by *Bybit) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
var subscriptions []stream.ChannelSubscription
var channels = []string{wsTicker, wsTrades, wsOrderbook, wsKlines}
pairs, err := by.GetEnabledPairs(asset.Spot)
if err != nil {
return nil, err
}
for z := range pairs {
for x := range channels {
subscriptions = append(subscriptions,
stream.ChannelSubscription{
Channel: channels[x],
Currency: pairs[z],
Asset: asset.Spot,
})
}
}
return subscriptions, nil
}
func stringToOrderStatus(status string) (order.Status, error) {
switch status {
case "NEW":
return order.New, nil
case "CANCELED":
return order.Cancelled, nil
case "REJECTED":
return order.Rejected, nil
case "TRADE":
return order.PartiallyFilled, nil
case "EXPIRED":
return order.Expired, nil
default:
return order.UnknownStatus, errors.New(status + " not recognised as order status")
}
}
// WsDataHandler handles data from wsReadData
func (by *Bybit) WsDataHandler() {
defer by.Websocket.Wg.Done()
for {
select {
case <-by.Websocket.ShutdownC:
return
case resp := <-comms:
err := by.wsHandleData(resp.Raw)
if err != nil {
by.Websocket.DataHandler <- err
}
}
}
}
func (by *Bybit) wsHandleData(respRaw []byte) error {
var result interface{}
err := json.Unmarshal(respRaw, &result)
if err != nil {
return err
}
switch d := result.(type) {
case map[string]interface{}:
if method, ok := d["event"].(string); ok {
if strings.EqualFold(method, sub) {
return nil
}
if strings.EqualFold(method, cancel) {
return nil
}
}
if t, ok := d["topic"].(string); ok {
switch t {
case wsOrderbook:
var data WsOrderbook
err := json.Unmarshal(respRaw, &data)
if err != nil {
return err
}
p, err := by.extractCurrencyPair(data.OBData.Symbol, asset.Spot)
if err != nil {
return err
}
err = by.wsUpdateOrderbook(&data.OBData, p, asset.Spot)
if err != nil {
return err
}
return nil
case wsTrades:
if !by.IsSaveTradeDataEnabled() {
return nil
}
var data WsTrade
err := json.Unmarshal(respRaw, &data)
if err != nil {
return err
}
p, err := by.extractCurrencyPair(data.Parameters.Symbol, asset.Spot)
if err != nil {
return err
}
side := order.Sell
if data.TradeData.Side {
side = order.Buy
}
return trade.AddTradesToBuffer(by.Name, trade.Data{
Timestamp: data.TradeData.Time.Time(),
CurrencyPair: p,
AssetType: asset.Spot,
Exchange: by.Name,
Price: data.TradeData.Price,
Amount: data.TradeData.Size,
Side: side,
TID: data.TradeData.ID,
})
case wsTicker:
var data WsSpotTicker
err := json.Unmarshal(respRaw, &data)
if err != nil {
return err
}
p, err := by.extractCurrencyPair(data.Ticker.Symbol, asset.Spot)
if err != nil {
return err
}
by.Websocket.DataHandler <- &ticker.Price{
ExchangeName: by.Name,
Bid: data.Ticker.Bid,
Ask: data.Ticker.Ask,
LastUpdated: data.Ticker.Time.Time(),
AssetType: asset.Spot,
Pair: p,
}
return nil
case wsKlines:
var data KlineStream
err := json.Unmarshal(respRaw, &data)
if err != nil {
return err
}
p, err := by.extractCurrencyPair(data.Kline.Symbol, asset.Spot)
if err != nil {
return err
}
by.Websocket.DataHandler <- stream.KlineData{
Pair: p,
AssetType: asset.Spot,
Exchange: by.Name,
StartTime: data.Kline.StartTime.Time(),
Interval: data.Parameters.KlineType,
OpenPrice: data.Kline.OpenPrice,
ClosePrice: data.Kline.ClosePrice,
HighPrice: data.Kline.HighPrice,
LowPrice: data.Kline.LowPrice,
Volume: data.Kline.Volume,
}
return nil
default:
by.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: by.Name + stream.UnhandledMessage + string(respRaw)}
}
}
if m, ok := d["auth"]; ok {
log.Infof(log.WebsocketMgr, "%v received auth response: %v", by.Name, m)
return nil
}
if m, ok := d["pong"]; ok {
log.Infof(log.WebsocketMgr, "%v received pong: %v", by.Name, m)
return nil
}
case []interface{}:
for i := range d {
obj, ok := d[i].(map[string]interface{})
if !ok {
return common.GetAssertError("map[string]interface{}", d[i])
}
e, ok := obj["e"].(string)
if !ok {
return common.GetAssertError("string", obj["e"])
}
switch e {
case wsAccountInfo:
var data []wsAccount
err := json.Unmarshal(respRaw, &data)
if err != nil {
return fmt.Errorf("%v - Could not convert to outboundAccountInfo structure %w",
by.Name,
err)
}
by.Websocket.DataHandler <- data
return nil
case wsOrderExecution:
var data []wsOrderUpdate
err := json.Unmarshal(respRaw, &data)
if err != nil {
return fmt.Errorf("%v - Could not convert to executionReport structure %w",
by.Name,
err)
}
for j := range data {
oType, err := order.StringToOrderType(data[j].OrderType)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: data[j].OrderID,
Err: err,
}
}
var oSide order.Side
oSide, err = order.StringToOrderSide(data[j].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: data[j].OrderID,
Err: err,
}
}
var oStatus order.Status
oStatus, err = stringToOrderStatus(data[j].OrderStatus)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: data[j].OrderID,
Err: err,
}
}
p, err := by.extractCurrencyPair(data[j].Symbol, asset.Spot)
if err != nil {
return err
}
by.Websocket.DataHandler <- order.Detail{
Price: data[j].Price,
Amount: data[j].Quantity,
ExecutedAmount: data[j].CumulativeFilledQuantity,
RemainingAmount: data[j].Quantity - data[j].CumulativeFilledQuantity,
Exchange: by.Name,
OrderID: data[j].OrderID,
Type: oType,
Side: oSide,
Status: oStatus,
AssetType: asset.Spot,
Date: data[j].OrderCreationTime.Time(),
Pair: p,
ClientOrderID: data[j].ClientOrderID,
Trades: []order.TradeHistory{
{
Price: data[j].Price,
Amount: data[j].Quantity,
Exchange: by.Name,
Timestamp: data[j].OrderCreationTime.Time(),
},
},
}
}
return nil
case wsTickerInfo:
var data []wsOrderFilled
err := json.Unmarshal(respRaw, &data)
if err != nil {
return fmt.Errorf("%v - Could not convert to ticketInfo structure %w",
by.Name,
err)
}
for j := range data {
var oSide order.Side
oSide, err = order.StringToOrderSide(data[j].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: data[j].OrderID,
Err: err,
}
}
p, err := by.extractCurrencyPair(data[j].Symbol, asset.Spot)
if err != nil {
return err
}
by.Websocket.DataHandler <- &order.Detail{
Exchange: by.Name,
OrderID: data[j].OrderID,
Side: oSide,
AssetType: asset.Spot,
Pair: p,
Price: data[j].Price,
Amount: data[j].Quantity,
Date: data[j].Timestamp.Time(),
Trades: []order.TradeHistory{
{
Price: data[j].Price,
Amount: data[j].Quantity,
Exchange: by.Name,
Timestamp: data[j].Timestamp.Time(),
TID: data[j].TradeID,
IsMaker: data[j].IsMaker,
},
},
}
}
return nil
}
}
}
return fmt.Errorf("unhandled stream data %s", string(respRaw))
}
func (by *Bybit) wsUpdateOrderbook(update *WsOrderbookData, p currency.Pair, assetType asset.Item) error {
if update == nil || (len(update.Asks) == 0 && len(update.Bids) == 0) {
return errors.New("no orderbook data")
}
asks := make([]orderbook.Item, len(update.Asks))
for i := range update.Asks {
target, err := strconv.ParseFloat(update.Asks[i][0], 64)
if err != nil {
return err
}
amount, err := strconv.ParseFloat(update.Asks[i][1], 64)
if err != nil {
return err
}
asks[i] = orderbook.Item{Price: target, Amount: amount}
}
bids := make([]orderbook.Item, len(update.Bids))
for i := range update.Bids {
target, err := strconv.ParseFloat(update.Bids[i][0], 64)
if err != nil {
return err
}
amount, err := strconv.ParseFloat(update.Bids[i][1], 64)
if err != nil {
return err
}
bids[i] = orderbook.Item{Price: target, Amount: amount}
}
return by.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{
Bids: bids,
Asks: asks,
Pair: p,
LastUpdated: update.Time.Time(),
Asset: assetType,
Exchange: by.Name,
VerifyOrderbook: by.CanVerifyOrderbook,
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,789 @@
package bybit
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
)
const (
wsCoinMarginedPath = "realtime"
subscribe = "subscribe"
unsubscribe = "unsubscribe"
dot = "."
// public endpoints
wsOrder25 = "orderBookL2_25"
wsOrder200 = "orderBook_200"
wsTrade = "trade"
wsInsurance = "insurance"
wsInstrument = "instrument_info"
wsCoinMarket = "klineV2"
wsLiquidation = "liquidation"
wsOperationSnapshot = "snapshot"
wsOperationDelta = "delta"
wsOrderbookActionDelete = "delete"
wsOrderbookActionUpdate = "update"
wsOrderbookActionInsert = "insert"
wsKlineV2 = "klineV2"
// private endpoints
wsPosition = "position"
wsExecution = "execution"
wsOrder = "order"
wsStopOrder = "stop_order"
wsWallet = "wallet"
)
var pingRequest = WsFuturesReq{Topic: stream.Ping}
// WsCoinConnect connects to a CMF websocket feed
func (by *Bybit) WsCoinConnect() error {
if !by.Websocket.IsEnabled() || !by.IsEnabled() {
return errors.New(stream.WebsocketNotEnabled)
}
var dialer websocket.Dialer
err := by.Websocket.Conn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
pingMsg, err := json.Marshal(pingRequest)
if err != nil {
return err
}
by.Websocket.Conn.SetupPingHandler(stream.PingHandler{
Message: pingMsg,
MessageType: websocket.PingMessage,
Delay: bybitWebsocketTimer,
})
if by.Verbose {
log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", by.Name)
}
by.Websocket.Wg.Add(1)
go by.wsCoinReadData()
if by.IsWebsocketAuthenticationSupported() {
err = by.WsCoinAuth(context.TODO())
if err != nil {
by.Websocket.DataHandler <- err
by.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
}
by.Websocket.Wg.Add(1)
go by.WsDataHandler()
return nil
}
// WsCoinAuth sends an authentication message to receive auth data
func (by *Bybit) WsCoinAuth(ctx context.Context) error {
creds, err := by.GetCredentials(ctx)
if err != nil {
return err
}
intNonce := (time.Now().Unix() + 1) * 1000
strNonce := strconv.FormatInt(intNonce, 10)
hmac, err := crypto.GetHMAC(
crypto.HashSHA256,
[]byte("GET/realtime"+strNonce),
[]byte(creds.Secret),
)
if err != nil {
return err
}
sign := crypto.HexEncodeToString(hmac)
req := Authenticate{
Operation: "auth",
Args: []interface{}{creds.Key, intNonce, sign},
}
return by.Websocket.Conn.SendJSONMessage(req)
}
// SubscribeCoin sends a websocket message to receive data from the channel
func (by *Bybit) SubscribeCoin(channelsToSubscribe []stream.ChannelSubscription) error {
var errs common.Errors
for i := range channelsToSubscribe {
var sub WsFuturesReq
sub.Topic = subscribe
sub.Args = append(sub.Args, formatArgs(channelsToSubscribe[i].Channel, channelsToSubscribe[i].Params))
err := by.Websocket.Conn.SendJSONMessage(sub)
if err != nil {
errs = append(errs, err)
continue
}
by.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
func formatArgs(channel string, params map[string]interface{}) string {
argStr := channel
for x := range params {
argStr += dot + fmt.Sprintf("%v", params[x])
}
return argStr
}
// UnsubscribeCoin sends a websocket message to stop receiving data from the channel
func (by *Bybit) UnsubscribeCoin(channelsToUnsubscribe []stream.ChannelSubscription) error {
var errs common.Errors
for i := range channelsToUnsubscribe {
var unSub WsFuturesReq
unSub.Topic = unsubscribe
formattedPair, err := by.FormatExchangeCurrency(channelsToUnsubscribe[i].Currency, asset.CoinMarginedFutures)
if err != nil {
errs = append(errs, err)
continue
}
unSub.Args = append(unSub.Args, channelsToUnsubscribe[i].Channel+dot+formattedPair.String())
err = by.Websocket.Conn.SendJSONMessage(unSub)
if err != nil {
errs = append(errs, err)
continue
}
by.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
// wsCoinReadData gets and passes on websocket messages for processing
func (by *Bybit) wsCoinReadData() {
by.Websocket.Wg.Add(1)
defer by.Websocket.Wg.Done()
for {
select {
case <-by.Websocket.ShutdownC:
return
default:
resp := by.Websocket.Conn.ReadMessage()
if resp.Raw == nil {
return
}
err := by.wsCoinHandleData(resp.Raw)
if err != nil {
by.Websocket.DataHandler <- err
}
}
}
}
func (by *Bybit) wsCoinHandleData(respRaw []byte) error {
var multiStreamData map[string]interface{}
err := json.Unmarshal(respRaw, &multiStreamData)
if err != nil {
return err
}
t, ok := multiStreamData["topic"].(string)
if !ok {
log.Errorf(log.ExchangeSys, "%s Received unhandle message on websocket: %v\n", by.Name, multiStreamData)
return nil
}
topics := strings.Split(t, dot)
if len(topics) < 1 {
return errors.New(by.Name + " - topic could not be extracted from response")
}
if wsType, ok := multiStreamData["type"].(string); ok {
switch topics[0] {
case wsOrder25, wsOrder200:
switch wsType {
case wsOperationSnapshot:
var response WsFuturesOrderbook
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
var p currency.Pair
p, err = by.extractCurrencyPair(response.OBData[0].Symbol, asset.CoinMarginedFutures)
if err != nil {
return err
}
err = by.processOrderbook(response.OBData,
response.Type,
p,
asset.CoinMarginedFutures)
if err != nil {
return err
}
case wsOperationDelta:
var response WsCoinDeltaOrderbook
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
if len(response.OBData.Delete) > 0 {
var p currency.Pair
p, err = by.extractCurrencyPair(response.OBData.Delete[0].Symbol, asset.CoinMarginedFutures)
if err != nil {
return err
}
err = by.processOrderbook(response.OBData.Delete,
wsOrderbookActionDelete,
p,
asset.CoinMarginedFutures)
if err != nil {
return err
}
}
if len(response.OBData.Update) > 0 {
var p currency.Pair
p, err = by.extractCurrencyPair(response.OBData.Update[0].Symbol, asset.CoinMarginedFutures)
if err != nil {
return err
}
err = by.processOrderbook(response.OBData.Update,
wsOrderbookActionUpdate,
p,
asset.CoinMarginedFutures)
if err != nil {
return err
}
}
if len(response.OBData.Insert) > 0 {
var p currency.Pair
p, err = by.extractCurrencyPair(response.OBData.Insert[0].Symbol, asset.CoinMarginedFutures)
if err != nil {
return err
}
err = by.processOrderbook(response.OBData.Insert,
wsOrderbookActionInsert,
p,
asset.CoinMarginedFutures)
if err != nil {
return err
}
}
default:
by.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: by.Name + stream.UnhandledMessage + "unsupported orderbook operation"}
}
case wsTrades:
if !by.IsSaveTradeDataEnabled() {
return nil
}
var response WsFuturesTrade
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
counter := 0
trades := make([]trade.Data, len(response.TradeData))
for i := range response.TradeData {
var p currency.Pair
p, err = by.extractCurrencyPair(response.TradeData[0].Symbol, asset.CoinMarginedFutures)
if err != nil {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(response.TradeData[i].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
Err: err,
}
}
trades[counter] = trade.Data{
TID: response.TradeData[i].ID,
Exchange: by.Name,
CurrencyPair: p,
AssetType: asset.CoinMarginedFutures,
Side: oSide,
Price: response.TradeData[i].Price,
Amount: response.TradeData[i].Size,
Timestamp: response.TradeData[i].Time,
}
counter++
}
return by.AddTradesToBuffer(trades...)
case wsKlineV2:
var response WsFuturesKline
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
var p currency.Pair
p, err = by.extractCurrencyPair(topics[len(topics)-1], asset.CoinMarginedFutures)
if err != nil {
return err
}
for i := range response.KlineData {
by.Websocket.DataHandler <- stream.KlineData{
Pair: p,
AssetType: asset.CoinMarginedFutures,
Exchange: by.Name,
OpenPrice: response.KlineData[i].Open,
HighPrice: response.KlineData[i].High,
LowPrice: response.KlineData[i].Low,
ClosePrice: response.KlineData[i].Close,
Volume: response.KlineData[i].Volume,
Timestamp: response.KlineData[i].Timestamp.Time(),
}
}
case wsInsurance:
var response WsInsurance
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
by.Websocket.DataHandler <- response.Data
case wsInstrument:
if wsType, ok := multiStreamData["type"].(string); ok {
switch wsType {
case wsOperationSnapshot:
var response WsTicker
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
var p currency.Pair
p, err = by.extractCurrencyPair(response.Ticker.Symbol, asset.CoinMarginedFutures)
if err != nil {
return err
}
by.Websocket.DataHandler <- &ticker.Price{
ExchangeName: by.Name,
Last: response.Ticker.LastPrice,
High: response.Ticker.HighPrice24h,
Low: response.Ticker.LowPrice24h,
Bid: response.Ticker.BidPrice,
Ask: response.Ticker.AskPrice,
Volume: response.Ticker.Volume24h,
Close: response.Ticker.PrevPrice24h,
LastUpdated: response.Ticker.UpdateAt,
AssetType: asset.CoinMarginedFutures,
Pair: p,
}
case wsOperationDelta:
var response WsDeltaTicker
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
if len(response.Data.Delete) > 0 {
for x := range response.Data.Delete {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data.Delete[x].Symbol, asset.CoinMarginedFutures)
if err != nil {
return err
}
by.Websocket.DataHandler <- &ticker.Price{
ExchangeName: by.Name,
Last: response.Data.Delete[x].LastPrice,
High: response.Data.Delete[x].HighPrice24h,
Low: response.Data.Delete[x].LowPrice24h,
Bid: response.Data.Delete[x].BidPrice,
Ask: response.Data.Delete[x].AskPrice,
Volume: response.Data.Delete[x].Volume24h,
Close: response.Data.Delete[x].PrevPrice24h,
LastUpdated: response.Data.Delete[x].UpdateAt,
AssetType: asset.CoinMarginedFutures,
Pair: p,
}
}
}
if len(response.Data.Update) > 0 {
for x := range response.Data.Update {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data.Update[x].Symbol, asset.CoinMarginedFutures)
if err != nil {
return err
}
by.Websocket.DataHandler <- &ticker.Price{
ExchangeName: by.Name,
Last: response.Data.Update[x].LastPrice,
High: response.Data.Update[x].HighPrice24h,
Low: response.Data.Update[x].LowPrice24h,
Bid: response.Data.Update[x].BidPrice,
Ask: response.Data.Update[x].AskPrice,
Volume: response.Data.Update[x].Volume24h,
Close: response.Data.Update[x].PrevPrice24h,
LastUpdated: response.Data.Update[x].UpdateAt,
AssetType: asset.CoinMarginedFutures,
Pair: p,
}
}
}
if len(response.Data.Insert) > 0 {
for x := range response.Data.Insert {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data.Insert[x].Symbol, asset.CoinMarginedFutures)
if err != nil {
return err
}
by.Websocket.DataHandler <- &ticker.Price{
ExchangeName: by.Name,
Last: response.Data.Insert[x].LastPrice,
High: response.Data.Insert[x].HighPrice24h,
Low: response.Data.Insert[x].LowPrice24h,
Bid: response.Data.Insert[x].BidPrice,
Ask: response.Data.Insert[x].AskPrice,
Volume: response.Data.Insert[x].Volume24h,
Close: response.Data.Insert[x].PrevPrice24h,
LastUpdated: response.Data.Insert[x].UpdateAt,
AssetType: asset.CoinMarginedFutures,
Pair: p,
}
}
}
default:
by.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: by.Name + stream.UnhandledMessage + "unsupported ticker operation"}
}
}
case wsLiquidation:
var response WsFuturesLiquidation
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
by.Websocket.DataHandler <- response.Data
case wsPosition:
var response WsFuturesPosition
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
by.Websocket.DataHandler <- response.Data
case wsExecution:
var response WsFuturesExecution
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
for i := range response.Data {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data[i].Symbol, asset.CoinMarginedFutures)
if err != nil {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(response.Data[i].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[i].OrderID,
Err: err,
}
}
var oStatus order.Status
oStatus, err = order.StringToOrderStatus(response.Data[i].ExecutionType)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[i].OrderID,
Err: err,
}
}
by.Websocket.DataHandler <- &order.Detail{
Exchange: by.Name,
OrderID: response.Data[i].OrderID,
AssetType: asset.CoinMarginedFutures,
Pair: p,
Price: response.Data[i].Price,
Amount: response.Data[i].OrderQty,
Side: oSide,
Status: oStatus,
Trades: []order.TradeHistory{
{
Price: response.Data[i].Price,
Amount: response.Data[i].OrderQty,
Exchange: by.Name,
Side: oSide,
Timestamp: response.Data[i].Time,
TID: response.Data[i].ExecutionID,
IsMaker: response.Data[i].IsMaker,
},
},
}
}
case wsOrder:
var response WsOrder
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
for x := range response.Data {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data[x].Symbol, asset.CoinMarginedFutures)
if err != nil {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(response.Data[x].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
var oType order.Type
oType, err = order.StringToOrderType(response.Data[x].OrderType)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
var oStatus order.Status
oStatus, err = order.StringToOrderStatus(response.Data[x].OrderStatus)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
by.Websocket.DataHandler <- &order.Detail{
Price: response.Data[x].Price,
Amount: response.Data[x].OrderQty,
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Type: oType,
Side: oSide,
Status: oStatus,
AssetType: asset.CoinMarginedFutures,
Date: response.Data[x].Time,
Pair: p,
Trades: []order.TradeHistory{
{
Price: response.Data[x].Price,
Amount: response.Data[x].OrderQty,
Exchange: by.Name,
Side: oSide,
Timestamp: response.Data[x].Time,
},
},
}
}
case wsStopOrder:
var response WsFuturesStopOrder
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
for x := range response.Data {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data[x].Symbol, asset.CoinMarginedFutures)
if err != nil {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(response.Data[x].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
var oType order.Type
oType, err = order.StringToOrderType(response.Data[x].OrderType)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
var oStatus order.Status
oStatus, err = order.StringToOrderStatus(response.Data[x].OrderStatus)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
by.Websocket.DataHandler <- &order.Detail{
Price: response.Data[x].Price,
Amount: response.Data[x].OrderQty,
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
AccountID: strconv.FormatInt(response.Data[x].UserID, 10),
Type: oType,
Side: oSide,
Status: oStatus,
AssetType: asset.CoinMarginedFutures,
Date: response.Data[x].Time,
Pair: p,
Trades: []order.TradeHistory{
{
Price: response.Data[x].Price,
Amount: response.Data[x].OrderQty,
Exchange: by.Name,
Side: oSide,
Timestamp: response.Data[x].Time,
},
},
}
}
case wsWallet:
var response WsFuturesWallet
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
by.Websocket.DataHandler <- response.Data
default:
by.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: by.Name + stream.UnhandledMessage + string(respRaw)}
}
}
return nil
}
// processOrderbook processes orderbook updates
func (by *Bybit) processOrderbook(data []WsFuturesOrderbookData, action string, p currency.Pair, a asset.Item) error {
if len(data) < 1 {
return errors.New("no orderbook data")
}
switch action {
case wsOperationSnapshot:
var book orderbook.Base
for i := range data {
item := orderbook.Item{
Price: data[i].Price,
Amount: data[i].Size,
ID: data[i].ID,
}
switch {
case strings.EqualFold(data[i].Side, sideSell):
book.Asks = append(book.Asks, item)
case strings.EqualFold(data[i].Side, sideBuy):
book.Bids = append(book.Bids, item)
default:
return fmt.Errorf("could not process websocket orderbook update, order side could not be matched for %s",
data[i].Side)
}
}
book.Asset = a
book.Pair = p
book.Exchange = by.Name
book.VerifyOrderbook = by.CanVerifyOrderbook
err := by.Websocket.Orderbook.LoadSnapshot(&book)
if err != nil {
return fmt.Errorf("process orderbook error - %s", err)
}
default:
updateAction, err := by.GetActionFromString(action)
if err != nil {
return err
}
var asks, bids []orderbook.Item
for i := range data {
item := orderbook.Item{
Price: data[i].Price,
Amount: data[i].Size,
ID: data[i].ID,
}
switch {
case strings.EqualFold(data[i].Side, sideSell):
asks = append(asks, item)
case strings.EqualFold(data[i].Side, sideBuy):
bids = append(bids, item)
default:
return fmt.Errorf("could not process websocket orderbook update, order side could not be matched for %s",
data[i].Side)
}
}
err = by.Websocket.Orderbook.Update(&orderbook.Update{
Bids: bids,
Asks: asks,
Pair: p,
Asset: a,
Action: updateAction,
})
if err != nil {
return err
}
}
return nil
}
// GetActionFromString matches a string action to an internal action.
func (by *Bybit) GetActionFromString(s string) (orderbook.Action, error) {
switch s {
case wsOrderbookActionUpdate:
return orderbook.Amend, nil
case wsOrderbookActionDelete:
return orderbook.Delete, nil
case wsOrderbookActionInsert:
return orderbook.Insert, nil
}
return 0, fmt.Errorf("%s %w", s, orderbook.ErrInvalidAction)
}

View File

@@ -0,0 +1,645 @@
package bybit
import (
"context"
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
)
const (
wsFuturesPath = "realtime"
)
// WsFuturesConnect connects to a Futures websocket feed
func (by *Bybit) WsFuturesConnect() error {
if !by.Websocket.IsEnabled() || !by.IsEnabled() {
return errors.New(stream.WebsocketNotEnabled)
}
var dialer websocket.Dialer
err := by.Websocket.Conn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
pingMsg, err := json.Marshal(pingRequest)
if err != nil {
return err
}
by.Websocket.Conn.SetupPingHandler(stream.PingHandler{
Message: pingMsg,
MessageType: websocket.PingMessage,
Delay: bybitWebsocketTimer,
})
if by.Verbose {
log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", by.Name)
}
go by.wsFuturesReadData()
if by.IsWebsocketAuthenticationSupported() {
err = by.WsFuturesAuth(context.TODO())
if err != nil {
by.Websocket.DataHandler <- err
by.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
}
return nil
}
// WsFuturesAuth sends an authentication message to receive auth data
func (by *Bybit) WsFuturesAuth(ctx context.Context) error {
creds, err := by.GetCredentials(ctx)
if err != nil {
return err
}
intNonce := (time.Now().Unix() + 1) * 1000
strNonce := strconv.FormatInt(intNonce, 10)
hmac, err := crypto.GetHMAC(
crypto.HashSHA256,
[]byte("GET/realtime"+strNonce),
[]byte(creds.Secret),
)
if err != nil {
return err
}
sign := crypto.HexEncodeToString(hmac)
req := Authenticate{
Operation: "auth",
Args: []interface{}{creds.Key, intNonce, sign},
}
return by.Websocket.Conn.SendJSONMessage(req)
}
// SubscribeFutures sends a websocket message to receive data from the channel
func (by *Bybit) SubscribeFutures(channelsToSubscribe []stream.ChannelSubscription) error {
var errs common.Errors
for i := range channelsToSubscribe {
var sub WsFuturesReq
sub.Topic = subscribe
sub.Args = append(sub.Args, formatArgs(channelsToSubscribe[i].Channel, channelsToSubscribe[i].Params))
err := by.Websocket.Conn.SendJSONMessage(sub)
if err != nil {
errs = append(errs, err)
continue
}
by.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
// UnsubscribeFutures sends a websocket message to stop receiving data from the channel
func (by *Bybit) UnsubscribeFutures(channelsToUnsubscribe []stream.ChannelSubscription) error {
var errs common.Errors
for i := range channelsToUnsubscribe {
var unSub WsFuturesReq
unSub.Topic = unsubscribe
formattedPair, err := by.FormatExchangeCurrency(channelsToUnsubscribe[i].Currency, asset.Futures)
if err != nil {
errs = append(errs, err)
continue
}
unSub.Args = append(unSub.Args, channelsToUnsubscribe[i].Channel+dot+formattedPair.String())
err = by.Websocket.Conn.SendJSONMessage(unSub)
if err != nil {
errs = append(errs, err)
continue
}
by.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
// wsFuturesReadData gets and passes on websocket messages for processing
func (by *Bybit) wsFuturesReadData() {
by.Websocket.Wg.Add(1)
defer by.Websocket.Wg.Done()
for {
select {
case <-by.Websocket.ShutdownC:
return
default:
resp := by.Websocket.Conn.ReadMessage()
if resp.Raw == nil {
return
}
err := by.wsFuturesHandleData(resp.Raw)
if err != nil {
by.Websocket.DataHandler <- err
}
}
}
}
func (by *Bybit) wsFuturesHandleData(respRaw []byte) error {
var multiStreamData map[string]interface{}
err := json.Unmarshal(respRaw, &multiStreamData)
if err != nil {
return err
}
t, ok := multiStreamData["topic"].(string)
if !ok {
log.Errorf(log.ExchangeSys, "%s Received unhandle message on websocket: %v\n", by.Name, multiStreamData)
return nil
}
topics := strings.Split(t, dot)
if len(topics) < 1 {
return errors.New(by.Name + " - topic could not be extracted from response")
}
switch topics[0] {
case wsOrder25, wsOrder200:
if wsType, ok := multiStreamData["type"].(string); ok {
switch wsType {
case wsOperationSnapshot:
var response WsFuturesOrderbook
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
var p currency.Pair
p, err = by.extractCurrencyPair(response.OBData[0].Symbol, asset.Futures)
if err != nil {
return err
}
err = by.processOrderbook(response.OBData,
response.Type,
p,
asset.Futures)
if err != nil {
return err
}
case wsOperationDelta:
var response WsCoinDeltaOrderbook
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
if len(response.OBData.Delete) > 0 {
var p currency.Pair
p, err = by.extractCurrencyPair(response.OBData.Delete[0].Symbol, asset.Futures)
if err != nil {
return err
}
err = by.processOrderbook(response.OBData.Delete,
wsOrderbookActionDelete,
p,
asset.Futures)
if err != nil {
return err
}
}
if len(response.OBData.Update) > 0 {
var p currency.Pair
p, err = by.extractCurrencyPair(response.OBData.Update[0].Symbol, asset.Futures)
if err != nil {
return err
}
err = by.processOrderbook(response.OBData.Update,
wsOrderbookActionUpdate,
p,
asset.Futures)
if err != nil {
return err
}
}
if len(response.OBData.Insert) > 0 {
var p currency.Pair
p, err = by.extractCurrencyPair(response.OBData.Insert[0].Symbol, asset.Futures)
if err != nil {
return err
}
err = by.processOrderbook(response.OBData.Insert,
wsOrderbookActionInsert,
p,
asset.Futures)
if err != nil {
return err
}
}
default:
by.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: by.Name + stream.UnhandledMessage + "unsupported orderbook operation"}
}
}
case wsTrades:
if !by.IsSaveTradeDataEnabled() {
return nil
}
var response WsFuturesTrade
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
counter := 0
trades := make([]trade.Data, len(response.TradeData))
for i := range response.TradeData {
var p currency.Pair
p, err = by.extractCurrencyPair(response.TradeData[0].Symbol, asset.Futures)
if err != nil {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(response.TradeData[i].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
Err: err,
}
}
trades[counter] = trade.Data{
TID: response.TradeData[i].ID,
Exchange: by.Name,
CurrencyPair: p,
AssetType: asset.Futures,
Side: oSide,
Price: response.TradeData[i].Price,
Amount: response.TradeData[i].Size,
Timestamp: response.TradeData[i].Time,
}
counter++
}
return by.AddTradesToBuffer(trades...)
case wsKlineV2:
var response WsFuturesKline
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
var p currency.Pair
p, err = by.extractCurrencyPair(topics[len(topics)-1], asset.Futures)
if err != nil {
return err
}
for i := range response.KlineData {
by.Websocket.DataHandler <- stream.KlineData{
Pair: p,
AssetType: asset.Futures,
Exchange: by.Name,
OpenPrice: response.KlineData[i].Open,
HighPrice: response.KlineData[i].High,
LowPrice: response.KlineData[i].Low,
ClosePrice: response.KlineData[i].Close,
Volume: response.KlineData[i].Volume,
Timestamp: response.KlineData[i].Timestamp.Time(),
}
}
case wsInstrument:
if wsType, ok := multiStreamData["type"].(string); ok {
switch wsType {
case wsOperationSnapshot:
var response WsFuturesTicker
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
var p currency.Pair
p, err = by.extractCurrencyPair(response.Ticker.Symbol, asset.Futures)
if err != nil {
return err
}
by.Websocket.DataHandler <- &ticker.Price{
ExchangeName: by.Name,
Last: response.Ticker.LastPrice,
High: response.Ticker.HighPrice24h,
Low: response.Ticker.LowPrice24h,
Bid: response.Ticker.BidPrice,
Ask: response.Ticker.AskPrice,
Volume: response.Ticker.Volume24h,
Close: response.Ticker.PrevPrice24h,
LastUpdated: response.Ticker.UpdateAt,
AssetType: asset.Futures,
Pair: p,
}
case wsOperationDelta:
var response WsDeltaFuturesTicker
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
if len(response.Data.Delete) > 0 {
for x := range response.Data.Delete {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data.Delete[x].Symbol, asset.Futures)
if err != nil {
return err
}
by.Websocket.DataHandler <- &ticker.Price{
ExchangeName: by.Name,
Last: response.Data.Delete[x].LastPrice,
High: response.Data.Delete[x].HighPrice24h,
Low: response.Data.Delete[x].LowPrice24h,
Bid: response.Data.Delete[x].BidPrice,
Ask: response.Data.Delete[x].AskPrice,
Volume: response.Data.Delete[x].Volume24h,
Close: response.Data.Delete[x].PrevPrice24h,
LastUpdated: response.Data.Delete[x].UpdateAt,
AssetType: asset.Futures,
Pair: p,
}
}
}
if len(response.Data.Update) > 0 {
for x := range response.Data.Update {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data.Update[x].Symbol, asset.Futures)
if err != nil {
return err
}
by.Websocket.DataHandler <- &ticker.Price{
ExchangeName: by.Name,
Last: response.Data.Update[x].LastPrice,
High: response.Data.Update[x].HighPrice24h,
Low: response.Data.Update[x].LowPrice24h,
Bid: response.Data.Update[x].BidPrice,
Ask: response.Data.Update[x].AskPrice,
Volume: response.Data.Update[x].Volume24h,
Close: response.Data.Update[x].PrevPrice24h,
LastUpdated: response.Data.Update[x].UpdateAt,
AssetType: asset.Futures,
Pair: p,
}
}
}
if len(response.Data.Insert) > 0 {
for x := range response.Data.Insert {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data.Insert[x].Symbol, asset.Futures)
if err != nil {
return err
}
by.Websocket.DataHandler <- &ticker.Price{
ExchangeName: by.Name,
Last: response.Data.Insert[x].LastPrice,
High: response.Data.Insert[x].HighPrice24h,
Low: response.Data.Insert[x].LowPrice24h,
Bid: response.Data.Insert[x].BidPrice,
Ask: response.Data.Insert[x].AskPrice,
Volume: response.Data.Insert[x].Volume24h,
Close: response.Data.Insert[x].PrevPrice24h,
LastUpdated: response.Data.Insert[x].UpdateAt,
AssetType: asset.Futures,
Pair: p,
}
}
}
default:
by.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: by.Name + stream.UnhandledMessage + "unsupported ticker operation"}
}
}
case wsInsurance:
var response WsInsurance
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
by.Websocket.DataHandler <- response.Data
case wsPosition:
var response WsFuturesPosition
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
by.Websocket.DataHandler <- response.Data
case wsExecution:
var response WsFuturesExecution
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
for i := range response.Data {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data[i].Symbol, asset.Futures)
if err != nil {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(response.Data[i].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[i].OrderID,
Err: err,
}
}
var oStatus order.Status
oStatus, err = order.StringToOrderStatus(response.Data[i].ExecutionType)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[i].OrderID,
Err: err,
}
}
by.Websocket.DataHandler <- &order.Detail{
Exchange: by.Name,
OrderID: response.Data[i].OrderID,
AssetType: asset.Futures,
Pair: p,
Price: response.Data[i].Price,
Amount: response.Data[i].OrderQty,
Side: oSide,
Status: oStatus,
Trades: []order.TradeHistory{
{
Price: response.Data[i].Price,
Amount: response.Data[i].OrderQty,
Exchange: by.Name,
Side: oSide,
Timestamp: response.Data[i].Time,
},
},
}
}
case wsOrder:
var response WsOrder
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
for x := range response.Data {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data[x].Symbol, asset.Futures)
if err != nil {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(response.Data[x].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
var oType order.Type
oType, err = order.StringToOrderType(response.Data[x].OrderType)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
var oStatus order.Status
oStatus, err = order.StringToOrderStatus(response.Data[x].OrderStatus)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
by.Websocket.DataHandler <- &order.Detail{
Price: response.Data[x].Price,
Amount: response.Data[x].OrderQty,
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Type: oType,
Side: oSide,
Status: oStatus,
AssetType: asset.Futures,
Date: response.Data[x].Time,
Pair: p,
Trades: []order.TradeHistory{
{
Price: response.Data[x].Price,
Amount: response.Data[x].OrderQty,
Exchange: by.Name,
Side: oSide,
Timestamp: response.Data[x].Time,
},
},
}
}
case wsStopOrder:
var response WsFuturesStopOrder
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
for x := range response.Data {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data[x].Symbol, asset.Futures)
if err != nil {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(response.Data[x].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
var oType order.Type
oType, err = order.StringToOrderType(response.Data[x].OrderType)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
var oStatus order.Status
oStatus, err = order.StringToOrderStatus(response.Data[x].OrderStatus)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
by.Websocket.DataHandler <- &order.Detail{
Price: response.Data[x].Price,
Amount: response.Data[x].OrderQty,
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
AccountID: strconv.FormatInt(response.Data[x].UserID, 10),
Type: oType,
Side: oSide,
Status: oStatus,
AssetType: asset.Futures,
Date: response.Data[x].Time,
Pair: p,
Trades: []order.TradeHistory{
{
Price: response.Data[x].Price,
Amount: response.Data[x].OrderQty,
Exchange: by.Name,
Side: oSide,
Timestamp: response.Data[x].Time,
},
},
}
}
default:
by.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: by.Name + stream.UnhandledMessage + string(respRaw)}
}
return nil
}

View File

@@ -0,0 +1,654 @@
package bybit
import (
"context"
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/log"
)
const (
wsUSDTMarginedPathPublic = "realtime_public"
wsUSDTMarginedPathPrivate = "realtime_private"
wsUSDTKline = "candle"
)
// WsUSDTConnect connects to a USDT websocket feed
func (by *Bybit) WsUSDTConnect() error {
if !by.Websocket.IsEnabled() || !by.IsEnabled() {
return errors.New(stream.WebsocketNotEnabled)
}
var dialer websocket.Dialer
err := by.Websocket.Conn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
pingMsg, err := json.Marshal(pingRequest)
if err != nil {
return err
}
by.Websocket.Conn.SetupPingHandler(stream.PingHandler{
Message: pingMsg,
MessageType: websocket.PingMessage,
Delay: bybitWebsocketTimer,
})
if by.Verbose {
log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", by.Name)
}
go by.wsUSDTReadData()
if by.IsWebsocketAuthenticationSupported() {
err = by.WsUSDTAuth(context.TODO())
if err != nil {
by.Websocket.DataHandler <- err
by.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
}
return nil
}
// WsUSDTAuth sends an authentication message to receive auth data
func (by *Bybit) WsUSDTAuth(ctx context.Context) error {
creds, err := by.GetCredentials(ctx)
if err != nil {
return err
}
intNonce := (time.Now().Unix() + 1) * 1000
strNonce := strconv.FormatInt(intNonce, 10)
hmac, err := crypto.GetHMAC(
crypto.HashSHA256,
[]byte("GET/realtime"+strNonce),
[]byte(creds.Secret),
)
if err != nil {
return err
}
sign := crypto.HexEncodeToString(hmac)
req := Authenticate{
Operation: "auth",
Args: []interface{}{creds.Key, intNonce, sign},
}
return by.Websocket.Conn.SendJSONMessage(req)
}
// SubscribeUSDT sends a websocket message to receive data from the channel
func (by *Bybit) SubscribeUSDT(channelsToSubscribe []stream.ChannelSubscription) error {
var errs common.Errors
for i := range channelsToSubscribe {
var sub WsFuturesReq
sub.Topic = subscribe
sub.Args = append(sub.Args, formatArgs(channelsToSubscribe[i].Channel, channelsToSubscribe[i].Params))
err := by.Websocket.Conn.SendJSONMessage(sub)
if err != nil {
errs = append(errs, err)
continue
}
by.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
// UnsubscribeUSDT sends a websocket message to stop receiving data from the channel
func (by *Bybit) UnsubscribeUSDT(channelsToUnsubscribe []stream.ChannelSubscription) error {
var errs common.Errors
for i := range channelsToUnsubscribe {
var unSub WsFuturesReq
unSub.Topic = unsubscribe
formattedPair, err := by.FormatExchangeCurrency(channelsToUnsubscribe[i].Currency, asset.USDTMarginedFutures)
if err != nil {
errs = append(errs, err)
continue
}
unSub.Args = append(unSub.Args, channelsToUnsubscribe[i].Channel+dot+formattedPair.String())
err = by.Websocket.Conn.SendJSONMessage(unSub)
if err != nil {
errs = append(errs, err)
continue
}
by.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
// wsUSDTReadData gets and passes on websocket messages for processing
func (by *Bybit) wsUSDTReadData() {
by.Websocket.Wg.Add(1)
defer by.Websocket.Wg.Done()
for {
select {
case <-by.Websocket.ShutdownC:
return
default:
resp := by.Websocket.Conn.ReadMessage()
if resp.Raw == nil {
return
}
err := by.wsUSDTHandleData(resp.Raw)
if err != nil {
by.Websocket.DataHandler <- err
}
}
}
}
func (by *Bybit) wsUSDTHandleData(respRaw []byte) error {
var multiStreamData map[string]interface{}
err := json.Unmarshal(respRaw, &multiStreamData)
if err != nil {
return err
}
t, ok := multiStreamData["topic"].(string)
if !ok {
log.Errorf(log.ExchangeSys, "%s Received unhandle message on websocket: %v\n", by.Name, multiStreamData)
return nil
}
topics := strings.Split(t, dot)
if len(topics) < 1 {
return errors.New(by.Name + " - topic could not be extracted from response")
}
switch topics[0] {
case wsOrder25, wsOrder200:
if wsType, ok := multiStreamData["type"].(string); ok {
switch wsType {
case wsOperationSnapshot:
var response WsUSDTOrderbook
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data.OBData[0].Symbol, asset.USDTMarginedFutures)
if err != nil {
return err
}
err = by.processOrderbook(response.Data.OBData,
response.Type,
p,
asset.USDTMarginedFutures)
if err != nil {
return err
}
case wsOperationDelta:
var response WsCoinDeltaOrderbook
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
if len(response.OBData.Delete) > 0 {
var p currency.Pair
p, err = by.extractCurrencyPair(response.OBData.Delete[0].Symbol, asset.USDTMarginedFutures)
if err != nil {
return err
}
err = by.processOrderbook(response.OBData.Delete,
wsOrderbookActionDelete,
p,
asset.USDTMarginedFutures)
if err != nil {
return err
}
}
if len(response.OBData.Update) > 0 {
var p currency.Pair
p, err = by.extractCurrencyPair(response.OBData.Update[0].Symbol, asset.USDTMarginedFutures)
if err != nil {
return err
}
err = by.processOrderbook(response.OBData.Update,
wsOrderbookActionUpdate,
p,
asset.USDTMarginedFutures)
if err != nil {
return err
}
}
if len(response.OBData.Insert) > 0 {
var p currency.Pair
p, err = by.extractCurrencyPair(response.OBData.Insert[0].Symbol, asset.USDTMarginedFutures)
if err != nil {
return err
}
err = by.processOrderbook(response.OBData.Insert,
wsOrderbookActionInsert,
p,
asset.USDTMarginedFutures)
if err != nil {
return err
}
}
default:
by.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: by.Name + stream.UnhandledMessage + "unsupported orderbook operation"}
}
}
case wsTrades:
if !by.IsSaveTradeDataEnabled() {
return nil
}
var response WsFuturesTrade
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
trades := make([]trade.Data, len(response.TradeData))
for i := range response.TradeData {
var p currency.Pair
p, err = by.extractCurrencyPair(response.TradeData[0].Symbol, asset.USDTMarginedFutures)
if err != nil {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(response.TradeData[i].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
Err: err,
}
}
trades[i] = trade.Data{
TID: response.TradeData[i].ID,
Exchange: by.Name,
CurrencyPair: p,
AssetType: asset.USDTMarginedFutures,
Side: oSide,
Price: response.TradeData[i].Price,
Amount: response.TradeData[i].Size,
Timestamp: response.TradeData[i].Time,
}
}
return by.AddTradesToBuffer(trades...)
case wsUSDTKline:
var response WsFuturesKline
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
var p currency.Pair
p, err = by.extractCurrencyPair(topics[len(topics)-1], asset.USDTMarginedFutures)
if err != nil {
return err
}
for i := range response.KlineData {
by.Websocket.DataHandler <- stream.KlineData{
Pair: p,
AssetType: asset.USDTMarginedFutures,
Exchange: by.Name,
OpenPrice: response.KlineData[i].Open,
HighPrice: response.KlineData[i].High,
LowPrice: response.KlineData[i].Low,
ClosePrice: response.KlineData[i].Close,
Volume: response.KlineData[i].Volume,
Timestamp: response.KlineData[i].Timestamp.Time(),
}
}
case wsInstrument:
if wsType, ok := multiStreamData["type"].(string); ok {
switch wsType {
case wsOperationSnapshot:
var response WsTicker
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
var p currency.Pair
p, err = by.extractCurrencyPair(response.Ticker.Symbol, asset.USDTMarginedFutures)
if err != nil {
return err
}
by.Websocket.DataHandler <- &ticker.Price{
ExchangeName: by.Name,
Last: response.Ticker.LastPrice,
High: response.Ticker.HighPrice24h,
Low: response.Ticker.LowPrice24h,
Bid: response.Ticker.BidPrice,
Ask: response.Ticker.AskPrice,
Volume: response.Ticker.Volume24h,
Close: response.Ticker.PrevPrice24h,
LastUpdated: response.Ticker.UpdateAt,
AssetType: asset.USDTMarginedFutures,
Pair: p,
}
case wsOperationDelta:
var response WsDeltaTicker
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
if len(response.Data.Delete) > 0 {
for x := range response.Data.Delete {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data.Delete[x].Symbol, asset.USDTMarginedFutures)
if err != nil {
return err
}
by.Websocket.DataHandler <- &ticker.Price{
ExchangeName: by.Name,
Last: response.Data.Delete[x].LastPrice,
High: response.Data.Delete[x].HighPrice24h,
Low: response.Data.Delete[x].LowPrice24h,
Bid: response.Data.Delete[x].BidPrice,
Ask: response.Data.Delete[x].AskPrice,
Volume: response.Data.Delete[x].Volume24h,
Close: response.Data.Delete[x].PrevPrice24h,
LastUpdated: response.Data.Delete[x].UpdateAt,
AssetType: asset.USDTMarginedFutures,
Pair: p,
}
}
}
if len(response.Data.Update) > 0 {
for x := range response.Data.Update {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data.Update[x].Symbol, asset.USDTMarginedFutures)
if err != nil {
return err
}
by.Websocket.DataHandler <- &ticker.Price{
ExchangeName: by.Name,
Last: response.Data.Update[x].LastPrice,
High: response.Data.Update[x].HighPrice24h,
Low: response.Data.Update[x].LowPrice24h,
Bid: response.Data.Update[x].BidPrice,
Ask: response.Data.Update[x].AskPrice,
Volume: response.Data.Update[x].Volume24h,
Close: response.Data.Update[x].PrevPrice24h,
LastUpdated: response.Data.Update[x].UpdateAt,
AssetType: asset.USDTMarginedFutures,
Pair: p,
}
}
}
if len(response.Data.Insert) > 0 {
for x := range response.Data.Insert {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data.Insert[x].Symbol, asset.USDTMarginedFutures)
if err != nil {
return err
}
by.Websocket.DataHandler <- &ticker.Price{
ExchangeName: by.Name,
Last: response.Data.Insert[x].LastPrice,
High: response.Data.Insert[x].HighPrice24h,
Low: response.Data.Insert[x].LowPrice24h,
Bid: response.Data.Insert[x].BidPrice,
Ask: response.Data.Insert[x].AskPrice,
Volume: response.Data.Insert[x].Volume24h,
Close: response.Data.Insert[x].PrevPrice24h,
LastUpdated: response.Data.Insert[x].UpdateAt,
AssetType: asset.USDTMarginedFutures,
Pair: p,
}
}
}
default:
by.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: by.Name + stream.UnhandledMessage + "unsupported ticker operation"}
}
}
case wsLiquidation:
var response WsFuturesLiquidation
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
by.Websocket.DataHandler <- response.Data
case wsPosition:
var response WsFuturesPosition
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
by.Websocket.DataHandler <- response.Data
case wsExecution:
var response WsFuturesExecution
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
for i := range response.Data {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data[i].Symbol, asset.USDTMarginedFutures)
if err != nil {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(response.Data[i].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[i].OrderID,
Err: err,
}
}
var oStatus order.Status
oStatus, err = order.StringToOrderStatus(response.Data[i].ExecutionType)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[i].OrderID,
Err: err,
}
}
by.Websocket.DataHandler <- &order.Detail{
Exchange: by.Name,
OrderID: response.Data[i].OrderID,
AssetType: asset.USDTMarginedFutures,
Pair: p,
Side: oSide,
Status: oStatus,
Price: response.Data[i].Price,
Amount: response.Data[i].OrderQty,
Trades: []order.TradeHistory{
{
Price: response.Data[i].Price,
Amount: response.Data[i].OrderQty,
Exchange: by.Name,
Side: oSide,
Timestamp: response.Data[i].Time,
},
},
}
}
case wsOrder:
var response WsOrder
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
for x := range response.Data {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data[x].Symbol, asset.USDTMarginedFutures)
if err != nil {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(response.Data[x].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
var oType order.Type
oType, err = order.StringToOrderType(response.Data[x].OrderType)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
var oStatus order.Status
oStatus, err = order.StringToOrderStatus(response.Data[x].OrderStatus)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
by.Websocket.DataHandler <- &order.Detail{
Price: response.Data[x].Price,
Amount: response.Data[x].OrderQty,
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Type: oType,
Side: oSide,
Status: oStatus,
AssetType: asset.USDTMarginedFutures,
Date: response.Data[x].CreateTime,
Pair: p,
Trades: []order.TradeHistory{
{
Price: response.Data[x].Price,
Amount: response.Data[x].OrderQty,
Exchange: by.Name,
Side: oSide,
Timestamp: response.Data[x].Time,
},
},
}
}
case wsStopOrder:
var response WsUSDTFuturesStopOrder
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
for x := range response.Data {
var p currency.Pair
p, err = by.extractCurrencyPair(response.Data[x].Symbol, asset.USDTMarginedFutures)
if err != nil {
return err
}
var oSide order.Side
oSide, err = order.StringToOrderSide(response.Data[x].Side)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
var oType order.Type
oType, err = order.StringToOrderType(response.Data[x].OrderType)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
var oStatus order.Status
oStatus, err = order.StringToOrderStatus(response.Data[x].OrderStatus)
if err != nil {
by.Websocket.DataHandler <- order.ClassificationError{
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
Err: err,
}
}
by.Websocket.DataHandler <- &order.Detail{
Price: response.Data[x].Price,
Amount: response.Data[x].OrderQty,
Exchange: by.Name,
OrderID: response.Data[x].OrderID,
AccountID: strconv.FormatInt(response.Data[x].UserID, 10),
Type: oType,
Side: oSide,
Status: oStatus,
AssetType: asset.USDTMarginedFutures,
Date: response.Data[x].CreateTime,
Pair: p,
Trades: []order.TradeHistory{
{
Price: response.Data[x].Price,
Amount: response.Data[x].OrderQty,
Exchange: by.Name,
Side: oSide,
Timestamp: response.Data[x].CreateTime,
},
},
}
}
case wsWallet:
var response WsFuturesWallet
err = json.Unmarshal(respRaw, &response)
if err != nil {
return err
}
by.Websocket.DataHandler <- response.Data
default:
by.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: by.Name + stream.UnhandledMessage + string(respRaw)}
}
return nil
}

View File

@@ -0,0 +1,965 @@
package bybit
import "time"
var (
validFuturesIntervals = []string{
"1", "3", "5", "15", "30", "60", "120", "240", "360", "720",
"D", "M", "W", "d", "m", "w",
}
validFuturesPeriods = []string{
"5min", "15min", "30min", "1h", "4h", "1d",
}
)
// OrderbookData stores ob data for cmargined futures
type OrderbookData struct {
Symbol string `json:"symbol"`
Price float64 `json:"price,string"`
Size float64 `json:"size"`
Side string `json:"side"`
}
// FuturesCandleStick holds kline data
type FuturesCandleStick struct {
ID int64 `json:"id"`
Symbol string `json:"symbol"`
Interval string `json:"interval"`
OpenTime int64 `json:"open_time"`
Open float64 `json:"open"`
High float64 `json:"high"`
Low float64 `json:"low"`
Close float64 `json:"close"`
Volume float64 `json:"volume"`
TurnOver float64 `json:"turnover"`
}
// FuturesCandleStickWithStringParam holds kline data
type FuturesCandleStickWithStringParam struct {
ID int64 `json:"id"`
Symbol string `json:"symbol"`
Interval string `json:"interval"`
OpenTime int64 `json:"open_time"`
Open float64 `json:"open,string"`
High float64 `json:"high,string"`
Low float64 `json:"low,string"`
Close float64 `json:"close,string"`
Volume float64 `json:"volume,string"`
TurnOver float64 `json:"turnover,string"`
}
// SymbolPriceTicker stores ticker price stats
type SymbolPriceTicker struct {
Symbol string `json:"symbol"`
BidPrice float64 `json:"bid_price,string"`
AskPrice float64 `json:"ask_price,string"`
LastPrice float64 `json:"last_price,string"`
LastTickDirection string `json:"last_tick_direction"`
Price24hAgo float64 `json:"prev_price_24h,string"`
PricePcntChange24h float64 `json:"price_24h_pcnt,string"`
HighPrice24h float64 `json:"high_price_24h,string"`
LowPrice24h float64 `json:"low_price_24h,string"`
Price1hAgo float64 `json:"prev_price_1h,string"`
PricePcntChange1h float64 `json:"price_1h_pcnt,string"`
MarkPrice float64 `json:"mark_price,string"`
IndexPrice float64 `json:"index_price,string"`
OpenInterest float64 `json:"open_interest"`
OpenValue float64 `json:"open_value,string"`
TotalTurnover float64 `json:"total_turnover,string"`
Turnover24h float64 `json:"turnover_24h,string"`
TotalVolume float64 `json:"total_volume"`
Volume24h float64 `json:"volume_24h"`
FundingRate float64 `json:"funding_rate,string"`
PredictedFundingRate float64 `json:"predicted_funding_rate,string"`
NextFundingTime string `json:"next_funding_time"`
CountdownHour int64 `json:"countdown_hour"`
DeliveryFeeRate string `json:"delivery_fee_rate"` // type is string because it comes as empty string in API response sometime
PredictedDeliveryPrice string `json:"predicted_delivery_price"` // type is string because it comes as empty string in API response sometime
DeliveryTime string `json:"delivery_time"`
}
// FuturesPublicTradesData stores recent public trades for futures
type FuturesPublicTradesData struct {
Symbol string `json:"symbol"`
Price float64 `json:"price"`
Qty float64 `json:"qty"`
Time time.Time `json:"time"`
Side string `json:"side"`
TimeInMilliSec int64 `json:"trade_time_ms"`
}
// SymbolInfo stores symbol information for futures pair
type SymbolInfo struct {
Name string `json:"name"`
Alias string `json:"alias"`
Status string `json:"status"`
BaseCurrency string `json:"base_currency"`
QuoteCurrency string `json:"quote_currency"`
PriceScale float64 `json:"price_scale"`
TakerFee string `json:"taker_fee"`
MakerFee string `json:"maker_fee"`
FundingFeeInterval int64 `json:"funding_interval"`
LeverageFilter struct {
MinLeverage float64 `json:"min_leverage"`
MaxLeverage float64 `json:"max_leverage"`
LeverageStep float64 `json:"leverage_step,string"`
} `json:"leverage_filter"`
PriceFilter struct {
MinPrice float64 `json:"min_price,string"`
MaxPrice float64 `json:"max_price,string"`
TickSize float64 `json:"tick_size,string"`
} `json:"price_filter"`
LotSizeFilter struct {
MinTradeQty float64 `json:"min_trading_qty"`
MaxTradeQty float64 `json:"max_trading_qty"`
QtyStep float64 `json:"qty_step"`
} `json:"lot_size_filter"`
}
// MarkPriceKlineData stores mark price kline data
type MarkPriceKlineData struct {
ID int64 `json:"id"`
Symbol string `json:"symbol"`
Interval string `json:"period"`
StartAt int64 `json:"start_at"`
Open float64 `json:"open"`
High float64 `json:"high"`
Low float64 `json:"low"`
Close float64 `json:"close"`
}
// IndexPriceKlineData stores index price kline data
type IndexPriceKlineData struct {
Symbol string `json:"symbol"`
Interval string `json:"period"`
StartAt int64 `json:"open_time"`
Open float64 `json:"open,string"`
High float64 `json:"high,string"`
Low float64 `json:"low,string"`
Close float64 `json:"close,string"`
}
// OpenInterestData stores open interest data
type OpenInterestData struct {
OpenInterest float64 `json:"open_interest"`
Symbol string `json:"symbol"`
Time int64 `json:"time"`
}
// BigDealData stores big deal data
type BigDealData struct {
Symbol string `json:"symbol"`
Side string `json:"side"`
Time int64 `json:"timestamp"`
Value int64 `json:"value"`
}
// AccountRatioData stores user accounts long short ratio
type AccountRatioData struct {
Symbol string `json:"symbol"`
BuyRatio float64 `json:"buy_ratio"`
SellRatio float64 `json:"sell_ratio"`
Time int64 `json:"timestamp"`
}
// BaseFuturesOrder is base future order structure
type BaseFuturesOrder struct {
UserID int64 `json:"user_id"`
Symbol string `json:"symbol"`
Side string `json:"side"`
OrderType string `json:"order_type"`
Price float64 `json:"price"`
Qty float64 `json:"qty"`
TimeInForce string `json:"time_in_force"`
}
// FuturesOrderData stores futures order data
type FuturesOrderData struct {
BaseFuturesOrder
OrderStatus string `json:"order_status"`
OrderLinkID string `json:"order_link_id"`
OrderID string `json:"order_id"`
LeavesQty float64 `json:"leaves_qty"`
CumulativeQty float64 `json:"cum_exec_qty"`
CumulativeValue float64 `json:"cum_exec_value"`
CumulativeFee float64 `json:"cum_exec_fee"`
RejectReason string `json:"reject_reason"`
CreatedAt time.Time `json:"create_at"`
}
// FuturesOrderCancelResp stores future order cancel response
type FuturesOrderCancelResp struct {
FuturesOrderData
LastExecutionTime string `json:"last_exec_time"`
LastExecutionPrice float64 `json:"last_exec_price"`
UpdateAt string `json:"updated_at"`
}
// FuturesOrderDataResp stores future order response
type FuturesOrderDataResp struct {
FuturesOrderCancelResp
TakeProfit float64 `json:"take_profit"`
StopLoss float64 `json:"stop_loss"`
TakeProfitTriggerBy string `json:"tp_trigger_by"`
StopLossTriggerBy string `json:"sl_trigger_by"`
}
// FuturesActiveOrderData stores future active order data
type FuturesActiveOrderData struct {
FuturesOrderData
LeaveValue float64 `json:"leaves_value"`
}
// FuturesActiveOrderResp stores future active order response
type FuturesActiveOrderResp struct {
FuturesActiveOrderData
TakeProfit float64 `json:"take_profit"`
StopLoss float64 `json:"stop_loss"`
TakeProfitTriggerBy string `json:"tp_trigger_by"`
StopLossTriggerBy string `json:"sl_trigger_by"`
}
// FuturesActiveOrder stores future active order
type FuturesActiveOrder struct {
FuturesActiveOrderData
PositionID int64 `json:"position_idx"`
UpdatedAt string `json:"updated_at"`
}
// FuturesRealtimeOrderData stores futures realtime order data
type FuturesRealtimeOrderData struct {
BaseFuturesOrder
OrderStatus string `json:"order_status"`
OrderLinkID string `json:"order_link_id"`
TakeProfit float64 `json:"take_profit"`
StopLoss float64 `json:"stop_loss"`
TakeProfitTriggerBy string `json:"tp_trigger_by"`
StopLossTriggerBy string `json:"sl_trigger_by"`
}
// FuturesActiveRealtimeOrder stores future active realtime order
type FuturesActiveRealtimeOrder struct {
FuturesRealtimeOrderData
ExtensionField map[string]interface{} `json:"ext_fields"`
LastExecutionTime string `json:"last_exec_time"`
LastExecutionPrice float64 `json:"last_exec_price"`
LeavesQty float64 `json:"leaves_qty"`
LeaveValue float64 `json:"leaves_value,string"`
CumulativeQty float64 `json:"cum_exec_qty,string"`
CumulativeValue float64 `json:"cum_exec_value,string"`
CumulativeFee float64 `json:"cum_exec_fee,string"`
RejectReason string `json:"reject_reason"`
CancelType string `json:"cancel_type"`
CreatedAt time.Time `json:"create_at"`
UpdatedAt time.Time `json:"updated_at"`
OrderID string `json:"order_id"`
}
// CoinFuturesConditionalRealtimeOrder stores CMF future coinditional realtime order
type CoinFuturesConditionalRealtimeOrder struct {
FuturesRealtimeOrderData
ExtensionField map[string]interface{} `json:"ext_fields"`
LeavesQty float64 `json:"leaves_qty"`
LeaveValue float64 `json:"leaves_value,string"`
CumulativeQty float64 `json:"cum_exec_qty,string"`
CumulativeValue float64 `json:"cum_exec_value,string"`
CumulativeFee float64 `json:"cum_exec_fee,string"`
RejectReason string `json:"reject_reason"`
CancelType string `json:"cancel_type"`
CreatedAt string `json:"create_at"`
UpdatedAt string `json:"updated_at"`
OrderID string `json:"order_id"`
}
// FuturesConditionalRealtimeOrder stores future conditional realtime order
type FuturesConditionalRealtimeOrder struct {
CoinFuturesConditionalRealtimeOrder
PositionID int64 `json:"position_idx"`
}
// USDTFuturesConditionalRealtimeOrder stores USDT future conditional realtime order
type USDTFuturesConditionalRealtimeOrder struct {
FuturesRealtimeOrderData
StopOrderID string `json:"stop_order_id"`
OrderStatus string `json:"order_status"`
TriggerPrice float64 `json:"trigger_price"`
CreatedAt string `json:"created_time"`
UpdatedAt string `json:"updated_time"`
BasePrice float64 `json:"base_price"`
TriggerBy string `json:"trigger_by"`
ReduceOnly bool `json:"reduce_only"`
CloseOnTrigger bool `json:"close_on_trigger"`
}
// FuturesConditionalOrderData stores futures conditional order data
type FuturesConditionalOrderData struct {
BaseFuturesOrder
TriggerBy string `json:"trigger_by"`
BasePrice float64 `json:"base_price"`
StopOrderID string `json:"stop_order_id"`
OrderLinkID string `json:"order_link_id"`
TakeProfitTriggerBy string `json:"tp_trigger_by"`
StopLossTriggerBy string `json:"sl_trigger_by"`
}
// FuturesConditionalOrderResp stores futures conditional order response
type FuturesConditionalOrderResp struct {
FuturesConditionalOrderData
Remark string `json:"remark"`
RejectReason string `json:"reject_reason"`
StopPrice float64 `json:"stop_px"`
TakeProfit float64 `json:"take_profit"`
StopLoss float64 `json:"stop_loss"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// USDTFuturesConditionalOrderResp stores USDT futures conditional order response
type USDTFuturesConditionalOrderResp struct {
FuturesConditionalOrderData
OrderStatus string `json:"order_status"`
TriggerPrice float64 `json:"trigger_price"`
ReduceOnly bool `json:"reduce_only"`
CloseOnTrigger bool `json:"close_on_trigger"`
CreatedAt string `json:"created_time"`
UpdatedAt string `json:"updated_time"`
}
// CoinFuturesConditionalOrders stores CMF future conditional order
type CoinFuturesConditionalOrders struct {
FuturesConditionalOrderData
StopOrderStatus string `json:"stop_order_status"`
StopOrderType string `json:"stop_order_type"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
StopPrice float64 `json:"stop_px"`
StopOrderID string `json:"stop_order_id"`
TakeProfit float64 `json:"take_profit"`
StopLoss float64 `json:"stop_loss"`
}
// FuturesConditionalOrders stores future conditional order
type FuturesConditionalOrders struct {
CoinFuturesConditionalOrders
PositionID int64 `json:"position_idx"`
}
// USDTFuturesConditionalOrders stores USDT futures conditional order
type USDTFuturesConditionalOrders struct {
FuturesConditionalOrderData
OrderStatus string `json:"order_status"`
TriggerPrice float64 `json:"trigger_price"`
CreatedAt string `json:"created_time"`
UpdatedAt string `json:"updated_time"`
TakeProfit float64 `json:"take_profit"`
StopLoss float64 `json:"stop_loss"`
}
// FuturesCancelOrderData stores future cancel order data
type FuturesCancelOrderData struct {
CancelOrderID string `json:"clOrdID"`
BaseFuturesOrder
CreateType string `json:"create_type"`
CancelType string `json:"cancel_type"`
OrderStatus string `json:"order_status"`
LeavesQty float64 `json:"leaves_qty"`
LeavesValue float64 `json:"leaves_value"`
CreatedAt string `json:"create_at"`
UpdateAt string `json:"updated_at"`
CrossStatus string `json:"cross_status"`
CrossSeq int64 `json:"cross_seq"`
}
// FuturesCancelOrderResp stores future cancel order response
type FuturesCancelOrderResp struct {
FuturesCancelOrderData
StopOrderType string `json:"stop_order_type"`
TriggerBy string `json:"trigger_by"`
BasePrice float64 `json:"base_price,string"`
ExpectedDirection string `json:"expected_direction"`
}
// RiskInfo stores risk information
type RiskInfo struct {
ID int64 `json:"id"`
Symbol string `json:"symbol"`
Limit int64 `json:"limit"`
MaintainMargin float64 `json:"maintain_margin"`
StartingMargin float64 `json:"starting_margin"`
Section []string `json:"section"`
IsLowestRisk int64 `json:"is_lowest_risk"`
CreatedAt string `json:"create_at"`
UpdateAt string `json:"updated_at"`
MaxLeverage float64 `json:"max_leverage"`
}
// RiskInfoWithStringParam stores risk information where string params
type RiskInfoWithStringParam struct {
ID int64 `json:"id"`
Symbol string `json:"symbol"`
Limit int64 `json:"limit"`
MaintainMargin float64 `json:"maintain_margin,string"`
StartingMargin float64 `json:"starting_margin,string"`
Section []string `json:"section"`
IsLowestRisk int64 `json:"is_lowest_risk"`
CreatedAt string `json:"create_at"`
UpdateAt string `json:"updated_at"`
MaxLeverage float64 `json:"max_leverage,string"`
}
// FundingInfo stores funding information
type FundingInfo struct {
Symbol string `json:"symbol"`
FundingRate float64 `json:"funding_rate,string"`
FundingRateTimestamp int64 `json:"funding_rate_timestamp"`
}
// USDTFundingInfo stores USDT funding information
type USDTFundingInfo struct {
Symbol string `json:"symbol"`
FundingRate float64 `json:"funding_rate"`
FundingRateTimestamp string `json:"funding_rate_timestamp"`
}
// AnnouncementInfo stores announcement information
type AnnouncementInfo struct {
ID int64 `json:"id"`
Title string `json:"title"`
Link string `json:"link"`
Summary string `json:"summary"`
CreatedAt string `json:"created_at"`
}
// Position stores position
type Position struct {
UserID int64 `json:"user_id"`
Symbol string `json:"symbol"`
Side string `json:"side"`
Size float64 `json:"size"`
PositionValue float64 `json:"position_value"`
EntryPrice float64 `json:"entry_price"`
LiquidationPrice float64 `json:"liq_price"`
BankruptcyPrice float64 `json:"bust_price"`
Leverage float64 `json:"leverage"`
PositionMargin float64 `json:"position_margin"`
OccupiedClosingFee float64 `json:"occ_closing_fee"`
RealisedPNL float64 `json:"realised_pnl"`
AccumulatedRealisedPNL float64 `json:"cum_realised_pnl"`
}
// PositionWithStringParam stores position with string params
type PositionWithStringParam struct {
UserID int64 `json:"user_id"`
Symbol string `json:"symbol"`
Side string `json:"side"`
Size float64 `json:"size"`
PositionValue float64 `json:"position_value,string"`
EntryPrice float64 `json:"entry_price,string"`
LiquidationPrice float64 `json:"liq_price,string"`
BankruptcyPrice float64 `json:"bust_price,string"`
Leverage float64 `json:"leverage,string"`
PositionMargin float64 `json:"position_margin,string"`
OccupiedClosingFee float64 `json:"occ_closing_fee,string"`
RealisedPNL float64 `json:"realised_pnl,string"`
AccumulatedRealisedPNL float64 `json:"cum_realised_pnl,string"`
}
// PositionData stores position data
type PositionData struct {
Position
IsIsolated bool `json:"is_isolated"`
AutoAddMargin int64 `json:"auto_add_margin"`
UnrealisedPNL float64 `json:"unrealised_pnl"`
DeleverageIndicator int64 `json:"deleverage_indicator"`
RiskID int64 `json:"risk_id"`
TakeProfit float64 `json:"take_profit"`
StopLoss float64 `json:"stop_loss"`
TrailingStop float64 `json:"trailing_stop"`
}
// PositionDataWithStringParam stores position data with string params
type PositionDataWithStringParam struct {
PositionWithStringParam
IsIsolated bool `json:"is_isolated"`
AutoAddMargin int64 `json:"auto_add_margin"`
UnrealisedPNL float64 `json:"unrealised_pnl"`
DeleverageIndicator int64 `json:"deleverage_indicator"`
RiskID int64 `json:"risk_id"`
TakeProfit float64 `json:"take_profit,string"`
StopLoss float64 `json:"stop_loss,string"`
TrailingStop float64 `json:"trailing_stop,string"`
}
// PositionResp stores position response
type PositionResp struct {
PositionDataWithStringParam
PositionID int64 `json:"position_idx"`
Mode int64 `json:"mode"`
ID int64 `json:"id"`
EffectiveLeverage float64 `json:"effective_leverage,string"`
OccupiedFundingFee float64 `json:"occ_funding_fee,string"`
PositionStatus string `json:"position_status"`
CalculatedData string `json:"oc_calc_data"`
OrderMargin float64 `json:"order_margin,string"`
WalletBalance float64 `json:"wallet_balance,string"`
CrossSequence int64 `json:"cross_seq"`
PositionSequence int64 `json:"position_seq"`
TakeProfitStopLossMode string `json:"tp_sl_mode"`
CreatedAt string `json:"created_at"`
UpdateAt string `json:"updated_at"`
}
// SetTradingAndStopResp stores set trading and stop response
type SetTradingAndStopResp struct {
PositionData
ID int64 `json:"id"`
RiskID int64 `json:"risk_id"`
AutoAddMargin int64 `json:"auto_add_margin"`
OccupiedFundingFee float64 `json:"occ_funding_fee,string"`
TakeProfit float64 `json:"take_profit,string"`
StopLoss float64 `json:"stop_loss,string"`
PositionStatus string `json:"position_status"`
DeleverageIndicator int64 `json:"deleverage_indicator"`
CalculatedData string `json:"oc_calc_data"`
OrderMargin float64 `json:"order_margin,string"`
WalletBalance float64 `json:"wallet_balance,string"`
CrossSequence int64 `json:"cross_seq"`
PositionSequence int64 `json:"position_seq"`
CreatedAt string `json:"created_at"`
UpdateAt string `json:"updated_at"`
ExtensionField map[string]interface{} `json:"ext_fields"`
}
// USDTPositionResp stores USDT position response
type USDTPositionResp struct {
PositionData
FreeQty float64 `json:"free_qty"`
TakeProfitStopLossMode string `json:"tp_sl_mode"`
}
// UpdateMarginResp stores update margin response
type UpdateMarginResp struct {
Position
FreeQty float64 `json:"free_qty"`
}
// TradeData stores trade data
type TradeData struct {
OrderID string `json:"order_id"`
OrderLinkedID string `json:"order_link_id"`
OrderSide string `json:"side"`
Symbol string `json:"symbol"`
ExecutionID string `json:"exec_id"`
OrderPrice float64 `json:"order_price"`
OrderQty float64 `json:"order_qty"`
OrderType string `json:"order_type"`
FeeRate float64 `json:"fee_rate"`
ExecutionFee float64 `json:"exec_fee,string"`
ExecutionPrice float64 `json:"exec_price,string"`
ExecutionQty float64 `json:"exec_qty"`
ExecutionType string `json:"exec_type"`
ExecutionValue float64 `json:"exec_value,string"`
LeavesQty float64 `json:"leaves_qty"`
ClosedSize float64 `json:"closed_size"`
LastLiquidity string `json:"last_liquidity_ind"`
TradeTimeMs int64 `json:"trade_time_ms"`
}
// TradeResp stores trade response
type TradeResp struct {
TradeData
CrossSequence int64 `json:"cross_seq"`
NthFill int64 `json:"nth_fill"`
UserID int64 `json:"user_id"`
}
// ClosedTrades stores closed trades
type ClosedTrades struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Symbol string `json:"symbol"`
OrderID string `json:"order_id"`
OrderSide string `json:"side"`
Qty float64 `json:"qty"`
OrderPrice float64 `json:"order_price"`
OrderType string `json:"order_type"`
ExecutionType string `json:"exec_type"`
ClosedSize float64 `json:"closed_size"`
CumulativeEntryValue float64 `json:"cum_entry_value"`
AvgEntryPrice float64 `json:"avg_entry_price"`
CumulativeExitValue float64 `json:"cum_exit_value"`
AvgEntryValue float64 `json:"avg_exit_price"`
ClosedProfitLoss float64 `json:"closed_pnl"`
FillCount int64 `json:"fill_count"`
Leverage float64 `json:"leverage"`
CreatedAt bybitTimeSec `json:"created_at"`
}
// FundingFee stores funding fee
type FundingFee struct {
Symbol string `json:"symbol"`
Side string `json:"side"`
Size float64 `json:"size"`
FundingRate float64 `json:"funding_rate"`
ExecutionFee float64 `json:"exec_fee"`
ExecutionTime int64 `json:"exec_timestamp"`
}
// APIKeyData stores API key data
type APIKeyData struct {
APIKey string `json:"api_key"`
Type string `json:"type"`
UserID int64 `json:"user_id"`
InviterID int64 `json:"inviter_id"`
IPs []string `json:"ips"`
Note string `json:"note"`
Permission []string `json:"permissions"`
CreatedAt string `json:"created_at"`
ExpiredAt string `json:"expired_at"`
ReadOnly bool `json:"read_only"`
VIPLevel string `json:"vip_level"`
MarketMakerLevel string `json:"mkt_maker_level"`
}
// LCPData stores LiquidityContributionPointsData data
type LCPData struct {
Date string `json:"date"`
SelfRatio float64 `json:"self_ratio"`
PlatformRatio float64 `json:"platform_ratio"`
Score float64 `json:"score"`
}
// WalletData stores wallet data
type WalletData struct {
Equity float64 `json:"equity"` // equity = wallet_balance + unrealised_pnl
AvailableBalance float64 `json:"available_balance"`
UserMargin float64 `json:"used_margin"`
OrderMargin float64 `json:"order_margin"`
PositionMargin float64 `json:"position_margin"`
PositionClosingFee float64 `json:"occ_closing_fee"`
PositionFundingFee float64 `json:"occ_funding_fee"`
WalletBalance float64 `json:"wallet_balance"`
RealisedPNL float64 `json:"realised_pnl"`
UnrealisedPNL float64 `json:"unrealised_pnl"`
CumulativeRealisedPNL float64 `json:"cum_realised_pnl"`
GivenCash float64 `json:"given_cash"`
ServiceCash float64 `json:"service_cash"`
}
// FundRecord stores funding records
type FundRecord struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Coin string `json:"coin"`
Type string `json:"type"`
Amount float64 `json:"amount,string"`
TxID string `json:"tx_id"`
Address string `json:"address"`
WalletBalance float64 `json:"wallet_balance,string"`
ExecutionTime string `json:"exec_time"`
CrossSequence int64 `json:"cross_seq"`
}
// FundWithdrawalRecord stores funding withdrawal records
type FundWithdrawalRecord struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Coin string `json:"coin"`
Status string `json:"status"`
Amount float64 `json:"amount,string"`
Fee float64 `json:"fee"`
Address string `json:"address"`
TxID string `json:"tx_id"`
SubmittedAt time.Time `json:"submited_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// AssetExchangeRecord stores asset exchange records
type AssetExchangeRecord struct {
ID int64 `json:"id"`
FromCoin string `json:"from_coin"`
FromAmount float64 `json:"from_amount"`
ToCoin string `json:"to_coin"`
ToAmount float64 `json:"to_amount"`
ExchangeRate float64 `json:"exchange_rate"`
FromFee float64 `json:"from_fee"`
CreatedAt string `json:"created_at"`
}
// USDCOrderbookData stores orderbook data for USDCMarginedFutures
type USDCOrderbookData struct {
Price float64 `json:"price,string"`
Size float64 `json:"size,string"`
Side string `json:"side"`
}
// USDCContract stores contract data
type USDCContract struct {
Symbol string `json:"symbol"`
Status string `json:"status"`
BaseCoin string `json:"baseCoin"`
QuoteCoin string `json:"quoteCoin"`
TakerFeeRate float64 `json:"takerFeeRate,string"`
MakerFeeRate float64 `json:"makerFeeRate,string"`
MinLeverage float64 `json:"minLeverage,string"`
MaxLeverage float64 `json:"maxLeverage,string"`
LeverageStep float64 `json:"leverageStep,string"`
MinPrice float64 `json:"minPrice,string"`
MaxPrice float64 `json:"maxPrice,string"`
TickSize float64 `json:"tickSize,string"`
MaxTradingQty float64 `json:"maxTradingQty,string"`
MinTradingQty float64 `json:"minTradingQty,string"`
QtyStep float64 `json:"qtyStep,string"`
DeliveryTime bybitTimeMilliSecStr `json:"deliveryTime"`
}
// USDCSymbol stores symbol data
type USDCSymbol struct {
Symbol string `json:"symbol"`
NextFundingTime string `json:"nextFundingTime"`
Bid float64 `json:"bid,string"`
BidSize float64 `json:"bidSize,string"`
Ask float64 `json:"ask,string"`
AskSize float64 `json:"askSize,string"`
LastPrice float64 `json:"lastPrice,string"`
OpenInterest float64 `json:"openInterest,string"`
IndexPrice float64 `json:"indexPrice,string"`
MarkPrice float64 `json:"markPrice,string"`
Change24h float64 `json:"change24h,string"`
High24h float64 `json:"high24h,string"`
Low24h float64 `json:"low24h,string"`
Volume24h float64 `json:"volume24h,string"`
Turnover24h float64 `json:"turnover24h,string"`
TotalVolume float64 `json:"totalVolume,string"`
TotalTurnover float64 `json:"totalTurnover,string"`
FundingRate float64 `json:"fundingRate,string"`
PredictedFundingRate float64 `json:"predictedFundingRate,string"`
CountdownHour float64 `json:"countdownHour,string"`
UnderlyingPrice string `json:"underlyingPrice"`
}
// USDCKlineBase stores Kline Base
type USDCKlineBase struct {
Symbol string `json:"symbol"`
Period string `json:"period"`
OpenTime bybitTimeSecStr `json:"openTime"`
Open float64 `json:"open,string"`
High float64 `json:"high,string"`
Low float64 `json:"low,string"`
Close float64 `json:"close,string"`
}
// USDCKline stores kline data
type USDCKline struct {
USDCKlineBase
Volume float64 `json:"volume,string"`
Turnover float64 `json:"turnover,string"`
}
// USDCOpenInterest stores open interest data
type USDCOpenInterest struct {
Symbol string `json:"symbol"`
Timestamp bybitTimeMilliSecStr `json:"timestamp"`
OpenInterest float64 `json:"openInterest,string"`
}
// USDCLargeOrder stores large order data
type USDCLargeOrder struct {
Symbol string `json:"symbol"`
Side string `json:"side"`
Timestamp bybitTimeMilliSecStr `json:"timestamp"`
Value float64 `json:"value"`
}
// USDCAccountRatio stores long-short ratio data
type USDCAccountRatio struct {
Symbol string `json:"symbol"`
BuyRatio float64 `json:"buyRatio"`
SellRatio float64 `json:"sellRatio"`
Timestamp bybitTimeMilliSecStr `json:"timestamp"`
}
// USDCTrade stores trade data
type USDCTrade struct {
ID string `json:"id"`
Symbol string `json:"symbol"`
OrderPrice float64 `json:"orderPrice,string"`
OrderQty float64 `json:"orderQty,string"`
Side string `json:"side"`
Timestamp bybitTimeMilliSecStr `json:"time"`
}
// USDCCreateOrderResp stores create order response
type USDCCreateOrderResp struct {
ID string `json:"orderId"`
OrderLinkID string `json:"orderLinkId"`
Symbol string `json:"symbol"`
OrderPrice float64 `json:"orderPrice,string"`
OrderQty float64 `json:"orderQty,string"`
OrderType string `json:"orderType"`
Side string `json:"side"`
}
// USDCOrder store order data
type USDCOrder struct {
ID string `json:"orderId"`
OrderLinkID string `json:"orderLinkId"`
Symbol string `json:"symbol"`
OrderType string `json:"orderType"`
Side string `json:"side"`
Qty float64 `json:"qty,string"`
Price float64 `json:"price,string"`
TimeInForce string `json:"timeInForce"`
TotalOrderValue float64 `json:"cumExecValue,string"`
TotalFilledQty float64 `json:"cumExecQty,string"`
TotalFee float64 `json:"cumExecFee,string"`
InitialMargin string `json:"orderIM"`
OrderStatus string `json:"orderStatus"`
TakeProfit float64 `json:"takeProfit,string"`
StopLoss float64 `json:"stopLoss,string"`
TPTriggerBy string `json:"tpTriggerBy"`
SLTriggerBy string `json:"slTriggerBy"`
LastExecPrice float64 `json:"lastExecPrice"`
BasePrice string `json:"basePrice"`
TriggerPrice float64 `json:"triggerPrice,string"`
TriggerBy string `json:"triggerBy"`
ReduceOnly bool `json:"reduceOnly"`
StopOrderType string `json:"stopOrderType"`
CloseOnTrigger string `json:"closeOnTrigger"`
CreatedAt bybitTimeMilliSecStr `json:"createdAt"`
}
// USDCOrderHistory stores order history
type USDCOrderHistory struct {
USDCOrder
LeavesQty float64 `json:"leavesQty,string"` // Est. unfilled order qty
CashFlow string `json:"cashFlow"`
RealisedPnl float64 `json:"realisedPnl,string"`
UpdatedAt bybitTimeMilliSecStr `json:"updatedAt"`
}
// USDCTradeHistory stores trade history
type USDCTradeHistory struct {
ID string `json:"orderId"`
OrderLinkID string `json:"orderLinkId"`
Symbol string `json:"symbol"`
Side string `json:"side"`
TradeID string `json:"tradeId"`
ExecPrice float64 `json:"execPrice,string"`
ExecQty float64 `json:"execQty,string"`
ExecFee float64 `json:"execFee,string"`
FeeRate float64 `json:"feeRate,string"`
ExecType string `json:"execType"`
ExecValue float64 `json:"execValue,string"`
TradeTime bybitTimeMilliSecStr `json:"tradeTime"`
LastLiquidityInd string `json:"lastLiquidityInd"`
}
// USDCTxLog stores transaction log data
type USDCTxLog struct {
TxTime bybitTimeMilliSecStr `json:"transactionTime"`
Symbol string `json:"symbol"`
Type string `json:"type"`
Side string `json:"side"`
Quantity float64 `json:"qty,string"`
Size float64 `json:"size,string"`
TradePrice float64 `json:"tradePrice,string"`
Funding float64 `json:"funding,string"`
Fee float64 `json:"fee,string"`
CashFlow string `json:"cashFlow"`
Change float64 `json:"change,string"`
WalletBalance float64 `json:"walletBalance,string"`
FeeRate float64 `json:"feeRate,string"`
TradeID string `json:"tradeId"`
OrderID string `json:"orderId"`
OrderLinkID string `json:"orderLinkId"`
Info string `json:"info"`
}
// USDCWalletBalance store USDC wallet balance
type USDCWalletBalance struct {
Equity float64 `json:"equity,string"`
WalletBalance float64 `json:"walletBalance,string"`
AvailableBalance float64 `json:"availableBalance,string"`
AccountIM float64 `json:"accountIM,string"`
AccountMM float64 `json:"accountMM,string"`
TotalRPL float64 `json:"totalRPL,string"`
TotalSessionUPL float64 `json:"totalSessionUPL,string"`
TotalSessionRPL float64 `json:"totalSessionRPL,string"`
}
// USDCAssetInfo stores USDC asset data
type USDCAssetInfo struct {
BaseCoin string `json:"baseCoin"`
TotalDelta float64 `json:"totalDelta,string"`
TotalGamma float64 `json:"totalGamma,string"`
TotalVega float64 `json:"totalVega,string"`
TotalTheta float64 `json:"totalTheta,string"`
TotalRPL float64 `json:"totalRPL,string"`
SessionUPL float64 `json:"sessionUPL,string"`
SessionRPL float64 `json:"sessionRPL,string"`
IM float64 `json:"im,string"`
MM float64 `json:"mm,string"`
}
// USDCPosition store USDC position data
type USDCPosition struct {
Symbol string `json:"symbol"`
Leverage float64 `json:"leverage,string"`
ClosingFee float64 `json:"occClosingFee,string"`
LiquidPrice string `json:"liqPrice"`
Position float64 `json:"positionValue"`
TakeProfit float64 `json:"takeProfit,string"`
RiskID string `json:"riskId"`
TrailingStop float64 `json:"trailingStop,string"`
UnrealisedPnl float64 `json:"unrealisedPnl,string"`
MarkPrice float64 `json:"markPrice,string"`
CumRealisedPnl float64 `json:"cumRealisedPnl,string"`
PositionMM float64 `json:"positionMM,string"`
PositionIM float64 `json:"positionIM,string"`
EntryPrice float64 `json:"entryPrice,string"`
Size float64 `json:"size,string"`
SessionRPL float64 `json:"sessionRPL,string"`
SessionUPL float64 `json:"sessionUPL,string"`
StopLoss float64 `json:"stopLoss,string"`
OrderMargin float64 `json:"orderMargin,string"`
SessionAvgPrice float64 `json:"sessionAvgPrice,string"`
CreatedAt bybitTimeMilliSecStr `json:"createdAt"`
UpdatedAt bybitTimeMilliSecStr `json:"updatedAt"`
TpSLMode string `json:"tpSLMode"`
Side string `json:"side"`
BustPrice string `json:"bustPrice"`
PositionStatus string `json:"positionStatus"`
DeleverageIndicator int64 `json:"deleverageIndicator"`
}
// USDCSettlementHistory store USDC settlement history data
type USDCSettlementHistory struct {
Symbol string `json:"symbol"`
Side string `json:"side"`
Time bybitTimeMilliSecStr `json:"time"`
Size float64 `json:"size,string"`
SessionAvgPrice float64 `json:"sessionAvgPrice,string"`
MarkPrice float64 `json:"markPrice,string"`
SessionRpl float64 `json:"sessionRpl,string"`
}
// USDCRiskLimit store USDC risk limit data
type USDCRiskLimit struct {
RiskID string `json:"riskId"`
Symbol string `json:"symbol"`
Limit string `json:"limit"`
Section []string `json:"section"`
StartingMargin float64 `json:"startingMargin,string"`
MaintainMargin float64 `json:"maintainMargin,string"`
IsLowestRisk bool `json:"isLowestRisk"`
MaxLeverage float64 `json:"maxLeverage,string"`
}
// USDCFundingInfo store USDC funding data
type USDCFundingInfo struct {
Symbol string `json:"symbol"`
Time bybitTimeMilliSecStr `json:"fundingRateTimestamp"`
Rate float64 `json:"fundingRate,string"`
}

View File

@@ -0,0 +1,403 @@
package bybit
import (
"context"
"fmt"
"time"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"golang.org/x/time/rate"
)
const (
spotInterval = time.Second
spotRequestRate = 70
futuresPublicInterval = time.Second
futuresRequestRate = 50
spotPrivateRequestRate = 20
futuresInterval = time.Minute
futuresDefaultRateCount = 100
futuresOrderRate = 100
futuresOrderListRate = 600
futuresExecutionRate = 120
futuresPositionRateCount = 75
futuresPositionListRate = 120
futuresFundingRate = 120
futuresWalletRate = 120
futuresAccountRate = 600
usdcPerpetualPublicRate = 50
usdcPerpetualCancelAllRate = 1
usdcPerpetualPrivateRate = 5
usdcPerpetualInterval = time.Second
)
const (
publicSpotRate request.EndpointLimit = iota
publicFuturesRate
privateSpotRate
cFuturesDefaultRate
cFuturesCancelActiveOrderRate
cFuturesCancelAllActiveOrderRate
cFuturesCreateConditionalOrderRate
cFuturesCancelConditionalOrderRate
cFuturesReplaceActiveOrderRate
cFuturesReplaceConditionalOrderRate
cFuturesCreateOrderRate
cFuturesCancelAllConditionalOrderRate
cFuturesGetActiveOrderRate
cFuturesGetConditionalOrderRate
cFuturesGetRealtimeOrderRate
cFuturesTradeRate
cFuturesSetLeverageRate
cFuturesUpdateMarginRate
cFuturesSetTradingRate
cFuturesSwitchPositionRate
cFuturesGetTradingFeeRate
cFuturesPositionRate
cFuturesWalletBalanceRate
cFuturesLastFundingFeeRate
cFuturesPredictFundingRate
cFuturesWalletFundRecordRate
cFuturesWalletWithdrawalRate
cFuturesAPIKeyInfoRate
uFuturesDefaultRate
uFuturesCreateOrderRate
uFuturesCancelOrderRate
uFuturesCancelAllOrderRate
uFuturesCreateConditionalOrderRate
uFuturesCancelConditionalOrderRate
uFuturesCancelAllConditionalOrderRate
uFuturesSetLeverageRate
uFuturesSwitchMargin
uFuturesSwitchPosition
uFuturesSetMarginRate
uFuturesSetTradingStopRate
uFuturesUpdateMarginRate
uFuturesPositionRate
uFuturesGetClosedTradesRate
uFuturesGetTradesRate
uFuturesGetActiveOrderRate
uFuturesGetActiveRealtimeOrderRate
uFuturesGetConditionalOrderRate
uFuturesGetConditionalRealtimeOrderRate
uFuturesGetMyLastFundingFeeRate
uFuturesPredictFundingRate
futuresDefaultRate
futuresCancelOrderRate
futuresCreateOrderRate
futuresReplaceOrderRate
futuresCancelAllOrderRate
futuresCancelAllConditionalOrderRate
futuresReplaceConditionalOrderRate
futuresCancelConditionalOrderRate
futuresCreateConditionalOrderRate
futuresGetActiveOrderRate
futuresGetConditionalOrderRate
futuresGetActiveRealtimeOrderRate
futuresGetConditionalRealtimeOrderRate
futuresGetTradeRate
futuresSetLeverageRate
futuresUpdateMarginRate
futuresSetTradingStopRate
futuresSwitchPositionModeRate
futuresSwitchMarginRate
futuresSwitchPositionRate
futuresPositionRate
usdcPublicRate
usdcCancelAllOrderRate
usdcPlaceOrderRate
usdcModifyOrderRate
usdcCancelOrderRate
usdcGetOrderRate
usdcGetOrderHistoryRate
usdcGetTradeHistoryRate
usdcGetTransactionRate
usdcGetWalletRate
usdcGetAssetRate
usdcGetMarginRate
usdcGetPositionRate
usdcSetLeverageRate
usdcGetSettlementRate
usdcSetRiskRate
usdcGetPredictedFundingRate
)
// RateLimit implements the request.Limiter interface
type RateLimit struct {
SpotRate *rate.Limiter
FuturesRate *rate.Limiter
PrivateSpotRate *rate.Limiter
CMFuturesDefaultRate *rate.Limiter
CMFuturesOrderRate *rate.Limiter
CMFuturesOrderListRate *rate.Limiter
CMFuturesExecutionRate *rate.Limiter
CMFuturesPositionRate *rate.Limiter
CMFuturesPositionListRate *rate.Limiter
CMFuturesFundingRate *rate.Limiter
CMFuturesWalletRate *rate.Limiter
CMFuturesAccountRate *rate.Limiter
UFuturesDefaultRate *rate.Limiter
UFuturesOrderRate *rate.Limiter
UFuturesPositionRate *rate.Limiter
UFuturesPositionListRate *rate.Limiter
UFuturesOrderListRate *rate.Limiter
UFuturesFundingRate *rate.Limiter
FuturesDefaultRate *rate.Limiter
FuturesOrderRate *rate.Limiter
FuturesOrderListRate *rate.Limiter
FuturesExecutionRate *rate.Limiter
FuturesPositionRate *rate.Limiter
FuturesPositionListRate *rate.Limiter
USDCPublic *rate.Limiter
USDCPlaceOrderRate *rate.Limiter
USDCModifyOrderRate *rate.Limiter
USDCCancelOrderRate *rate.Limiter
USDCCancelAllOrderRate *rate.Limiter
USDCGetOrderRate *rate.Limiter
USDCGetOrderHistoryRate *rate.Limiter
USDCGetTradeHistoryRate *rate.Limiter
USDCGetTransactionRate *rate.Limiter
USDCGetWalletRate *rate.Limiter
USDCGetAssetRate *rate.Limiter
USDCGetMarginRate *rate.Limiter
USDCGetPositionRate *rate.Limiter
USDCSetLeverageRate *rate.Limiter
USDCGetSettlementRate *rate.Limiter
USDCSetRiskRate *rate.Limiter
USDCGetPredictedFundingRate *rate.Limiter
}
// Limit executes rate limiting functionality for Binance
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
var limiter *rate.Limiter
var tokens int
switch f {
case publicSpotRate:
limiter, tokens = r.SpotRate, 1
case privateSpotRate:
limiter, tokens = r.PrivateSpotRate, 1
case cFuturesDefaultRate:
limiter, tokens = r.CMFuturesDefaultRate, 1
case cFuturesCancelActiveOrderRate, cFuturesCreateConditionalOrderRate, cFuturesCancelConditionalOrderRate, cFuturesReplaceActiveOrderRate,
cFuturesReplaceConditionalOrderRate, cFuturesCreateOrderRate:
limiter, tokens = r.CMFuturesOrderRate, 1
case cFuturesCancelAllActiveOrderRate, cFuturesCancelAllConditionalOrderRate:
limiter, tokens = r.CMFuturesOrderRate, 10
case cFuturesGetActiveOrderRate, cFuturesGetConditionalOrderRate, cFuturesGetRealtimeOrderRate:
limiter, tokens = r.CMFuturesOrderListRate, 1
case cFuturesTradeRate:
limiter, tokens = r.CMFuturesExecutionRate, 1
case cFuturesSetLeverageRate, cFuturesUpdateMarginRate, cFuturesSetTradingRate, cFuturesSwitchPositionRate, cFuturesGetTradingFeeRate:
limiter, tokens = r.CMFuturesPositionRate, 1
case cFuturesPositionRate, cFuturesWalletBalanceRate:
limiter, tokens = r.CMFuturesPositionListRate, 1
case cFuturesLastFundingFeeRate, cFuturesPredictFundingRate:
limiter, tokens = r.CMFuturesFundingRate, 1
case cFuturesWalletFundRecordRate, cFuturesWalletWithdrawalRate:
limiter, tokens = r.CMFuturesWalletRate, 1
case cFuturesAPIKeyInfoRate:
limiter, tokens = r.CMFuturesAccountRate, 1
case uFuturesDefaultRate:
limiter, tokens = r.UFuturesDefaultRate, 1
case uFuturesCreateOrderRate, uFuturesCancelOrderRate, uFuturesCreateConditionalOrderRate, uFuturesCancelConditionalOrderRate:
limiter, tokens = r.UFuturesOrderRate, 1
case uFuturesCancelAllOrderRate, uFuturesCancelAllConditionalOrderRate:
limiter, tokens = r.UFuturesOrderRate, 10
case uFuturesSetLeverageRate, uFuturesSwitchMargin, uFuturesSwitchPosition, uFuturesSetMarginRate, uFuturesSetTradingStopRate, uFuturesUpdateMarginRate:
limiter, tokens = r.UFuturesPositionRate, 1
case uFuturesPositionRate, uFuturesGetClosedTradesRate, uFuturesGetTradesRate:
limiter, tokens = r.UFuturesPositionListRate, 1
case uFuturesGetActiveOrderRate, uFuturesGetActiveRealtimeOrderRate, uFuturesGetConditionalOrderRate, uFuturesGetConditionalRealtimeOrderRate:
limiter, tokens = r.UFuturesOrderListRate, 1
case uFuturesGetMyLastFundingFeeRate, uFuturesPredictFundingRate:
limiter, tokens = r.UFuturesFundingRate, 1
case futuresDefaultRate:
limiter, tokens = r.FuturesDefaultRate, 1
case futuresCancelOrderRate, futuresCreateOrderRate, futuresReplaceOrderRate, futuresReplaceConditionalOrderRate, futuresCancelConditionalOrderRate,
futuresCreateConditionalOrderRate:
limiter, tokens = r.FuturesOrderRate, 1
case futuresCancelAllOrderRate, futuresCancelAllConditionalOrderRate:
limiter, tokens = r.FuturesOrderRate, 10
case futuresGetActiveOrderRate, futuresGetConditionalOrderRate, futuresGetActiveRealtimeOrderRate, futuresGetConditionalRealtimeOrderRate:
limiter, tokens = r.FuturesOrderListRate, 1
case futuresGetTradeRate:
limiter, tokens = r.FuturesExecutionRate, 1
case futuresSetLeverageRate, futuresUpdateMarginRate, futuresSetTradingStopRate, futuresSwitchPositionModeRate, futuresSwitchMarginRate, futuresSwitchPositionRate:
limiter, tokens = r.FuturesPositionRate, 1
case futuresPositionRate:
limiter, tokens = r.FuturesPositionListRate, 1
case usdcPublicRate:
limiter, tokens = r.USDCPublic, 1
case usdcCancelAllOrderRate:
limiter, tokens = r.USDCCancelAllOrderRate, 1
case usdcPlaceOrderRate:
limiter, tokens = r.USDCPlaceOrderRate, 1
case usdcModifyOrderRate:
limiter, tokens = r.USDCModifyOrderRate, 1
case usdcCancelOrderRate:
limiter, tokens = r.USDCCancelOrderRate, 1
case usdcGetOrderRate:
limiter, tokens = r.USDCGetOrderRate, 1
case usdcGetOrderHistoryRate:
limiter, tokens = r.USDCGetOrderHistoryRate, 1
case usdcGetTradeHistoryRate:
limiter, tokens = r.USDCGetTradeHistoryRate, 1
case usdcGetTransactionRate:
limiter, tokens = r.USDCGetTransactionRate, 1
case usdcGetWalletRate:
limiter, tokens = r.USDCGetWalletRate, 1
case usdcGetAssetRate:
limiter, tokens = r.USDCGetAssetRate, 1
case usdcGetMarginRate:
limiter, tokens = r.USDCGetMarginRate, 1
case usdcGetPositionRate:
limiter, tokens = r.USDCGetPositionRate, 1
case usdcSetLeverageRate:
limiter, tokens = r.USDCSetLeverageRate, 1
case usdcGetSettlementRate:
limiter, tokens = r.USDCGetSettlementRate, 1
case usdcSetRiskRate:
limiter, tokens = r.USDCSetRiskRate, 1
case usdcGetPredictedFundingRate:
limiter, tokens = r.USDCGetPredictedFundingRate, 1
default:
limiter, tokens = r.SpotRate, 1
}
var finalDelay time.Duration
var reserves = make([]*rate.Reservation, tokens)
for i := 0; i < tokens; i++ {
// Consume tokens 1 at a time as this avoids needing burst capacity in the limiter,
// which would otherwise allow the rate limit to be exceeded over short periods
reserves[i] = limiter.Reserve()
finalDelay = limiter.Reserve().Delay()
}
if dl, ok := ctx.Deadline(); ok && dl.Before(time.Now().Add(finalDelay)) {
// Cancel all potential reservations to free up rate limiter if deadline
// is exceeded.
for x := range reserves {
reserves[x].Cancel()
}
return fmt.Errorf("rate limit delay of %s will exceed deadline: %w",
finalDelay,
context.DeadlineExceeded)
}
time.Sleep(finalDelay)
return nil
}
// SetRateLimit returns the rate limit for the exchange
func SetRateLimit() *RateLimit {
return &RateLimit{
SpotRate: request.NewRateLimit(spotInterval, spotRequestRate),
FuturesRate: request.NewRateLimit(futuresPublicInterval, futuresRequestRate),
PrivateSpotRate: request.NewRateLimit(spotInterval, spotPrivateRequestRate),
CMFuturesDefaultRate: request.NewRateLimit(futuresInterval, futuresDefaultRateCount),
CMFuturesOrderRate: request.NewRateLimit(futuresInterval, futuresOrderRate),
CMFuturesOrderListRate: request.NewRateLimit(futuresInterval, futuresOrderListRate),
CMFuturesExecutionRate: request.NewRateLimit(futuresInterval, futuresExecutionRate),
CMFuturesPositionRate: request.NewRateLimit(futuresInterval, futuresPositionRateCount),
CMFuturesPositionListRate: request.NewRateLimit(futuresInterval, futuresPositionListRate),
CMFuturesFundingRate: request.NewRateLimit(futuresInterval, futuresFundingRate),
CMFuturesWalletRate: request.NewRateLimit(futuresInterval, futuresWalletRate),
CMFuturesAccountRate: request.NewRateLimit(futuresInterval, futuresAccountRate),
UFuturesDefaultRate: request.NewRateLimit(futuresInterval, futuresDefaultRateCount),
UFuturesOrderRate: request.NewRateLimit(futuresInterval, futuresOrderRate),
UFuturesPositionRate: request.NewRateLimit(futuresInterval, futuresPositionRateCount),
UFuturesPositionListRate: request.NewRateLimit(futuresInterval, futuresPositionListRate),
UFuturesOrderListRate: request.NewRateLimit(futuresInterval, futuresOrderListRate),
UFuturesFundingRate: request.NewRateLimit(futuresInterval, futuresFundingRate),
FuturesDefaultRate: request.NewRateLimit(futuresInterval, futuresDefaultRateCount),
FuturesOrderRate: request.NewRateLimit(futuresInterval, futuresOrderRate),
FuturesOrderListRate: request.NewRateLimit(futuresInterval, futuresOrderListRate),
FuturesExecutionRate: request.NewRateLimit(futuresInterval, futuresExecutionRate),
FuturesPositionRate: request.NewRateLimit(futuresInterval, futuresPositionRateCount),
FuturesPositionListRate: request.NewRateLimit(futuresInterval, futuresPositionListRate),
USDCPublic: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPublicRate),
USDCPlaceOrderRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCModifyOrderRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCCancelOrderRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCCancelAllOrderRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualCancelAllRate),
USDCGetOrderRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCGetOrderHistoryRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCGetTradeHistoryRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCGetTransactionRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCGetWalletRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCGetAssetRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCGetMarginRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCGetPositionRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCSetLeverageRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCGetSettlementRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCSetRiskRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
USDCGetPredictedFundingRate: request.NewRateLimit(usdcPerpetualInterval, usdcPerpetualPrivateRate),
}
}

View File

@@ -483,7 +483,7 @@ func (c *CoinbasePro) GetFundingHistory(_ context.Context) ([]exchange.FundHisto
}
// GetWithdrawalsHistory returns previous withdrawals data
func (c *CoinbasePro) GetWithdrawalsHistory(_ context.Context, _ currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (c *CoinbasePro) GetWithdrawalsHistory(_ context.Context, _ currency.Code, _ asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -540,7 +540,7 @@ func (c *COINUT) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory,
}
// GetWithdrawalsHistory returns previous withdrawals data
func (c *COINUT) GetWithdrawalsHistory(_ context.Context, _ currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (c *COINUT) GetWithdrawalsHistory(_ context.Context, _ currency.Code, _ asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -1175,6 +1175,8 @@ func (u URL) String() string {
return restCoinMarginedFuturesURL
case RestFutures:
return restFuturesURL
case RestUSDCMargined:
return restUSDCMarginedFuturesURL
case RestSandbox:
return restSandboxURL
case RestSwap:
@@ -1209,6 +1211,8 @@ func getURLTypeFromString(ep string) (URL, error) {
return RestCoinMargined, nil
case restFuturesURL:
return RestFutures, nil
case restUSDCMarginedFuturesURL:
return RestUSDCMargined, nil
case restSandboxURL:
return RestSandbox, nil
case restSwapURL:

View File

@@ -2008,6 +2008,9 @@ func TestString(t *testing.T) {
if RestFutures.String() != "RestFuturesURL" {
t.Errorf("invalid string conversion")
}
if RestUSDCMargined.String() != "RestUSDCMarginedFuturesURL" {
t.Errorf("invalid string conversion")
}
if RestSandbox.String() != "RestSandboxURL" {
t.Errorf("invalid string conversion")
}
@@ -2202,6 +2205,7 @@ func TestGetGetURLTypeFromString(t *testing.T) {
{Endpoint: "RestUSDTMarginedFuturesURL", Expected: RestUSDTMargined},
{Endpoint: "RestCoinMarginedFuturesURL", Expected: RestCoinMargined},
{Endpoint: "RestFuturesURL", Expected: RestFutures},
{Endpoint: "RestUSDCMarginedFuturesURL", Expected: RestUSDCMargined},
{Endpoint: "RestSandboxURL", Expected: RestSandbox},
{Endpoint: "RestSwapURL", Expected: RestSwap},
{Endpoint: "WebsocketSpotURL", Expected: WebsocketSpot},

View File

@@ -241,6 +241,7 @@ const (
RestUSDTMargined
RestCoinMargined
RestFutures
RestUSDCMargined
RestSwap
RestSandbox
WebsocketSpot
@@ -254,6 +255,7 @@ const (
restSpotSupplementaryURL = "RestSpotSupplementaryURL"
restUSDTMarginedFuturesURL = "RestUSDTMarginedFuturesURL"
restCoinMarginedFuturesURL = "RestCoinMarginedFuturesURL"
restUSDCMarginedFuturesURL = "RestUSDCMarginedFuturesURL"
restFuturesURL = "RestFuturesURL"
restSandboxURL = "RestSandboxURL"
restSwapURL = "RestSwapURL"
@@ -270,6 +272,7 @@ var keyURLs = []URL{RestSpot,
RestUSDTMargined,
RestCoinMargined,
RestFutures,
RestUSDCMargined,
RestSwap,
RestSandbox,
WebsocketSpot,

View File

@@ -415,7 +415,7 @@ func (e *EXMO) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory, e
}
// GetWithdrawalsHistory returns previous withdrawals data
func (e *EXMO) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (e *EXMO) GetWithdrawalsHistory(ctx context.Context, c currency.Code, _ asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -571,7 +571,7 @@ func (f *FTX) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory, er
}
// GetWithdrawalsHistory returns previous withdrawals data
func (f *FTX) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (f *FTX) GetWithdrawalsHistory(ctx context.Context, c currency.Code, _ asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -458,7 +458,7 @@ func (g *Gateio) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory,
}
// GetWithdrawalsHistory returns previous withdrawals data
func (g *Gateio) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (g *Gateio) GetWithdrawalsHistory(ctx context.Context, c currency.Code, _ asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -467,7 +467,7 @@ func (g *Gemini) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory,
}
// GetWithdrawalsHistory returns previous withdrawals data
func (g *Gemini) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (g *Gemini) GetWithdrawalsHistory(ctx context.Context, c currency.Code, _ asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -486,7 +486,7 @@ func (h *HitBTC) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory,
}
// GetWithdrawalsHistory returns previous withdrawals data
func (h *HitBTC) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (h *HitBTC) GetWithdrawalsHistory(ctx context.Context, c currency.Code, _ asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -838,7 +838,7 @@ func (h *HUOBI) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory,
}
// GetWithdrawalsHistory returns previous withdrawals data
func (h *HUOBI) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (h *HUOBI) GetWithdrawalsHistory(ctx context.Context, c currency.Code, _ asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -52,7 +52,7 @@ type IBotExchange interface {
GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, accountID, chain string) (*deposit.Address, error)
GetAvailableTransferChains(ctx context.Context, cryptocurrency currency.Code) ([]string, error)
GetWithdrawalsHistory(ctx context.Context, code currency.Code) ([]WithdrawalHistory, error)
GetWithdrawalsHistory(ctx context.Context, code currency.Code, a asset.Item) ([]WithdrawalHistory, error)
WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error)
WithdrawFiatFunds(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error)
WithdrawFiatFundsToInternationalBank(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error)

View File

@@ -334,7 +334,7 @@ func (i *ItBit) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory,
}
// GetWithdrawalsHistory returns previous withdrawals data
func (i *ItBit) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (i *ItBit) GetWithdrawalsHistory(ctx context.Context, c currency.Code, _ asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -660,7 +660,7 @@ func (k *Kraken) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory,
}
// GetWithdrawalsHistory returns previous withdrawals data
func (k *Kraken) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (k *Kraken) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
withdrawals, err := k.WithdrawStatus(ctx, c, "")
for i := range withdrawals {
resp = append(resp, exchange.WithdrawalHistory{

View File

@@ -384,7 +384,7 @@ func (l *Lbank) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory,
}
// GetWithdrawalsHistory returns previous withdrawals data
func (l *Lbank) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (l *Lbank) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -332,7 +332,7 @@ func (l *LocalBitcoins) GetFundingHistory(ctx context.Context) ([]exchange.FundH
}
// GetWithdrawalsHistory returns previous withdrawals data
func (l *LocalBitcoins) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (l *LocalBitcoins) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -500,7 +500,7 @@ func (o *OKGroup) WithdrawFiatFundsToInternationalBank(_ context.Context, _ *wit
}
// GetWithdrawalsHistory returns previous withdrawals data
func (o *OKGroup) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (o *OKGroup) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -459,7 +459,7 @@ func (p *Poloniex) GetFundingHistory(ctx context.Context) ([]exchange.FundHistor
}
// GetWithdrawalsHistory returns previous withdrawals data
func (p *Poloniex) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (p *Poloniex) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -174,7 +174,7 @@ func (c *CustomEx) GetOrderHistory(ctx context.Context, getOrdersRequest *order.
return nil, nil
}
func (c *CustomEx) GetWithdrawalsHistory(ctx context.Context, code currency.Code) ([]exchange.WithdrawalHistory, error) {
func (c *CustomEx) GetWithdrawalsHistory(ctx context.Context, code currency.Code, a asset.Item) ([]exchange.WithdrawalHistory, error) {
return []exchange.WithdrawalHistory{}, nil
}

View File

@@ -23,6 +23,7 @@ var Exchanges = []string{
"bittrex",
"btc markets",
"btse",
"bybit",
"coinbasepro",
"coinut",
"exmo",

View File

@@ -69,6 +69,7 @@ _b in this context is an `IBotExchange` implemented struct_
| Bittrex | Yes | Yes | No |
| BTCMarkets | Yes | Yes | No |
| BTSE | Yes | Yes | No |
| Bybit | Yes | Yes | Yes |
| CoinbasePro | Yes | Yes | No|
| COINUT | Yes | Yes | No |
| Exmo | Yes | NA | No |

View File

@@ -360,7 +360,7 @@ func (y *Yobit) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory,
}
// GetWithdrawalsHistory returns previous withdrawals data
func (y *Yobit) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (y *Yobit) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -428,7 +428,7 @@ func (z *ZB) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory, err
}
// GetWithdrawalsHistory returns previous withdrawals data
func (z *ZB) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
func (z *ZB) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a asset.Item) (resp []exchange.WithdrawalHistory, err error) {
return nil, common.ErrNotYetImplemented
}