exchanges: Remove FTX implementation and fix minor test issues (#1100)

* exchanges: Start removal of FTX

* Get tests happy again

* okx: improve logic and add basic coverage

* Fix linterino

* Round 2 plus rm useless assignment in test

* Fix exchange_wrapper_issues test error

* Fix nitters

* Address nitters
This commit is contained in:
Adrian Gallagher
2023-01-25 16:40:04 +11:00
committed by GitHub
parent 05558aabfb
commit c785ae73a7
57 changed files with 211 additions and 9661 deletions

View File

@@ -33,7 +33,6 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| CoinbasePro | Yes | Yes | No|
| COINUT | Yes | Yes | NA |
| Exmo | Yes | NA | NA |
| FTX | Yes | Yes | No |
| GateIO | Yes | Yes | NA |
| Gemini | Yes | Yes | No |
| HitBTC | Yes | Yes | No |

View File

@@ -489,7 +489,7 @@ func TestGenerateConfigForDCAAPICandles(t *testing.T) {
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
ExchangeName: "bybit",
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,

View File

@@ -11,7 +11,7 @@
},
"currency-settings": [
{
"exchange-name": "binance",
"exchange-name": "bybit",
"asset": "spot",
"base": "BTC",
"quote": "USDT",

View File

@@ -14,7 +14,7 @@ import (
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
)
const testExchange = "binance"
const testExchange = "binanceus"
func TestLoadCandles(t *testing.T) {
t.Parallel()

View File

@@ -14,7 +14,7 @@ import (
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
)
const testExchange = "binance"
const testExchange = "binanceus"
func TestLoadCandles(t *testing.T) {
t.Parallel()

View File

@@ -42,11 +42,12 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/binance"
"github.com/thrasher-corp/gocryptotrader/exchanges/binanceus"
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order"
)
const testExchange = "binance"
const testExchange = "binanceus"
var leet = decimal.NewFromInt(1337)
@@ -71,11 +72,14 @@ func TestSetupFromConfig(t *testing.T) {
if !errors.Is(err, base.ErrStrategyNotFound) {
t.Errorf("received: %v, expected: %v", err, base.ErrStrategyNotFound)
}
const testExchange = "bybit"
cfg.CurrencySettings = []config.CurrencySettings{
{
ExchangeName: testExchange,
Base: currency.BTC,
Quote: currency.NewCode("0624"),
Quote: currency.USDT,
Asset: asset.Spot,
},
{
@@ -96,8 +100,6 @@ func TestSetupFromConfig(t *testing.T) {
"hello": "moto",
},
}
cfg.CurrencySettings[0].Base = currency.BTC
cfg.CurrencySettings[0].Quote = currency.USDT
cfg.DataSettings.APIData = &config.APIData{}
err = bt.SetupFromConfig(cfg, "", "", false)
@@ -156,7 +158,7 @@ func TestLoadDataAPI(t *testing.T) {
cfg := &config.Config{
CurrencySettings: []config.CurrencySettings{
{
ExchangeName: "Binance",
ExchangeName: testExchange,
Asset: asset.Spot,
Base: cp.Base,
Quote: cp.Quote,
@@ -180,7 +182,7 @@ func TestLoadDataAPI(t *testing.T) {
},
}
em := engine.ExchangeManager{}
exch, err := em.NewExchangeByName("Binance")
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
t.Fatal(err)
}
@@ -209,7 +211,7 @@ func TestLoadDataCSV(t *testing.T) {
cfg := &config.Config{
CurrencySettings: []config.CurrencySettings{
{
ExchangeName: "Binance",
ExchangeName: testExchange,
Asset: asset.Spot,
Base: cp.Base,
Quote: cp.Quote,
@@ -234,7 +236,7 @@ func TestLoadDataCSV(t *testing.T) {
},
}
em := engine.ExchangeManager{}
exch, err := em.NewExchangeByName("Binance")
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
t.Fatal(err)
}
@@ -1543,7 +1545,7 @@ func TestSetExchangeCredentials(t *testing.T) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
cfg := &config.Config{}
f := &binance.Binance{}
f := &binanceus.Binanceus{}
f.SetDefaults()
b := f.GetBase()
err = setExchangeCredentials(cfg, b)

View File

@@ -20,6 +20,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/engine"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/binance"
"github.com/thrasher-corp/gocryptotrader/exchanges/binanceus"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
)
@@ -328,7 +329,7 @@ func TestFetchLatestData(t *testing.T) {
currency.PairFormat{
Uppercase: true,
})
f := &binance.Binance{}
f := &binanceus.Binanceus{}
f.SetDefaults()
fb := f.GetBase()
fbA := fb.CurrencyPairs.Pairs[asset.Spot]
@@ -392,7 +393,7 @@ func TestLoadCandleData(t *testing.T) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}
exch := &binance.Binance{}
exch := &binanceus.Binanceus{}
exch.SetDefaults()
cp := currency.NewPair(currency.BTC, currency.USDT).Format(
currency.PairFormat{
@@ -443,7 +444,7 @@ func TestSetDataForClosingAllPositions(t *testing.T) {
currency.PairFormat{
Uppercase: true,
})
f := &binance.Binance{}
f := &binanceus.Binanceus{}
f.SetDefaults()
fb := f.GetBase()
fbA := fb.CurrencyPairs.Pairs[asset.Spot]

View File

@@ -21,6 +21,8 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/binance"
"github.com/thrasher-corp/gocryptotrader/exchanges/binanceus"
"github.com/thrasher-corp/gocryptotrader/exchanges/btcmarkets"
"github.com/thrasher-corp/gocryptotrader/exchanges/currencystate"
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order"
@@ -237,6 +239,7 @@ func TestExecuteOrder(t *testing.T) {
bot := &engine.Engine{}
var err error
em := engine.SetupExchangeManager()
const testExchange = "binanceus"
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
t.Fatal(err)
@@ -261,7 +264,7 @@ func TestExecuteOrder(t *testing.T) {
if err != nil {
t.Fatal(err)
}
f := &binance.Binance{}
f := &binanceus.Binanceus{}
f.Name = testExchange
cs := Settings{
Exchange: f,
@@ -357,6 +360,7 @@ func TestExecuteOrderBuySellSizeLimit(t *testing.T) {
bot := &engine.Engine{}
var err error
em := engine.SetupExchangeManager()
const testExchange = "BTC Markets"
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
t.Fatal(err)
@@ -374,7 +378,7 @@ func TestExecuteOrderBuySellSizeLimit(t *testing.T) {
if !errors.Is(err, nil) {
t.Errorf("received: %v, expected: %v", err, nil)
}
p := currency.NewPair(currency.BTC, currency.USDT)
p := currency.NewPair(currency.BTC, currency.AUD)
a := asset.Spot
_, err = exch.FetchOrderbook(context.Background(), p, a)
if err != nil {
@@ -390,7 +394,7 @@ func TestExecuteOrderBuySellSizeLimit(t *testing.T) {
if err != nil {
t.Fatal(err)
}
f := &binance.Binance{}
f := &btcmarkets.BTCMarkets{}
f.Name = testExchange
cs := Settings{
Exchange: f,

View File

@@ -34,7 +34,6 @@ const (
htmlScrape = "HTML String Check"
pathBinance = "https://binance-docs.github.io/apidocs/spot/en/#change-log"
pathOkCoin = "https://www.okcoin.com/docs/en/#change-change"
pathFTX = "https://github.com/ftexchange/ftx"
pathBTSE = "https://www.btse.com/apiexplorer/spot/#btse-spot-api"
pathBitfinex = "https://docs.bitfinex.com/docs/changelog"
pathBitmex = "https://www.bitmex.com/static/md/en-US/apiChangelog"
@@ -463,8 +462,6 @@ func checkChangeLog(htmlData *HTMLScrapingData) (string, error) {
dataStrings, err = htmlScrapeBinance(htmlData)
case pathBTSE:
dataStrings, err = htmlScrapeBTSE(htmlData)
case pathFTX:
dataStrings, err = htmlScrapeFTX(htmlData)
case pathBitfinex:
dataStrings, err = htmlScrapeBitfinex(htmlData)
case pathBitmex:
@@ -1596,95 +1593,6 @@ loop:
return resp, nil
}
// htmlScrapeFTX gets the check string for FTX exchange
func htmlScrapeFTX(htmlData *HTMLScrapingData) ([]string, error) {
temp, err := sendHTTPGetRequest(htmlData.Path, nil)
if err != nil {
return nil, err
}
defer temp.Body.Close()
a := temp.Body
tokenizer := html.NewTokenizer(a)
var respStr string
loop:
for {
next := tokenizer.Next()
switch next {
case html.ErrorToken:
break loop
case html.StartTagToken:
token := tokenizer.Token()
if token.Data == htmlData.TokenData {
for _, a := range token.Attr {
if a.Key == htmlData.Key && a.Val == htmlData.Val {
loop2:
for {
anotherToken := tokenizer.Next()
switch anotherToken {
case html.StartTagToken:
z := tokenizer.Token()
if z.Data == "a" {
for _, m := range z.Attr {
if m.Key == "title" {
switch m.Val {
case "rest":
loop3:
for {
nextToken := tokenizer.Next()
switch nextToken {
case html.StartTagToken:
f := tokenizer.Token()
if f.Data == "time-ago" {
for _, b := range f.Attr {
if b.Key == "datetime" {
respStr += b.Val
}
}
}
case html.EndTagToken:
tk := tokenizer.Token()
if tk.Data == htmlData.TokenDataEnd {
break loop3
}
}
}
case "websocket":
loop4:
for {
nextToken := tokenizer.Next()
switch nextToken {
case html.StartTagToken:
f := tokenizer.Token()
if f.Data == "time-ago" {
for _, b := range f.Attr {
if b.Key == "datetime" {
respStr += b.Val
}
}
}
case html.EndTagToken:
tk := tokenizer.Token()
if tk.Data == htmlData.TokenDataEnd {
break loop4
}
}
}
}
}
}
}
case html.ErrorToken:
break loop2
}
}
}
}
}
}
}
return []string{respStr}, nil
}
// htmlScrapeBitfinex gets the check string for Bitfinex exchange
func htmlScrapeBitfinex(htmlData *HTMLScrapingData) ([]string, error) {
temp, err := sendHTTPGetRequest(htmlData.Path, nil)

View File

@@ -660,19 +660,6 @@ func TestTrelloDeleteCheckItems(t *testing.T) {
}
}
func TestHTMLScrapeFTX(t *testing.T) {
data := HTMLScrapingData{
TokenData: "span",
Key: "class",
Val: "css-truncate css-truncate-target d-block width-fit",
TokenDataEnd: "svg",
Path: "https://github.com/ftexchange/ftx"}
a, err := htmlScrapeFTX(&data)
if err != nil || len(a) != 1 {
t.Error(err)
}
}
func TestHTMLScrapeBinance(t *testing.T) {
data := HTMLScrapingData{
TokenData: "h1",

View File

@@ -375,20 +375,6 @@
}
},
"Disabled": false
},
{
"Name": "FTX",
"CheckType": "HTML String Check",
"Data": {
"HTMLData": {
"Key": "class",
"Val": "css-truncate css-truncate-target",
"TokenDataEnd": "td",
"CheckString": "65e8800b5c6800aad896f888b2a62afc-d29c4f140f6ca068db9970054076ba63e51a9e96bf56d570e62cdb573c86b18526296117-1c388b9ec24e52ca4537240db3f48025ec9fca7a",
"Path": "https://github.com/ftexchange/ftx"
}
},
"Disabled": false
}
]
}

View File

@@ -375,20 +375,6 @@
}
},
"Disabled": false
},
{
"Name": "FTX",
"CheckType": "HTML String Check",
"Data": {
"HTMLData": {
"Key": "class",
"Val": "css-truncate css-truncate-target",
"TokenDataEnd": "td",
"CheckString": "65e8800b5c6800aad896f888b2a62afc-d29c4f140f6ca068db9970054076ba63e51a9e96bf56d570e62cdb573c86b18526296117-1c388b9ec24e52ca4537240db3f48025ec9fca7a",
"Path": "https://github.com/ftexchange/ftx"
}
},
"Disabled": false
}
]
}

View File

@@ -98,7 +98,6 @@ exchange
An example of this is:
```
binance,
ftx,
btc markets,
```

View File

@@ -155,7 +155,7 @@ The following is a screenshot of the relationship between relevant data history
| created | The date the job was created | `2020-01-01T13:33:37Z` |
| conversion_interval | When converting data as a job, this determines the resulting interval | `86400000000000` |
| overwrite_data | If data already exists, the setting allows you to overwrite it | `true` |
| secondary_exchange_id | For a `secondaryvalidatecandles` job, the exchange id of the exchange to compare data to | `ftx` |
| secondary_exchange_id | For a `secondaryvalidatecandles` job, the exchange id of the exchange to compare data to | `bybit` |
| decimal_place_comparison | When validating API candles, this will round the data to the supplied decimal point to check for equality | `3` |
| replace_on_issue | When there is an issue validating candles for a `validatecandles` job, the API data will overwrite the existing candle data | `false` |

View File

@@ -56,7 +56,6 @@ _b in this context is an `IBotExchange` implemented struct_
| CoinbasePro | Yes | Yes | No|
| COINUT | Yes | Yes | No |
| Exmo | Yes | NA | No |
| FTX | Yes | Yes | Yes |
| GateIO | Yes | Yes | No |
| Gemini | Yes | Yes | Yes |
| HitBTC | Yes | Yes | Yes |

View File

@@ -1,99 +0,0 @@
{{define "exchanges ftx" -}}
{{template "header" .}}
## FTX 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 f exchange.IBotExchange
for i := range bot.Exchanges {
if bot.Exchanges[i].GetName() == "FTX" {
f = bot.Exchanges[i]
}
}
// Public calls - wrapper functions
// Fetches current ticker information
tick, err := f.FetchTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := f.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 := f.GetAccountInfo()
if err != nil {
// Handle error
}
```
+ If enabled via individually importing package, rudimentary example below:
```go
// Public calls
// Fetches current ticker information
ticker, err := f.GetTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := f.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 := f.GetUserInfo(...)
if err != nil {
// Handle error
}
// Submits an order and the exchange and returns its tradeID
tradeID, err := f.Trade(...)
if err != nil {
// Handle error
}
```
### Please click GoDocs chevron above to view current GoDoc information for this package
{{template "contributions"}}
{{template "donations" .}}
{{end}}

View File

@@ -34,7 +34,6 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| CoinbasePro | Yes | Yes | No|
| COINUT | Yes | Yes | NA |
| Exmo | Yes | NA | NA |
| FTX | Yes | Yes | No |
| GateIO | Yes | Yes | NA |
| Gemini | Yes | Yes | No |
| HitBTC | Yes | Yes | No |

View File

@@ -12,7 +12,7 @@ func TestDisruptFormatting(t *testing.T) {
t.Fatal("error cannot be nil")
}
_, err = disruptFormatting(currency.Pair{Base: currency.BTC})
_, err = disruptFormatting(currency.Pair{Quote: currency.BTC})
if err == nil {
t.Fatal("error cannot be nil")
}

View File

@@ -106,11 +106,6 @@
"secret": "Secret",
"otpSecret": "-"
},
"ftx": {
"key": "Key",
"secret": "Secret",
"otpSecret": "-"
},
"gateio": {
"key": "Key",
"secret": "Secret",

View File

@@ -1480,95 +1480,6 @@
}
]
},
{
"name": "FTX",
"enabled": true,
"verbose": false,
"httpTimeout": 15000000000,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"websocketTrafficTimeout": 30000000000,
"websocketOrderbookBufferLimit": 5,
"baseCurrencies": "USD",
"currencyPairs": {
"assetTypes": [
"spot",
"futures"
],
"pairs": {
"futures": {
"enabled": "BTC-PERP",
"available": "ADA-PERP,ADA-0925,ALGO-PERP,ALGO-0925,ALT-PERP,ALT-0925,ATOM-PERP,ATOM-0925,BAL-PERP,BAL-0925,BCH-PERP,BCH-0925,BNB-PERP,BNB-0925,BRZ-PERP,BRZ-0925,BSV-PERP,BSV-0925,BTC-PERP,BTC-MOVE-0630,BTC-MOVE-0701,BTC-MOVE-WK-0703,BTC-MOVE-WK-0710,BTC-MOVE-WK-0717,BTC-MOVE-WK-0724,BTC-0925,BTC-MOVE-2020Q3,BTC-1225,BTC-MOVE-2020Q4,BTC-MOVE-2021Q1,BTC-HASH-2020Q3,BTC-HASH-2020Q4,BTC-HASH-2021Q1,BTMX-PERP,BTMX-0925,COMP-PERP,COMP-0925,CUSDT-PERP,CUSDT-0925,DEFI-PERP,DEFI-0925,DOGE-PERP,DOGE-0925,DRGN-PERP,DRGN-0925,EOS-PERP,EOS-0925,ETC-PERP,ETC-0925,ETH-PERP,ETH-0925,EXCH-PERP,EXCH-0925,HT-PERP,HT-0925,KNC-PERP,KNC-0925,LEO-PERP,LEO-0925,LINK-PERP,LINK-0925,LTC-PERP,LTC-0925,MATIC-PERP,MATIC-0925,MID-PERP,MID-0925,OIL100-0629,OKB-PERP,OKB-0925,PAXG-PERP,PAXG-0925,BERNIE,BIDEN,BLOOMBERG,PETE,TRUMP,WARREN,PRIV-PERP,PRIV-0925,SHIT-PERP,SHIT-0925,THETA-PERP,THETA-0925,TOMO-PERP,TOMO-0925,TRX-PERP,TRX-0925,TRYB-PERP,TRYB-0925,USDT-PERP,USDT-0925,XAUT-PERP,XAUT-0925,XRP-PERP,XRP-0925,XTZ-PERP,XTZ-0925",
"requestFormat": {
"uppercase": true,
"delimiter": "-"
},
"configFormat": {
"uppercase": true,
"delimiter": "-"
}
},
"spot": {
"enabled": "BTC/USD",
"available": "BAL/USD,BAL/USDT,BCH/USD,BCH/USDT,BNB/USD,BNB/USDT,BRL/USD,BRL/USDT,BTC/BRL,BTC/USD,BTC/USDT,BTMX/USD,COMP/USD,COMP/USDT,CUSDT/USD,CUSDT/USDT,ETH/BTC,ETH/USD,ETH/USDT,FTT/BTC,FTT/USD,FTT/USDT,KNC/USD,KNC/USDT,LINK/USD,LINK/USDT,LTC/USD,LTC/USDT,PAXG/USD,PAXG/USDT,TRX/USD,TRX/USDT,TRYB/USD,USDT/USD,WRX/USD,WRX/USDT,XAUT/USD,XAUT/USDT,ADABEAR/USD,ADABULL/USD,ADAHALF/USD,ADAHALF/USDT,ADAHEDGE/USD,ALGOBEAR/USD,ALGOBULL/USD,ALGOHALF/USD,ALGOHALF/USDT,ALGOHEDGE/USD,ALTBEAR/USD,ALTBULL/USD,ALTHALF/USD,ALTHALF/USDT,ALTHEDGE/USD,ATOMBEAR/USD,ATOMBULL/USD,ATOMHALF/USD,ATOMHALF/USDT,ATOMHEDGE/USD,BALBEAR/USD,BALBEAR/USDT,BALBULL/USD,BALBULL/USDT,BALHALF/USD,BALHEDGE/USD,BCHBEAR/USD,BCHBEAR/USDT,BCHBULL/USD,BCHBULL/USDT,BCHHALF/USD,BCHHALF/USDT,BCHHEDGE/USD,BEAR/USD,BEAR/USDT,BEARSHIT/USD,BNBBEAR/USD,BNBBEAR/USDT,BNBBULL/USD,BNBBULL/USDT,BNBHALF/USD,BNBHALF/USDT,BNBHEDGE/USD,BSVBEAR/USD,BSVBEAR/USDT,BSVBULL/USD,BSVBULL/USDT,BSVHALF/USD,BSVHALF/USDT,BSVHEDGE/USD,BTMXBEAR/USD,BTMXBEAR/USDT,BTMXBULL/USD,BTMXBULL/USDT,BTMXHALF/USD,BTMXHALF/USDT,BTMXHEDGE/USD,BULL/USD,BULL/USDT,BULLSHIT/USD,BVOL/USD,BVOL/USDT,COMPBEAR/USD,COMPBEAR/USDT,COMPBULL/USD,COMPBULL/USDT,COMPHALF/USD,COMPHEDGE/USD,CUSDTBEAR/USD,CUSDTBEAR/USDT,CUSDTBULL/USD,CUSDTBULL/USDT,CUSDTHALF/USD,CUSDTHEDGE/USD,DEFIBEAR/USD,DEFIBEAR/USDT,DEFIBULL/USD,DEFIBULL/USDT,DEFIHALF/USD,DEFIHALF/USDT,DEFIHEDGE/USD,DOGEBEAR/USD,DOGEBULL/USD,DOGEHALF/USD,DOGEHALF/USDT,DOGEHEDGE/USD,DRGNBEAR/USD,DRGNBULL/USD,DRGNHALF/USD,DRGNHALF/USDT,DRGNHEDGE/USD,EOSBEAR/USD,EOSBEAR/USDT,EOSBULL/USD,EOSBULL/USDT,EOSHALF/USD,EOSHALF/USDT,EOSHEDGE/USD,ETCBEAR/USD,ETCBULL/USD,ETCHALF/USD,ETCHALF/USDT,ETCHEDGE/USD,ETHBEAR/USD,ETHBEAR/USDT,ETHBULL/USD,ETHBULL/USDT,ETHHALF/USD,ETHHALF/USDT,ETHHEDGE/USD,EXCHBEAR/USD,EXCHBULL/USD,EXCHHALF/USD,EXCHHALF/USDT,EXCHHEDGE/USD,HALF/USD,HALF/USDT,HALFSHIT/USD,HALFSHIT/USDT,HEDGE/USD,HEDGESHIT/USD,HTBEAR/USD,HTBULL/USD,HTHALF/USD,HTHALF/USDT,HTHEDGE/USD,IBVOL/USD,IBVOL/USDT,KNCBEAR/USD,KNCBEAR/USDT,KNCBULL/USD,KNCBULL/USDT,KNCHALF/USD,KNCHEDGE/USD,LEOBEAR/USD,LEOBULL/USD,LEOHALF/USD,LEOHALF/USDT,LEOHEDGE/USD,LINKBEAR/USD,LINKBEAR/USDT,LINKBULL/USD,LINKBULL/USDT,LINKHALF/USD,LINKHALF/USDT,LINKHEDGE/USD,LTCBEAR/USD,LTCBEAR/USDT,LTCBULL/USD,LTCBULL/USDT,LTCHALF/USD,LTCHALF/USDT,LTCHEDGE/USD,MATICBEAR/USD,MATICBULL/USD,MATICHALF/USD,MATICHALF/USDT,MATICHEDGE/USD,MIDBEAR/USD,MIDBULL/USD,MIDHALF/USD,MIDHALF/USDT,MIDHEDGE/USD,OKBBEAR/USD,OKBBULL/USD,OKBHALF/USD,OKBHALF/USDT,OKBHEDGE/USD,PAXGBEAR/USD,PAXGBULL/USD,PAXGHALF/USD,PAXGHALF/USDT,PAXGHEDGE/USD,PRIVBEAR/USD,PRIVBULL/USD,PRIVHALF/USD,PRIVHALF/USDT,PRIVHEDGE/USD,THETABEAR/USD,THETABULL/USD,THETAHALF/USD,THETAHALF/USDT,THETAHEDGE/USD,TOMOBEAR/USD,TOMOBULL/USD,TOMOHALF/USD,TOMOHALF/USDT,TOMOHEDGE/USD,TRXBEAR/USD,TRXBULL/USD,TRXHALF/USD,TRXHALF/USDT,TRXHEDGE/USD,TRYBBEAR/USD,TRYBBULL/USD,TRYBHALF/USD,TRYBHALF/USDT,TRYBHEDGE/USD,USDTBEAR/USD,USDTBULL/USD,USDTHALF/USD,USDTHALF/USDT,USDTHEDGE/USD,XAUTBEAR/USD,XAUTBULL/USD,XAUTHALF/USD,XAUTHALF/USDT,XAUTHEDGE/USD,XRPBEAR/USD,XRPBEAR/USDT,XRPBULL/USD,XRPBULL/USDT,XRPHALF/USD,XRPHALF/USDT,XRPHEDGE/USD,XTZBEAR/USD,XTZBEAR/USDT,XTZBULL/USD,XTZBULL/USDT,XTZHALF/USD,XTZHALF/USDT,XTZHEDGE/USD",
"requestFormat": {
"uppercase": true,
"delimiter": "/"
},
"configFormat": {
"uppercase": true,
"delimiter": "/"
}
}
}
},
"api": {
"authenticatedSupport": false,
"authenticatedWebsocketApiSupport": false,
"endpoints": {
"url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
"urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
"websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API"
},
"credentials": {
"key": "Key",
"secret": "Secret"
},
"credentialsValidator": {
"requiresKey": true,
"requiresSecret": true
}
},
"features": {
"supports": {
"restAPI": true,
"restCapabilities": {
"autoPairUpdates": true
},
"websocketAPI": true,
"websocketCapabilities": {}
},
"enabled": {
"autoPairUpdates": true,
"websocketAPI": false
}
},
"bankAccounts": [
{
"enabled": false,
"bankName": "",
"bankAddress": "",
"bankPostalCode": "",
"bankPostalCity": "",
"bankCountry": "",
"accountName": "",
"accountNumber": "",
"swiftCode": "",
"iban": "",
"supportedCurrencies": ""
}
]
},
{
"name": "GateIO",
"enabled": true,

View File

@@ -9,7 +9,7 @@ A simple demonstration using `gctcli` is as follows:
## Obtaining a list of supported transfer chains
```sh
$ ./gctcli getavailabletransferchains --exchange=ftx --cryptocurrency=usdt
$ ./gctcli getavailabletransferchains --exchange=binance --cryptocurrency=usdt
{
"chains": [
"erc20",
@@ -23,7 +23,7 @@ $ ./gctcli getavailabletransferchains --exchange=ftx --cryptocurrency=usdt
## Obtaining a deposit address based on a specific cryptocurrency and chain
```sh
$ ./gctcli getcryptocurrencydepositaddress --exchange=ftx --cryptocurrency=usdt --chain=sol
$ ./gctcli getcryptocurrencydepositaddress --exchange=binance --cryptocurrency=usdt --chain=sol
{
"address": "GW3oT9JpFyTkCWPnt6Yw9ugppSQwDv4ZMG1vabC8WmHS"
}
@@ -32,7 +32,7 @@ $ ./gctcli getcryptocurrencydepositaddress --exchange=ftx --cryptocurrency=usdt
## Withdrawing
```sh
$ ./gctcli withdrawcryptofunds --exchange=ftx --currency=USDT --address=TJU9piX2WA8WTvxVKMqpvTzZGhvXQAZKSY --amount=10 --chain=trx
$ ./gctcli withdrawcryptofunds --exchange=binance --currency=USDT --address=TJU9piX2WA8WTvxVKMqpvTzZGhvXQAZKSY --amount=10 --chain=trx
{
"id": "01234567-0000-0000-0000-000000000000",
}
@@ -57,7 +57,6 @@ $ ./gctcli withdrawcryptofunds --exchange=ftx --currency=USDT --address=TJU9piX2
| CoinbasePro | No | No | No|
| COINUT | No | No | NA |
| Exmo | Yes | Yes | Addresses must be created via their website first |
| FTX | Yes | Yes | |
| GateIO | Yes | Yes | |
| Gemini | No | No | |
| HitBTC | No | No | |

View File

@@ -83,7 +83,6 @@ A helper tool [cmd/dbseed](../cmd/dbseed/README.md) has been created for assisti
| Gemini | |
| HitBTC | Y |
| Huobi | Y |
| FTX | Y |
| itBIT | |
| Kraken | Y |
| lBank | Y |

View File

@@ -173,7 +173,7 @@ The following is a screenshot of the relationship between relevant data history
| created | The date the job was created | `2020-01-01T13:33:37Z` |
| conversion_interval | When converting data as a job, this determines the resulting interval | `86400000000000` |
| overwrite_data | If data already exists, the setting allows you to overwrite it | `true` |
| secondary_exchange_id | For a `secondaryvalidatecandles` job, the exchange id of the exchange to compare data to | `ftx` |
| secondary_exchange_id | For a `secondaryvalidatecandles` job, the exchange id of the exchange to compare data to | `bybit` |
| decimal_place_comparison | When validating API candles, this will round the data to the supplied decimal point to check for equality | `3` |
| replace_on_issue | When there is an issue validating candles for a `validatecandles` job, the API data will overwrite the existing candle data | `false` |

View File

@@ -21,7 +21,6 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/coinbasepro"
"github.com/thrasher-corp/gocryptotrader/exchanges/coinut"
"github.com/thrasher-corp/gocryptotrader/exchanges/exmo"
"github.com/thrasher-corp/gocryptotrader/exchanges/ftx"
"github.com/thrasher-corp/gocryptotrader/exchanges/gateio"
"github.com/thrasher-corp/gocryptotrader/exchanges/gemini"
"github.com/thrasher-corp/gocryptotrader/exchanges/hitbtc"
@@ -175,8 +174,6 @@ func (m *ExchangeManager) NewExchangeByName(name string) (exchange.IBotExchange,
exch = new(exmo.EXMO)
case "coinbasepro":
exch = new(coinbasepro.CoinbasePro)
case "ftx":
exch = new(ftx.FTX)
case "gateio":
exch = new(gateio.Gateio)
case "gemini":

View File

@@ -82,7 +82,7 @@ func TestExchangeManagerRemoveExchange(t *testing.T) {
func TestNewExchangeByName(t *testing.T) {
m := SetupExchangeManager()
exchanges := []string{"binanceus", "binance", "bitfinex", "bitflyer", "bithumb", "bitmex", "bitstamp", "bittrex", "btc markets", "btse", "bybit", "coinut", "exmo", "coinbasepro", "ftx", "gateio", "gemini", "hitbtc", "huobi", "itbit", "kraken", "lbank", "localbitcoins", "okcoin international", "okx", "poloniex", "yobit", "zb", "fake"}
exchanges := []string{"binanceus", "binance", "bitfinex", "bitflyer", "bithumb", "bitmex", "bitstamp", "bittrex", "btc markets", "btse", "bybit", "coinut", "exmo", "coinbasepro", "gateio", "gemini", "hitbtc", "huobi", "itbit", "kraken", "lbank", "localbitcoins", "okcoin international", "okx", "poloniex", "yobit", "zb", "fake"}
for i := range exchanges {
exch, err := m.NewExchangeByName(exchanges[i])
if err != nil && exchanges[i] != "fake" {

View File

@@ -1482,7 +1482,7 @@ func TestGetOpenFuturesPosition(t *testing.T) {
}
em := SetupExchangeManager()
exch, err := em.NewExchangeByName("ftx")
exch, err := em.NewExchangeByName("binance")
if err != nil {
t.Fatal(err)
}
@@ -1558,7 +1558,7 @@ func TestProcessFuturesPositions(t *testing.T) {
t.Errorf("received '%v', expected '%v'", err, errFuturesTrackingDisabled)
}
em := SetupExchangeManager()
exch, err := em.NewExchangeByName("ftx")
exch, err := em.NewExchangeByName("binance")
if err != nil {
t.Fatal(err)
}
@@ -1639,7 +1639,7 @@ func TestProcessFuturesPositions(t *testing.T) {
position.Orders[0].AssetType = asset.Futures
position.Asset = asset.Futures
err = o.processFuturesPositions(fakeExchange, position)
if !errors.Is(err, nil) {
t.Errorf("received '%v', expected '%v'", err, nil)
if !errors.Is(err, common.ErrNotYetImplemented) {
t.Errorf("received '%v', expected '%v'", err, common.ErrNotYetImplemented)
}
}

View File

@@ -2210,7 +2210,7 @@ func TestCurrencyStateTradingPair(t *testing.T) {
func TestGetFuturesPositions(t *testing.T) {
t.Parallel()
em := SetupExchangeManager()
exch, err := em.NewExchangeByName("ftx")
exch, err := em.NewExchangeByName("binance")
if err != nil {
t.Fatal(err)
}
@@ -2873,8 +2873,9 @@ func TestGetMarginRatesHistory(t *testing.T) {
func TestGetFundingRates(t *testing.T) {
t.Parallel()
em := SetupExchangeManager()
exch, err := em.NewExchangeByName("ftx")
exch, err := em.NewExchangeByName("binance")
if err != nil {
t.Fatal(err)
}
@@ -2966,7 +2967,7 @@ func TestGetFundingRates(t *testing.T) {
func TestGetManagedPosition(t *testing.T) {
t.Parallel()
em := SetupExchangeManager()
exch, err := em.NewExchangeByName("ftx")
exch, err := em.NewExchangeByName("binance")
if err != nil {
t.Fatal(err)
}
@@ -3103,8 +3104,9 @@ func TestGetManagedPosition(t *testing.T) {
func TestGetAllManagedPositions(t *testing.T) {
t.Parallel()
em := SetupExchangeManager()
exch, err := em.NewExchangeByName("ftx")
exch, err := em.NewExchangeByName("binance")
if err != nil {
t.Fatal(err)
}
@@ -3211,7 +3213,7 @@ func TestGetAllManagedPositions(t *testing.T) {
func TestGetOrderbookMovement(t *testing.T) {
t.Parallel()
em := SetupExchangeManager()
exch, err := em.NewExchangeByName("ftx")
exch, err := em.NewExchangeByName("binance")
if err != nil {
t.Fatal(err)
}
@@ -3318,7 +3320,7 @@ func TestGetOrderbookMovement(t *testing.T) {
func TestGetOrderbookAmountByNominal(t *testing.T) {
t.Parallel()
em := SetupExchangeManager()
exch, err := em.NewExchangeByName("ftx")
exch, err := em.NewExchangeByName("binance")
if err != nil {
t.Fatal(err)
}
@@ -3418,7 +3420,7 @@ func TestGetOrderbookAmountByNominal(t *testing.T) {
func TestGetOrderbookAmountByImpact(t *testing.T) {
t.Parallel()
em := SetupExchangeManager()
exch, err := em.NewExchangeByName("ftx")
exch, err := em.NewExchangeByName("binance")
if err != nil {
t.Fatal(err)
}

View File

@@ -10,20 +10,20 @@ import (
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/binance"
"github.com/thrasher-corp/gocryptotrader/exchanges/bybit"
"github.com/thrasher-corp/gocryptotrader/portfolio"
"github.com/thrasher-corp/gocryptotrader/portfolio/banking"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
const (
exchangeName = "Binance"
exchangeName = "Bybit"
)
func withdrawManagerTestHelper(t *testing.T) (*ExchangeManager, *portfolioManager) {
t.Helper()
em := SetupExchangeManager()
b := new(binance.Binance)
b := new(bybit.Bybit)
b.SetDefaults()
cfg, err := b.GetDefaultConfig()
if err != nil {

View File

@@ -237,18 +237,18 @@ func (bi *Binanceus) GetAggregateTrades(ctx context.Context, agg *AggregatedTrad
if agg.FromID != 0 {
params.Set("fromId", strconv.FormatInt(agg.FromID, 10))
}
startTime := time.UnixMilli(int64(agg.StartTime))
endTime := time.UnixMilli(int64(agg.EndTime))
startTime := time.UnixMilli(agg.StartTime)
endTime := time.UnixMilli(agg.EndTime)
if (endTime.UnixNano() - startTime.UnixNano()) >= int64(time.Hour) {
endTime = startTime.Add(time.Minute * 59)
}
if !startTime.IsZero() && startTime.Unix() != 0 {
params.Set("startTime", strconv.Itoa(int(agg.StartTime)))
params.Set("startTime", strconv.FormatInt(agg.StartTime, 10))
}
if !endTime.IsZero() && endTime.Unix() != 0 {
params.Set("endTime", strconv.Itoa(int(agg.EndTime)))
params.Set("endTime", strconv.FormatInt(agg.EndTime, 10))
}
needBatch = needBatch || (!startTime.IsZero() && !endTime.IsZero() && endTime.Sub(startTime) > time.Hour)
if needBatch {
@@ -277,8 +277,8 @@ func (bi *Binanceus) batchAggregateTrades(ctx context.Context, arg *AggregatedTr
// Extend from the default of 500
params.Set("limit", "1000")
}
startTime := time.UnixMilli(int64(arg.StartTime))
endTime := time.UnixMilli(int64(arg.EndTime))
startTime := time.UnixMilli(arg.StartTime)
endTime := time.UnixMilli(arg.EndTime)
var fromID int64
if arg.FromID > 0 {
fromID = arg.FromID
@@ -292,8 +292,8 @@ func (bi *Binanceus) batchAggregateTrades(ctx context.Context, arg *AggregatedTr
// All requests returned empty
return nil, nil
}
params.Set("startTime", strconv.Itoa(int(startTime.UnixMilli())))
params.Set("endTime", strconv.Itoa(int(startTime.Add(increment).UnixMilli())))
params.Set("startTime", strconv.FormatInt(startTime.UnixMilli(), 10))
params.Set("endTime", strconv.FormatInt(startTime.Add(increment).UnixMilli(), 10))
path := common.EncodeURLValues(aggregatedTrades, params)
err := bi.SendHTTPRequest(ctx,
exchange.RestSpotSupplementary, path, spotDefaultRate, &resp)

View File

@@ -129,8 +129,8 @@ type AggregatedTradeRequestParams struct {
// The first trade to retrieve
FromID int64
// The API seems to accept (start and end time) or FromID and no other combinations
StartTime uint64
EndTime uint64
StartTime int64
EndTime int64
// Default 500; max 1000.
Limit int
}

View File

@@ -539,8 +539,8 @@ func (bi *Binanceus) GetHistoricTrades(ctx context.Context, p currency.Pair,
assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
req := AggregatedTradeRequestParams{
Symbol: p,
StartTime: uint64(timestampStart.UnixMilli()),
EndTime: uint64(timestampEnd.UnixMilli()),
StartTime: timestampStart.UnixMilli(),
EndTime: timestampEnd.UnixMilli(),
}
trades, err := bi.GetAggregateTrades(ctx, &req)
if err != nil {

View File

@@ -2173,12 +2173,18 @@ func TestUpdateTicker(t *testing.T) {
t.Error(err)
}
pair2, err := currency.NewPairFromString("BTCUSD-Z22")
// Futures update dynamically, so fetch the available tradable futures for this test
availPairs, err := b.FetchTradablePairs(context.Background(), asset.Futures)
if err != nil {
t.Fatal(err)
}
_, err = b.UpdateTicker(context.Background(), pair2, asset.Futures)
// Needs to be set before calling extractCurrencyPair
if err = b.SetPairs(availPairs, asset.Futures, true); err != nil {
t.Fatal(err)
}
_, err = b.UpdateTicker(context.Background(), availPairs[0], asset.Futures)
if err != nil {
t.Error(err)
}

View File

@@ -1439,7 +1439,7 @@ func (b *Base) ScaleCollateral(context.Context, *order.CollateralCalculator) (*o
}
// CalculateTotalCollateral takes in n collateral calculators to determine an overall
// standing in a singular currency. See FTX's implementation
// standing in a singular currency
func (b *Base) CalculateTotalCollateral(ctx context.Context, calculator *order.TotalCollateralCalculator) (*order.TotalCollateralResponse, error) {
return nil, common.ErrNotYetImplemented
}
@@ -1450,7 +1450,7 @@ func (b *Base) GetCollateralCurrencyForContract(a asset.Item, cp currency.Pair)
}
// GetCurrencyForRealisedPNL returns where to put realised PNL
// example 1: FTX PNL is paid out in USD to your spot wallet
// example 1: Bybit universal margin PNL is paid out in USD to your spot wallet
// example 2: Binance coin margined futures pays returns using the same currency eg BTC
func (b *Base) GetCurrencyForRealisedPNL(_ asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
return currency.Code{}, asset.Empty, common.ErrNotYetImplemented

View File

@@ -1,133 +0,0 @@
# GoCryptoTrader package Ftx
<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/ftx)
[![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 ftx 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)
## FTX 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 f exchange.IBotExchange
for i := range bot.Exchanges {
if bot.Exchanges[i].GetName() == "FTX" {
f = bot.Exchanges[i]
}
}
// Public calls - wrapper functions
// Fetches current ticker information
tick, err := f.FetchTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := f.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 := f.GetAccountInfo()
if err != nil {
// Handle error
}
```
+ If enabled via individually importing package, rudimentary example below:
```go
// Public calls
// Fetches current ticker information
ticker, err := f.GetTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := f.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 := f.GetUserInfo(...)
if err != nil {
// Handle error
}
// Submits an order and the exchange and returns its tradeID
tradeID, err := f.Trade(...)
if err != nil {
// Handle error
}
```
### 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***

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,624 +0,0 @@
package ftx
import (
"context"
"encoding/json"
"errors"
"fmt"
"hash/crc32"
"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/fill"
"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 (
ftxWSURL = "wss://ftx.com/ws/"
ftxWebsocketTimer = 13 * time.Second
wsTicker = "ticker"
wsTrades = "trades"
wsOrderbook = "orderbook"
wsMarkets = "markets"
wsFills = "fills"
wsOrders = "orders"
wsUpdate = "update"
wsPartial = "partial"
subscribe = "subscribe"
unsubscribe = "unsubscribe"
)
var obSuccess = make(map[currency.Pair]bool)
// WsConnect connects to a websocket feed
func (f *FTX) WsConnect() error {
if !f.Websocket.IsEnabled() || !f.IsEnabled() {
return errors.New(stream.WebsocketNotEnabled)
}
var dialer websocket.Dialer
err := f.Websocket.Conn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
f.Websocket.Conn.SetupPingHandler(stream.PingHandler{
MessageType: websocket.PingMessage,
Delay: ftxWebsocketTimer,
})
if f.Verbose {
log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", f.Name)
}
f.Websocket.Wg.Add(1)
go f.wsReadData()
if f.IsWebsocketAuthenticationSupported() {
err = f.WsAuth(context.TODO())
if err != nil {
f.Websocket.DataHandler <- err
f.Websocket.SetCanUseAuthenticatedEndpoints(false)
}
}
return nil
}
// WsAuth sends an authentication message to receive auth data
func (f *FTX) WsAuth(ctx context.Context) error {
creds, err := f.GetCredentials(ctx)
if err != nil {
return err
}
intNonce := time.Now().UnixMilli()
strNonce := strconv.FormatInt(intNonce, 10)
hmac, err := crypto.GetHMAC(
crypto.HashSHA256,
[]byte(strNonce+"websocket_login"),
[]byte(creds.Secret),
)
if err != nil {
return err
}
sign := crypto.HexEncodeToString(hmac)
req := Authenticate{Operation: "login",
Args: AuthenticationData{
Key: creds.Key,
Sign: sign,
Time: intNonce,
SubAccount: creds.SubAccount,
},
}
return f.Websocket.Conn.SendJSONMessage(req)
}
// Subscribe sends a websocket message to receive data from the channel
func (f *FTX) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error {
var errs common.Errors
channels:
for i := range channelsToSubscribe {
var sub WsSub
sub.Channel = channelsToSubscribe[i].Channel
sub.Operation = subscribe
switch channelsToSubscribe[i].Channel {
case wsFills, wsOrders, wsMarkets:
default:
a, err := f.GetPairAssetType(channelsToSubscribe[i].Currency)
if err != nil {
errs = append(errs, err)
continue channels
}
formattedPair, err := f.FormatExchangeCurrency(channelsToSubscribe[i].Currency, a)
if err != nil {
errs = append(errs, err)
continue channels
}
sub.Market = formattedPair.String()
}
err := f.Websocket.Conn.SendJSONMessage(sub)
if err != nil {
errs = append(errs, err)
continue
}
f.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
// Unsubscribe sends a websocket message to stop receiving data from the channel
func (f *FTX) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error {
var errs common.Errors
channels:
for i := range channelsToUnsubscribe {
var unSub WsSub
unSub.Operation = unsubscribe
unSub.Channel = channelsToUnsubscribe[i].Channel
switch channelsToUnsubscribe[i].Channel {
case wsFills, wsOrders, wsMarkets:
default:
a, err := f.GetPairAssetType(channelsToUnsubscribe[i].Currency)
if err != nil {
errs = append(errs, err)
continue channels
}
formattedPair, err := f.FormatExchangeCurrency(channelsToUnsubscribe[i].Currency, a)
if err != nil {
errs = append(errs, err)
continue channels
}
unSub.Market = formattedPair.String()
}
err := f.Websocket.Conn.SendJSONMessage(unSub)
if err != nil {
errs = append(errs, err)
continue
}
f.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
// GenerateDefaultSubscriptions generates default subscription
func (f *FTX) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) {
var subscriptions []stream.ChannelSubscription
subscriptions = append(subscriptions, stream.ChannelSubscription{
Channel: wsMarkets,
})
var channels = []string{wsTicker, wsTrades, wsOrderbook}
assets := f.GetAssetTypes(true)
for a := range assets {
pairs, err := f.GetEnabledPairs(assets[a])
if err != nil {
return nil, err
}
for z := range pairs {
newPair := currency.NewPairWithDelimiter(pairs[z].Base.String(),
pairs[z].Quote.String(),
"-")
for x := range channels {
subscriptions = append(subscriptions,
stream.ChannelSubscription{
Channel: channels[x],
Currency: newPair,
Asset: assets[a],
})
}
}
}
if f.IsWebsocketAuthenticationSupported() {
var authchan = []string{wsOrders, wsFills}
for x := range authchan {
subscriptions = append(subscriptions, stream.ChannelSubscription{
Channel: authchan[x],
})
}
}
return subscriptions, nil
}
// wsReadData gets and passes on websocket messages for processing
func (f *FTX) wsReadData() {
defer f.Websocket.Wg.Done()
for {
select {
case <-f.Websocket.ShutdownC:
return
default:
resp := f.Websocket.Conn.ReadMessage()
if resp.Raw == nil {
return
}
err := f.wsHandleData(resp.Raw)
if err != nil {
f.Websocket.DataHandler <- err
}
}
}
}
func timestampFromFloat64(ts float64) time.Time {
secs := int64(ts)
nsecs := int64((ts - float64(secs)) * 1e9)
return time.Unix(secs, nsecs).UTC()
}
func (f *FTX) wsHandleData(respRaw []byte) error {
var result map[string]interface{}
err := json.Unmarshal(respRaw, &result)
if err != nil {
return err
}
switch result["type"] {
case wsUpdate:
var p currency.Pair
var a asset.Item
market, ok := result["market"]
if ok {
p, err = currency.NewPairFromString(market.(string))
if err != nil {
return err
}
a, err = f.GetPairAssetType(p)
if err != nil {
return err
}
}
switch result["channel"] {
case wsTicker:
var resultData WsTickerDataStore
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
f.Websocket.DataHandler <- &ticker.Price{
ExchangeName: f.Name,
Bid: resultData.Ticker.Bid,
BidSize: resultData.Ticker.BidSize,
Ask: resultData.Ticker.Ask,
AskSize: resultData.Ticker.AskSize,
Last: resultData.Ticker.Last,
LastUpdated: timestampFromFloat64(resultData.Ticker.Time),
Pair: p,
AssetType: a,
}
case wsOrderbook:
var resultData WsOrderbookDataStore
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
if len(resultData.OBData.Asks) == 0 && len(resultData.OBData.Bids) == 0 {
return nil
}
err = f.WsProcessUpdateOB(&resultData.OBData, p, a)
if err != nil {
err2 := f.wsResubToOB(p)
if err2 != nil {
f.Websocket.DataHandler <- err2
}
return err
}
case wsTrades:
saveTradeData := f.IsSaveTradeDataEnabled()
if !saveTradeData &&
!f.IsTradeFeedEnabled() {
return nil
}
var resultData WsTradeDataStore
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
var trades []trade.Data
for z := range resultData.TradeData {
var oSide order.Side
oSide, err = order.StringToOrderSide(resultData.TradeData[z].Side)
if err != nil {
f.Websocket.DataHandler <- order.ClassificationError{
Exchange: f.Name,
Err: err,
}
}
trades = append(trades, trade.Data{
Timestamp: resultData.TradeData[z].Time,
CurrencyPair: p,
AssetType: a,
Exchange: f.Name,
Price: resultData.TradeData[z].Price,
Amount: resultData.TradeData[z].Size,
Side: oSide,
TID: strconv.FormatInt(resultData.TradeData[z].ID, 10),
})
}
return f.Websocket.Trade.Update(saveTradeData, trades...)
case wsOrders:
var resultData WsOrderDataStore
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
var pair currency.Pair
pair, err = currency.NewPairFromString(resultData.OrderData.Market)
if err != nil {
return err
}
var assetType asset.Item
assetType, err = f.GetPairAssetType(pair)
if err != nil {
return err
}
var orderVars OrderVars
orderVars, err = f.compatibleOrderVars(context.TODO(),
resultData.OrderData.Side,
resultData.OrderData.Status,
resultData.OrderData.OrderType,
resultData.OrderData.Size,
resultData.OrderData.FilledSize,
resultData.OrderData.AvgFillPrice)
if err != nil {
return err
}
var resp order.Detail
resp.PostOnly = resultData.OrderData.PostOnly
resp.Price = resultData.OrderData.Price
resp.Amount = resultData.OrderData.Size
resp.AverageExecutedPrice = resultData.OrderData.AvgFillPrice
resp.ExecutedAmount = resultData.OrderData.FilledSize
resp.RemainingAmount = resultData.OrderData.Size - resultData.OrderData.FilledSize
resp.Cost = resp.AverageExecutedPrice * resultData.OrderData.FilledSize
// Fee: orderVars.Fee is incorrect.
resp.Exchange = f.Name
resp.OrderID = strconv.FormatInt(resultData.OrderData.ID, 10)
resp.ClientOrderID = resultData.OrderData.ClientID
resp.Type = orderVars.OrderType
resp.Side = orderVars.Side
resp.Status = orderVars.Status
resp.AssetType = assetType
resp.Date = resultData.OrderData.CreatedAt
// There's no current timestamp, so this is the best we can get.
resp.LastUpdated = resultData.OrderData.CreatedAt
resp.Pair = pair
f.Websocket.DataHandler <- &resp
case wsFills:
if !f.IsFillsFeedEnabled() {
return nil
}
var resultData WsFillsDataStore
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
var side order.Side
side, err = order.StringToOrderSide(resultData.FillsData.Side)
if err != nil {
f.Websocket.DataHandler <- order.ClassificationError{
Exchange: f.Name,
Err: err,
}
}
p, err = currency.NewPairFromString(resultData.FillsData.Market)
if err != nil {
return err
}
a, err = f.GetPairAssetType(p)
if err != nil {
return err
}
return f.Websocket.Fills.Update(fill.Data{
ID: strconv.FormatInt(resultData.FillsData.ID, 10),
Timestamp: resultData.FillsData.Time,
Exchange: f.Name,
AssetType: a,
CurrencyPair: p,
Side: side,
OrderID: strconv.FormatInt(resultData.FillsData.OrderID, 10),
TradeID: strconv.FormatInt(resultData.FillsData.TradeID, 10),
Price: resultData.FillsData.Price,
Amount: resultData.FillsData.Size,
})
default:
f.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: f.Name + stream.UnhandledMessage + string(respRaw)}
}
case wsPartial:
switch result["channel"] {
case "orderbook":
var p currency.Pair
var a asset.Item
market, ok := result["market"]
if ok {
p, err = currency.NewPairFromString(market.(string))
if err != nil {
return err
}
a, err = f.GetPairAssetType(p)
if err != nil {
return err
}
}
var resultData WsOrderbookDataStore
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
err = f.WsProcessPartialOB(&resultData.OBData, p, a)
if err != nil {
err2 := f.wsResubToOB(p)
if err2 != nil {
f.Websocket.DataHandler <- err2
}
return err
}
// reset obchecksum failure blockage for pair
delete(obSuccess, p)
case wsMarkets:
var resultData WSMarkets
err = json.Unmarshal(respRaw, &resultData)
if err != nil {
return err
}
f.Websocket.DataHandler <- resultData.Data
}
case "error":
f.Websocket.DataHandler <- stream.UnhandledMessageWarning{
Message: f.Name + stream.UnhandledMessage + string(respRaw),
}
}
return nil
}
// WsProcessUpdateOB processes an update on the orderbook
func (f *FTX) WsProcessUpdateOB(data *WsOrderbookData, p currency.Pair, a asset.Item) error {
update := orderbook.Update{
Asset: a,
Pair: p,
Bids: make([]orderbook.Item, len(data.Bids)),
Asks: make([]orderbook.Item, len(data.Asks)),
UpdateTime: timestampFromFloat64(data.Time),
}
for x := range data.Bids {
update.Bids[x] = orderbook.Item{
Price: data.Bids[x][0],
Amount: data.Bids[x][1],
}
}
for x := range data.Asks {
update.Asks[x] = orderbook.Item{
Price: data.Asks[x][0],
Amount: data.Asks[x][1],
}
}
err := f.Websocket.Orderbook.Update(&update)
if err != nil {
return err
}
updatedOb, err := f.Websocket.Orderbook.GetOrderbook(p, a)
if err != nil {
return err
}
checksum := f.CalcUpdateOBChecksum(updatedOb)
if checksum != data.Checksum {
log.Warnf(log.ExchangeSys, "%s checksum failure for item %s",
f.Name,
p)
return errors.New("checksum failed")
}
return nil
}
func (f *FTX) wsResubToOB(p currency.Pair) error {
if ok := obSuccess[p]; ok {
return nil
}
obSuccess[p] = true
channelToResubscribe := &stream.ChannelSubscription{
Channel: wsOrderbook,
Currency: p,
}
err := f.Websocket.ResubscribeToChannel(channelToResubscribe)
if err != nil {
return fmt.Errorf("%s resubscribe to orderbook failure %s", f.Name, err)
}
return nil
}
// WsProcessPartialOB creates an OB from websocket data
func (f *FTX) WsProcessPartialOB(data *WsOrderbookData, p currency.Pair, a asset.Item) error {
signedChecksum := f.CalcPartialOBChecksum(data)
if signedChecksum != data.Checksum {
return fmt.Errorf("%s channel: %s. Orderbook partial for %v checksum invalid",
f.Name,
a,
p)
}
bids := make(orderbook.Items, len(data.Bids))
asks := make(orderbook.Items, len(data.Asks))
for x := range data.Bids {
bids[x] = orderbook.Item{
Price: data.Bids[x][0],
Amount: data.Bids[x][1],
}
}
for x := range data.Asks {
asks[x] = orderbook.Item{
Price: data.Asks[x][0],
Amount: data.Asks[x][1],
}
}
newOrderBook := orderbook.Base{
Asks: asks,
Bids: bids,
Asset: a,
LastUpdated: timestampFromFloat64(data.Time),
Pair: p,
Exchange: f.Name,
VerifyOrderbook: f.CanVerifyOrderbook,
}
return f.Websocket.Orderbook.LoadSnapshot(&newOrderBook)
}
// CalcPartialOBChecksum calculates checksum of partial OB data received from WS
func (f *FTX) CalcPartialOBChecksum(data *WsOrderbookData) int64 {
var checksum strings.Builder
var price, amount string
for i := 0; i < 100; i++ {
if len(data.Bids)-1 >= i {
price = checksumParseNumber(data.Bids[i][0])
amount = checksumParseNumber(data.Bids[i][1])
checksum.WriteString(price + ":" + amount + ":")
}
if len(data.Asks)-1 >= i {
price = checksumParseNumber(data.Asks[i][0])
amount = checksumParseNumber(data.Asks[i][1])
checksum.WriteString(price + ":" + amount + ":")
}
}
checksumStr := strings.TrimSuffix(checksum.String(), ":")
return int64(crc32.ChecksumIEEE([]byte(checksumStr)))
}
// CalcUpdateOBChecksum calculates checksum of update OB data received from WS
func (f *FTX) CalcUpdateOBChecksum(data *orderbook.Base) int64 {
var checksum strings.Builder
var price, amount string
for i := 0; i < 100; i++ {
if len(data.Bids)-1 >= i {
price = checksumParseNumber(data.Bids[i].Price)
amount = checksumParseNumber(data.Bids[i].Amount)
checksum.WriteString(price + ":" + amount + ":")
}
if len(data.Asks)-1 >= i {
price = checksumParseNumber(data.Asks[i].Price)
amount = checksumParseNumber(data.Asks[i].Amount)
checksum.WriteString(price + ":" + amount + ":")
}
}
checksumStr := strings.TrimSuffix(checksum.String(), ":")
return int64(crc32.ChecksumIEEE([]byte(checksumStr)))
}
func checksumParseNumber(num float64) string {
modifier := byte('f')
if num < 0.0001 {
modifier = 'e'
}
r := strconv.FormatFloat(num, modifier, -1, 64)
if strings.IndexByte(r, '.') == -1 && modifier != 'e' {
r += ".0"
}
return r
}

View File

@@ -1,422 +0,0 @@
package ftx
import (
"fmt"
"testing"
"time"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/fill"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
)
func parseRaw(t *testing.T, input string) interface{} {
t.Helper()
pairs := currency.Pairs{
currency.Pair{
Base: currency.BTC,
Quote: currency.USDT,
},
}
dataC := make(chan interface{}, 1)
fills := fill.Fills{}
fills.Setup(true, dataC)
x := FTX{
exchange.Base{
Name: "FTX",
Features: exchange.Features{
Enabled: exchange.FeaturesEnabled{
FillsFeed: true,
},
},
CurrencyPairs: currency.PairsManager{
Pairs: map[asset.Item]*currency.PairStore{
asset.Spot: {
Available: pairs,
Enabled: pairs,
ConfigFormat: &currency.PairFormat{
Delimiter: "^",
Uppercase: true,
},
},
},
},
Websocket: &stream.Websocket{
DataHandler: dataC,
Fills: fills,
},
},
CollateralWeightHolder{},
}
if err := x.wsHandleData([]byte(input)); err != nil {
t.Fatal(err)
}
var ret interface{}
select {
case ret = <-x.Websocket.DataHandler:
default:
t.Error(fmt.Errorf("timed out waiting for channel data"))
}
return ret
}
func TestFTX_wsHandleData_Details(t *testing.T) {
const inputPartiallyCancelled = `{
"channel": "orders",
"type": "update",
"data": {
"id": 69350095302,
"clientId": "192ab87ae99970b79f624ef8bd783351",
"market": "BTC/USDT",
"type": "limit",
"side": "sell",
"price": 65536,
"size": 12,
"status": "closed",
"filledSize": 4,
"remainingSize": 8,
"reduceOnly": false,
"liquidation": false,
"avgFillPrice": 32768,
"postOnly": true,
"ioc": true,
"createdAt": "2021-08-08T10:35:02.649437+00:00"
}
}`
p := parseRaw(t, inputPartiallyCancelled)
x, ok := p.(*order.Detail)
if !ok {
t.Fatalf("have %T, want *order.Detail", p)
}
// "reduceOnly" and "liquidation" do not have corresponding fields in
// order.Detail.
if x.OrderID != "69350095302" ||
x.ClientOrderID != "192ab87ae99970b79f624ef8bd783351" ||
x.Pair.Base.Item.Symbol != "BTC" ||
x.Pair.Quote.Item.Symbol != "USDT" ||
x.Type != order.Limit ||
x.Side != order.Sell ||
x.Price != 65536 ||
x.Amount != 12 ||
x.Status != order.PartiallyCancelled ||
x.ExecutedAmount != 4 ||
x.RemainingAmount != 8 ||
x.AverageExecutedPrice != 32768 ||
!x.PostOnly ||
!x.Date.Equal(time.Unix(1628418902, 649437000).UTC()) {
t.Error("parsed values do not match")
}
const inputFilled = `{
"channel": "orders",
"type": "update",
"data": {
"id": 69350095302,
"clientId": "192ab87ae99970b79f624ef8bd783351",
"market": "BTC/USDT",
"type": "limit",
"side": "sell",
"price": 65536,
"size": 12,
"status": "closed",
"filledSize": 12,
"remainingSize": 0,
"reduceOnly": false,
"liquidation": false,
"avgFillPrice": 32768,
"postOnly": true,
"ioc": true,
"createdAt": "2021-08-08T10:35:02.649437+00:00"
}
}`
orderDetail, ok := parseRaw(t, inputFilled).(*order.Detail)
if !ok {
t.Error("unable to type asset order detail")
} else if orderDetail.Status != order.Filled {
t.Errorf("have %s, want %s", orderDetail.Status, order.Filled)
}
const inputCancelled = `{
"channel": "orders",
"type": "update",
"data": {
"id": 69350095302,
"clientId": "192ab87ae99970b79f624ef8bd783351",
"market": "BTC/USDT",
"type": "limit",
"side": "sell",
"price": 65536,
"size": 12,
"status": "closed",
"filledSize": 0,
"remainingSize": 12,
"reduceOnly": false,
"liquidation": false,
"avgFillPrice": 32768,
"postOnly": true,
"ioc": true,
"createdAt": "2021-08-08T10:35:02.649437+00:00"
}
}`
orderDetail, ok = parseRaw(t, inputCancelled).(*order.Detail)
if !ok {
t.Error("unable to type asset order detail")
} else if orderDetail.Status != order.Cancelled {
t.Errorf("have %s, want %s", orderDetail.Status, order.Cancelled)
}
}
func TestFTX_wsHandleData_wsFills(t *testing.T) {
const input = `{
"channel": "fills",
"type": "update",
"data": {
"id": 1234567890,
"market": "BTC-USDT",
"type": "order",
"side": "sell",
"price": 32768,
"size": 2,
"orderId": 23456789012,
"time": "2021-08-07T14:32:42.373010+00:00",
"tradeId": 3456789012,
"feeRate": 8,
"fee": 16,
"feeCurrency": "FTT",
"liquidity": "maker"
}
}`
p := parseRaw(t, input)
x, ok := p.([]fill.Data)
if !ok {
t.Fatalf("have %T, want []fill.Data", p)
}
if x[0].Exchange != "FTX" ||
x[0].ID != "1234567890" ||
x[0].OrderID != "23456789012" ||
x[0].CurrencyPair.Base.String() != "BTC" ||
x[0].CurrencyPair.Quote.String() != "USDT" ||
x[0].Side != order.Sell ||
x[0].TradeID != "3456789012" ||
x[0].Price != 32768 ||
x[0].Amount != 2 ||
!x[0].Timestamp.Equal(time.Unix(1628346762, 373010000).UTC()) {
t.Errorf("parsed values do not match, x: %#v", x)
}
}
func TestFTX_wsHandleData_Price(t *testing.T) {
const input = `{
"channel": "ticker",
"market": "BTC/USDT",
"type": "update",
"data": {
"bid": 16.0,
"ask": 32.0,
"bidSize": 64.0,
"askSize": 128.0,
"last": 256.0,
"time": 1073741824.0
}
}`
p := parseRaw(t, input)
x, ok := p.(*ticker.Price)
if !ok {
t.Fatalf("have %T, want *ticker.Price", p)
}
if x.AssetType != asset.Spot ||
!x.Pair.Equal(currency.NewPair(currency.BTC, currency.USDT)) ||
x.Bid != 16 ||
x.BidSize != 64 ||
x.Ask != 32 ||
x.AskSize != 128 ||
x.Last != 256 ||
!x.LastUpdated.Equal(time.Unix(1073741824, 0)) {
t.Error("parsed values do not match")
}
}
func TestParsingOrders(t *testing.T) {
t.Parallel()
data := []byte(`{
"channel": "fills",
"data": {
"id": 24852229,
"clientId": null,
"market": "XRP-PERP",
"type": "limit",
"side": "buy",
"size": 42353.0,
"price": 0.2977,
"reduceOnly": false,
"ioc": false,
"postOnly": false,
"status": "closed",
"filledSize": 0.0,
"remainingSize": 0.0,
"avgFillPrice": 0.2978
},
"type": "update"
}`)
if err := f.wsHandleData(data); err != nil {
t.Error(err)
}
}
func TestParsingWSTradesData(t *testing.T) {
t.Parallel()
data := []byte(`{
"channel": "trades",
"market": "BTC-PERP",
"type": "update",
"data": [
{
"id": 44200173,
"price": 9761.0,
"size": 0.0008,
"side": "buy",
"liquidation": false,
"time": "2020-05-15T01:10:04.369194+00:00"
}
]
}`)
if err := f.wsHandleData(data); err != nil {
t.Error(err)
}
}
func TestParsingWSTickerData(t *testing.T) {
t.Parallel()
data := []byte(`{
"channel": "ticker",
"market": "BTC-PERP",
"type": "update",
"data": {
"bid": 9760.5,
"ask": 9761.0,
"bidSize": 3.36,
"askSize": 71.8484,
"last": 9761.0,
"time": 1589505004.4237103
}
}`)
if err := f.wsHandleData(data); err != nil {
t.Error(err)
}
}
func TestParsingWSOrdersData(t *testing.T) {
t.Parallel()
data := []byte(`{
"channel": "orders",
"data": {
"id": 24852229,
"clientId": null,
"market": "BTC-PERP",
"type": "limit",
"side": "buy",
"size": 42353.0,
"price": 0.2977,
"reduceOnly": false,
"ioc": false,
"postOnly": false,
"status": "closed",
"filledSize": 0.0,
"remainingSize": 0.0,
"avgFillPrice": 0.2978
},
"type": "update"
}`)
if err := f.wsHandleData(data); err != nil {
t.Error(err)
}
}
func TestParsingMarketsData(t *testing.T) {
t.Parallel()
data := []byte(`{"channel": "markets",
"type": "partial",
"data": {
"ADA-0626": {
"name": "ADA-0626",
"enabled": true,
"priceIncrement": 5e-06,
"sizeIncrement": 1.0,
"type": "future",
"baseCurrency": null,
"quoteCurrency": null,
"restricted": false,
"underlying": "ADA",
"future": {
"name": "ADA-0626",
"underlying": "ADA",
"description": "Cardano June 2020 Futures",
"type": "future", "expiry": "2020-06-26T003:00:00+00:00",
"perpetual": false,
"expired": false,
"enabled": true,
"postOnly": false,
"imfFactor": 4e-05,
"underlyingDescription": "Cardano",
"expiryDescription": "June 2020",
"moveStart": null, "positionLimitWeight": 10.0,
"group": "quarterly"}}},
"action": "partial"
}`)
if err := f.wsHandleData(data); err != nil {
t.Error(err)
}
}
func TestParsingWSOBData(t *testing.T) {
data := []byte(`{"channel": "orderbook", "market": "BTC-PERP", "type": "partial", "data": {"time": 1589855831.4606245, "checksum": 225973019, "bids": [[9602.0, 3.2903], [9601.5, 3.11], [9601.0, 2.1356], [9600.5, 3.0991], [9600.0, 8.014], [9599.5, 4.1571], [9599.0, 79.1846], [9598.5, 3.099], [9598.0, 3.985], [9597.5, 3.999], [9597.0, 16.4335], [9596.5, 4.006], [9596.0, 3.2596], [9595.0, 6.334], [9594.0, 3.5685], [9593.0, 14.2717], [9592.5, 0.5], [9591.0, 2.181], [9590.5, 40.4246], [9590.0, 1.0], [9589.0, 1.357], [9588.5, 0.4738], [9587.5, 0.15], [9587.0, 16.811], [9586.5, 1.2], [9586.0, 0.2], [9585.5, 1.0], [9584.5, 0.002], [9584.0, 1.51], [9583.5, 0.01], [9583.0, 1.4], [9582.5, 0.1], [9582.0, 24.7921], [9581.0, 2.087], [9580.5, 2.0], [9580.0, 0.1], [9579.0, 1.1588], [9578.0, 0.9477], [9577.5, 22.216], [9576.0, 0.2], [9574.0, 22.0], [9573.5, 1.0], [9572.0, 0.203], [9570.0, 0.1026], [9565.5, 5.5332], [9565.0, 27.5243], [9563.5, 2.6], [9562.0, 0.0175], [9561.0, 2.0085], [9552.0, 1.6], [9550.5, 27.3399], [9550.0, 0.1046], [9548.0, 0.0175], [9544.0, 4.8197], [9542.5, 26.5754], [9542.0, 0.003], [9541.0, 0.0549], [9540.0, 0.1984], [9537.5, 0.0008], [9535.5, 0.0105], [9535.0, 1.514], [9534.5, 36.5858], [9532.5, 4.7798], [9531.0, 40.6564], [9525.0, 0.001], [9523.5, 1.6], [9522.0, 0.0894], [9521.0, 0.315], [9520.5, 5.4525], [9520.0, 0.07], [9518.0, 0.034], [9517.5, 4.0], [9513.0, 0.0175], [9512.5, 15.6016], [9512.0, 32.7882], [9511.5, 0.0482], [9510.5, 0.0482], [9510.0, 0.2999], [9509.0, 2.0], [9508.5, 0.0482], [9506.0, 0.0416], [9505.5, 0.0492], [9505.0, 0.2], [9502.5, 0.01], [9502.0, 0.01], [9501.5, 0.0592], [9501.0, 0.001], [9500.0, 3.4913], [9499.5, 39.8683], [9498.0, 4.6108], [9497.0, 0.0481], [9492.0, 41.3559], [9490.0, 1.1104], [9488.0, 0.0105], [9486.0, 5.4443], [9485.5, 0.0482], [9484.0, 4.0], [9482.0, 0.25], [9481.5, 2.0], [9481.0, 8.1572]], "asks": [[9602.5, 3.0], [9603.0, 2.8979], [9603.5, 54.49], [9604.0, 5.9982], [9604.5, 3.028], [9605.0, 4.657], [9606.5, 5.2512], [9607.0, 4.003], [9607.5, 4.011], [9608.0, 13.7505], [9608.5, 3.994], [9609.0, 2.974], [9609.5, 3.002], [9612.0, 10.298], [9612.5, 13.455], [9613.5, 3.013], [9614.0, 2.02], [9614.5, 3.359], [9615.0, 21.2429], [9616.0, 0.5], [9616.5, 0.01], [9617.0, 2.182], [9617.5, 23.0223], [9618.0, 0.0623], [9618.5, 1.5795], [9619.0, 0.3065], [9620.0, 3.9], [9621.0, 1.5], [9622.0, 1.5], [9622.5, 1.216], [9625.0, 1.0], [9625.5, 0.9477], [9626.0, 0.05], [9628.5, 1.1588], [9629.0, 1.4], [9630.0, 4.2332], [9630.5, 1.228], [9631.0, 1.5], [9631.5, 0.0104], [9632.5, 26.7529], [9633.0, 0.25], [9638.0, 1.0], [9640.0, 0.2], [9641.0, 1.001], [9642.0, 0.0175], [9643.0, 0.25], [9643.5, 1.6], [9644.0, 31.4166], [9646.5, 41.6609], [9649.5, 0.2], [9653.5, 1.5], [9656.5, 1.6], [9657.0, 0.2], [9658.0, 1.5], [9659.5, 4.7804], [9660.5, 43.3405], [9665.5, 40.6564], [9670.0, 0.1034], [9671.5, 4.9098], [9674.0, 0.25], [9678.0, 15.6016], [9678.5, 1.5], [9681.0, 34.9683], [9683.0, 0.2], [9683.5, 5.3845], [9684.5, 5.087], [9685.0, 0.1032], [9686.5, 0.0075], [9689.0, 1.6], [9691.0, 34.7472], [9692.0, 0.001], [9694.0, 0.5], [9695.0, 0.0109], [9696.5, 4.825], [9700.0, 1.0595], [9701.5, 2.0], [9702.0, 0.011], [9702.5, 0.01], [9706.0, 1.2], [9708.0, 0.0175], [9710.0, 39.153], [9712.0, 48.6163], [9712.5, 1.5], [9713.0, 8.1572], [9715.5, 0.5021], [9716.5, 2.0], [9719.0, 0.0245], [9721.0, 0.5], [9724.0, 0.251], [9726.0, 0.12], [9727.5, 0.5075], [9730.0, 0.015], [9732.0, 58.5394], [9733.0, 0.001], [9734.0, 20.0], [9743.0, 0.06], [9750.0, 9.5], [9755.0, 52.4404], [9757.0, 48.6121], [9764.0, 0.015]], "action": "partial"}}`)
err := f.wsHandleData(data)
if err != nil {
t.Error(err)
}
data = []byte(`{"channel": "orderbook", "market": "BTC-PERP", "type": "update", "data": {"time": 1589855831.5128105, "checksum": 365946911, "bids": [[9596.0, 4.2656], [9512.0, 32.7912]], "asks": [[9613.5, 4.012], [9702.0, 0.021]], "action": "update"}}`)
err = f.wsHandleData(data)
if err != nil {
t.Error(err)
}
}
func TestParsingWSOBData2(t *testing.T) {
t.Parallel()
data := []byte(`{"channel": "orderbook", "market": "PRIVBEAR/USD", "type": "partial", "data": {"time": 1593498757.0915809, "checksum": 87356415, "bids": [[1389.5, 5.1019], [1384.5, 16.6318], [1371.5, 23.5531], [1365.5, 23.3001], [1354.0, 26.758], [1352.5, 24.6891], [1337.5, 30.3091], [1333.5, 24.9583], [1323.0, 30.9597], [1302.0, 40.9241], [1282.5, 38.0319], [1272.5, 39.1436], [1084.5, 1.8934], [1080.0, 2.0595], [1075.0, 2.0527], [1069.0, 1.8077], [1053.5, 1.855], [1.0, 2.0]], "asks": [[1403.5, 6.8077], [1407.5, 17.6482], [1417.0, 14.6401], [1418.5, 22.6664], [1426.0, 20.3936], [1430.5, 34.2797], [1435.0, 30.6073], [1443.0, 20.2036], [1471.5, 35.5789], [1494.5, 29.2815], [1505.0, 30.9842], [1511.5, 39.4325], [1799.5, 1.7529], [1810.5, 2.0379], [1813.5, 2.0423], [1817.5, 2.0393], [1821.0, 1.7148], [86347.5, 9e-05], [94982.5, 0.0001], [104480.0, 0.0001], [114930.0, 0.00011], [126420.0, 0.00011], [139065.0, 0.00011], [152970.0, 0.00012], [168267.5, 0.00012], [185092.5, 0.00012], [223962.5, 0.00013], [246360.0, 0.00014], [270995.0, 0.00017], [1203602.5, 0.00013]], "action": "partial"}}`)
err := f.wsHandleData(data)
if err != nil {
t.Fatal(err)
}
data = []byte(`{"channel": "orderbook", "market": "DOGE-PERP", "type": "partial", "data": {"time": 1593395710.072698, "checksum": 2591057682, "bids": [[0.0023085, 507742.0], [0.002308, 7000.0], [0.0023075, 100000.0], [0.0023065, 324770.0], [0.002305, 46000.0], [0.0023035, 879600.0], [0.002303, 49000.0], [0.0023025, 1076421.0], [0.002296, 30511800.0], [0.002293, 3006300.0], [0.0022925, 1256349.0], [0.0022895, 11855700.0], [0.0022855, 1008960.0], [0.0022775, 1047578.0], [0.0022745, 3070200.0], [0.00227, 2939100.0], [0.002269, 1599711.0], [0.00226, 1671504.0], [0.00225, 1957119.0], [0.00224, 5225404.0], [0.0022395, 250.0], [0.002233, 2994000.0], [0.002229, 2336857.0], [0.002218, 2144227.0], [0.002205, 2101662.0], [0.0021985, 7406099.0], [0.0021915, 2470187.0], [0.0021775, 2690545.0], [0.0021755, 250.0], [0.002162, 2997201.0], [0.00215, 11464856.0], [0.002148, 16178857.0], [0.0021255, 11063510.0], [0.002119, 164239.0], [0.0020435, 19124572.0], [0.0020395, 18376430.0], [0.0020125, 1250.0], [0.0019655, 50.0], [0.001958, 97012.0], [0.001942, 50000.0], [0.001899, 50000.0], [0.001895, 1250.0], [0.001712, 2500.0], [0.0012075, 70190.0], [0.00112, 22321.0], [1.65e-05, 31889.0]], "asks": [[0.0023145, 359557.0], [0.0023155, 222497.0], [0.0023175, 40000.0], [0.002319, 879600.0], [0.0023195, 50000.0], [0.0023205, 1067334.0], [0.0023215, 45000.0], [0.002326, 33518100.0], [0.0023265, 1113997.0], [0.0023285, 1170756.0], [0.002331, 11855700.0], [0.002336, 1105442.0], [0.002344, 1244804.0], [0.002348, 3070200.0], [0.0023525, 1546561.0], [0.0023555, 2939100.0], [0.0023575, 2928000.0], [0.002362, 1509707.0], [0.0023725, 1786697.0], [0.002374, 5710.0], [0.0023795, 151098.0], [0.0023835, 1747428.0], [0.002385, 2994000.0], [0.002395, 1721532.0], [0.0024015, 5710.0], [0.002408, 2552142.0], [0.002422, 2188855.0], [0.002429, 5710.0], [0.0024295, 8441953.0], [0.002437, 2196750.0], [0.002445, 122574.0], [0.002454, 1974273.0], [0.0024565, 5710.0], [0.0024715, 2864643.0], [0.00248, 15238408.0], [0.002484, 5710.0], [0.002497, 16343646.0], [0.0025025, 12177084.0], [0.0025115, 5710.0], [0.002539, 5710.0], [0.002566, 16643688.0], [0.0025665, 5710.0], [0.002594, 5710.0], [0.002617, 50.0], [0.002623, 10.0], [0.0027685, 20825893.0], [0.003178, 50000.0], [0.003811, 68952.0], [0.0074, 41460.0]], "action": "partial"}}`)
err = f.wsHandleData(data)
if err != nil {
t.Error(err)
}
data = []byte(`{"channel": "orderbook", "market": "BTC-PERP", "type": "partial", "data": {"time": 1589855831.4606245, "checksum": 225973019, "bids": [[9602.0, 3.2903], [9601.5, 3.11], [9601.0, 2.1356], [9600.5, 3.0991], [9600.0, 8.014], [9599.5, 4.1571], [9599.0, 79.1846], [9598.5, 3.099], [9598.0, 3.985], [9597.5, 3.999], [9597.0, 16.4335], [9596.5, 4.006], [9596.0, 3.2596], [9595.0, 6.334], [9594.0, 3.5685], [9593.0, 14.2717], [9592.5, 0.5], [9591.0, 2.181], [9590.5, 40.4246], [9590.0, 1.0], [9589.0, 1.357], [9588.5, 0.4738], [9587.5, 0.15], [9587.0, 16.811], [9586.5, 1.2], [9586.0, 0.2], [9585.5, 1.0], [9584.5, 0.002], [9584.0, 1.51], [9583.5, 0.01], [9583.0, 1.4], [9582.5, 0.1], [9582.0, 24.7921], [9581.0, 2.087], [9580.5, 2.0], [9580.0, 0.1], [9579.0, 1.1588], [9578.0, 0.9477], [9577.5, 22.216], [9576.0, 0.2], [9574.0, 22.0], [9573.5, 1.0], [9572.0, 0.203], [9570.0, 0.1026], [9565.5, 5.5332], [9565.0, 27.5243], [9563.5, 2.6], [9562.0, 0.0175], [9561.0, 2.0085], [9552.0, 1.6], [9550.5, 27.3399], [9550.0, 0.1046], [9548.0, 0.0175], [9544.0, 4.8197], [9542.5, 26.5754], [9542.0, 0.003], [9541.0, 0.0549], [9540.0, 0.1984], [9537.5, 0.0008], [9535.5, 0.0105], [9535.0, 1.514], [9534.5, 36.5858], [9532.5, 4.7798], [9531.0, 40.6564], [9525.0, 0.001], [9523.5, 1.6], [9522.0, 0.0894], [9521.0, 0.315], [9520.5, 5.4525], [9520.0, 0.07], [9518.0, 0.034], [9517.5, 4.0], [9513.0, 0.0175], [9512.5, 15.6016], [9512.0, 32.7882], [9511.5, 0.0482], [9510.5, 0.0482], [9510.0, 0.2999], [9509.0, 2.0], [9508.5, 0.0482], [9506.0, 0.0416], [9505.5, 0.0492], [9505.0, 0.2], [9502.5, 0.01], [9502.0, 0.01], [9501.5, 0.0592], [9501.0, 0.001], [9500.0, 3.4913], [9499.5, 39.8683], [9498.0, 4.6108], [9497.0, 0.0481], [9492.0, 41.3559], [9490.0, 1.1104], [9488.0, 0.0105], [9486.0, 5.4443], [9485.5, 0.0482], [9484.0, 4.0], [9482.0, 0.25], [9481.5, 2.0], [9481.0, 8.1572]], "asks": [[9602.5, 3.0], [9603.0, 2.8979], [9603.5, 54.49], [9604.0, 5.9982], [9604.5, 3.028], [9605.0, 4.657], [9606.5, 5.2512], [9607.0, 4.003], [9607.5, 4.011], [9608.0, 13.7505], [9608.5, 3.994], [9609.0, 2.974], [9609.5, 3.002], [9612.0, 10.298], [9612.5, 13.455], [9613.5, 3.013], [9614.0, 2.02], [9614.5, 3.359], [9615.0, 21.2429], [9616.0, 0.5], [9616.5, 0.01], [9617.0, 2.182], [9617.5, 23.0223], [9618.0, 0.0623], [9618.5, 1.5795], [9619.0, 0.3065], [9620.0, 3.9], [9621.0, 1.5], [9622.0, 1.5], [9622.5, 1.216], [9625.0, 1.0], [9625.5, 0.9477], [9626.0, 0.05], [9628.5, 1.1588], [9629.0, 1.4], [9630.0, 4.2332], [9630.5, 1.228], [9631.0, 1.5], [9631.5, 0.0104], [9632.5, 26.7529], [9633.0, 0.25], [9638.0, 1.0], [9640.0, 0.2], [9641.0, 1.001], [9642.0, 0.0175], [9643.0, 0.25], [9643.5, 1.6], [9644.0, 31.4166], [9646.5, 41.6609], [9649.5, 0.2], [9653.5, 1.5], [9656.5, 1.6], [9657.0, 0.2], [9658.0, 1.5], [9659.5, 4.7804], [9660.5, 43.3405], [9665.5, 40.6564], [9670.0, 0.1034], [9671.5, 4.9098], [9674.0, 0.25], [9678.0, 15.6016], [9678.5, 1.5], [9681.0, 34.9683], [9683.0, 0.2], [9683.5, 5.3845], [9684.5, 5.087], [9685.0, 0.1032], [9686.5, 0.0075], [9689.0, 1.6], [9691.0, 34.7472], [9692.0, 0.001], [9694.0, 0.5], [9695.0, 0.0109], [9696.5, 4.825], [9700.0, 1.0595], [9701.5, 2.0], [9702.0, 0.011], [9702.5, 0.01], [9706.0, 1.2], [9708.0, 0.0175], [9710.0, 39.153], [9712.0, 48.6163], [9712.5, 1.5], [9713.0, 8.1572], [9715.5, 0.5021], [9716.5, 2.0], [9719.0, 0.0245], [9721.0, 0.5], [9724.0, 0.251], [9726.0, 0.12], [9727.5, 0.5075], [9730.0, 0.015], [9732.0, 58.5394], [9733.0, 0.001], [9734.0, 20.0], [9743.0, 0.06], [9750.0, 9.5], [9755.0, 52.4404], [9757.0, 48.6121], [9764.0, 0.015]], "action": "partial"}}`)
err = f.wsHandleData(data)
if err != nil {
t.Error(err)
}
data = []byte(`{"channel": "orderbook", "market": "BTC-PERP", "type": "update", "data": {"time": 1589855831.5128105, "checksum": 365946911, "bids": [[9596.0, 4.2656], [9512.0, 32.7912]], "asks": [[9613.5, 4.012], [9702.0, 0.021]], "action": "update"}}`)
err = f.wsHandleData(data)
if err != nil {
t.Error(err)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -530,19 +530,16 @@ type TraderSentimentIndexPositionData struct {
// LiquidationOrdersData stores data of liquidation orders
type LiquidationOrdersData struct {
Data struct {
Orders []struct {
Symbol string `json:"symbol"`
ContractCode string `json:"contract_code"`
Direction string `json:"buy"`
Offset string `json:"offset"`
Volume float64 `json:"volume"`
Price float64 `json:"price"`
CreatedAt int64 `json:"created_at"`
} `json:"orders"`
TotalPage int64 `json:"totalPage"`
CurrentPage int64 `json:"current_page"`
TotalSize int64 `json:"total_size"`
Data []struct {
QueryID int64 `json:"query_id"`
ContractCode string `json:"contract_code"`
Symbol string `json:"symbol"`
Direction string `json:"direction"`
Offset string `json:"offset"`
Volume float64 `json:"volume"`
Price float64 `json:"price"`
CreatedAt int64 `json:"created_at"`
Amount float64 `json:"amount"`
} `json:"data"`
}

View File

@@ -35,7 +35,7 @@ const (
huobiSwapSystemStatus = "/swap-api/v1/swap_api_state"
huobiSwapSentimentAccountData = "/swap-api/v1/swap_elite_account_ratio"
huobiSwapSentimentPosition = "/swap-api/v1/swap_elite_position_ratio"
huobiSwapLiquidationOrders = "/swap-api/v1/swap_liquidation_orders"
huobiSwapLiquidationOrders = "/swap-api/v3/swap_liquidation_orders"
huobiSwapHistoricalFundingRate = "/swap-api/v1/swap_historical_funding_rate"
huobiPremiumIndexKlineData = "/index/market/history/swap_premium_index_kline"
huobiPredictedFundingRateData = "/index/market/history/swap_estimated_rate_kline"
@@ -318,28 +318,31 @@ func (h *HUOBI) GetTraderSentimentIndexPosition(ctx context.Context, code curren
}
// GetLiquidationOrders gets liquidation orders for a given perp
func (h *HUOBI) GetLiquidationOrders(ctx context.Context, code currency.Pair, tradeType string, pageIndex, pageSize, createDate int64) (LiquidationOrdersData, error) {
func (h *HUOBI) GetLiquidationOrders(ctx context.Context, contract currency.Pair, tradeType string, startTime, endTime int64, direction string, fromID int64) (LiquidationOrdersData, error) {
var resp LiquidationOrdersData
codeValue, err := h.FormatSymbol(code, asset.CoinMarginedFutures)
formattedContract, err := h.FormatSymbol(contract, asset.CoinMarginedFutures)
if err != nil {
return resp, err
}
if createDate != 7 && createDate != 90 {
return resp, fmt.Errorf("invalid createDate. 7 and 90 are the only supported values")
}
tType, ok := validTradeTypes[tradeType]
if !ok {
return resp, fmt.Errorf("invalid trade type")
}
params := url.Values{}
params.Set("contract_code", codeValue)
params.Set("create_date", strconv.FormatInt(createDate, 10))
params.Set("contract", formattedContract)
params.Set("trade_type", strconv.FormatInt(tType, 10))
if pageIndex != 0 {
params.Set("page_index", strconv.FormatInt(pageIndex, 10))
if startTime != 0 {
params.Set("start_time", strconv.FormatInt(startTime, 10))
}
if pageSize != 0 {
params.Set("page_size", strconv.FormatInt(pageIndex, 10))
if endTime != 0 {
params.Set("end_time", strconv.FormatInt(startTime, 10))
}
if direction != "" {
params.Set("direct", direction)
}
if fromID != 0 {
params.Set("from_id", strconv.FormatInt(fromID, 10))
}
path := common.EncodeURLValues(huobiSwapLiquidationOrders, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)

View File

@@ -40,7 +40,7 @@ const (
fSystemStatus = "/api/v1/contract_api_state"
fTopAccountsSentiment = "/api/v1/contract_elite_account_ratio"
fTopPositionsSentiment = "/api/v1/contract_elite_position_ratio"
fLiquidationOrders = "/api/v1/contract_liquidation_orders"
fLiquidationOrders = "/api/v3/contract_liquidation_orders"
fIndexKline = "/index/market/history/index"
fBasisData = "/index/market/history/basis"
@@ -403,24 +403,27 @@ func (h *HUOBI) FQueryTopPositionsRatio(ctx context.Context, symbol, period stri
}
// FLiquidationOrders gets liquidation orders for futures contracts
func (h *HUOBI) FLiquidationOrders(ctx context.Context, symbol, tradeType string, pageIndex, pageSize, createDate int64) (FLiquidationOrdersInfo, error) {
var resp FLiquidationOrdersInfo
params := url.Values{}
params.Set("symbol", symbol)
if createDate != 7 && createDate != 90 {
return resp, fmt.Errorf("invalid createDate. 7 and 90 are the only supported values")
}
params.Set("create_date", strconv.FormatInt(createDate, 10))
func (h *HUOBI) FLiquidationOrders(ctx context.Context, symbol currency.Code, tradeType string, startTime, endTime int64, direction string, fromID int64) (LiquidationOrdersData, error) {
var resp LiquidationOrdersData
tType, ok := validTradeTypes[tradeType]
if !ok {
return resp, fmt.Errorf("invalid trade type")
}
params := url.Values{}
params.Set("symbol", symbol.String())
params.Set("trade_type", strconv.FormatInt(tType, 10))
if pageIndex != 0 {
params.Set("page_index", strconv.FormatInt(pageIndex, 10))
if startTime != 0 {
params.Set("start_time", strconv.FormatInt(startTime, 10))
}
if pageSize != 0 {
params.Set("page_size", strconv.FormatInt(pageIndex, 10))
if endTime != 0 {
params.Set("end_time", strconv.FormatInt(startTime, 10))
}
if direction != "" {
params.Set("direct", direction)
}
if fromID != 0 {
params.Set("from_id", strconv.FormatInt(fromID, 10))
}
path := common.EncodeURLValues(fLiquidationOrders, params)
return resp, h.SendHTTPRequest(ctx, exchange.RestFutures, path, &resp)

View File

@@ -264,8 +264,7 @@ func TestFQueryTopPositionsRatio(t *testing.T) {
func TestFLiquidationOrders(t *testing.T) {
t.Parallel()
_, err := h.FLiquidationOrders(context.Background(), "BTC", "filled", 0, 0, 7)
if err != nil {
if _, err := h.FLiquidationOrders(context.Background(), currency.BTC, "filled", 0, 0, "", 0); err != nil {
t.Error(err)
}
}
@@ -1002,8 +1001,8 @@ func TestGetLiquidationOrders(t *testing.T) {
if err != nil {
t.Error(err)
}
_, err = h.GetLiquidationOrders(context.Background(), cp, "closed", 0, 0, 7)
if err != nil {
if _, err = h.GetLiquidationOrders(context.Background(), cp, "closed", 0, 0, "", 0); err != nil {
t.Error(err)
}
}

View File

@@ -270,6 +270,8 @@ func durationToWord(in Interval) string {
return "oneday"
case ThreeDay:
return "threeday"
case FiveDay:
return "fiveday"
case FifteenDay:
return "fifteenday"
case OneWeek:

View File

@@ -214,6 +214,10 @@ func TestDurationToWord(t *testing.T) {
"ThreeDay",
ThreeDay,
},
{
"FiveDay",
FiveDay,
},
{
"FifteenDay",
FifteenDay,
@@ -337,6 +341,11 @@ func TestTotalCandlesPerInterval(t *testing.T) {
ThreeDay,
121,
},
{
"FiveDay",
FiveDay,
73,
},
{
"FifteenDay",
FifteenDay,

View File

@@ -28,6 +28,7 @@ const (
OneDay = 24 * OneHour
TwoDay = 2 * OneDay
ThreeDay = 3 * OneDay
FiveDay = 5 * OneDay
SevenDay = 7 * OneDay
FifteenDay = 15 * OneDay
OneWeek = 7 * OneDay

View File

@@ -836,7 +836,7 @@ func (ok *Okx) PlaceTWAPOrder(ctx context.Context, arg *AlgoOrderParams) (*AlgoO
if arg.PriceLimit <= 0 {
return nil, errInvalidPriceLimit
}
if ok.GetIntervalEnum(arg.TimeInterval) == "" {
if ok.GetIntervalEnum(arg.TimeInterval, true) == "" {
return nil, errMissingIntervalValue
}
return ok.PlaceAlgoOrder(ctx, arg)
@@ -3075,7 +3075,7 @@ func (ok *Okx) GetOrderBookDepth(ctx context.Context, instrumentID string, depth
}
// GetIntervalEnum allowed interval params by Okx Exchange
func (ok *Okx) GetIntervalEnum(interval kline.Interval) string {
func (ok *Okx) GetIntervalEnum(interval kline.Interval, appendUTC bool) string {
switch interval {
case kline.OneMin:
return "1m"
@@ -3093,31 +3093,41 @@ func (ok *Okx) GetIntervalEnum(interval kline.Interval) string {
return "2H"
case kline.FourHour:
return "4H"
case kline.SixHour: // NOTE: Cases here and below force UTC return instead of hong Kong time.
return "6Hutc"
case kline.EightHour:
return "8Hutc"
case kline.TwelveHour:
return "12Hutc"
case kline.OneDay:
return "1Dutc"
case kline.TwoDay:
return "2Dutc"
case kline.ThreeDay:
return "3Dutc"
case kline.OneWeek:
return "1Wutc"
case kline.OneMonth:
return "1Mutc"
case kline.ThreeMonth:
return "3Mutc"
case kline.SixMonth:
return "6Mutc"
case kline.OneYear:
return "1Yutc"
default:
return ""
}
duration := ""
switch interval {
case kline.SixHour: // NOTE: Cases here and below can either be local Hong Kong time or UTC time.
duration = "6H"
case kline.TwelveHour:
duration = "12H"
case kline.OneDay:
duration = "1D"
case kline.TwoDay:
duration = "2D"
case kline.ThreeDay:
duration = "3D"
case kline.FiveDay:
duration = "5D"
case kline.OneWeek:
duration = "1W"
case kline.OneMonth:
duration = "1M"
case kline.ThreeMonth:
duration = "3M"
case kline.SixMonth:
duration = "6M"
case kline.OneYear:
duration = "1Y"
default:
return duration
}
if appendUTC {
duration += "utc"
}
return duration
}
// GetCandlesticks Retrieve the candlestick charts. This endpoint can retrieve the latest 1,440 data entries. Charts are returned in groups based on the requested bar.
@@ -3160,7 +3170,7 @@ func (ok *Okx) GetCandlestickData(ctx context.Context, instrumentID string, inte
if !after.IsZero() {
params.Set("after", strconv.FormatInt(after.UnixMilli(), 10))
}
bar := ok.GetIntervalEnum(interval)
bar := ok.GetIntervalEnum(interval, true)
if bar != "" {
params.Set("bar", bar)
}
@@ -3719,7 +3729,7 @@ func (ok *Okx) GetTakerVolume(ctx context.Context, currency, instrumentType stri
return nil, errInvalidInstrumentType
}
params.Set("instType", strings.ToUpper(instrumentType))
interval := ok.GetIntervalEnum(period)
interval := ok.GetIntervalEnum(period, false)
if interval != "" {
params.Set("period", interval)
}
@@ -3773,7 +3783,7 @@ func (ok *Okx) GetMarginLendingRatio(ctx context.Context, currency string, begin
if !end.IsZero() {
params.Set("end", strconv.FormatInt(begin.UnixMilli(), 10))
}
interval := ok.GetIntervalEnum(period)
interval := ok.GetIntervalEnum(period, false)
if interval != "" {
params.Set("period", interval)
}
@@ -3813,7 +3823,7 @@ func (ok *Okx) GetLongShortRatio(ctx context.Context, currency string, begin, en
if !end.IsZero() {
params.Set("end", strconv.FormatInt(begin.UnixMilli(), 10))
}
interval := ok.GetIntervalEnum(period)
interval := ok.GetIntervalEnum(period, false)
if interval != "" {
params.Set("period", interval)
}
@@ -3857,7 +3867,7 @@ func (ok *Okx) GetContractsOpenInterestAndVolume(
if !end.IsZero() {
params.Set("end", strconv.FormatInt(begin.UnixMilli(), 10))
}
interval := ok.GetIntervalEnum(period)
interval := ok.GetIntervalEnum(period, false)
if interval != "" {
params.Set("period", interval)
}
@@ -3901,7 +3911,7 @@ func (ok *Okx) GetOptionsOpenInterestAndVolume(ctx context.Context, currency str
if currency != "" {
params.Set("ccy", currency)
}
interval := ok.GetIntervalEnum(period)
interval := ok.GetIntervalEnum(period, false)
if interval != "" {
params.Set("period", interval)
}
@@ -3945,7 +3955,7 @@ func (ok *Okx) GetPutCallRatio(ctx context.Context, currency string,
if currency != "" {
params.Set("ccy", currency)
}
interval := ok.GetIntervalEnum(period)
interval := ok.GetIntervalEnum(period, false)
if interval != "" {
params.Set("period", interval)
}
@@ -3984,7 +3994,7 @@ func (ok *Okx) GetOpenInterestAndVolumeExpiry(ctx context.Context, currency stri
if currency != "" {
params.Set("ccy", currency)
}
interval := ok.GetIntervalEnum(period)
interval := ok.GetIntervalEnum(period, false)
if interval != "" {
params.Set("period", interval)
}
@@ -4068,7 +4078,7 @@ func (ok *Okx) GetOpenInterestAndVolumeStrike(ctx context.Context, currency stri
if currency != "" {
params.Set("ccy", currency)
}
interval := ok.GetIntervalEnum(period)
interval := ok.GetIntervalEnum(period, false)
if interval != "" {
params.Set("period", interval)
}
@@ -4128,7 +4138,7 @@ func (ok *Okx) GetTakerFlow(ctx context.Context, currency string, period kline.I
if currency != "" {
params.Set("ccy", currency)
}
interval := ok.GetIntervalEnum(period)
interval := ok.GetIntervalEnum(period, false)
if interval != "" {
params.Set("period", interval)
}

View File

@@ -370,7 +370,7 @@ func TestGetSupportCoins(t *testing.T) {
func TestGetTakerVolume(t *testing.T) {
t.Parallel()
if _, err := ok.GetTakerVolume(context.Background(), "BTC", "SPOT", time.Time{}, time.Time{}, kline.FiveMin); err != nil {
if _, err := ok.GetTakerVolume(context.Background(), "BTC", "SPOT", time.Time{}, time.Time{}, kline.OneDay); err != nil {
t.Error("Okx GetTakerVolume() error", err)
}
}
@@ -383,7 +383,7 @@ func TestGetMarginLendingRatio(t *testing.T) {
func TestGetLongShortRatio(t *testing.T) {
t.Parallel()
if _, err := ok.GetLongShortRatio(context.Background(), "BTC", time.Time{}, time.Time{}, kline.FiveMin); err != nil {
if _, err := ok.GetLongShortRatio(context.Background(), "BTC", time.Time{}, time.Time{}, kline.OneDay); err != nil {
t.Error("Okx GetLongShortRatio() error", err)
}
}
@@ -3193,3 +3193,30 @@ func TestGuessAssetTypeFromInstrumentID(t *testing.T) {
t.Error("unexpected result")
}
}
func TestGetIntervalEnum(t *testing.T) {
t.Parallel()
tests := []struct {
Description string
Interval kline.Interval
Expected string
AppendUTC bool
}{
{Description: "4hr with UTC", Interval: kline.FourHour, Expected: "4H", AppendUTC: true},
{Description: "6H without UTC", Interval: kline.SixHour, Expected: "6H"},
{Description: "6H with UTC", Interval: kline.SixHour, Expected: "6Hutc", AppendUTC: true},
{Description: "Unsupported interval with UTC", Expected: "", AppendUTC: true},
}
for x := range tests {
tt := tests[x]
t.Run(tt.Description, func(t *testing.T) {
t.Parallel()
if r := ok.GetIntervalEnum(tt.Interval, tt.AppendUTC); r != tt.Expected {
t.Errorf("%s: received: %s but expected: %s", tt.Description, r, tt.Expected)
}
})
}
}

View File

@@ -142,7 +142,9 @@ func (ok *Okx) SetDefaults() {
kline.SixHour,
kline.TwelveHour,
kline.OneDay,
kline.TwoDay,
kline.ThreeDay,
kline.FiveDay,
kline.OneWeek,
kline.OneMonth,
kline.ThreeMonth,

View File

@@ -87,7 +87,7 @@ type CollateralByPosition struct {
// CollateralByCurrency individual collateral contribution
// along with what the potentially scaled collateral
// currency it is represented as
// eg in FTX ScaledCurrency is USD
// eg in Bybit ScaledCurrency is USDC
type CollateralByCurrency struct {
Currency currency.Code
SkipContribution bool
@@ -222,7 +222,7 @@ type TotalCollateralCalculator struct {
// CollateralCalculator is used to determine
// the size of collateral holdings for an exchange
// eg on FTX, the collateral is scaled depending on what
// eg on Bybit, the collateral is scaled depending on what
// currency it is
type CollateralCalculator struct {
CalculateOffline bool

View File

@@ -28,7 +28,6 @@ var Exchanges = []string{
"coinbasepro",
"coinut",
"exmo",
"ftx",
"gateio",
"gemini",
"hitbtc",

View File

@@ -74,7 +74,6 @@ _b in this context is an `IBotExchange` implemented struct_
| CoinbasePro | Yes | Yes | No|
| COINUT | Yes | Yes | No |
| Exmo | Yes | NA | No |
| FTX | Yes | Yes | Yes |
| GateIO | Yes | Yes | No |
| Gemini | Yes | Yes | Yes |
| HitBTC | Yes | Yes | Yes |

View File

@@ -20,7 +20,7 @@ load := func() {
// account details while utilizing the configured config.json apikeys.
// ctx = global.set_sub_account(ctx, "sub_account_str")
info := exch.accountinfo(ctx, "ftx", "spot")
info := exch.accountinfo(ctx, "binance", "spot")
if is_error(info) {
// handle error
}

View File

@@ -16,7 +16,7 @@ load := func() {
// NOTE: Get account info is cached and updated by another worker thread
// therefore calling this script multiple times, if data has already been
// fetched request verbosity will be limited.
info := exch.accountinfo(ctx, "ftx", "spot")
info := exch.accountinfo(ctx, "binance", "spot")
if is_error(info) {
// handle error
}

View File

@@ -882,7 +882,7 @@
}
},
"api": {
"authenticatedSupport": true,
"authenticatedSupport": false,
"authenticatedWebsocketApiSupport": false,
"credentials": {
"key": "Key",
@@ -905,13 +905,13 @@
"supports": {
"restAPI": true,
"restCapabilities": {
"autoPairUpdates": false
"autoPairUpdates": true
},
"websocketAPI": true,
"websocketCapabilities": {}
},
"enabled": {
"autoPairUpdates": false,
"autoPairUpdates": true,
"websocketAPI": true,
"saveTradeData": false,
"tradeFeed": false,
@@ -2452,63 +2452,6 @@
"supportedCurrencies": ""
}
]
},
{
"name": "FTX",
"enabled": true,
"verbose": false,
"httpTimeout": 0,
"websocketResponseCheckTimeout": 0,
"websocketResponseMaxLimit": 0,
"websocketTrafficTimeout": 0,
"websocketOrderbookBufferLimit": 0,
"baseCurrencies": "USD",
"currencyPairs": {
"assetTypes": [
"spot",
"futures"
],
"pairs": {
"futures": {
"enabled": "DOGE-PERP",
"available": "ADA-PERP,ADA-0626,ALGO-PERP,ALGO-0626,ALT-PERP,ALT-0626,ATOM-PERP,ATOM-0626,BCH-PERP,BCH-0626,BNB-PERP,BNB-0626,BSV-PERP,BSV-0626,BTC-PERP,BTC-MOVE,BTC-MOVE,BTC-MOVE,BTC-MOVE,BTC-MOVE,BTC-MOVE,BTC-0626,BTC-MOVE,BTC-0925,BTC-MOVE,BTC-MOVE,BTMX-PERP,BTMX-0626,DOGE-PERP,DOGE-0626,DRGN-PERP,DRGN-0626,EOS-PERP,EOS-0626,ETC-PERP,ETC-0626,ETH-PERP,ETH-0626,EXCH-PERP,EXCH-0626,HT-PERP,HT-0626,LEO-PERP,LEO-0626,LINK-PERP,LINK-0626,LTC-PERP,LTC-0626,MATIC-PERP,MATIC-0626,MID-PERP,MID-0626,OIL100-0525,OKB-PERP,OKB-0626,PAXG-PERP,PAXG-0626,BERNIE,BIDEN,BLOOMBERG,PETE,TRUMP,WARREN,PRIV-PERP,PRIV-0626,SHIT-PERP,SHIT-0626,TOMO-PERP,TOMO-0626,TRX-PERP,TRX-0626,TRYB-PERP,TRYB-0626,USDT-PERP,USDT-0626,XAUT-PERP,XAUT-0626,XRP-PERP,XRP-0626,XTZ-PERP,XTZ-0626",
"requestFormat": {
"uppercase": true,
"delimiter": "-"
},
"configFormat": {
"uppercase": true,
"delimiter": "-"
}
},
"spot": {
"enabled": "BTC/USD",
"available": "BCH/USD,BCH/USDT,BNB/USD,BNB/USDT,BTC/USD,BTC/USDT,BTMX/USD,ETH/USD,ETH/USDT,FTT/BTC,FTT/USD,FTT/USDT,LINK/USD,LINK/USDT,LTC/USD,LTC/USDT,PAXG/USD,PAXG/USDT,TRX/USD,TRX/USDT,TRYB/USD,USDT/USD,XAUT/USD,XAUT/USDT,ADABEAR/USD,ADABULL/USD,ADAHALF/USD,ADAHALF/USDT,ADAHEDGE/USD,ALGOBEAR/USD,ALGOBULL/USD,ALGOHALF/USD,ALGOHALF/USDT,ALGOHEDGE/USD,ALTBEAR/USD,ALTBULL/USD,ALTHALF/USD,ALTHALF/USDT,ALTHEDGE/USD,ATOMBEAR/USD,ATOMBULL/USD,ATOMHALF/USD,ATOMHALF/USDT,ATOMHEDGE/USD,BCHBEAR/USD,BCHBEAR/USDT,BCHBULL/USD,BCHBULL/USDT,BCHHALF/USD,BCHHALF/USDT,BCHHEDGE/USD,BEAR/USD,BEAR/USDT,BEARSHIT/USD,BNBBEAR/USD,BNBBEAR/USDT,BNBBULL/USD,BNBBULL/USDT,BNBHALF/USD,BNBHALF/USDT,BNBHEDGE/USD,BSVBEAR/USD,BSVBEAR/USDT,BSVBULL/USD,BSVBULL/USDT,BSVHALF/USD,BSVHALF/USDT,BSVHEDGE/USD,BTMXBEAR/USD,BTMXBEAR/USDT,BTMXBULL/USD,BTMXBULL/USDT,BTMXHALF/USD,BTMXHALF/USDT,BTMXHEDGE/USD,BULL/USD,BULL/USDT,BULLSHIT/USD,BVOL/USD,BVOL/USDT,DOGEBEAR/USD,DOGEBULL/USD,DOGEHALF/USD,DOGEHALF/USDT,DOGEHEDGE/USD,DRGNBEAR/USD,DRGNBULL/USD,DRGNHALF/USD,DRGNHALF/USDT,DRGNHEDGE/USD,EOSBEAR/USD,EOSBEAR/USDT,EOSBULL/USD,EOSBULL/USDT,EOSHALF/USD,EOSHALF/USDT,EOSHEDGE/USD,ETCBEAR/USD,ETCBULL/USD,ETCHALF/USD,ETCHALF/USDT,ETCHEDGE/USD,ETHBEAR/USD,ETHBEAR/USDT,ETHBULL/USD,ETHBULL/USDT,ETHHALF/USD,ETHHALF/USDT,ETHHEDGE/USD,EXCHBEAR/USD,EXCHBULL/USD,EXCHHALF/USD,EXCHHALF/USDT,EXCHHEDGE/USD,HALF/USD,HALF/USDT,HALFSHIT/USD,HALFSHIT/USDT,HEDGE/USD,HEDGESHIT/USD,HTBEAR/USD,HTBULL/USD,HTHALF/USD,HTHALF/USDT,HTHEDGE/USD,IBVOL/USD,IBVOL/USDT,LEOBEAR/USD,LEOBULL/USD,LEOHALF/USD,LEOHALF/USDT,LEOHEDGE/USD,LINKBEAR/USD,LINKBEAR/USDT,LINKBULL/USD,LINKBULL/USDT,LINKHALF/USD,LINKHALF/USDT,LINKHEDGE/USD,LTCBEAR/USD,LTCBEAR/USDT,LTCBULL/USD,LTCBULL/USDT,LTCHALF/USD,LTCHALF/USDT,LTCHEDGE/USD,MATICBEAR/USD,MATICBULL/USD,MATICHALF/USD,MATICHALF/USDT,MATICHEDGE/USD,MIDBEAR/USD,MIDBULL/USD,MIDHALF/USD,MIDHALF/USDT,MIDHEDGE/USD,OKBBEAR/USD,OKBBULL/USD,OKBHALF/USD,OKBHALF/USDT,OKBHEDGE/USD,PAXGBEAR/USD,PAXGBULL/USD,PAXGHALF/USD,PAXGHALF/USDT,PAXGHEDGE/USD,PRIVBEAR/USD,PRIVBULL/USD,PRIVHALF/USD,PRIVHALF/USDT,PRIVHEDGE/USD,TOMOBEAR/USD,TOMOBULL/USD,TOMOHALF/USD,TOMOHALF/USDT,TOMOHEDGE/USD,TRXBEAR/USD,TRXBULL/USD,TRXHALF/USD,TRXHALF/USDT,TRXHEDGE/USD,TRYBBEAR/USD,TRYBBULL/USD,TRYBHALF/USD,TRYBHALF/USDT,TRYBHEDGE/USD,USDTBEAR/USD,USDTBULL/USD,USDTHALF/USD,USDTHALF/USDT,USDTHEDGE/USD,XAUTBEAR/USD,XAUTBULL/USD,XAUTHALF/USD,XAUTHALF/USDT,XAUTHEDGE/USD,XRPBEAR/USD,XRPBEAR/USDT,XRPBULL/USD,XRPBULL/USDT,XRPHALF/USD,XRPHALF/USDT,XRPHEDGE/USD,XTZBEAR/USD,XTZBEAR/USDT,XTZBULL/USD,XTZBULL/USDT,XTZHALF/USD,XTZHALF/USDT,XTZHEDGE/USD",
"requestFormat": {
"uppercase": true,
"delimiter": "/"
},
"configFormat": {
"uppercase": true,
"delimiter": "/"
}
}
}
},
"api": {
"authenticatedSupport": false,
"authenticatedWebsocketApiSupport": false,
"endpoints": {
"url": "",
"urlSecondary": "",
"websocketURL": ""
},
"credentials": {
"key": "Key",
"secret": "Secret"
}
},
"features": null
}
],
"bankAccounts": [

View File

@@ -11,7 +11,6 @@ btse,
coinbasepro,
coinut,
exmo,
ftx,
gateio,
gemini,
hitbtc,
1 binanceus
11 coinbasepro
12 coinut
13 exmo
ftx
14 gateio
15 gemini
16 hitbtc