mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
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:
@@ -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 |
|
||||
|
||||
@@ -489,7 +489,7 @@ func TestGenerateConfigForDCAAPICandles(t *testing.T) {
|
||||
},
|
||||
CurrencySettings: []CurrencySettings{
|
||||
{
|
||||
ExchangeName: mainExchange,
|
||||
ExchangeName: "bybit",
|
||||
Asset: asset.Spot,
|
||||
Base: mainCurrencyPair.Base,
|
||||
Quote: mainCurrencyPair.Quote,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"currency-settings": [
|
||||
{
|
||||
"exchange-name": "binance",
|
||||
"exchange-name": "bybit",
|
||||
"asset": "spot",
|
||||
"base": "BTC",
|
||||
"quote": "USDT",
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -98,7 +98,6 @@ exchange
|
||||
An example of this is:
|
||||
```
|
||||
binance,
|
||||
ftx,
|
||||
btc markets,
|
||||
```
|
||||
|
||||
|
||||
@@ -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` |
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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}}
|
||||
@@ -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 |
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -106,11 +106,6 @@
|
||||
"secret": "Secret",
|
||||
"otpSecret": "-"
|
||||
},
|
||||
"ftx": {
|
||||
"key": "Key",
|
||||
"secret": "Secret",
|
||||
"otpSecret": "-"
|
||||
},
|
||||
"gateio": {
|
||||
"key": "Key",
|
||||
"secret": "Secret",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 | |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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` |
|
||||
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
# GoCryptoTrader package Ftx
|
||||
|
||||
<img src="/common/gctlogo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/ftx)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](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***
|
||||
1828
exchanges/ftx/ftx.go
1828
exchanges/ftx/ftx.go
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
@@ -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
|
||||
}
|
||||
@@ -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: ¤cy.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
@@ -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"`
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,7 +28,6 @@ var Exchanges = []string{
|
||||
"coinbasepro",
|
||||
"coinut",
|
||||
"exmo",
|
||||
"ftx",
|
||||
"gateio",
|
||||
"gemini",
|
||||
"hitbtc",
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
63
testdata/configtest.json
vendored
63
testdata/configtest.json
vendored
@@ -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": [
|
||||
|
||||
1
testdata/exchangelist.csv
vendored
1
testdata/exchangelist.csv
vendored
@@ -11,7 +11,6 @@ btse,
|
||||
coinbasepro,
|
||||
coinut,
|
||||
exmo,
|
||||
ftx,
|
||||
gateio,
|
||||
gemini,
|
||||
hitbtc,
|
||||
|
||||
|
Reference in New Issue
Block a user