diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index d14f27ee..1efcd088 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -16,19 +16,26 @@ import ( // Alphapoint exchange func (a *Alphapoint) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = a.GetName() + response.Exchange = a.GetName() account, err := a.GetAccountInformation() if err != nil { return response, err } + + var currencies []exchange.AccountCurrencyInfo for i := 0; i < len(account.Currencies); i++ { var exchangeCurrency exchange.AccountCurrencyInfo exchangeCurrency.CurrencyName = account.Currencies[i].Name exchangeCurrency.TotalValue = float64(account.Currencies[i].Balance) exchangeCurrency.Hold = float64(account.Currencies[i].Hold) - response.Currencies = append(response.Currencies, exchangeCurrency) + currencies = append(currencies, exchangeCurrency) } + + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -164,7 +171,7 @@ func (a *Alphapoint) GetOrderInfo(orderID int64) (float64, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (a *Alphapoint) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { +func (a *Alphapoint) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { addreses, err := a.GetDepositAddresses() if err != nil { return "", err diff --git a/exchanges/anx/anx.go b/exchanges/anx/anx.go index 68d1f88d..4ce4744f 100644 --- a/exchanges/anx/anx.go +++ b/exchanges/anx/anx.go @@ -385,7 +385,6 @@ func (a *ANX) GetDepositAddressByCurrency(currency, name string, new bool) (stri } err := a.SendAuthenticatedHTTPRequest(path, request, &response) - if err != nil { return "", err } diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index 92b50fc0..5e050127 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -395,3 +395,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + if areTestAPIKeysSet() { + _, err := a.GetDepositAddress(symbol.BTC, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } + } else { + _, err := a.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } + } +} diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 22b759fe..089eabb9 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -199,8 +199,10 @@ func (a *ANX) GetAccountInfo() (exchange.AccountInfo, error) { }) } - info.ExchangeName = a.GetName() - info.Currencies = balance + info.Exchange = a.GetName() + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: balance, + }) return info, nil } @@ -305,8 +307,8 @@ func (a *ANX) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (a *ANX) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (a *ANX) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + return a.GetDepositAddressByCurrency(cryptocurrency.String(), "", false) } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 5e1502bc..8ed455fa 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -2,6 +2,7 @@ package binance import ( "bytes" + "encoding/json" "errors" "fmt" "net/url" @@ -603,17 +604,36 @@ func (b *Binance) SendAuthHTTPRequest(method, path string, params url.Values, re signature := params.Encode() hmacSigned := common.GetHMAC(common.HashSHA256, []byte(signature), []byte(b.APISecret)) hmacSignedStr := common.HexEncodeToString(hmacSigned) - params.Set("signature", hmacSignedStr) headers := make(map[string]string) headers["X-MBX-APIKEY"] = b.APIKey if b.Verbose { - log.Debugf("sent path: \n%s\n", path) + log.Debugf("sent path: %s", path) } - path = common.EncodeURLValues(path, params) - return b.SendPayload(method, path, headers, bytes.NewBufferString(""), result, true, b.Verbose) + path = common.EncodeURLValues(path, params) + path += fmt.Sprintf("&signature=%s", hmacSignedStr) + + interim := json.RawMessage{} + + errCap := struct { + Success bool `json:"success"` + Message string `json:"msg"` + }{} + + err := b.SendPayload(method, path, headers, bytes.NewBuffer(nil), &interim, true, b.Verbose) + if err != nil { + return err + } + + if err := common.JSONDecode(interim, &errCap); err == nil { + if !errCap.Success && errCap.Message != "" { + return errors.New(errCap.Message) + } + } + + return common.JSONDecode(interim, result) } // CheckLimit checks value against a variable list @@ -742,15 +762,19 @@ func (b *Binance) WithdrawCrypto(asset, address, addressTag, name, amount string } //GetDepositAddressForCurrency retrieves the wallet address for a given currency -func (b *Binance) GetDepositAddressForCurrency(currency string) error { +func (b *Binance) GetDepositAddressForCurrency(currency string) (string, error) { path := fmt.Sprintf("%s%s", b.APIUrl, depositAddress) - var resp interface{} + + resp := struct { + Address string `json:"address"` + Success bool `json:"success"` + AddressTag string `json:"addressTag"` + }{} + params := url.Values{} params.Set("asset", currency) + params.Set("status", "true") - if err := b.SendAuthHTTPRequest("GET", path, params, &resp); err != nil { - return err - } - - return nil + return resp.Address, + b.SendAuthHTTPRequest("GET", path, params, &resp) } diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index fa4c98b6..ea9295f4 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -4,10 +4,9 @@ import ( "testing" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/currency/symbol" - - "github.com/thrasher-/gocryptotrader/config" exchange "github.com/thrasher-/gocryptotrader/exchanges" ) @@ -425,7 +424,7 @@ func TestGetAccountInfo(t *testing.T) { _, err := b.GetAccountInfo() if err != nil { - t.Error("test failed - GetAccountInfo() error:", err) + t.Error("test failed - GetAccountInfo() error", err) } } @@ -491,3 +490,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + if areTestAPIKeysSet() { + _, err := b.GetDepositAddress(symbol.BTC, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } + } else { + _, err := b.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } + } +} diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 7b79faea..fdf28e02 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -157,8 +157,11 @@ func (b *Binance) GetAccountInfo() (exchange.AccountInfo, error) { }) } - info.ExchangeName = b.GetName() - info.Currencies = currencyBalance + info.Exchange = b.GetName() + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: currencyBalance, + }) + return info, nil } @@ -264,8 +267,8 @@ func (b *Binance) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (b *Binance) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (b *Binance) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + return b.GetDepositAddressForCurrency(cryptocurrency.String()) } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index 7cb26949..6065fa20 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -1087,3 +1087,31 @@ func (b *Bitfinex) ConvertSymbolToWithdrawalType(currency string) string { return common.StringToLower(currency) } } + +// ConvertSymbolToDepositMethod returns a converted currency deposit method +func (b *Bitfinex) ConvertSymbolToDepositMethod(currency string) (method string, err error) { + switch currency { + case symbol.BTC: + method = "bitcoin" + case symbol.LTC: + method = "litecoin" + case symbol.ETH: + method = "ethereum" + case symbol.ETC: + method = "ethereumc" + case symbol.USDT: + method = "tetheruso" + case symbol.ZEC: + method = "zcash" + case symbol.XMR: + method = "monero" + case symbol.BCH: + method = "bcash" + case symbol.MIOTA: + method = "iota" + default: + err = fmt.Errorf("currency %s not supported in method list", + currency) + } + return +} diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index e4820716..454eb07a 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -241,14 +241,14 @@ func TestGetSymbolsDetails(t *testing.T) { } func TestGetAccountInfo(t *testing.T) { - if b.APIKey == "" || b.APISecret == "" { + if !areTestAPIKeysSet() { t.SkipNow() } t.Parallel() _, err := b.GetAccountInfo() - if err == nil { - t.Error("Test Failed - GetAccountInfo error") + if err != nil { + t.Error("Test Failed - GetAccountInfo error", err) } } @@ -903,3 +903,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Withdraw failed to be placed: %v", err) } } + +func TestGetDepositAddress(t *testing.T) { + if areTestAPIKeysSet() { + _, err := b.GetDepositAddress(symbol.BTC, "deposit") + if err != nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } + } else { + _, err := b.GetDepositAddress(symbol.BTC, "deposit") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } + } +} diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index b535913e..78442b45 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -118,46 +118,32 @@ func (b *Bitfinex) UpdateOrderbook(p pair.CurrencyPair, assetType string) (order // Bitfinex exchange func (b *Bitfinex) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = b.GetName() + response.Exchange = b.GetName() accountBalance, err := b.GetAccountBalance() if err != nil { return response, err } - if !b.Enabled { - return response, nil + + var Accounts = []exchange.Account{ + {ID: "deposit"}, + {ID: "exchange"}, + {ID: "trading"}, } - type bfxCoins struct { - OnHold float64 - Available float64 - } - - accounts := make(map[string]bfxCoins) - - for i := range accountBalance { - onHold := accountBalance[i].Amount - accountBalance[i].Available - coins := bfxCoins{ - OnHold: onHold, - Available: accountBalance[i].Available, - } - result, ok := accounts[accountBalance[i].Currency] - if !ok { - accounts[accountBalance[i].Currency] = coins - } else { - result.Available += accountBalance[i].Available - result.OnHold += onHold - accounts[accountBalance[i].Currency] = result + for _, bal := range accountBalance { + for i := range Accounts { + if Accounts[i].ID == bal.Type { + Accounts[i].Currencies = append(Accounts[i].Currencies, + exchange.AccountCurrencyInfo{ + CurrencyName: bal.Currency, + TotalValue: bal.Amount, + Hold: bal.Amount - bal.Available, + }) + } } } - for x, y := range accounts { - var exchangeCurrency exchange.AccountCurrencyInfo - exchangeCurrency.CurrencyName = common.StringToUpper(x) - exchangeCurrency.TotalValue = y.Available + y.OnHold - exchangeCurrency.Hold = y.OnHold - response.Currencies = append(response.Currencies, exchangeCurrency) - } - + response.Accounts = Accounts return response, nil } @@ -229,8 +215,18 @@ func (b *Bitfinex) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (b *Bitfinex) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (b *Bitfinex) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + method, err := b.ConvertSymbolToDepositMethod(cryptocurrency.String()) + if err != nil { + return "", err + } + + resp, err := b.NewDeposit(method, accountID, 0) + if err != nil { + return "", err + } + + return resp.Address, nil } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is submitted diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index 1ccd4a93..bfab32b7 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -124,7 +124,7 @@ func (b *Bitflyer) UpdateOrderbook(p pair.CurrencyPair, assetType string) (order // Bitflyer exchange func (b *Bitflyer) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = b.GetName() + response.Exchange = b.GetName() // accountBalance, err := b.GetAccountBalance() // if err != nil { // return response, err @@ -184,7 +184,7 @@ func (b *Bitflyer) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (b *Bitflyer) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { +func (b *Bitflyer) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { return "", common.ErrNotYetImplemented } diff --git a/exchanges/bithumb/bithumb.go b/exchanges/bithumb/bithumb.go index 0c3c85e1..83973eaf 100644 --- a/exchanges/bithumb/bithumb.go +++ b/exchanges/bithumb/bithumb.go @@ -321,8 +321,18 @@ func (b *Bithumb) GetWalletAddress(currency string) (WalletAddressRes, error) { params := url.Values{} params.Set("currency", common.StringToUpper(currency)) - return response, - b.SendAuthenticatedHTTPRequest(privateWalletAdd, params, &response) + err := b.SendAuthenticatedHTTPRequest(privateWalletAdd, params, &response) + if err != nil { + return response, err + } + + if response.Data.WalletAddress == "" { + return response, + fmt.Errorf("deposit address needs to be created via the Bithumb website before retreival for currency %s", + currency) + } + + return response, nil } // GetLastTransaction returns customer last transaction diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go index 8d52f3d6..e95684cc 100644 --- a/exchanges/bithumb/bithumb_test.go +++ b/exchanges/bithumb/bithumb_test.go @@ -7,7 +7,7 @@ import ( "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/currency/symbol" - "github.com/thrasher-/gocryptotrader/exchanges" + exchange "github.com/thrasher-/gocryptotrader/exchanges" ) // Please supply your own keys here for due diligence testing @@ -482,3 +482,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + if testAPIKey != "" && testAPISecret != "" { + _, err := b.GetDepositAddress(symbol.BTC, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } + } else { + _, err := b.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } + } +} diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index b903ce03..f4237dcb 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -146,8 +146,11 @@ func (b *Bithumb) GetAccountInfo() (exchange.AccountInfo, error) { }) } - info.Currencies = exchangeBalances - info.ExchangeName = b.GetName() + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: exchangeBalances, + }) + + info.Exchange = b.GetName() return info, nil } @@ -248,8 +251,13 @@ func (b *Bithumb) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (b *Bithumb) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (b *Bithumb) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + addr, err := b.GetWalletAddress(cryptocurrency.String()) + if err != nil { + return "", err + } + + return addr.Data.WalletAddress, nil } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/bitmex/bitmex.go b/exchanges/bitmex/bitmex.go index de155e74..35097ce8 100644 --- a/exchanges/bitmex/bitmex.go +++ b/exchanges/bitmex/bitmex.go @@ -5,11 +5,13 @@ import ( "encoding/json" "fmt" "strconv" + "strings" "time" "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/currency/symbol" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -700,9 +702,15 @@ func (b *Bitmex) ConfirmWithdrawal(token string) (TransactionInfo, error) { func (b *Bitmex) GetCryptoDepositAddress(currency string) (string, error) { var address string + if !strings.EqualFold(currency, symbol.XBT) { + return "", + fmt.Errorf("cryptocurrency %s deposits are not supported by exchange only bitcoin", + currency) + } + return address, b.SendAuthenticatedHTTPRequest("GET", bitmexEndpointUserDepositAddress, - UserCurrencyParams{Currency: currency}, + UserCurrencyParams{Currency: "XBt"}, &address) } diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index bdff988b..3b06c967 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -6,11 +6,10 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/currency/symbol" - "github.com/thrasher-/gocryptotrader/exchanges" - - "github.com/thrasher-/gocryptotrader/config" + exchange "github.com/thrasher-/gocryptotrader/exchanges" ) // Please supply your own keys here for due diligence testing @@ -638,3 +637,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + if areTestAPIKeysSet() { + _, err := b.GetDepositAddress(symbol.BTC, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } + } else { + _, err := b.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } + } +} diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 44932eb5..fe74be31 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -142,8 +142,11 @@ func (b *Bitmex) GetAccountInfo() (exchange.AccountInfo, error) { }) } - info.ExchangeName = b.GetName() - info.Currencies = balances + info.Exchange = b.GetName() + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: balances, + }) + return info, nil } @@ -250,8 +253,8 @@ func (b *Bitmex) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (b *Bitmex) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (b *Bitmex) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + return b.GetCryptoDepositAddress(cryptocurrency.String()) } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/bitmex/swagger/swagger.json b/exchanges/bitmex/swagger/swagger.json deleted file mode 100644 index e1232a61..00000000 --- a/exchanges/bitmex/swagger/swagger.json +++ /dev/null @@ -1,6125 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "title": "BitMEX Testnet API", - "description": "## REST API for the BitMEX Trading Platform\n\n[View Changelog](/app/apiChangelog)\n\n----\n\n#### Getting Started\n\nBase URI: [https://testnet.bitmex.com/api/v1](/api/v1)\n\n##### Fetching Data\n\nAll REST endpoints are documented below. You can try out any query right from this interface.\n\nMost table queries accept `count`, `start`, and `reverse` params. Set `reverse=true` to get rows newest-first.\n\nAdditional documentation regarding filters, timestamps, and authentication\nis available in [the main API documentation](/app/restAPI).\n\n*All* table data is available via the [Websocket](/app/wsAPI). We highly recommend using the socket if you want\nto have the quickest possible data without being subject to ratelimits.\n\n##### Return Types\n\nBy default, all data is returned as JSON. Send `?_format=csv` to get CSV data or `?_format=xml` to get XML data.\n\n##### Trade Data Queries\n\n*This is only a small subset of what is available, to get you started.*\n\nFill in the parameters and click the `Try it out!` button to try any of these queries.\n\n* [Pricing Data](#!/Quote/Quote_get)\n\n* [Trade Data](#!/Trade/Trade_get)\n\n* [OrderBook Data](#!/OrderBook/OrderBook_getL2)\n\n* [Settlement Data](#!/Settlement/Settlement_get)\n\n* [Exchange Statistics](#!/Stats/Stats_history)\n\nEvery function of the BitMEX.com platform is exposed here and documented. Many more functions are available.\n\n##### Swagger Specification\n\n[⇩ Download Swagger JSON](swagger.json)\n\n----\n\n## All API Endpoints\n\nClick to expand a section.\n", - "termsOfService": "https://testnet.bitmex.com/app/terms", - "contact": { - "email": "support@bitmex.com" - }, - "version": "1.2.0" - }, - "basePath": "/api/v1", - "paths": { - "/announcement": { - "get": { - "tags": ["Announcement"], - "summary": "Get site announcements.", - "operationId": "Announcement.get", - "parameters": [{ - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.", - "required": false, - "type": "string", - "format": "JSON" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Announcement" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/announcement/urgent": { - "get": { - "tags": ["Announcement"], - "summary": "Get urgent (banner) announcements.", - "operationId": "Announcement.getUrgent", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Announcement" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/apiKey": { - "post": { - "tags": ["APIKey"], - "summary": "Create a new API Key.", - "description": "API Keys can only be created via the frontend.", - "operationId": "APIKey.new", - "parameters": [{ - "name": "name", - "in": "formData", - "description": "Key name. This name is for reference only.", - "required": false, - "type": "string" - }, { - "name": "cidr", - "in": "formData", - "description": "CIDR block to restrict this key to. To restrict to a single address, append \"/32\", e.g. 207.39.29.22/32. Leave blank or set to 0.0.0.0/0 to allow all IPs. Only one block may be set. More on CIDR blocks", - "required": false, - "type": "string" - }, { - "name": "permissions", - "in": "formData", - "description": "Key Permissions. All keys can read margin and position data. Additional permissions must be added. Available: [\"order\", \"orderCancel\", \"withdraw\"].", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "enabled", - "in": "formData", - "description": "Set to true to enable this key on creation. Otherwise, it must be explicitly enabled via /apiKey/enable.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "token", - "in": "formData", - "description": "OTP Token (YubiKey, Google Authenticator)", - "required": false, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/APIKey" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - }, - "get": { - "tags": ["APIKey"], - "summary": "Get your API Keys.", - "operationId": "APIKey.get", - "parameters": [{ - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/APIKey" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - }, - "delete": { - "tags": ["APIKey"], - "summary": "Remove an API Key.", - "operationId": "APIKey.remove", - "parameters": [{ - "name": "apiKeyID", - "in": "formData", - "description": "API Key ID (public component).", - "required": true, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/apiKey/disable": { - "post": { - "tags": ["APIKey"], - "summary": "Disable an API Key.", - "operationId": "APIKey.disable", - "parameters": [{ - "name": "apiKeyID", - "in": "formData", - "description": "API Key ID (public component).", - "required": true, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/APIKey" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/apiKey/enable": { - "post": { - "tags": ["APIKey"], - "summary": "Enable an API Key.", - "operationId": "APIKey.enable", - "parameters": [{ - "name": "apiKeyID", - "in": "formData", - "description": "API Key ID (public component).", - "required": true, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/APIKey" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/chat": { - "get": { - "tags": ["Chat"], - "summary": "Get chat messages.", - "operationId": "Chat.get", - "parameters": [{ - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting ID for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": true, - "type": "boolean" - }, { - "name": "channelID", - "in": "query", - "description": "Channel id. GET /chat/channels for ids. Leave blank for all.", - "required": false, - "type": "number", - "format": "double" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Chat" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - }, - "post": { - "tags": ["Chat"], - "summary": "Send a chat message.", - "operationId": "Chat.new", - "parameters": [{ - "name": "message", - "in": "formData", - "required": true, - "type": "string" - }, { - "name": "channelID", - "in": "formData", - "description": "Channel to post to. Default 1 (English).", - "required": false, - "default": 1, - "type": "number", - "format": "double" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/Chat" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/chat/channels": { - "get": { - "tags": ["Chat"], - "summary": "Get available channels.", - "operationId": "Chat.getChannels", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/ChatChannel" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/chat/connected": { - "get": { - "tags": ["Chat"], - "summary": "Get connected users.", - "description": "Returns an array with browser users in the first position and API users (bots) in the second position.", - "operationId": "Chat.getConnected", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/ConnectedUsers" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/execution": { - "get": { - "tags": ["Execution"], - "summary": "Get all raw executions for your account.", - "description": "This returns all raw transactions, which includes order opening and cancelation, and order status\nchanges. It can be quite noisy. More focused information is available at `/execution/tradeHistory`.\n\nYou may also use the `filter` param to target your query. Specify an array as a filter value, such as\n`{\"execType\": [\"Settlement\", \"Trade\"]}` to filter on multiple values.\n\nSee [the FIX Spec](http://www.onixs.biz/fix-dictionary/5.0.SP2/msgType_8_8.html) for explanations of these fields.\n", - "operationId": "Execution.get", - "parameters": [{ - "name": "symbol", - "in": "query", - "description": "Instrument symbol. Send a bare series (e.g. XBU) to get data for the nearest expiring contract in that series.\n\nYou can also send a timeframe, e.g. `XBU:monthly`. Timeframes are `daily`, `weekly`, `monthly`, `quarterly`, and `biquarterly`.", - "required": false, - "type": "string" - }, { - "name": "filter", - "in": "query", - "description": "Generic table filter. Send JSON key/value pairs, such as `{\"key\": \"value\"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://testnet.bitmex.com/app/restAPI#Timestamp-Filters) for more details.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.\n\nNote that this method will always return item keys, even when not specified, so you may receive more columns that you expect.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting point for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "startTime", - "in": "query", - "description": "Starting date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }, { - "name": "endTime", - "in": "query", - "description": "Ending date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Execution" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/execution/tradeHistory": { - "get": { - "tags": ["Execution"], - "summary": "Get all balance-affecting executions. This includes each trade, insurance charge, and settlement.", - "operationId": "Execution.getTradeHistory", - "parameters": [{ - "name": "symbol", - "in": "query", - "description": "Instrument symbol. Send a bare series (e.g. XBU) to get data for the nearest expiring contract in that series.\n\nYou can also send a timeframe, e.g. `XBU:monthly`. Timeframes are `daily`, `weekly`, `monthly`, `quarterly`, and `biquarterly`.", - "required": false, - "type": "string" - }, { - "name": "filter", - "in": "query", - "description": "Generic table filter. Send JSON key/value pairs, such as `{\"key\": \"value\"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://testnet.bitmex.com/app/restAPI#Timestamp-Filters) for more details.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.\n\nNote that this method will always return item keys, even when not specified, so you may receive more columns that you expect.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting point for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "startTime", - "in": "query", - "description": "Starting date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }, { - "name": "endTime", - "in": "query", - "description": "Ending date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Execution" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/funding": { - "get": { - "tags": ["Funding"], - "summary": "Get funding history.", - "operationId": "Funding.get", - "parameters": [{ - "name": "symbol", - "in": "query", - "description": "Instrument symbol. Send a bare series (e.g. XBU) to get data for the nearest expiring contract in that series.\n\nYou can also send a timeframe, e.g. `XBU:monthly`. Timeframes are `daily`, `weekly`, `monthly`, `quarterly`, and `biquarterly`.", - "required": false, - "type": "string" - }, { - "name": "filter", - "in": "query", - "description": "Generic table filter. Send JSON key/value pairs, such as `{\"key\": \"value\"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://testnet.bitmex.com/app/restAPI#Timestamp-Filters) for more details.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.\n\nNote that this method will always return item keys, even when not specified, so you may receive more columns that you expect.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting point for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "startTime", - "in": "query", - "description": "Starting date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }, { - "name": "endTime", - "in": "query", - "description": "Ending date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Funding" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/instrument": { - "get": { - "tags": ["Instrument"], - "summary": "Get instruments.", - "description": "This returns all instruments and indices, including those that have settled or are unlisted. Use this endpoint if you want to query for individual instruments or use a complex filter. Use `/instrument/active` to return active instruments, or use a filter like `{\"state\": \"Open\"}`.", - "operationId": "Instrument.get", - "parameters": [{ - "name": "symbol", - "in": "query", - "description": "Instrument symbol. Send a bare series (e.g. XBU) to get data for the nearest expiring contract in that series.\n\nYou can also send a timeframe, e.g. `XBU:monthly`. Timeframes are `daily`, `weekly`, `monthly`, `quarterly`, and `biquarterly`.", - "required": false, - "type": "string" - }, { - "name": "filter", - "in": "query", - "description": "Generic table filter. Send JSON key/value pairs, such as `{\"key\": \"value\"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://testnet.bitmex.com/app/restAPI#Timestamp-Filters) for more details.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.\n\nNote that this method will always return item keys, even when not specified, so you may receive more columns that you expect.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting point for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "startTime", - "in": "query", - "description": "Starting date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }, { - "name": "endTime", - "in": "query", - "description": "Ending date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Instrument" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/instrument/active": { - "get": { - "tags": ["Instrument"], - "summary": "Get all active instruments and instruments that have expired in <24hrs.", - "operationId": "Instrument.getActive", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Instrument" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/instrument/indices": { - "get": { - "tags": ["Instrument"], - "summary": "Get all price indices.", - "operationId": "Instrument.getIndices", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Instrument" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/instrument/activeAndIndices": { - "get": { - "tags": ["Instrument"], - "summary": "Helper method. Gets all active instruments and all indices. This is a join of the result of /indices and /active.", - "operationId": "Instrument.getActiveAndIndices", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Instrument" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/instrument/activeIntervals": { - "get": { - "tags": ["Instrument"], - "summary": "Return all active contract series and interval pairs.", - "description": "This endpoint is useful for determining which pairs are live. It returns two arrays of strings. The first is intervals, such as `[\"XBT:perpetual\", \"XBT:monthly\", \"XBT:quarterly\", \"ETH:monthly\", ...]`. These identifiers are usable in any query's `symbol` param. The second array is the current resolution of these intervals. Results are mapped at the same index.", - "operationId": "Instrument.getActiveIntervals", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/InstrumentInterval" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/instrument/compositeIndex": { - "get": { - "tags": ["Instrument"], - "summary": "Show constituent parts of an index.", - "description": "Composite indices are built from multiple external price sources.\n\nUse this endpoint to get the underlying prices of an index. For example, send a `symbol` of `.XBT` to\nget the ticks and weights of the constituent exchanges that build the \".XBT\" index.\n\nA tick with reference `\"BMI\"` and weight `null` is the composite index tick.\n", - "operationId": "Instrument.getCompositeIndex", - "parameters": [{ - "name": "symbol", - "in": "query", - "description": "The composite index symbol.", - "required": false, - "default": ".XBT", - "type": "string" - }, { - "name": "filter", - "in": "query", - "description": "Generic table filter. Send JSON key/value pairs, such as `{\"key\": \"value\"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://testnet.bitmex.com/app/restAPI#Timestamp-Filters) for more details.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.\n\nNote that this method will always return item keys, even when not specified, so you may receive more columns that you expect.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting point for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "startTime", - "in": "query", - "description": "Starting date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }, { - "name": "endTime", - "in": "query", - "description": "Ending date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/IndexComposite" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/insurance": { - "get": { - "tags": ["Insurance"], - "summary": "Get insurance fund history.", - "operationId": "Insurance.get", - "parameters": [{ - "name": "symbol", - "in": "query", - "description": "Instrument symbol. Send a bare series (e.g. XBU) to get data for the nearest expiring contract in that series.\n\nYou can also send a timeframe, e.g. `XBU:monthly`. Timeframes are `daily`, `weekly`, `monthly`, `quarterly`, and `biquarterly`.", - "required": false, - "type": "string" - }, { - "name": "filter", - "in": "query", - "description": "Generic table filter. Send JSON key/value pairs, such as `{\"key\": \"value\"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://testnet.bitmex.com/app/restAPI#Timestamp-Filters) for more details.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.\n\nNote that this method will always return item keys, even when not specified, so you may receive more columns that you expect.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting point for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "startTime", - "in": "query", - "description": "Starting date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }, { - "name": "endTime", - "in": "query", - "description": "Ending date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Insurance" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/leaderboard": { - "get": { - "tags": ["Leaderboard"], - "summary": "Get current leaderboard.", - "operationId": "Leaderboard.get", - "parameters": [{ - "name": "method", - "in": "query", - "description": "Ranking type. Options: \"notional\", \"ROE\"", - "required": false, - "default": "notional", - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Leaderboard" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/leaderboard/name": { - "get": { - "tags": ["Leaderboard"], - "summary": "Get your alias on the leaderboard.", - "operationId": "Leaderboard.getName", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - } - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/liquidation": { - "get": { - "tags": ["Liquidation"], - "summary": "Get liquidation orders.", - "operationId": "Liquidation.get", - "parameters": [{ - "name": "symbol", - "in": "query", - "description": "Instrument symbol. Send a bare series (e.g. XBU) to get data for the nearest expiring contract in that series.\n\nYou can also send a timeframe, e.g. `XBU:monthly`. Timeframes are `daily`, `weekly`, `monthly`, `quarterly`, and `biquarterly`.", - "required": false, - "type": "string" - }, { - "name": "filter", - "in": "query", - "description": "Generic table filter. Send JSON key/value pairs, such as `{\"key\": \"value\"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://testnet.bitmex.com/app/restAPI#Timestamp-Filters) for more details.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.\n\nNote that this method will always return item keys, even when not specified, so you may receive more columns that you expect.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting point for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "startTime", - "in": "query", - "description": "Starting date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }, { - "name": "endTime", - "in": "query", - "description": "Ending date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Liquidation" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/notification": { - "get": { - "tags": ["Notification"], - "summary": "Get your current notifications.", - "description": "This is an upcoming feature and currently does not return data.", - "operationId": "Notification.get", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Notification" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/order": { - "get": { - "tags": ["Order"], - "summary": "Get your orders.", - "description": "To get open orders only, send {\"open\": true} in the filter param.\n\nSee the FIX Spec for explanations of these fields.", - "operationId": "Order.getOrders", - "parameters": [{ - "name": "symbol", - "in": "query", - "description": "Instrument symbol. Send a bare series (e.g. XBU) to get data for the nearest expiring contract in that series.\n\nYou can also send a timeframe, e.g. `XBU:monthly`. Timeframes are `daily`, `weekly`, `monthly`, `quarterly`, and `biquarterly`.", - "required": false, - "type": "string" - }, { - "name": "filter", - "in": "query", - "description": "Generic table filter. Send JSON key/value pairs, such as `{\"key\": \"value\"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://testnet.bitmex.com/app/restAPI#Timestamp-Filters) for more details.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.\n\nNote that this method will always return item keys, even when not specified, so you may receive more columns that you expect.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting point for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "startTime", - "in": "query", - "description": "Starting date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }, { - "name": "endTime", - "in": "query", - "description": "Ending date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Order" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - }, - "post": { - "tags": ["Order"], - "summary": "Create a new order.", - "description": "## Placing Orders\n\nThis endpoint is used for placing orders. See individual fields below for more details on their use.\n\n#### Order Types\n\nAll orders require a `symbol`. All other fields are optional except when otherwise specified.\n\nThese are the valid `ordType`s:\n\n* **Limit**: The default order type. Specify an `orderQty` and `price`.\n* **Market**: A traditional Market order. A Market order will execute until filled or your bankruptcy price is reached, at\n which point it will cancel.\n* **MarketWithLeftOverAsLimit**: A market order that, after eating through the order book as far as\n permitted by available margin, will become a limit order. The difference between this type and `Market` only\n affects the behavior in thin books. Upon reaching the deepest possible price, if there is quantity left over,\n a `Market` order will cancel the remaining quantity. `MarketWithLeftOverAsLimit` will keep the remaining\n quantity in the books as a `Limit`.\n* **Stop**: A Stop Market order. Specify an `orderQty` and `stopPx`. When the `stopPx` is reached, the order will be entered\n into the book.\n * On sell orders, the order will trigger if the triggering price is lower than the `stopPx`. On buys, higher.\n * Note: Stop orders do not consume margin until triggered. Be sure that the required margin is available in your\n account so that it may trigger fully.\n * `Close` Stops don't require an `orderQty`. See Execution Instructions below.\n* **StopLimit**: Like a Stop Market, but enters a Limit order instead of a Market order. Specify an `orderQty`, `stopPx`,\n and `price`.\n* **MarketIfTouched**: Similar to a Stop, but triggers are done in the opposite direction. Useful for Take Profit orders.\n* **LimitIfTouched**: As above; use for Take Profit Limit orders.\n\n#### Execution Instructions\n\nThe following `execInst`s are supported. If using multiple, separate with a comma (e.g. `LastPrice,Close`).\n\n* **ParticipateDoNotInitiate**: Also known as a Post-Only order. If this order would have executed on placement,\n it will cancel instead.\n* **MarkPrice, LastPrice, IndexPrice**: Used by stop and if-touched orders to determine the triggering price.\n Use only one. By default, `'MarkPrice'` is used. Also used for Pegged orders to define the value of `'LastPeg'`.\n* **ReduceOnly**: A `'ReduceOnly'` order can only reduce your position, not increase it. If you have a `'ReduceOnly'`\n limit order that rests in the order book while the position is reduced by other orders, then its order quantity will\n be amended down or canceled. If there are multiple `'ReduceOnly'` orders the least aggressive will be amended first.\n* **Close**: `'Close'` implies `'ReduceOnly'`. A `'Close'` order will cancel other active limit orders with the same side\n and symbol if the open quantity exceeds the current position. This is useful for stops: by canceling these orders, a\n `'Close'` Stop is ensured to have the margin required to execute, and can only execute up to the full size of your\n position. If `orderQty` is not specified, a `'Close'` order has an `orderQty` equal to your current position's size.\n * Note that a `Close` order without an `orderQty` requires a `side`, so that BitMEX knows if it should trigger\n above or below the `stopPx`.\n\n#### Linked Orders\n\nLinked Orders are an advanced capability. It is very powerful, but its use requires careful coding and testing.\nPlease follow this document carefully and use the [Testnet Exchange](https://testnet.bitmex.com) while developing.\n\nBitMEX offers four advanced Linked Order types:\n\n* **OCO**: *One Cancels the Other*. A very flexible version of the standard Stop / Take Profit technique.\n Multiple orders may be linked together using a single `clOrdLinkID`. Send a `contingencyType` of\n `OneCancelsTheOther` on the orders. The first order that fully or partially executes (or activates\n for `Stop` orders) will cancel all other orders with the same `clOrdLinkID`.\n* **OTO**: *One Triggers the Other*. Send a `contingencyType` of `'OneTriggersTheOther'` on the primary order and\n then subsequent orders with the same `clOrdLinkID` will be not be triggered until the primary order fully executes.\n* **OUOA**: *One Updates the Other Absolute*. Send a `contingencyType` of `'OneUpdatesTheOtherAbsolute'` on the orders. Then\n as one order has a execution, other orders with the same `clOrdLinkID` will have their order quantity amended\n down by the execution quantity.\n* **OUOP**: *One Updates the Other Proportional*. Send a `contingencyType` of `'OneUpdatesTheOtherProportional'` on the orders. Then\n as one order has a execution, other orders with the same `clOrdLinkID` will have their order quantity reduced proportionally\n by the fill percentage.\n\n#### Trailing Stops\n\nYou may use `pegPriceType` of `'TrailingStopPeg'` to create Trailing Stops. The pegged `stopPx` will move as the market\nmoves away from the peg, and freeze as the market moves toward it.\n\nTo use, combine with `pegOffsetValue` to set the `stopPx` of your order. The peg is set to the triggering price\nspecified in the `execInst` (default `'MarkPrice'`). Use a negative offset for stop-sell and buy-if-touched orders.\n\nRequires `ordType`: `'Stop', 'StopLimit', 'MarketIfTouched', 'LimitIfTouched'`.\n\n#### Simple Quantities\n\nSend a `simpleOrderQty` instead of an `orderQty` to create an order denominated in the underlying currency.\nThis is useful for opening up a position with 1 XBT of exposure without having to calculate how many contracts it is.\n\n#### Rate Limits\n\nSee the [Bulk Order Documentation](#!/Order/Order_newBulk) if you need to place multiple orders at the same time.\nBulk orders require fewer risk checks in the trading engine and thus are ratelimited at **1/10** the normal rate.\n\nYou can also improve your reactivity to market movements while staying under your ratelimit by using the\n[Amend](#!/Order/Order_amend) and [Amend Bulk](#!/Order/Order_amendBulk) endpoints. This allows you to stay\nin the market and avoids the cancel/replace cycle.\n\n#### Tracking Your Orders\n\nIf you want to keep track of order IDs yourself, set a unique `clOrdID` per order.\nThis `clOrdID` will come back as a property on the order and any related executions (including on the WebSocket),\nand can be used to get or cancel the order. Max length is 36 characters.\n\nYou can also change the `clOrdID` by amending an order, supplying an `origClOrdID`, and your desired new\nID as the `clOrdID` param, like so:\n\n```\n# Amends an order's leavesQty, and updates its clOrdID to \"def-456\"\nPUT /api/v1/order {\"origClOrdID\": \"abc-123\", \"clOrdID\": \"def-456\", \"leavesQty\": 1000}\n```\n", - "operationId": "Order.new", - "parameters": [{ - "name": "symbol", - "in": "formData", - "description": "Instrument symbol. e.g. 'XBTUSD'.", - "required": true, - "type": "string" - }, { - "name": "side", - "in": "formData", - "description": "Order side. Valid options: Buy, Sell. Defaults to 'Buy' unless `orderQty` or `simpleOrderQty` is negative.", - "required": false, - "type": "string" - }, { - "name": "simpleOrderQty", - "in": "formData", - "description": "Order quantity in units of the underlying instrument (i.e. Bitcoin).", - "required": false, - "type": "number", - "format": "double" - }, { - "name": "orderQty", - "in": "formData", - "description": "Order quantity in units of the instrument (i.e. contracts).", - "required": false, - "format": "int32", - "type": "number" - }, { - "name": "price", - "in": "formData", - "description": "Optional limit price for 'Limit', 'StopLimit', and 'LimitIfTouched' orders.", - "required": false, - "type": "number", - "format": "double" - }, { - "name": "displayQty", - "in": "formData", - "description": "Optional quantity to display in the book. Use 0 for a fully hidden order.", - "required": false, - "format": "int32", - "type": "number" - }, { - "name": "stopPx", - "in": "formData", - "description": "Optional trigger price for 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders. Use a price below the current price for stop-sell orders and buy-if-touched orders. Use `execInst` of 'MarkPrice' or 'LastPrice' to define the current price used for triggering.", - "required": false, - "type": "number", - "format": "double" - }, { - "name": "clOrdID", - "in": "formData", - "description": "Optional Client Order ID. This clOrdID will come back on the order and any related executions.", - "required": false, - "type": "string" - }, { - "name": "clOrdLinkID", - "in": "formData", - "description": "Optional Client Order Link ID for contingent orders.", - "required": false, - "type": "string" - }, { - "name": "pegOffsetValue", - "in": "formData", - "description": "Optional trailing offset from the current price for 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders; use a negative offset for stop-sell orders and buy-if-touched orders. Optional offset from the peg price for 'Pegged' orders.", - "required": false, - "type": "number", - "format": "double" - }, { - "name": "pegPriceType", - "in": "formData", - "description": "Optional peg price type. Valid options: LastPeg, MidPricePeg, MarketPeg, PrimaryPeg, TrailingStopPeg.", - "required": false, - "type": "string" - }, { - "name": "ordType", - "in": "formData", - "description": "Order type. Valid options: Market, Limit, Stop, StopLimit, MarketIfTouched, LimitIfTouched, MarketWithLeftOverAsLimit, Pegged. Defaults to 'Limit' when `price` is specified. Defaults to 'Stop' when `stopPx` is specified. Defaults to 'StopLimit' when `price` and `stopPx` are specified.", - "required": false, - "default": "Limit", - "type": "string" - }, { - "name": "timeInForce", - "in": "formData", - "description": "Time in force. Valid options: Day, GoodTillCancel, ImmediateOrCancel, FillOrKill. Defaults to 'GoodTillCancel' for 'Limit', 'StopLimit', 'LimitIfTouched', and 'MarketWithLeftOverAsLimit' orders.", - "required": false, - "type": "string" - }, { - "name": "execInst", - "in": "formData", - "description": "Optional execution instructions. Valid options: ParticipateDoNotInitiate, AllOrNone, MarkPrice, IndexPrice, LastPrice, Close, ReduceOnly, Fixed. 'AllOrNone' instruction requires `displayQty` to be 0. 'MarkPrice', 'IndexPrice' or 'LastPrice' instruction valid for 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders.", - "required": false, - "type": "string" - }, { - "name": "contingencyType", - "in": "formData", - "description": "Optional contingency type for use with `clOrdLinkID`. Valid options: OneCancelsTheOther, OneTriggersTheOther, OneUpdatesTheOtherAbsolute, OneUpdatesTheOtherProportional.", - "required": false, - "type": "string" - }, { - "name": "text", - "in": "formData", - "description": "Optional order annotation. e.g. 'Take profit'.", - "required": false, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/Order" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - }, - "put": { - "tags": ["Order"], - "summary": "Amend the quantity or price of an open order.", - "description": "Send an `orderID` or `origClOrdID` to identify the order you wish to amend.\n\nBoth order quantity and price can be amended. Only one `qty` field can be used to amend.\n\nUse the `leavesQty` field to specify how much of the order you wish to remain open. This can be useful\nif you want to adjust your position's delta by a certain amount, regardless of how much of the order has\nalready filled.\n\n> A `leavesQty` can be used to make a \"Filled\" order live again, if it is received within 60 seconds of the fill.\n\nUse the `simpleOrderQty` and `simpleLeavesQty` fields to specify order size in Bitcoin, rather than contracts.\nThese fields will round up to the nearest contract.\n\nLike order placement, amending can be done in bulk. Simply send a request to `PUT /api/v1/order/bulk` with\na JSON body of the shape: `{\"orders\": [{...}, {...}]}`, each object containing the fields used in this endpoint.\n", - "operationId": "Order.amend", - "parameters": [{ - "name": "orderID", - "in": "formData", - "description": "Order ID", - "required": false, - "type": "string" - }, { - "name": "origClOrdID", - "in": "formData", - "description": "Client Order ID. See POST /order.", - "required": false, - "type": "string" - }, { - "name": "clOrdID", - "in": "formData", - "description": "Optional new Client Order ID, requires `origClOrdID`.", - "required": false, - "type": "string" - }, { - "name": "simpleOrderQty", - "in": "formData", - "description": "Optional order quantity in units of the underlying instrument (i.e. Bitcoin).", - "required": false, - "type": "number", - "format": "double" - }, { - "name": "orderQty", - "in": "formData", - "description": "Optional order quantity in units of the instrument (i.e. contracts).", - "required": false, - "format": "int32", - "type": "number" - }, { - "name": "simpleLeavesQty", - "in": "formData", - "description": "Optional leaves quantity in units of the underlying instrument (i.e. Bitcoin). Useful for amending partially filled orders.", - "required": false, - "type": "number", - "format": "double" - }, { - "name": "leavesQty", - "in": "formData", - "description": "Optional leaves quantity in units of the instrument (i.e. contracts). Useful for amending partially filled orders.", - "required": false, - "format": "int32", - "type": "number" - }, { - "name": "price", - "in": "formData", - "description": "Optional limit price for 'Limit', 'StopLimit', and 'LimitIfTouched' orders.", - "required": false, - "type": "number", - "format": "double" - }, { - "name": "stopPx", - "in": "formData", - "description": "Optional trigger price for 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders. Use a price below the current price for stop-sell orders and buy-if-touched orders.", - "required": false, - "type": "number", - "format": "double" - }, { - "name": "pegOffsetValue", - "in": "formData", - "description": "Optional trailing offset from the current price for 'Stop', 'StopLimit', 'MarketIfTouched', and 'LimitIfTouched' orders; use a negative offset for stop-sell orders and buy-if-touched orders. Optional offset from the peg price for 'Pegged' orders.", - "required": false, - "type": "number", - "format": "double" - }, { - "name": "text", - "in": "formData", - "description": "Optional amend annotation. e.g. 'Adjust skew'.", - "required": false, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/Order" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - }, - "delete": { - "tags": ["Order"], - "summary": "Cancel order(s). Send multiple order IDs to cancel in bulk.", - "description": "Either an orderID or a clOrdID must be provided.", - "operationId": "Order.cancel", - "parameters": [{ - "name": "orderID", - "in": "formData", - "description": "Order ID(s).", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "clOrdID", - "in": "formData", - "description": "Client Order ID(s). See POST /order.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "text", - "in": "formData", - "description": "Optional cancellation annotation. e.g. 'Spread Exceeded'.", - "required": false, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Order" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/order/bulk": { - "post": { - "tags": ["Order"], - "summary": "Create multiple new orders for the same symbol.", - "description": "This endpoint is used for placing bulk orders. Valid order types are Market, Limit, Stop, StopLimit, MarketIfTouched, LimitIfTouched, MarketWithLeftOverAsLimit, and Pegged.\n\nEach individual order object in the array should have the same properties as an individual POST /order call.\n\nThis endpoint is much faster for getting many orders into the book at once. Because it reduces load on BitMEX\nsystems, this endpoint is ratelimited at `ceil(0.1 * orders)`. Submitting 10 orders via a bulk order call\nwill only count as 1 request, 15 as 2, 32 as 4, and so on.\n\nFor now, only `application/json` is supported on this endpoint.\n", - "operationId": "Order.newBulk", - "parameters": [{ - "name": "orders", - "in": "formData", - "description": "An array of orders.", - "required": false, - "type": "string", - "format": "JSON" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Order" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - }, - "put": { - "tags": ["Order"], - "summary": "Amend multiple orders for the same symbol.", - "description": "Similar to POST /amend, but with multiple orders. `application/json` only. Ratelimited at 10%.", - "operationId": "Order.amendBulk", - "parameters": [{ - "name": "orders", - "in": "formData", - "description": "An array of orders.", - "required": false, - "type": "string", - "format": "JSON" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Order" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/order/closePosition": { - "post": { - "tags": ["Order"], - "summary": "Close a position. [Deprecated, use POST /order with execInst: 'Close']", - "description": "If no `price` is specified, a market order will be submitted to close the whole of your position. This will also close all other open orders in this symbol.", - "operationId": "Order.closePosition", - "parameters": [{ - "name": "symbol", - "in": "formData", - "description": "Symbol of position to close.", - "required": true, - "type": "string" - }, { - "name": "price", - "in": "formData", - "description": "Optional limit price.", - "required": false, - "type": "number", - "format": "double" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "description": "Resulting close order.", - "$ref": "#/definitions/Order" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/order/all": { - "delete": { - "tags": ["Order"], - "summary": "Cancels all of your orders.", - "operationId": "Order.cancelAll", - "parameters": [{ - "name": "symbol", - "in": "formData", - "description": "Optional symbol. If provided, only cancels orders for that symbol.", - "required": false, - "type": "string" - }, { - "name": "filter", - "in": "formData", - "description": "Optional filter for cancellation. Use to only cancel some orders, e.g. `{\"side\": \"Buy\"}`.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "text", - "in": "formData", - "description": "Optional cancellation annotation. e.g. 'Spread Exceeded'", - "required": false, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Order" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/order/cancelAllAfter": { - "post": { - "tags": ["Order"], - "summary": "Automatically cancel all your orders after a specified timeout.", - "description": "Useful as a dead-man's switch to ensure your orders are canceled in case of an outage.\nIf called repeatedly, the existing offset will be canceled and a new one will be inserted in its place.\n\nExample usage: call this route at 15s intervals with an offset of 60000 (60s).\nIf this route is not called within 60 seconds, all your orders will be automatically canceled.\n\nThis is also available via [WebSocket](https://testnet.bitmex.com/app/wsAPI#Dead-Mans-Switch-Auto-Cancel).\n", - "operationId": "Order.cancelAllAfter", - "parameters": [{ - "name": "timeout", - "in": "formData", - "description": "Timeout in ms. Set to 0 to cancel this timer. ", - "required": true, - "type": "number", - "format": "double" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "object" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/orderBook/L2": { - "get": { - "tags": ["OrderBook"], - "summary": "Get current orderbook in vertical format.", - "operationId": "OrderBook.getL2", - "parameters": [{ - "name": "symbol", - "in": "query", - "description": "Instrument symbol. Send a series (e.g. XBT) to get data for the nearest contract in that series.", - "required": true, - "type": "string" - }, { - "name": "depth", - "in": "query", - "description": "Orderbook depth per side. Send 0 for full depth.", - "required": false, - "minimum": 0, - "format": "int32", - "default": 25, - "type": "number" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/OrderBookL2" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/position": { - "get": { - "tags": ["Position"], - "summary": "Get your positions.", - "description": "This endpoint is used for retrieving position information. The fields largely follow the [FIX spec](http://www.onixs.biz/fix-dictionary/5.0.SP2/msgType_AP_6580.html) definitions. Some selected fields are explained in more detail below.\n\nThe fields _account_, _symbol_, _currency_ are unique to each position and form its key.\n\n* **account**: Your unique account ID.\n* **symbol**: The contract for this position.\n* **currency**: The margin currency for this position.\n* **underlying**: Meta data of the _symbol_.\n* **quoteCurrency**: Meta data of the _symbol_, All prices are in the _quoteCurrency_\n* **commission**: The maximum of the maker, taker, and settlement fee.\n* **initMarginReq**: The initial margin requirement. This will be at least the symbol's default initial maintenance margin, but can be higher if you choose lower leverage.\n* **maintMarginReq**: The maintenance margin requirement. This will be at least the symbol's default maintenance maintenance margin, but can be higher if you choose a higher risk limit.\n* **riskLimit**: This is a function of your _maintMarginReq_.\n* **leverage**: 1 / initMarginReq.\n* **crossMargin**: True/false depending on whether you set cross margin on this position.\n* **deleveragePercentile**: Indicates where your position is in the ADL queue.\n* **rebalancedPnl**: The value of realised PNL that has transferred to your wallet for this position.\n* **prevRebalancedPnl**: The value of realised PNL that has transferred to your wallet for this position since the position was closed.\n* **currentQty**: The current position amount in contracts.\n* **currentCost**: The current cost of the position in the settlement currency of the symbol (_currency_).\n* **currentComm**: The current commission of the position in the settlement currency of the symbol (_currency_).\n* **realisedCost**: The realised cost of this position calculated with regard to average cost accounting.\n* **unrealisedCost**: _currentCost_ - _realisedCost_.\n* **grossOpenCost**: The absolute value of your open orders for this symbol.\n* **grossOpenPremium**: The amount your bidding above the mark price in the settlement currency of the symbol (_currency_).\n* **markPrice**: The mark price of the symbol in _quoteCurrency_.\n* **markValue**: The _currentQty_ at the mark price in the settlement currency of the symbol (_currency_).\n* **homeNotional**: Value of position in units of _underlying_.\n* **foreignNotional**: Value of position in units of _quoteCurrency_.\n* **realisedPnl**: The negative of _realisedCost_.\n* **unrealisedGrossPnl**: _markValue_ - _unrealisedCost_.\n* **unrealisedPnl**: _unrealisedGrossPnl_.\n* **liquidationPrice**: Once markPrice reaches this price, this position will be liquidated.\n* **bankruptPrice**: Once markPrice reaches this price, this position will have no equity.\n", - "operationId": "Position.get", - "parameters": [{ - "name": "filter", - "in": "query", - "description": "Table filter. For example, send {\"symbol\": \"XBTUSD\"}.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Which columns to fetch. For example, send [\"columnName\"].", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of rows to fetch.", - "required": false, - "format": "int32", - "type": "number" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Position" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/position/isolate": { - "post": { - "tags": ["Position"], - "summary": "Enable isolated margin or cross margin per-position.", - "operationId": "Position.isolateMargin", - "parameters": [{ - "name": "symbol", - "in": "formData", - "description": "Position symbol to isolate.", - "required": true, - "type": "string" - }, { - "name": "enabled", - "in": "formData", - "description": "True for isolated margin, false for cross margin.", - "required": false, - "default": true, - "type": "boolean" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "description": "Affected position.", - "$ref": "#/definitions/Position" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/position/riskLimit": { - "post": { - "tags": ["Position"], - "summary": "Update your risk limit.", - "operationId": "Position.updateRiskLimit", - "parameters": [{ - "name": "symbol", - "in": "formData", - "description": "Symbol of position to update risk limit on.", - "required": true, - "type": "string" - }, { - "name": "riskLimit", - "in": "formData", - "description": "New Risk Limit, in Satoshis.", - "required": true, - "format": "int64", - "type": "number" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "description": "Affected position.", - "$ref": "#/definitions/Position" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/position/transferMargin": { - "post": { - "tags": ["Position"], - "summary": "Transfer equity in or out of a position.", - "operationId": "Position.transferIsolatedMargin", - "parameters": [{ - "name": "symbol", - "in": "formData", - "description": "Symbol of position to isolate.", - "required": true, - "type": "string" - }, { - "name": "amount", - "in": "formData", - "description": "Amount to transfer, in Satoshis. May be negative.", - "required": true, - "format": "int64", - "type": "number" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "description": "Affected position.", - "$ref": "#/definitions/Position" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/position/leverage": { - "post": { - "tags": ["Position"], - "summary": "Choose leverage for a position.", - "operationId": "Position.updateLeverage", - "parameters": [{ - "name": "symbol", - "in": "formData", - "description": "Symbol of position to adjust.", - "required": true, - "type": "string" - }, { - "name": "leverage", - "in": "formData", - "description": "Leverage value. Send a number between 0.01 and 100 to enable isolated margin with a fixed leverage. Send 0 to enable cross margin.", - "required": true, - "type": "number", - "format": "double" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "description": "Affected position.", - "$ref": "#/definitions/Position" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false - } - }, - "/quote": { - "get": { - "tags": ["Quote"], - "summary": "Get Quotes.", - "operationId": "Quote.get", - "parameters": [{ - "name": "symbol", - "in": "query", - "description": "Instrument symbol. Send a bare series (e.g. XBU) to get data for the nearest expiring contract in that series.\n\nYou can also send a timeframe, e.g. `XBU:monthly`. Timeframes are `daily`, `weekly`, `monthly`, `quarterly`, and `biquarterly`.", - "required": false, - "type": "string" - }, { - "name": "filter", - "in": "query", - "description": "Generic table filter. Send JSON key/value pairs, such as `{\"key\": \"value\"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://testnet.bitmex.com/app/restAPI#Timestamp-Filters) for more details.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.\n\nNote that this method will always return item keys, even when not specified, so you may receive more columns that you expect.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting point for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "startTime", - "in": "query", - "description": "Starting date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }, { - "name": "endTime", - "in": "query", - "description": "Ending date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Quote" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/quote/bucketed": { - "get": { - "tags": ["Quote"], - "summary": "Get previous quotes in time buckets.", - "operationId": "Quote.getBucketed", - "parameters": [{ - "name": "binSize", - "in": "query", - "description": "Time interval to bucket by. Available options: [1m,5m,1h,1d].", - "required": false, - "default": "1m", - "type": "string" - }, { - "name": "partial", - "in": "query", - "description": "If true, will send in-progress (incomplete) bins for the current time period.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "symbol", - "in": "query", - "description": "Instrument symbol. Send a bare series (e.g. XBU) to get data for the nearest expiring contract in that series.\n\nYou can also send a timeframe, e.g. `XBU:monthly`. Timeframes are `daily`, `weekly`, `monthly`, `quarterly`, and `biquarterly`.", - "required": false, - "type": "string" - }, { - "name": "filter", - "in": "query", - "description": "Generic table filter. Send JSON key/value pairs, such as `{\"key\": \"value\"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://testnet.bitmex.com/app/restAPI#Timestamp-Filters) for more details.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.\n\nNote that this method will always return item keys, even when not specified, so you may receive more columns that you expect.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting point for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "startTime", - "in": "query", - "description": "Starting date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }, { - "name": "endTime", - "in": "query", - "description": "Ending date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Quote" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/schema": { - "get": { - "tags": ["Schema"], - "summary": "Get model schemata for data objects returned by this API.", - "operationId": "Schema.get", - "parameters": [{ - "name": "model", - "in": "query", - "description": "Optional model filter. If omitted, will return all models.", - "required": false, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "object" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/schema/websocketHelp": { - "get": { - "tags": ["Schema"], - "summary": "Returns help text & subject list for websocket usage.", - "operationId": "Schema.websocketHelp", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "object" - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/settlement": { - "get": { - "tags": ["Settlement"], - "summary": "Get settlement history.", - "operationId": "Settlement.get", - "parameters": [{ - "name": "symbol", - "in": "query", - "description": "Instrument symbol. Send a bare series (e.g. XBU) to get data for the nearest expiring contract in that series.\n\nYou can also send a timeframe, e.g. `XBU:monthly`. Timeframes are `daily`, `weekly`, `monthly`, `quarterly`, and `biquarterly`.", - "required": false, - "type": "string" - }, { - "name": "filter", - "in": "query", - "description": "Generic table filter. Send JSON key/value pairs, such as `{\"key\": \"value\"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://testnet.bitmex.com/app/restAPI#Timestamp-Filters) for more details.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.\n\nNote that this method will always return item keys, even when not specified, so you may receive more columns that you expect.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting point for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "startTime", - "in": "query", - "description": "Starting date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }, { - "name": "endTime", - "in": "query", - "description": "Ending date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Settlement" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/stats": { - "get": { - "tags": ["Stats"], - "summary": "Get exchange-wide and per-series turnover and volume statistics.", - "operationId": "Stats.get", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Stats" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/stats/history": { - "get": { - "tags": ["Stats"], - "summary": "Get historical exchange-wide and per-series turnover and volume statistics.", - "operationId": "Stats.history", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/StatsHistory" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/stats/historyUSD": { - "get": { - "tags": ["Stats"], - "summary": "Get a summary of exchange statistics in USD.", - "operationId": "Stats.historyUSD", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/StatsUSD" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/trade": { - "get": { - "tags": ["Trade"], - "summary": "Get Trades.", - "description": "Please note that indices (symbols starting with `.`) post trades at intervals to the trade feed. These have a `size` of 0 and are used only to indicate a changing price.\n\nSee [the FIX Spec](http://www.onixs.biz/fix-dictionary/5.0.SP2/msgType_AE_6569.html) for explanations of these fields.", - "operationId": "Trade.get", - "parameters": [{ - "name": "symbol", - "in": "query", - "description": "Instrument symbol. Send a bare series (e.g. XBU) to get data for the nearest expiring contract in that series.\n\nYou can also send a timeframe, e.g. `XBU:monthly`. Timeframes are `daily`, `weekly`, `monthly`, `quarterly`, and `biquarterly`.", - "required": false, - "type": "string" - }, { - "name": "filter", - "in": "query", - "description": "Generic table filter. Send JSON key/value pairs, such as `{\"key\": \"value\"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://testnet.bitmex.com/app/restAPI#Timestamp-Filters) for more details.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.\n\nNote that this method will always return item keys, even when not specified, so you may receive more columns that you expect.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting point for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "startTime", - "in": "query", - "description": "Starting date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }, { - "name": "endTime", - "in": "query", - "description": "Ending date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Trade" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/trade/bucketed": { - "get": { - "tags": ["Trade"], - "summary": "Get previous trades in time buckets.", - "operationId": "Trade.getBucketed", - "parameters": [{ - "name": "binSize", - "in": "query", - "description": "Time interval to bucket by. Available options: [1m,5m,1h,1d].", - "required": false, - "default": "1m", - "type": "string" - }, { - "name": "partial", - "in": "query", - "description": "If true, will send in-progress (incomplete) bins for the current time period.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "symbol", - "in": "query", - "description": "Instrument symbol. Send a bare series (e.g. XBU) to get data for the nearest expiring contract in that series.\n\nYou can also send a timeframe, e.g. `XBU:monthly`. Timeframes are `daily`, `weekly`, `monthly`, `quarterly`, and `biquarterly`.", - "required": false, - "type": "string" - }, { - "name": "filter", - "in": "query", - "description": "Generic table filter. Send JSON key/value pairs, such as `{\"key\": \"value\"}`. You can key on individual fields, and do more advanced querying on timestamps. See the [Timestamp Docs](https://testnet.bitmex.com/app/restAPI#Timestamp-Filters) for more details.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "columns", - "in": "query", - "description": "Array of column names to fetch. If omitted, will return all columns.\n\nNote that this method will always return item keys, even when not specified, so you may receive more columns that you expect.", - "required": false, - "type": "string", - "format": "JSON" - }, { - "name": "count", - "in": "query", - "description": "Number of results to fetch.", - "required": false, - "format": "int32", - "default": 100, - "type": "number" - }, { - "name": "start", - "in": "query", - "description": "Starting point for results.", - "required": false, - "format": "int32", - "default": 0, - "type": "number" - }, { - "name": "reverse", - "in": "query", - "description": "If true, will sort results newest first.", - "required": false, - "default": false, - "type": "boolean" - }, { - "name": "startTime", - "in": "query", - "description": "Starting date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }, { - "name": "endTime", - "in": "query", - "description": "Ending date filter for results.", - "required": false, - "type": "string", - "format": "date-time" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/TradeBin" - } - } - }, - "400": { - "description": "Parameter Error", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/Error" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/user/depositAddress": { - "get": { - "tags": ["User"], - "summary": "Get a deposit address.", - "operationId": "User.getDepositAddress", - "parameters": [{ - "name": "currency", - "in": "query", - "required": false, - "default": "XBt", - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "string" - } - } - }, - "deprecated": false - } - }, - "/user/wallet": { - "get": { - "tags": ["User"], - "summary": "Get your current wallet information.", - "operationId": "User.getWallet", - "parameters": [{ - "name": "currency", - "in": "query", - "required": false, - "default": "XBt", - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/Wallet" - } - } - }, - "deprecated": false - } - }, - "/user/walletHistory": { - "get": { - "tags": ["User"], - "summary": "Get a history of all of your wallet transactions (deposits, withdrawals, PNL).", - "operationId": "User.getWalletHistory", - "parameters": [{ - "name": "currency", - "in": "query", - "required": false, - "default": "XBt", - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Transaction" - } - } - } - }, - "deprecated": false - } - }, - "/user/walletSummary": { - "get": { - "tags": ["User"], - "summary": "Get a summary of all of your wallet transactions (deposits, withdrawals, PNL).", - "operationId": "User.getWalletSummary", - "parameters": [{ - "name": "currency", - "in": "query", - "required": false, - "default": "XBt", - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/Transaction" - } - } - } - }, - "deprecated": false - } - }, - "/user/minWithdrawalFee": { - "get": { - "tags": ["User"], - "summary": "Get the minimum withdrawal fee for a currency.", - "description": "This is changed based on network conditions to ensure timely withdrawals. During network congestion, this may be high. The fee is returned in the same currency.", - "operationId": "User.minWithdrawalFee", - "parameters": [{ - "name": "currency", - "in": "query", - "required": false, - "default": "XBt", - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "object" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/user/requestWithdrawal": { - "post": { - "tags": ["User"], - "summary": "Request a withdrawal to an external wallet.", - "description": "This will send a confirmation email to the email address on record, unless requested via an API Key with the `withdraw` permission.", - "operationId": "User.requestWithdrawal", - "parameters": [{ - "name": "otpToken", - "in": "formData", - "description": "2FA token. Required if 2FA is enabled on your account.", - "required": false, - "type": "string" - }, { - "name": "currency", - "in": "formData", - "description": "Currency you're withdrawing. Options: `XBt`", - "required": true, - "default": "XBt", - "type": "string" - }, { - "name": "amount", - "in": "formData", - "description": "Amount of withdrawal currency.", - "required": true, - "format": "int64", - "type": "number" - }, { - "name": "address", - "in": "formData", - "description": "Destination Address.", - "required": true, - "type": "string" - }, { - "name": "fee", - "in": "formData", - "description": "Network fee for Bitcoin withdrawals. If not specified, a default value will be calculated based on Bitcoin network conditions. You will have a chance to confirm this via email.", - "required": false, - "type": "number", - "format": "double" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/Transaction" - } - } - }, - "deprecated": false - } - }, - "/user/cancelWithdrawal": { - "post": { - "tags": ["User"], - "summary": "Cancel a withdrawal.", - "operationId": "User.cancelWithdrawal", - "parameters": [{ - "name": "token", - "in": "formData", - "required": true, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/Transaction" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/user/confirmWithdrawal": { - "post": { - "tags": ["User"], - "summary": "Confirm a withdrawal.", - "operationId": "User.confirmWithdrawal", - "parameters": [{ - "name": "token", - "in": "formData", - "required": true, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/Transaction" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/user/requestEnableTFA": { - "post": { - "tags": ["User"], - "summary": "Get secret key for setting up two-factor auth.", - "description": "Use /confirmEnableTFA directly for Yubikeys. This fails if TFA is already enabled.", - "operationId": "User.requestEnableTFA", - "parameters": [{ - "name": "type", - "in": "formData", - "description": "Two-factor auth type. Supported types: 'GA' (Google Authenticator)", - "required": false, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "boolean" - } - } - }, - "deprecated": false - } - }, - "/user/confirmEnableTFA": { - "post": { - "tags": ["User"], - "summary": "Confirm two-factor auth for this account. If using a Yubikey, simply send a token to this endpoint.", - "operationId": "User.confirmEnableTFA", - "parameters": [{ - "name": "type", - "in": "formData", - "description": "Two-factor auth type. Supported types: 'GA' (Google Authenticator), 'Yubikey'", - "required": false, - "type": "string" - }, { - "name": "token", - "in": "formData", - "description": "Token from your selected TFA type.", - "required": true, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "boolean" - } - } - }, - "deprecated": false - } - }, - "/user/disableTFA": { - "post": { - "tags": ["User"], - "summary": "Disable two-factor auth for this account.", - "operationId": "User.disableTFA", - "parameters": [{ - "name": "type", - "in": "formData", - "description": "Two-factor auth type. Supported types: 'GA' (Google Authenticator)", - "required": false, - "type": "string" - }, { - "name": "token", - "in": "formData", - "description": "Token from your selected TFA type.", - "required": true, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "boolean" - } - } - }, - "deprecated": false - } - }, - "/user/confirmEmail": { - "post": { - "tags": ["User"], - "summary": "Confirm your email address with a token.", - "operationId": "User.confirm", - "parameters": [{ - "name": "token", - "in": "formData", - "required": true, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/AccessToken" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/user/affiliateStatus": { - "get": { - "tags": ["User"], - "summary": "Get your current affiliate/referral status.", - "operationId": "User.getAffiliateStatus", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/Affiliate" - } - } - }, - "deprecated": false - } - }, - "/user/checkReferralCode": { - "get": { - "tags": ["User"], - "summary": "Check if a referral code is valid.", - "description": "If the code is valid, responds with the referral code's discount (e.g. `0.1` for 10%). Otherwise, will return a 404.", - "operationId": "User.checkReferralCode", - "parameters": [{ - "name": "referralCode", - "in": "query", - "required": false, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "number", - "format": "double" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/user/logout": { - "post": { - "tags": ["User"], - "summary": "Log out of BitMEX.", - "operationId": "User.logout", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "null" - } - } - }, - "deprecated": false, - "security": [] - } - }, - "/user/logoutAll": { - "post": { - "tags": ["User"], - "summary": "Log all systems out of BitMEX. This will revoke all of your account's access tokens, logging you out on all devices.", - "operationId": "User.logoutAll", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "number", - "format": "double" - } - } - }, - "deprecated": false - } - }, - "/user/preferences": { - "post": { - "tags": ["User"], - "summary": "Save user preferences.", - "operationId": "User.savePreferences", - "parameters": [{ - "name": "prefs", - "in": "formData", - "required": true, - "type": "string", - "format": "JSON" - }, { - "name": "overwrite", - "in": "formData", - "description": "If true, will overwrite all existing preferences.", - "required": false, - "default": false, - "type": "boolean" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/User" - } - } - }, - "deprecated": false - } - }, - "/user": { - "get": { - "tags": ["User"], - "summary": "Get your user model.", - "operationId": "User.get", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/User" - } - } - }, - "deprecated": false - }, - "put": { - "tags": ["User"], - "summary": "Update your password, name, and other attributes.", - "operationId": "User.update", - "parameters": [{ - "name": "oldPassword", - "in": "formData", - "required": false, - "type": "string" - }, { - "name": "newPassword", - "in": "formData", - "required": false, - "type": "string" - }, { - "name": "newPasswordConfirm", - "in": "formData", - "required": false, - "type": "string" - }, { - "name": "username", - "in": "formData", - "description": "Username can only be set once. To reset, email support.", - "required": false, - "type": "string" - }, { - "name": "country", - "in": "formData", - "description": "Country of residence.", - "required": false, - "type": "string" - }, { - "name": "pgpPubKey", - "in": "formData", - "description": "PGP Public Key. If specified, automated emails will be sentwith this key.", - "required": false, - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/User" - } - } - }, - "deprecated": false - } - }, - "/user/commission": { - "get": { - "tags": ["User"], - "summary": "Get your account's commission status.", - "operationId": "User.getCommission", - "parameters": [], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/UserCommission" - } - } - } - }, - "deprecated": false - } - }, - "/user/margin": { - "get": { - "tags": ["User"], - "summary": "Get your account's margin status. Send a currency of \"all\" to receive an array of all supported currencies.", - "operationId": "User.getMargin", - "parameters": [{ - "name": "currency", - "in": "query", - "required": false, - "default": "XBt", - "type": "string" - }], - "responses": { - "200": { - "description": "Request was successful", - "schema": { - "$ref": "#/definitions/Margin" - } - } - }, - "deprecated": false - } - } - }, - "tags": [{ - "name": "Announcement", - "description": "Public Announcements" - }, { - "name": "APIKey", - "description": "Persistent API Keys for Developers" - }, { - "name": "Chat", - "description": "Trollbox Data" - }, { - "name": "Execution", - "description": "Raw Order and Balance Data" - }, { - "name": "Funding", - "description": "Swap Funding History" - }, { - "name": "Instrument", - "description": "Tradeable Contracts, Indices, and History" - }, { - "name": "Insurance", - "description": "Insurance Fund Data" - }, { - "name": "Leaderboard", - "description": "Information on Top Users" - }, { - "name": "Liquidation", - "description": "Active Liquidations" - }, { - "name": "Notification", - "description": "Account Notifications" - }, { - "name": "Order", - "description": "Placement, Cancellation, Amending, and History" - }, { - "name": "OrderBook", - "description": "Level 2 Book Data" - }, { - "name": "Position", - "description": "Summary of Open and Closed Positions" - }, { - "name": "Quote", - "description": "Best Bid/Offer Snapshots & Historical Bins" - }, { - "name": "Schema", - "description": "Dynamic Schemata for Developers" - }, { - "name": "Settlement", - "description": "Historical Settlement Data" - }, { - "name": "Stats", - "description": "Exchange Statistics" - }, { - "name": "Trade", - "description": "Individual & Bucketed Trades" - }, { - "name": "User", - "description": "Account Operations" - }], - "consumes": ["application/json", "application/x-www-form-urlencoded"], - "produces": ["application/json", "application/xml", "text/xml", "application/javascript", "text/javascript"], - "definitions": { - "Announcement": { - "description": "Public Announcements", - "properties": { - "id": { - "format": "int32", - "type": "number" - }, - "link": { - "type": "string" - }, - "title": { - "type": "string" - }, - "content": { - "type": "string" - }, - "date": { - "type": "string", - "format": "date-time" - } - }, - "required": ["id"], - "type": "object" - }, - "Error": { - "properties": { - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "name": { - "type": "string" - } - } - } - }, - "required": ["error"], - "type": "object" - }, - "x-any": { - "properties": {}, - "type": "object" - }, - "APIKey": { - "description": "Persistent API Keys for Developers", - "properties": { - "id": { - "maxLength": 24, - "type": "string" - }, - "secret": { - "maxLength": 48, - "type": "string" - }, - "name": { - "maxLength": 64, - "type": "string" - }, - "nonce": { - "format": "int64", - "default": 0, - "type": "number" - }, - "cidr": { - "maxLength": 18, - "type": "string" - }, - "permissions": { - "default": [], - "type": "array", - "items": { - "$ref": "#/definitions/x-any" - } - }, - "enabled": { - "default": false, - "type": "boolean" - }, - "userId": { - "format": "int32", - "type": "number" - }, - "created": { - "type": "string", - "format": "date-time" - } - }, - "required": ["id", "secret", "name", "nonce", "userId"], - "type": "object" - }, - "Chat": { - "description": "Trollbox Data", - "properties": { - "id": { - "format": "int32", - "type": "number" - }, - "date": { - "type": "string", - "format": "date-time" - }, - "user": { - "type": "string" - }, - "message": { - "type": "string" - }, - "html": { - "type": "string" - }, - "fromBot": { - "default": false, - "type": "boolean" - }, - "channelID": { - "type": "number", - "format": "double" - } - }, - "required": ["date", "user", "message", "html"], - "type": "object" - }, - "ChatChannel": { - "properties": { - "id": { - "format": "int32", - "type": "number" - }, - "name": { - "type": "string" - } - }, - "required": ["name"], - "type": "object" - }, - "ConnectedUsers": { - "properties": { - "users": { - "format": "int32", - "type": "number" - }, - "bots": { - "format": "int32", - "type": "number" - } - }, - "type": "object" - }, - "Execution": { - "description": "Raw Order and Balance Data", - "properties": { - "execID": { - "format": "guid", - "type": "string" - }, - "orderID": { - "format": "guid", - "type": "string" - }, - "clOrdID": { - "type": "string" - }, - "clOrdLinkID": { - "type": "string" - }, - "account": { - "format": "int64", - "type": "number" - }, - "symbol": { - "type": "string" - }, - "side": { - "type": "string" - }, - "lastQty": { - "format": "int64", - "type": "number" - }, - "lastPx": { - "format": "double", - "type": "number" - }, - "underlyingLastPx": { - "format": "double", - "type": "number" - }, - "lastMkt": { - "type": "string" - }, - "lastLiquidityInd": { - "type": "string" - }, - "simpleOrderQty": { - "format": "double", - "type": "number" - }, - "orderQty": { - "format": "int64", - "type": "number" - }, - "price": { - "format": "double", - "type": "number" - }, - "displayQty": { - "format": "int64", - "type": "number" - }, - "stopPx": { - "format": "double", - "type": "number" - }, - "pegOffsetValue": { - "format": "double", - "type": "number" - }, - "pegPriceType": { - "type": "string" - }, - "currency": { - "type": "string" - }, - "settlCurrency": { - "type": "string" - }, - "execType": { - "type": "string" - }, - "ordType": { - "type": "string" - }, - "timeInForce": { - "type": "string" - }, - "execInst": { - "type": "string" - }, - "contingencyType": { - "type": "string" - }, - "exDestination": { - "type": "string" - }, - "ordStatus": { - "type": "string" - }, - "triggered": { - "type": "string" - }, - "workingIndicator": { - "type": "boolean" - }, - "ordRejReason": { - "type": "string" - }, - "simpleLeavesQty": { - "format": "double", - "type": "number" - }, - "leavesQty": { - "format": "int64", - "type": "number" - }, - "simpleCumQty": { - "format": "double", - "type": "number" - }, - "cumQty": { - "format": "int64", - "type": "number" - }, - "avgPx": { - "format": "double", - "type": "number" - }, - "commission": { - "format": "double", - "type": "number" - }, - "tradePublishIndicator": { - "type": "string" - }, - "multiLegReportingType": { - "type": "string" - }, - "text": { - "type": "string" - }, - "trdMatchID": { - "format": "guid", - "type": "string" - }, - "execCost": { - "format": "int64", - "type": "number" - }, - "execComm": { - "format": "int64", - "type": "number" - }, - "homeNotional": { - "format": "double", - "type": "number" - }, - "foreignNotional": { - "format": "double", - "type": "number" - }, - "transactTime": { - "format": "date-time", - "type": "string" - }, - "timestamp": { - "format": "date-time", - "type": "string" - } - }, - "required": ["execID"], - "type": "object" - }, - "Funding": { - "description": "Swap Funding History", - "properties": { - "timestamp": { - "format": "date-time", - "type": "string" - }, - "symbol": { - "type": "string" - }, - "fundingInterval": { - "format": "date-time", - "type": "string" - }, - "fundingRate": { - "format": "double", - "type": "number" - }, - "fundingRateDaily": { - "format": "double", - "type": "number" - } - }, - "required": ["timestamp", "symbol"], - "type": "object" - }, - "Instrument": { - "description": "Tradeable Contracts, Indices, and History", - "properties": { - "symbol": { - "type": "string" - }, - "rootSymbol": { - "type": "string" - }, - "state": { - "type": "string" - }, - "typ": { - "type": "string" - }, - "listing": { - "format": "date-time", - "type": "string" - }, - "front": { - "format": "date-time", - "type": "string" - }, - "expiry": { - "format": "date-time", - "type": "string" - }, - "settle": { - "format": "date-time", - "type": "string" - }, - "relistInterval": { - "format": "date-time", - "type": "string" - }, - "inverseLeg": { - "type": "string" - }, - "sellLeg": { - "type": "string" - }, - "buyLeg": { - "type": "string" - }, - "optionStrikePcnt": { - "format": "double", - "type": "number" - }, - "optionStrikeRound": { - "format": "double", - "type": "number" - }, - "optionStrikePrice": { - "format": "double", - "type": "number" - }, - "optionMultiplier": { - "format": "double", - "type": "number" - }, - "positionCurrency": { - "type": "string" - }, - "underlying": { - "type": "string" - }, - "quoteCurrency": { - "type": "string" - }, - "underlyingSymbol": { - "type": "string" - }, - "reference": { - "type": "string" - }, - "referenceSymbol": { - "type": "string" - }, - "calcInterval": { - "format": "date-time", - "type": "string" - }, - "publishInterval": { - "format": "date-time", - "type": "string" - }, - "publishTime": { - "format": "date-time", - "type": "string" - }, - "maxOrderQty": { - "format": "int64", - "type": "number" - }, - "maxPrice": { - "format": "double", - "type": "number" - }, - "lotSize": { - "format": "int64", - "type": "number" - }, - "tickSize": { - "format": "double", - "type": "number" - }, - "multiplier": { - "format": "int64", - "type": "number" - }, - "settlCurrency": { - "type": "string" - }, - "underlyingToPositionMultiplier": { - "format": "int64", - "type": "number" - }, - "underlyingToSettleMultiplier": { - "format": "int64", - "type": "number" - }, - "quoteToSettleMultiplier": { - "format": "int64", - "type": "number" - }, - "isQuanto": { - "type": "boolean" - }, - "isInverse": { - "type": "boolean" - }, - "initMargin": { - "format": "double", - "type": "number" - }, - "maintMargin": { - "format": "double", - "type": "number" - }, - "riskLimit": { - "format": "int64", - "type": "number" - }, - "riskStep": { - "format": "int64", - "type": "number" - }, - "limit": { - "format": "double", - "type": "number" - }, - "capped": { - "type": "boolean" - }, - "taxed": { - "type": "boolean" - }, - "deleverage": { - "type": "boolean" - }, - "makerFee": { - "format": "double", - "type": "number" - }, - "takerFee": { - "format": "double", - "type": "number" - }, - "settlementFee": { - "format": "double", - "type": "number" - }, - "insuranceFee": { - "format": "double", - "type": "number" - }, - "fundingBaseSymbol": { - "type": "string" - }, - "fundingQuoteSymbol": { - "type": "string" - }, - "fundingPremiumSymbol": { - "type": "string" - }, - "fundingTimestamp": { - "format": "date-time", - "type": "string" - }, - "fundingInterval": { - "format": "date-time", - "type": "string" - }, - "fundingRate": { - "format": "double", - "type": "number" - }, - "indicativeFundingRate": { - "format": "double", - "type": "number" - }, - "rebalanceTimestamp": { - "format": "date-time", - "type": "string" - }, - "rebalanceInterval": { - "format": "date-time", - "type": "string" - }, - "openingTimestamp": { - "format": "date-time", - "type": "string" - }, - "closingTimestamp": { - "format": "date-time", - "type": "string" - }, - "sessionInterval": { - "format": "date-time", - "type": "string" - }, - "prevClosePrice": { - "format": "double", - "type": "number" - }, - "limitDownPrice": { - "format": "double", - "type": "number" - }, - "limitUpPrice": { - "format": "double", - "type": "number" - }, - "bankruptLimitDownPrice": { - "format": "double", - "type": "number" - }, - "bankruptLimitUpPrice": { - "format": "double", - "type": "number" - }, - "prevTotalVolume": { - "format": "int64", - "type": "number" - }, - "totalVolume": { - "format": "int64", - "type": "number" - }, - "volume": { - "format": "int64", - "type": "number" - }, - "volume24h": { - "format": "int64", - "type": "number" - }, - "prevTotalTurnover": { - "format": "int64", - "type": "number" - }, - "totalTurnover": { - "format": "int64", - "type": "number" - }, - "turnover": { - "format": "int64", - "type": "number" - }, - "turnover24h": { - "format": "int64", - "type": "number" - }, - "prevPrice24h": { - "format": "double", - "type": "number" - }, - "vwap": { - "format": "double", - "type": "number" - }, - "highPrice": { - "format": "double", - "type": "number" - }, - "lowPrice": { - "format": "double", - "type": "number" - }, - "lastPrice": { - "format": "double", - "type": "number" - }, - "lastPriceProtected": { - "format": "double", - "type": "number" - }, - "lastTickDirection": { - "type": "string" - }, - "lastChangePcnt": { - "format": "double", - "type": "number" - }, - "bidPrice": { - "format": "double", - "type": "number" - }, - "midPrice": { - "format": "double", - "type": "number" - }, - "askPrice": { - "format": "double", - "type": "number" - }, - "impactBidPrice": { - "format": "double", - "type": "number" - }, - "impactMidPrice": { - "format": "double", - "type": "number" - }, - "impactAskPrice": { - "format": "double", - "type": "number" - }, - "hasLiquidity": { - "type": "boolean" - }, - "openInterest": { - "format": "int64", - "type": "number" - }, - "openValue": { - "format": "int64", - "type": "number" - }, - "fairMethod": { - "type": "string" - }, - "fairBasisRate": { - "format": "double", - "type": "number" - }, - "fairBasis": { - "format": "double", - "type": "number" - }, - "fairPrice": { - "format": "double", - "type": "number" - }, - "markMethod": { - "type": "string" - }, - "markPrice": { - "format": "double", - "type": "number" - }, - "indicativeTaxRate": { - "format": "double", - "type": "number" - }, - "indicativeSettlePrice": { - "format": "double", - "type": "number" - }, - "optionUnderlyingPrice": { - "format": "double", - "type": "number" - }, - "settledPrice": { - "format": "double", - "type": "number" - }, - "timestamp": { - "format": "date-time", - "type": "string" - } - }, - "required": ["symbol"], - "type": "object" - }, - "InstrumentInterval": { - "properties": { - "intervals": { - "type": "array", - "items": { - "type": "string" - } - }, - "symbols": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["intervals", "symbols"], - "type": "object" - }, - "IndexComposite": { - "properties": { - "timestamp": { - "type": "string", - "format": "date-time" - }, - "symbol": { - "type": "string" - }, - "indexSymbol": { - "type": "string" - }, - "reference": { - "type": "string" - }, - "lastPrice": { - "type": "number", - "format": "double" - }, - "weight": { - "type": "number", - "format": "double" - }, - "logged": { - "type": "string", - "format": "date-time" - } - }, - "required": ["timestamp"], - "type": "object" - }, - "Insurance": { - "description": "Insurance Fund Data", - "properties": { - "currency": { - "type": "string" - }, - "timestamp": { - "format": "date-time", - "type": "string" - }, - "walletBalance": { - "format": "int64", - "type": "number" - } - }, - "required": ["currency", "timestamp"], - "type": "object" - }, - "Leaderboard": { - "description": "Information on Top Users", - "properties": { - "name": { - "type": "string" - }, - "isRealName": { - "type": "boolean" - }, - "profit": { - "type": "number", - "format": "double" - } - }, - "required": ["name"], - "type": "object" - }, - "Liquidation": { - "description": "Active Liquidations", - "properties": { - "orderID": { - "format": "guid", - "type": "string" - }, - "symbol": { - "type": "string" - }, - "side": { - "type": "string" - }, - "price": { - "format": "double", - "type": "number" - }, - "leavesQty": { - "format": "int64", - "type": "number" - } - }, - "required": ["orderID"], - "type": "object" - }, - "Notification": { - "description": "Account Notifications", - "properties": { - "id": { - "format": "int32", - "type": "number" - }, - "date": { - "type": "string", - "format": "date-time" - }, - "title": { - "type": "string" - }, - "body": { - "type": "string" - }, - "ttl": { - "format": "int32", - "type": "number" - }, - "type": { - "enum": ["success", "error", "info"], - "type": "string" - }, - "closable": { - "default": true, - "type": "boolean" - }, - "persist": { - "default": true, - "type": "boolean" - }, - "waitForVisibility": { - "default": true, - "type": "boolean" - }, - "sound": { - "type": "string" - } - }, - "required": ["date", "title", "body", "ttl"], - "type": "object" - }, - "Order": { - "description": "Placement, Cancellation, Amending, and History", - "properties": { - "orderID": { - "format": "guid", - "type": "string" - }, - "clOrdID": { - "type": "string" - }, - "clOrdLinkID": { - "type": "string" - }, - "account": { - "format": "int64", - "type": "number" - }, - "symbol": { - "type": "string" - }, - "side": { - "type": "string" - }, - "simpleOrderQty": { - "format": "double", - "type": "number" - }, - "orderQty": { - "format": "int64", - "type": "number" - }, - "price": { - "format": "double", - "type": "number" - }, - "displayQty": { - "format": "int64", - "type": "number" - }, - "stopPx": { - "format": "double", - "type": "number" - }, - "pegOffsetValue": { - "format": "double", - "type": "number" - }, - "pegPriceType": { - "type": "string" - }, - "currency": { - "type": "string" - }, - "settlCurrency": { - "type": "string" - }, - "ordType": { - "type": "string" - }, - "timeInForce": { - "type": "string" - }, - "execInst": { - "type": "string" - }, - "contingencyType": { - "type": "string" - }, - "exDestination": { - "type": "string" - }, - "ordStatus": { - "type": "string" - }, - "triggered": { - "type": "string" - }, - "workingIndicator": { - "type": "boolean" - }, - "ordRejReason": { - "type": "string" - }, - "simpleLeavesQty": { - "format": "double", - "type": "number" - }, - "leavesQty": { - "format": "int64", - "type": "number" - }, - "simpleCumQty": { - "format": "double", - "type": "number" - }, - "cumQty": { - "format": "int64", - "type": "number" - }, - "avgPx": { - "format": "double", - "type": "number" - }, - "multiLegReportingType": { - "type": "string" - }, - "text": { - "type": "string" - }, - "transactTime": { - "format": "date-time", - "type": "string" - }, - "timestamp": { - "format": "date-time", - "type": "string" - } - }, - "required": ["orderID"], - "type": "object" - }, - "OrderBookL2": { - "properties": { - "symbol": { - "type": "string" - }, - "id": { - "format": "int64", - "type": "number" - }, - "side": { - "type": "string" - }, - "size": { - "format": "int64", - "type": "number" - }, - "price": { - "format": "double", - "type": "number" - } - }, - "required": ["symbol", "id", "side"], - "type": "object" - }, - "Position": { - "description": "Summary of Open and Closed Positions", - "properties": { - "account": { - "format": "int64", - "default": 0, - "type": "number" - }, - "symbol": { - "type": "string" - }, - "currency": { - "type": "string" - }, - "underlying": { - "type": "string" - }, - "quoteCurrency": { - "type": "string" - }, - "commission": { - "format": "double", - "default": 0, - "type": "number" - }, - "initMarginReq": { - "format": "double", - "default": 0, - "type": "number" - }, - "maintMarginReq": { - "format": "double", - "default": 0, - "type": "number" - }, - "riskLimit": { - "format": "int64", - "default": 0, - "type": "number" - }, - "leverage": { - "format": "double", - "default": 0, - "type": "number" - }, - "crossMargin": { - "type": "boolean" - }, - "deleveragePercentile": { - "format": "double", - "default": 0, - "type": "number" - }, - "rebalancedPnl": { - "format": "int64", - "default": 0, - "type": "number" - }, - "prevRealisedPnl": { - "format": "int64", - "default": 0, - "type": "number" - }, - "prevUnrealisedPnl": { - "format": "int64", - "default": 0, - "type": "number" - }, - "prevClosePrice": { - "format": "double", - "default": 0, - "type": "number" - }, - "openingTimestamp": { - "format": "date-time", - "type": "string" - }, - "openingQty": { - "format": "int64", - "default": 0, - "type": "number" - }, - "openingCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "openingComm": { - "format": "int64", - "default": 0, - "type": "number" - }, - "openOrderBuyQty": { - "format": "int64", - "default": 0, - "type": "number" - }, - "openOrderBuyCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "openOrderBuyPremium": { - "format": "int64", - "default": 0, - "type": "number" - }, - "openOrderSellQty": { - "format": "int64", - "default": 0, - "type": "number" - }, - "openOrderSellCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "openOrderSellPremium": { - "format": "int64", - "default": 0, - "type": "number" - }, - "execBuyQty": { - "format": "int64", - "default": 0, - "type": "number" - }, - "execBuyCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "execSellQty": { - "format": "int64", - "default": 0, - "type": "number" - }, - "execSellCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "execQty": { - "format": "int64", - "default": 0, - "type": "number" - }, - "execCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "execComm": { - "format": "int64", - "default": 0, - "type": "number" - }, - "currentTimestamp": { - "format": "date-time", - "type": "string" - }, - "currentQty": { - "format": "int64", - "default": 0, - "type": "number" - }, - "currentCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "currentComm": { - "format": "int64", - "default": 0, - "type": "number" - }, - "realisedCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "unrealisedCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "grossOpenCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "grossOpenPremium": { - "format": "int64", - "default": 0, - "type": "number" - }, - "grossExecCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "isOpen": { - "type": "boolean" - }, - "markPrice": { - "format": "double", - "default": 0, - "type": "number" - }, - "markValue": { - "format": "int64", - "default": 0, - "type": "number" - }, - "riskValue": { - "format": "int64", - "default": 0, - "type": "number" - }, - "homeNotional": { - "format": "double", - "default": 0, - "type": "number" - }, - "foreignNotional": { - "format": "double", - "default": 0, - "type": "number" - }, - "posState": { - "type": "string" - }, - "posCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "posCost2": { - "format": "int64", - "default": 0, - "type": "number" - }, - "posCross": { - "format": "int64", - "default": 0, - "type": "number" - }, - "posInit": { - "format": "int64", - "default": 0, - "type": "number" - }, - "posComm": { - "format": "int64", - "default": 0, - "type": "number" - }, - "posLoss": { - "format": "int64", - "default": 0, - "type": "number" - }, - "posMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "posMaint": { - "format": "int64", - "default": 0, - "type": "number" - }, - "posAllowance": { - "format": "int64", - "default": 0, - "type": "number" - }, - "taxableMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "initMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "maintMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "sessionMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "targetExcessMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "varMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "realisedGrossPnl": { - "format": "int64", - "default": 0, - "type": "number" - }, - "realisedTax": { - "format": "int64", - "default": 0, - "type": "number" - }, - "realisedPnl": { - "format": "int64", - "default": 0, - "type": "number" - }, - "unrealisedGrossPnl": { - "format": "int64", - "default": 0, - "type": "number" - }, - "longBankrupt": { - "format": "int64", - "default": 0, - "type": "number" - }, - "shortBankrupt": { - "format": "int64", - "default": 0, - "type": "number" - }, - "taxBase": { - "format": "int64", - "default": 0, - "type": "number" - }, - "indicativeTaxRate": { - "format": "double", - "default": 0, - "type": "number" - }, - "indicativeTax": { - "format": "int64", - "default": 0, - "type": "number" - }, - "unrealisedTax": { - "format": "int64", - "default": 0, - "type": "number" - }, - "unrealisedPnl": { - "format": "int64", - "default": 0, - "type": "number" - }, - "unrealisedPnlPcnt": { - "format": "double", - "default": 0, - "type": "number" - }, - "unrealisedRoePcnt": { - "format": "double", - "default": 0, - "type": "number" - }, - "simpleQty": { - "format": "double", - "default": 0, - "type": "number" - }, - "simpleCost": { - "format": "double", - "default": 0, - "type": "number" - }, - "simpleValue": { - "format": "double", - "default": 0, - "type": "number" - }, - "simplePnl": { - "format": "double", - "default": 0, - "type": "number" - }, - "simplePnlPcnt": { - "format": "double", - "default": 0, - "type": "number" - }, - "avgCostPrice": { - "format": "double", - "default": 0, - "type": "number" - }, - "avgEntryPrice": { - "format": "double", - "default": 0, - "type": "number" - }, - "breakEvenPrice": { - "format": "double", - "default": 0, - "type": "number" - }, - "marginCallPrice": { - "format": "double", - "default": 0, - "type": "number" - }, - "liquidationPrice": { - "format": "double", - "default": 0, - "type": "number" - }, - "bankruptPrice": { - "format": "double", - "default": 0, - "type": "number" - }, - "timestamp": { - "format": "date-time", - "type": "string" - }, - "lastPrice": { - "format": "double", - "default": 0, - "type": "number" - }, - "lastValue": { - "format": "int64", - "default": 0, - "type": "number" - } - }, - "required": ["account", "symbol", "currency"], - "type": "object" - }, - "Quote": { - "description": "Best Bid/Offer Snapshots & Historical Bins", - "properties": { - "timestamp": { - "format": "date-time", - "type": "string" - }, - "symbol": { - "type": "string" - }, - "bidSize": { - "format": "int64", - "type": "number" - }, - "bidPrice": { - "format": "double", - "type": "number" - }, - "askPrice": { - "format": "double", - "type": "number" - }, - "askSize": { - "format": "int64", - "type": "number" - } - }, - "required": ["timestamp", "symbol"], - "type": "object" - }, - "Settlement": { - "description": "Historical Settlement Data", - "properties": { - "timestamp": { - "format": "date-time", - "type": "string" - }, - "symbol": { - "type": "string" - }, - "settlementType": { - "type": "string" - }, - "settledPrice": { - "format": "double", - "type": "number" - }, - "optionStrikePrice": { - "format": "double", - "type": "number" - }, - "optionUnderlyingPrice": { - "format": "double", - "type": "number" - }, - "bankrupt": { - "format": "int64", - "type": "number" - }, - "taxBase": { - "format": "int64", - "type": "number" - }, - "taxRate": { - "format": "double", - "type": "number" - } - }, - "required": ["timestamp", "symbol"], - "type": "object" - }, - "Stats": { - "description": "Exchange Statistics", - "properties": { - "rootSymbol": { - "type": "string" - }, - "currency": { - "type": "string" - }, - "volume24h": { - "format": "int64", - "type": "number" - }, - "turnover24h": { - "format": "int64", - "type": "number" - }, - "openInterest": { - "format": "int64", - "type": "number" - }, - "openValue": { - "format": "int64", - "type": "number" - } - }, - "required": ["rootSymbol"], - "type": "object" - }, - "StatsHistory": { - "properties": { - "date": { - "type": "string", - "format": "date-time" - }, - "rootSymbol": { - "type": "string" - }, - "currency": { - "type": "string" - }, - "volume": { - "format": "int64", - "type": "number" - }, - "turnover": { - "format": "int64", - "type": "number" - } - }, - "required": ["date", "rootSymbol"], - "type": "object" - }, - "StatsUSD": { - "properties": { - "rootSymbol": { - "type": "string" - }, - "currency": { - "type": "string" - }, - "turnover24h": { - "format": "int64", - "type": "number" - }, - "turnover30d": { - "format": "int64", - "type": "number" - }, - "turnover365d": { - "format": "int64", - "type": "number" - }, - "turnover": { - "format": "int64", - "type": "number" - } - }, - "required": ["rootSymbol"], - "type": "object" - }, - "Trade": { - "description": "Individual & Bucketed Trades", - "properties": { - "timestamp": { - "format": "date-time", - "type": "string" - }, - "symbol": { - "type": "string" - }, - "side": { - "type": "string" - }, - "size": { - "format": "int64", - "type": "number" - }, - "price": { - "format": "double", - "type": "number" - }, - "tickDirection": { - "type": "string" - }, - "trdMatchID": { - "format": "guid", - "type": "string" - }, - "grossValue": { - "format": "int64", - "type": "number" - }, - "homeNotional": { - "format": "double", - "type": "number" - }, - "foreignNotional": { - "format": "double", - "type": "number" - } - }, - "required": ["timestamp", "symbol"], - "type": "object" - }, - "TradeBin": { - "properties": { - "timestamp": { - "format": "date-time", - "type": "string" - }, - "symbol": { - "type": "string" - }, - "open": { - "format": "double", - "type": "number" - }, - "high": { - "format": "double", - "type": "number" - }, - "low": { - "format": "double", - "type": "number" - }, - "close": { - "format": "double", - "type": "number" - }, - "trades": { - "format": "int64", - "type": "number" - }, - "volume": { - "format": "int64", - "type": "number" - }, - "vwap": { - "format": "double", - "type": "number" - }, - "lastSize": { - "format": "int64", - "type": "number" - }, - "turnover": { - "format": "int64", - "type": "number" - }, - "homeNotional": { - "format": "double", - "type": "number" - }, - "foreignNotional": { - "format": "double", - "type": "number" - } - }, - "required": ["timestamp", "symbol"], - "type": "object" - }, - "Wallet": { - "properties": { - "account": { - "format": "int64", - "type": "number" - }, - "currency": { - "type": "string" - }, - "prevDeposited": { - "format": "int64", - "type": "number" - }, - "prevWithdrawn": { - "format": "int64", - "type": "number" - }, - "prevTransferIn": { - "format": "int64", - "type": "number" - }, - "prevTransferOut": { - "format": "int64", - "type": "number" - }, - "prevAmount": { - "format": "int64", - "type": "number" - }, - "prevTimestamp": { - "format": "date-time", - "type": "string" - }, - "deltaDeposited": { - "format": "int64", - "type": "number" - }, - "deltaWithdrawn": { - "format": "int64", - "type": "number" - }, - "deltaTransferIn": { - "format": "int64", - "type": "number" - }, - "deltaTransferOut": { - "format": "int64", - "type": "number" - }, - "deltaAmount": { - "format": "int64", - "type": "number" - }, - "deposited": { - "format": "int64", - "type": "number" - }, - "withdrawn": { - "format": "int64", - "type": "number" - }, - "transferIn": { - "format": "int64", - "type": "number" - }, - "transferOut": { - "format": "int64", - "type": "number" - }, - "amount": { - "format": "int64", - "type": "number" - }, - "pendingCredit": { - "format": "int64", - "type": "number" - }, - "pendingDebit": { - "format": "int64", - "type": "number" - }, - "confirmedDebit": { - "format": "int64", - "type": "number" - }, - "timestamp": { - "format": "date-time", - "type": "string" - }, - "addr": { - "type": "string" - }, - "script": { - "type": "string" - }, - "withdrawalLock": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["account", "currency"], - "type": "object" - }, - "Transaction": { - "properties": { - "transactID": { - "format": "guid", - "type": "string" - }, - "account": { - "format": "int64", - "type": "number" - }, - "currency": { - "type": "string" - }, - "transactType": { - "type": "string" - }, - "amount": { - "format": "int64", - "type": "number" - }, - "fee": { - "format": "int64", - "type": "number" - }, - "transactStatus": { - "type": "string" - }, - "address": { - "type": "string" - }, - "tx": { - "type": "string" - }, - "text": { - "type": "string" - }, - "transactTime": { - "format": "date-time", - "type": "string" - }, - "timestamp": { - "format": "date-time", - "type": "string" - } - }, - "required": ["transactID"], - "type": "object" - }, - "AccessToken": { - "properties": { - "id": { - "type": "string" - }, - "ttl": { - "default": 1209600, - "description": "time to live in seconds (2 weeks by default)", - "type": "number", - "format": "double" - }, - "created": { - "type": "string", - "format": "date-time" - }, - "userId": { - "type": "number", - "format": "double" - } - }, - "required": ["id"], - "type": "object" - }, - "Affiliate": { - "properties": { - "account": { - "format": "int64", - "type": "number" - }, - "currency": { - "type": "string" - }, - "prevPayout": { - "format": "int64", - "type": "number" - }, - "prevTurnover": { - "format": "int64", - "type": "number" - }, - "prevComm": { - "format": "int64", - "type": "number" - }, - "prevTimestamp": { - "format": "date-time", - "type": "string" - }, - "execTurnover": { - "format": "int64", - "type": "number" - }, - "execComm": { - "format": "int64", - "type": "number" - }, - "totalReferrals": { - "format": "int64", - "type": "number" - }, - "totalTurnover": { - "format": "int64", - "type": "number" - }, - "totalComm": { - "format": "int64", - "type": "number" - }, - "payoutPcnt": { - "format": "double", - "type": "number" - }, - "pendingPayout": { - "format": "int64", - "type": "number" - }, - "timestamp": { - "format": "date-time", - "type": "string" - }, - "referrerAccount": { - "type": "number", - "format": "double" - } - }, - "required": ["account", "currency"], - "type": "object" - }, - "User": { - "description": "Account Operations", - "properties": { - "id": { - "format": "int32", - "type": "number" - }, - "ownerId": { - "format": "int32", - "type": "number" - }, - "firstname": { - "type": "string" - }, - "lastname": { - "type": "string" - }, - "username": { - "type": "string" - }, - "email": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "created": { - "type": "string", - "format": "date-time" - }, - "lastUpdated": { - "type": "string", - "format": "date-time" - }, - "preferences": { - "default": null, - "$ref": "#/definitions/UserPreferences" - }, - "TFAEnabled": { - "type": "string" - }, - "affiliateID": { - "maxLength": 6, - "type": "string" - }, - "pgpPubKey": { - "maxLength": 16384, - "type": "string" - }, - "country": { - "maxLength": 3, - "type": "string" - }, - "geoipCountry": { - "maxLength": 2, - "type": "string" - }, - "geoipRegion": { - "maxLength": 2, - "type": "string" - }, - "typ": { - "type": "string" - } - }, - "required": ["username", "email"], - "type": "object" - }, - "UserCommission": { - "properties": { - "makerFee": { - "type": "number", - "format": "double" - }, - "takerFee": { - "type": "number", - "format": "double" - }, - "settlementFee": { - "type": "number", - "format": "double" - }, - "maxFee": { - "type": "number", - "format": "double" - } - }, - "type": "object" - }, - "Margin": { - "properties": { - "account": { - "format": "int64", - "default": 0, - "type": "number" - }, - "currency": { - "type": "string" - }, - "riskLimit": { - "format": "int64", - "default": 0, - "type": "number" - }, - "prevState": { - "type": "string" - }, - "state": { - "type": "string" - }, - "action": { - "type": "string" - }, - "amount": { - "format": "int64", - "default": 0, - "type": "number" - }, - "pendingCredit": { - "format": "int64", - "default": 0, - "type": "number" - }, - "pendingDebit": { - "format": "int64", - "default": 0, - "type": "number" - }, - "confirmedDebit": { - "format": "int64", - "default": 0, - "type": "number" - }, - "prevRealisedPnl": { - "format": "int64", - "default": 0, - "type": "number" - }, - "prevUnrealisedPnl": { - "format": "int64", - "default": 0, - "type": "number" - }, - "grossComm": { - "format": "int64", - "default": 0, - "type": "number" - }, - "grossOpenCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "grossOpenPremium": { - "format": "int64", - "default": 0, - "type": "number" - }, - "grossExecCost": { - "format": "int64", - "default": 0, - "type": "number" - }, - "grossMarkValue": { - "format": "int64", - "default": 0, - "type": "number" - }, - "riskValue": { - "format": "int64", - "default": 0, - "type": "number" - }, - "taxableMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "initMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "maintMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "sessionMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "targetExcessMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "varMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "realisedPnl": { - "format": "int64", - "default": 0, - "type": "number" - }, - "unrealisedPnl": { - "format": "int64", - "default": 0, - "type": "number" - }, - "indicativeTax": { - "format": "int64", - "default": 0, - "type": "number" - }, - "unrealisedProfit": { - "format": "int64", - "default": 0, - "type": "number" - }, - "syntheticMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "walletBalance": { - "format": "int64", - "default": 0, - "type": "number" - }, - "marginBalance": { - "format": "int64", - "default": 0, - "type": "number" - }, - "marginBalancePcnt": { - "format": "double", - "default": 0, - "type": "number" - }, - "marginLeverage": { - "format": "double", - "default": 0, - "type": "number" - }, - "marginUsedPcnt": { - "format": "double", - "default": 0, - "type": "number" - }, - "excessMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "excessMarginPcnt": { - "format": "double", - "default": 0, - "type": "number" - }, - "availableMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "withdrawableMargin": { - "format": "int64", - "default": 0, - "type": "number" - }, - "timestamp": { - "format": "date-time", - "type": "string" - }, - "grossLastValue": { - "format": "int64", - "default": 0, - "type": "number" - }, - "commission": { - "format": "double", - "default": 0, - "type": "number" - } - }, - "required": ["account", "currency"], - "type": "object" - }, - "UserPreferences": { - "properties": { - "alertOnLiquidations": { - "type": "boolean" - }, - "animationsEnabled": { - "type": "boolean" - }, - "announcementsLastSeen": { - "type": "string", - "format": "date-time" - }, - "chatChannelID": { - "type": "number", - "format": "double" - }, - "colorTheme": { - "type": "string" - }, - "currency": { - "type": "string" - }, - "debug": { - "type": "boolean" - }, - "disableEmails": { - "type": "array", - "items": { - "type": "string" - } - }, - "hideConfirmDialogs": { - "type": "array", - "items": { - "type": "string" - } - }, - "hideConnectionModal": { - "type": "boolean" - }, - "hideFromLeaderboard": { - "default": false, - "type": "boolean" - }, - "hideNameFromLeaderboard": { - "default": true, - "type": "boolean" - }, - "hideNotifications": { - "type": "array", - "items": { - "type": "string" - } - }, - "locale": { - "default": "en-US", - "type": "string" - }, - "msgsSeen": { - "type": "array", - "items": { - "type": "string" - } - }, - "orderBookBinning": { - "type": "object" - }, - "orderBookType": { - "type": "string" - }, - "orderClearImmediate": { - "default": false, - "type": "boolean" - }, - "orderControlsPlusMinus": { - "type": "boolean" - }, - "showLocaleNumbers": { - "default": true, - "type": "boolean" - }, - "sounds": { - "type": "array", - "items": { - "type": "string" - } - }, - "strictIPCheck": { - "default": false, - "type": "boolean" - }, - "strictTimeout": { - "default": true, - "type": "boolean" - }, - "tickerGroup": { - "type": "string" - }, - "tickerPinned": { - "type": "boolean" - }, - "tradeLayout": { - "type": "string" - } - }, - "type": "object" - } - }, - "securityDefinitions": { - "apiKey": { - "type": "apiKey", - "in": "header", - "name": "api-key" - }, - "apiSignature": { - "type": "apiKey", - "in": "header", - "name": "api-signature" - }, - "apiNonce": { - "type": "apiKey", - "in": "header", - "name": "api-nonce" - } - }, - "security": [{ - "apiKey": [], - "apiSignature": [], - "apiNonce": [] - }] -} diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 827ebdb8..e2730e1c 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -1,6 +1,7 @@ package bitstamp import ( + "encoding/json" "errors" "fmt" "net/url" @@ -43,6 +44,7 @@ const ( bitstampAPIBitcoinDeposit = "bitcoin_deposit_address" bitstampAPILitecoinDeposit = "ltc_address" bitstampAPIEthereumDeposit = "eth_address" + bitstampAPIBitcoinCashDeposit = "bch_address" bitstampAPIUnconfirmedBitcoin = "unconfirmed_btc" bitstampAPITransferToMain = "transfer-to-main" bitstampAPITransferFromMain = "transfer-from-main" @@ -565,29 +567,34 @@ func (b *Bitstamp) OpenInternationalBankWithdrawal(amount float64, currency, } // GetCryptoDepositAddress returns a depositing address by crypto -// crypto - example "btc", "ltc", "eth", or "xrp" +// crypto - example "btc", "ltc", "eth", "xrp" or "bch" func (b *Bitstamp) GetCryptoDepositAddress(crypto string) (string, error) { - type response struct { - Address string `json:"address"` - } - resp := response{} + var resp string - switch common.StringToLower(crypto) { - case "btc": - return resp.Address, - b.SendAuthenticatedHTTPRequest(bitstampAPIBitcoinDeposit, false, nil, &resp.Address) - case "ltc": - return resp.Address, + switch crypto { + case symbol.BTC: + return resp, + b.SendAuthenticatedHTTPRequest(bitstampAPIBitcoinDeposit, false, nil, &resp) + + case symbol.LTC: + return resp, b.SendAuthenticatedHTTPRequest(bitstampAPILitecoinDeposit, true, nil, &resp) - case "eth": - return resp.Address, - b.SendAuthenticatedHTTPRequest(bitstampAPIEthereumDeposit, true, nil, &resp) - case "xrp": - return resp.Address, - b.SendAuthenticatedHTTPRequest(bitstampAPIXrpDeposit, true, nil, &resp) - } - return resp.Address, errors.New("incorrect cryptocurrency string") + case symbol.ETH: + return resp, + b.SendAuthenticatedHTTPRequest(bitstampAPIEthereumDeposit, true, nil, &resp) + + case symbol.XRP: + return resp, + b.SendAuthenticatedHTTPRequest(bitstampAPIXrpDeposit, true, nil, &resp) + + case symbol.BCH: + return resp, + b.SendAuthenticatedHTTPRequest(bitstampAPIBitcoinCashDeposit, true, nil, &resp) + + default: + return resp, fmt.Errorf("unsupported cryptocurrency string %s", crypto) + } } // GetUnconfirmedBitcoinDeposits returns unconfirmed transactions @@ -628,7 +635,7 @@ func (b *Bitstamp) SendHTTPRequest(path string, result interface{}) error { } // SendAuthenticatedHTTPRequest sends an authenticated request -func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url.Values, result interface{}) (err error) { +func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url.Values, result interface{}) error { if !b.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) } @@ -663,5 +670,23 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url encodedValues := values.Encode() readerValues := strings.NewReader(encodedValues) - return b.SendPayload("POST", path, headers, readerValues, result, true, b.Verbose) + + interim := json.RawMessage{} + + errCap := struct { + Error string `json:"error"` + }{} + + err := b.SendPayload("POST", path, headers, readerValues, &interim, true, b.Verbose) + if err != nil { + return err + } + + if err := common.JSONDecode(interim, &errCap); err == nil { + if errCap.Error != "" { + return errors.New(errCap.Error) + } + } + + return common.JSONDecode(interim, result) } diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index ae8505a7..3cdabfbb 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -5,11 +5,10 @@ import ( "testing" "time" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/currency/symbol" "github.com/thrasher-/gocryptotrader/exchanges" - - "github.com/thrasher-/gocryptotrader/config" ) // Please add your private keys and customerID for better tests @@ -567,3 +566,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Withdraw failed to be placed: %v", err) } } + +func TestGetDepositAddress(t *testing.T) { + if areTestAPIKeysSet() && customerID != "" { + _, err := b.GetDepositAddress(symbol.BTC, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress error", err) + } + } else { + _, err := b.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress error cannot be nil") + } + } +} diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index a3294237..2e4e43f8 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -120,35 +120,42 @@ func (b *Bitstamp) UpdateOrderbook(p pair.CurrencyPair, assetType string) (order // Bitstamp exchange func (b *Bitstamp) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = b.GetName() + response.Exchange = b.GetName() accountBalance, err := b.GetBalance() if err != nil { return response, err } - response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{ + var currencies []exchange.AccountCurrencyInfo + + currencies = append(currencies, exchange.AccountCurrencyInfo{ CurrencyName: "BTC", TotalValue: accountBalance.BTCAvailable, Hold: accountBalance.BTCReserved, }) - response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{ + currencies = append(currencies, exchange.AccountCurrencyInfo{ CurrencyName: "XRP", TotalValue: accountBalance.XRPAvailable, Hold: accountBalance.XRPReserved, }) - response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{ + currencies = append(currencies, exchange.AccountCurrencyInfo{ CurrencyName: "USD", TotalValue: accountBalance.USDAvailable, Hold: accountBalance.USDReserved, }) - response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{ + currencies = append(currencies, exchange.AccountCurrencyInfo{ CurrencyName: "EUR", TotalValue: accountBalance.EURAvailable, Hold: accountBalance.EURReserved, }) + + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -219,8 +226,8 @@ func (b *Bitstamp) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (b *Bitstamp) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (b *Bitstamp) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + return b.GetCryptoDepositAddress(cryptocurrency.String()) } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index ac0f4936..5c537bcd 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -168,15 +168,6 @@ func TestGetAccountBalanceByCurrency(t *testing.T) { } } -func TestGetDepositAddress(t *testing.T) { - t.Parallel() - - _, err := b.GetDepositAddress("btc") - if err == nil { - t.Error("Test Failed - Bittrex - GetDepositAddress() error") - } -} - func TestGetOrder(t *testing.T) { t.Parallel() @@ -481,3 +472,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + if areTestAPIKeysSet() { + _, err := b.GetDepositAddress(symbol.BTC, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } + } else { + _, err := b.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } + } +} diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index d9ebb42d..18774ffe 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -65,19 +65,25 @@ func (b *Bittrex) Run() { // Bittrex exchange func (b *Bittrex) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = b.GetName() + response.Exchange = b.GetName() accountBalance, err := b.GetAccountBalances() if err != nil { return response, err } + var currencies []exchange.AccountCurrencyInfo for i := 0; i < len(accountBalance.Result); i++ { var exchangeCurrency exchange.AccountCurrencyInfo exchangeCurrency.CurrencyName = accountBalance.Result[i].Currency exchangeCurrency.TotalValue = accountBalance.Result[i].Balance exchangeCurrency.Hold = accountBalance.Result[i].Balance - accountBalance.Result[i].Available - response.Currencies = append(response.Currencies, exchangeCurrency) + currencies = append(currencies, exchangeCurrency) } + + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -237,8 +243,13 @@ func (b *Bittrex) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (b *Bittrex) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (b *Bittrex) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + depositAddr, err := b.GetCryptoDepositAddress(cryptocurrency.String()) + if err != nil { + return "", err + } + + return depositAddr.Result.Address, nil } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/btcc/btcc_wrapper.go b/exchanges/btcc/btcc_wrapper.go index 00151b8e..53feabcf 100644 --- a/exchanges/btcc/btcc_wrapper.go +++ b/exchanges/btcc/btcc_wrapper.go @@ -180,8 +180,8 @@ func (b *BTCC) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (b *BTCC) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (b *BTCC) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + return "", common.ErrFunctionNotSupported } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index 65a4150b..acd007cc 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -32,7 +32,7 @@ func TestSetup(t *testing.T) { t.Error("Test Failed - BTC Markets Setup() init error") } - if apiKey != "" && apiSecret != "" { + if areTestAPIKeysSet() { bConfig.APIKey = apiKey bConfig.APISecret = apiSecret bConfig.AuthenticatedAPISupport = true @@ -450,3 +450,10 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + _, err := b.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } +} diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 49b2702b..fb77e0ef 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -122,21 +122,27 @@ func (b *BTCMarkets) UpdateOrderbook(p pair.CurrencyPair, assetType string) (ord // BTCMarkets exchange func (b *BTCMarkets) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = b.GetName() + response.Exchange = b.GetName() accountBalance, err := b.GetAccountBalance() if err != nil { return response, err } + var currencies []exchange.AccountCurrencyInfo for i := 0; i < len(accountBalance); i++ { var exchangeCurrency exchange.AccountCurrencyInfo exchangeCurrency.CurrencyName = accountBalance[i].Currency exchangeCurrency.TotalValue = accountBalance[i].Balance exchangeCurrency.Hold = accountBalance[i].PendingFunds - response.Currencies = append(response.Currencies, exchangeCurrency) + currencies = append(currencies, exchangeCurrency) } + + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -256,7 +262,7 @@ func (b *BTCMarkets) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (b *BTCMarkets) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { +func (b *BTCMarkets) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { return "", common.ErrFunctionNotSupported } diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 562721bc..89027f5d 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -577,3 +577,10 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Withdraw failed to be placed: %v", err) } } + +func TestGetDepositAddress(t *testing.T) { + _, err := c.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } +} diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 8229cbf5..40c2ad8c 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -51,19 +51,26 @@ func (c *CoinbasePro) Run() { // coinbasepro exchange func (c *CoinbasePro) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = c.GetName() + response.Exchange = c.GetName() accountBalance, err := c.GetAccounts() if err != nil { return response, err } + + var currencies []exchange.AccountCurrencyInfo for i := 0; i < len(accountBalance); i++ { var exchangeCurrency exchange.AccountCurrencyInfo exchangeCurrency.CurrencyName = accountBalance[i].Currency exchangeCurrency.TotalValue = accountBalance[i].Available exchangeCurrency.Hold = accountBalance[i].Hold - response.Currencies = append(response.Currencies, exchangeCurrency) + currencies = append(currencies, exchangeCurrency) } + + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -194,8 +201,8 @@ func (c *CoinbasePro) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) } // GetDepositAddress returns a deposit address for a specified currency -func (c *CoinbasePro) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (c *CoinbasePro) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + return "", common.ErrFunctionNotSupported } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index 13183072..75a41945 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -360,3 +360,10 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + _, err := c.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() function unsupported cannot be nil") + } +} diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index bdea8979..faf600b6 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -131,8 +131,11 @@ func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { TotalValue: bal.ZEC, }) - info.ExchangeName = c.GetName() - info.Currencies = balances + info.Exchange = c.GetName() + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: balances, + }) + return info, nil } @@ -345,8 +348,8 @@ func (c *COINUT) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (c *COINUT) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (c *COINUT) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + return "", common.ErrFunctionNotSupported } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/exchange.go b/exchanges/exchange.go index ba88bb9e..814b97e1 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -188,8 +188,14 @@ const ( // AccountInfo is a Generic type to hold each exchange's holdings in // all enabled currencies type AccountInfo struct { - ExchangeName string - Currencies []AccountCurrencyInfo + Exchange string + Accounts []Account +} + +// Account defines a singular account type with asocciated currencies +type Account struct { + ID string + Currencies []AccountCurrencyInfo } // AccountCurrencyInfo is a sub type to store currency name and value @@ -308,7 +314,7 @@ type IBotExchange interface { CancelOrder(order OrderCancellation) error CancelAllOrders(orders OrderCancellation) (CancelAllOrdersResponse, error) GetOrderInfo(orderID int64) (OrderDetail, error) - GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) + GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) WithdrawCryptocurrencyFunds(wtihdrawRequest WithdrawRequest) (string, error) WithdrawFiatFunds(wtihdrawRequest WithdrawRequest) (string, error) diff --git a/exchanges/exmo/exmo.go b/exchanges/exmo/exmo.go index e582414a..1f7b2df8 100644 --- a/exchanges/exmo/exmo.go +++ b/exchanges/exmo/exmo.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "net/url" - "reflect" "strconv" "strings" "time" @@ -272,10 +271,26 @@ func (e *EXMO) GetRequiredAmount(pair string, amount float64) (RequiredAmount, e // GetCryptoDepositAddress returns a list of addresses for cryptocurrency deposits func (e *EXMO) GetCryptoDepositAddress() (map[string]string, error) { - result := make(map[string]string) + var result interface{} err := e.SendAuthenticatedHTTPRequest("POST", exmoDepositAddress, url.Values{}, &result) - log.Debug(reflect.TypeOf(result).String()) - return result, err + if err != nil { + return nil, err + } + + switch result.(type) { + case map[string]interface{}: + mapString := make(map[string]string) + + for key, value := range result.(map[string]interface{}) { + strValue := fmt.Sprintf("%v", value) + mapString[key] = strValue + } + + return mapString, nil + + default: + return nil, errors.New("no addresses found, generate required addresses via site") + } } // WithdrawCryptocurrency withdraws a cryptocurrency from the exchange to the desired address diff --git a/exchanges/exmo/exmo_test.go b/exchanges/exmo/exmo_test.go index b4b0d801..eb7b98de 100644 --- a/exchanges/exmo/exmo_test.go +++ b/exchanges/exmo/exmo_test.go @@ -93,18 +93,6 @@ func TestGetRequiredAmount(t *testing.T) { } } -func TestGetCryptoDepositAddress(t *testing.T) { - t.Parallel() - if APIKey == "" || APISecret == "" { - t.Skip() - } - TestSetup(t) - _, err := e.GetCryptoDepositAddress() - if err == nil { - t.Errorf("Test failed. Err: %s", err) - } -} - func setFeeBuilder() exchange.FeeBuilder { return exchange.FeeBuilder{ Amount: 1, @@ -400,3 +388,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + if areTestAPIKeysSet() { + _, err := e.GetDepositAddress(symbol.LTC, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } + } else { + _, err := e.GetDepositAddress(symbol.LTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } + } +} diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index 139ac703..2a4cf64d 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -140,12 +140,13 @@ func (e *EXMO) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook // Exmo exchange func (e *EXMO) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() + response.Exchange = e.GetName() result, err := e.GetUserInfo() if err != nil { return response, err } + var currencies []exchange.AccountCurrencyInfo for x, y := range result.Balances { var exchangeCurrency exchange.AccountCurrencyInfo exchangeCurrency.CurrencyName = common.StringToUpper(x) @@ -157,8 +158,13 @@ func (e *EXMO) GetAccountInfo() (exchange.AccountInfo, error) { exchangeCurrency.Hold = reserved } } - response.Currencies = append(response.Currencies, exchangeCurrency) + currencies = append(currencies, exchangeCurrency) } + + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -249,8 +255,18 @@ func (e *EXMO) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (e *EXMO) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (e *EXMO) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + fullAddr, err := e.GetCryptoDepositAddress() + if err != nil { + return "", err + } + + addr, ok := fullAddr[cryptocurrency.String()] + if !ok { + return "", fmt.Errorf("currency %s could not be found, please generate via the exmo website", cryptocurrency.String()) + } + + return addr, nil } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/gateio/gateio.go b/exchanges/gateio/gateio.go index 618f0330..bc923b25 100644 --- a/exchanges/gateio/gateio.go +++ b/exchanges/gateio/gateio.go @@ -30,12 +30,15 @@ const ( gateioCancelAllOrders = "private/cancelAllOrders" gateioWithdraw = "private/withdraw" gateioOpenOrders = "private/openOrders" + gateioDepositAddress = "private/depositAddress" gateioTicker = "ticker" gateioTickers = "tickers" gateioOrderbook = "orderBook" gateioAuthRate = 100 gateioUnauthRate = 100 + + gateioGenerateAddress = "New address is being generated for you, please wait a moment and refresh this page. " ) // Gateio is the overarching type across this package @@ -534,3 +537,28 @@ func (g *Gateio) WithdrawCrypto(currency, address string, amount float64) (strin return "", nil } + +// GetCryptoDepositAddress returns a deposit address for a cryptocurrency +func (g *Gateio) GetCryptoDepositAddress(currency string) (string, error) { + type response struct { + Result bool `json:"result,string"` + Code int `json:"code"` + Message string `json:"message"` + Address string `json:"addr"` + } + + var result response + params := fmt.Sprintf("currency=%s", + currency) + + err := g.SendAuthenticatedHTTPRequest("POST", gateioDepositAddress, params, &result) + if err != nil { + return "", err + } + + if !result.Result { + return "", fmt.Errorf("code:%d message:%s", result.Code, result.Message) + } + + return result.Address, nil +} diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index 502f1380..dcb1b659 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -164,7 +164,7 @@ func TestGetFee(t *testing.T) { TestSetup(t) var feeBuilder = setFeeBuilder() - if apiKey != "" && apiSecret != "" { + if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic if resp, err := g.GetFee(feeBuilder); resp != float64(0.002) || err != nil { t.Error(err) @@ -421,3 +421,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + if areTestAPIKeysSet() { + _, err := g.GetDepositAddress(symbol.ETC, "") + if err != nil { + t.Error("Test Fail - GetDepositAddress error", err) + } + } else { + _, err := g.GetDepositAddress(symbol.ETC, "") + if err == nil { + t.Error("Test Fail - GetDepositAddress error cannot be nil") + } + } +} diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 73d8aa14..2ce7cfaf 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -1,9 +1,11 @@ package gateio import ( + "errors" "fmt" "strconv" "sync" + "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -161,8 +163,11 @@ func (g *Gateio) GetAccountInfo() (exchange.AccountInfo, error) { } } - info.Currencies = balances - info.ExchangeName = g.GetName() + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: balances, + }) + + info.Exchange = g.GetName() return info, nil } @@ -262,8 +267,24 @@ func (g *Gateio) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (g *Gateio) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (g *Gateio) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + addr, err := g.GetCryptoDepositAddress(cryptocurrency.String()) + if err != nil { + return "", err + } + + // Waits for new generated address if not created yet, its variable per + // currency + if addr == gateioGenerateAddress { + time.Sleep(10 * time.Second) + addr, err = g.GetCryptoDepositAddress(cryptocurrency.String()) + if addr == gateioGenerateAddress { + return "", errors.New("address not generated in time") + } + return addr, nil + } + + return addr, err } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index 891251bb..b5c21eeb 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -502,3 +502,10 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + _, err := Session[1].GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress error cannot be nil") + } +} diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 66d5f2ad..cd5c0698 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -46,18 +46,25 @@ func (g *Gemini) Run() { // Gemini exchange func (g *Gemini) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = g.GetName() + response.Exchange = g.GetName() accountBalance, err := g.GetBalances() if err != nil { return response, err } + + var currencies []exchange.AccountCurrencyInfo for i := 0; i < len(accountBalance); i++ { var exchangeCurrency exchange.AccountCurrencyInfo exchangeCurrency.CurrencyName = accountBalance[i].Currency exchangeCurrency.TotalValue = accountBalance[i].Amount exchangeCurrency.Hold = accountBalance[i].Available - response.Currencies = append(response.Currencies, exchangeCurrency) + currencies = append(currencies, exchangeCurrency) } + + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -186,8 +193,12 @@ func (g *Gemini) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (g *Gemini) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (g *Gemini) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + addr, err := g.GetCryptoDepositAddress("", cryptocurrency.String()) + if err != nil { + return "", err + } + return addr.Address, nil } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/hitbtc/hitbtc.go b/exchanges/hitbtc/hitbtc.go index 887efd7d..02cdd486 100644 --- a/exchanges/hitbtc/hitbtc.go +++ b/exchanges/hitbtc/hitbtc.go @@ -364,10 +364,13 @@ func (h *HitBTC) GetBalances() (map[string]Balance, error) { // GetDepositAddresses returns a deposit address for a specific currency func (h *HitBTC) GetDepositAddresses(currency string) (DepositCryptoAddresses, error) { - resp := DepositCryptoAddresses{} - err := h.SendAuthenticatedHTTPRequest("GET", apiV2CryptoAddress+"/"+currency, url.Values{}, &resp) + var resp DepositCryptoAddresses - return resp, err + return resp, + h.SendAuthenticatedHTTPRequest("GET", + apiV2CryptoAddress+"/"+currency, + url.Values{}, + &resp) } // GenerateNewAddress generates a new deposit address for a currency diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index fc207142..73224f7f 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -13,7 +13,6 @@ import ( var h HitBTC // Please supply your own APIKEYS here for due diligence testing - const ( apiKey = "" apiSecret = "" @@ -86,7 +85,7 @@ func TestGetFee(t *testing.T) { TestSetup(t) var feeBuilder = setFeeBuilder() - if apiKey != "" && apiSecret != "" { + if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic if resp, err := h.GetFee(feeBuilder); resp != float64(0.001) || err != nil { t.Error(err) @@ -332,3 +331,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + if areTestAPIKeysSet() { + _, err := h.GetDepositAddress(symbol.BTC, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } + } else { + _, err := h.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } + } +} diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 78158a1a..2ddbc2df 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -126,19 +126,25 @@ func (h *HitBTC) UpdateOrderbook(currencyPair pair.CurrencyPair, assetType strin // HitBTC exchange func (h *HitBTC) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = h.GetName() + response.Exchange = h.GetName() accountBalance, err := h.GetBalances() if err != nil { return response, err } + var currencies []exchange.AccountCurrencyInfo for _, item := range accountBalance { var exchangeCurrency exchange.AccountCurrencyInfo exchangeCurrency.CurrencyName = item.Currency exchangeCurrency.TotalValue = item.Available exchangeCurrency.Hold = item.Reserved - response.Currencies = append(response.Currencies, exchangeCurrency) + currencies = append(currencies, exchangeCurrency) } + + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -215,8 +221,13 @@ func (h *HitBTC) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (h *HitBTC) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (h *HitBTC) GetDepositAddress(currency pair.CurrencyItem, accountID string) (string, error) { + resp, err := h.GetDepositAddresses(currency.String()) + if err != nil { + return "", err + } + + return resp.Address, nil } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index 03a32a03..fea31885 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -574,3 +574,10 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + _, err := h.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } +} diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 06df2786..ce8d2cd7 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -149,76 +149,83 @@ func (h *HUOBI) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderboo return orderbook.GetOrderbook(h.Name, p, assetType) } -var mtx sync.Mutex - -// GetAccountID returns the account ID for trades NOTE interim implementation -// does not account for multiple account IDs -func (h *HUOBI) GetAccountID() (string, error) { - mtx.Lock() - defer mtx.Unlock() - - if h.AccountID == "" { - acc, err := h.GetAccounts() - if err != nil { - return "", err - } - - if len(acc) > 0 { - return strconv.FormatInt(acc[0].ID, 10), nil - } - - return "", errors.New("no user ID fetched") +// GetAccountID returns the account ID for trades +func (h *HUOBI) GetAccountID() ([]Account, error) { + acc, err := h.GetAccounts() + if err != nil { + return nil, err } - return h.AccountID, nil + if len(acc) < 1 { + return nil, errors.New("no account returned") + } + + return acc, nil } //GetAccountInfo retrieves balances for all enabled currencies for the // HUOBI exchange - to-do func (h *HUOBI) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo - info.ExchangeName = h.GetName() + info.Exchange = h.GetName() - accID, err := h.GetAccountID() + accounts, err := h.GetAccountID() if err != nil { return info, err } - acc, err := h.GetAccountBalance(accID) - if err != nil { - return info, err - } + for _, account := range accounts { + var acc exchange.Account - type hold struct { - Avail float64 - Hold float64 - } + acc.ID = strconv.FormatInt(account.ID, 10) - var currencyData = make(map[string]*hold) - for _, data := range acc { - _, ok := currencyData[data.Currency] - if !ok { - currencyData[data.Currency] = &hold{} + balances, err := h.GetAccountBalance(acc.ID) + if err != nil { + return info, err } - if data.Type == "trade" { - currencyData[data.Currency].Avail = data.Balance - } else { - currencyData[data.Currency].Hold = data.Balance + var currencyDetails []exchange.AccountCurrencyInfo + for _, balance := range balances { + var frozen bool + if balance.Type == "frozen" { + frozen = true + } + + var updated bool + for i := range currencyDetails { + if currencyDetails[i].CurrencyName == balance.Currency { + if frozen { + currencyDetails[i].Hold = balance.Balance + } else { + currencyDetails[i].TotalValue = balance.Balance + } + updated = true + } + } + + if updated { + continue + } + + if frozen { + currencyDetails = append(currencyDetails, + exchange.AccountCurrencyInfo{ + CurrencyName: balance.Currency, + Hold: balance.Balance, + }) + } else { + currencyDetails = append(currencyDetails, + exchange.AccountCurrencyInfo{ + CurrencyName: balance.Currency, + TotalValue: balance.Balance, + }) + } } + + acc.Currencies = currencyDetails + info.Accounts = append(info.Accounts, acc) } - var balances []exchange.AccountCurrencyInfo - - for key, data := range currencyData { - balances = append(balances, exchange.AccountCurrencyInfo{ - CurrencyName: key, - TotalValue: data.Avail + data.Hold, - Hold: data.Hold, - }) - } - - info.Currencies = balances return info, nil } @@ -330,8 +337,8 @@ func (h *HUOBI) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (h *HUOBI) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (h *HUOBI) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + return "", common.ErrFunctionNotSupported } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/huobihadax/huobihadax.go b/exchanges/huobihadax/huobihadax.go index 94e6f01b..ee9391a3 100644 --- a/exchanges/huobihadax/huobihadax.go +++ b/exchanges/huobihadax/huobihadax.go @@ -51,6 +51,7 @@ const ( huobihadaxMarginAccountBalance = "margin/accounts/balance" huobihadaxWithdrawCreate = "dw/withdraw/api/create" huobihadaxWithdrawCancel = "dw/withdraw-virtual/%s/cancel" + huobiHadaxDepositAddress = "query/deposit-withdraw" huobihadaxAuthRate = 100 huobihadaxUnauthRate = 100 @@ -58,7 +59,6 @@ const ( // HUOBIHADAX is the overarching type across this package type HUOBIHADAX struct { - AccountID string exchange.Base } @@ -886,3 +886,33 @@ func calculateTradingFee(purchasePrice, amount float64) float64 { feePercent := 0.002 return feePercent * purchasePrice * amount } + +// GetDepositWithdrawalHistory returns deposit or withdrawal data +func (h *HUOBIHADAX) GetDepositWithdrawalHistory(associatedID string, currency string, isDeposit bool, size int64) ([]History, error) { + var resp = struct { + Response + Data []History `json:"data"` + }{} + + vals := url.Values{} + + if isDeposit { + vals.Set("type", "deposit") + } else { + vals.Set("type", "withdraw") + } + + vals.Set("from", associatedID) + vals.Set("size", strconv.FormatInt(size, 10)) + vals.Set("currency", common.StringToLower(currency)) + + err := h.SendAuthenticatedHTTPRequest("GET", + huobiHadaxDepositAddress, + vals, + &resp) + + if resp.ErrorMessage != "" { + return resp.Data, errors.New(resp.ErrorMessage) + } + return resp.Data, err +} diff --git a/exchanges/huobihadax/huobihadax_test.go b/exchanges/huobihadax/huobihadax_test.go index d47d1abb..20592138 100644 --- a/exchanges/huobihadax/huobihadax_test.go +++ b/exchanges/huobihadax/huobihadax_test.go @@ -558,3 +558,10 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + _, err := h.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } +} diff --git a/exchanges/huobihadax/huobihadax_types.go b/exchanges/huobihadax/huobihadax_types.go index c4059aec..3c7f09cc 100644 --- a/exchanges/huobihadax/huobihadax_types.go +++ b/exchanges/huobihadax/huobihadax_types.go @@ -88,10 +88,10 @@ type Symbol struct { // Account stores the account data type Account struct { - ID int64 `json:"id"` - Type string `json:"type"` - State string `json:"working"` - UserID int64 `json:"user-id"` + ID int64 `json:"id"` + Type string `json:"type"` + SubType string `json:"subtype"` + State string `json:"state"` } // AccountBalance stores the user all account balance @@ -234,3 +234,17 @@ var ( TimeIntervalMohth = TimeInterval("1mon") TimeIntervalYear = TimeInterval("1year") ) + +// History defines currency deposit or withdrawal data +type History struct { + ID int64 `json:"id"` + Type string `json:"type"` + Currency string `json:"currency"` + TxHash string `json:"tx-hash"` + Amount float64 `json:"amount"` + Address string `json:"address"` + Fee float64 `json:"fee"` + State string `json:"state"` + CreatedAt int64 `json:"created-at"` + UpdatedAt int64 `json:"Updated-at"` +} diff --git a/exchanges/huobihadax/huobihadax_wrapper.go b/exchanges/huobihadax/huobihadax_wrapper.go index 12f65538..8c5665e9 100644 --- a/exchanges/huobihadax/huobihadax_wrapper.go +++ b/exchanges/huobihadax/huobihadax_wrapper.go @@ -114,76 +114,83 @@ func (h *HUOBIHADAX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (ord return orderbook.GetOrderbook(h.Name, p, assetType) } -var mtx sync.Mutex - -// GetAccountID returns the account ID for trades NOTE interim implementation -// does not account for multiple account IDs -func (h *HUOBIHADAX) GetAccountID() (string, error) { - mtx.Lock() - defer mtx.Unlock() - - if h.AccountID == "" { - acc, err := h.GetAccounts() - if err != nil { - return "", err - } - - if len(acc) > 0 { - return strconv.FormatInt(acc[0].ID, 10), nil - } - - return "", errors.New("no account ID fetched") +// GetAccountID returns the account info +func (h *HUOBIHADAX) GetAccountID() ([]Account, error) { + acc, err := h.GetAccounts() + if err != nil { + return nil, err } - return h.AccountID, nil + if len(acc) < 1 { + return nil, errors.New("no account returned") + } + + return acc, nil } // GetAccountInfo retrieves balances for all enabled currencies for the // HUOBIHADAX exchange func (h *HUOBIHADAX) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo - info.ExchangeName = h.GetName() + info.Exchange = h.GetName() - accID, err := h.GetAccountID() + accounts, err := h.GetAccountID() if err != nil { return info, err } - acc, err := h.GetAccountBalance(accID) - if err != nil { - return info, err - } + for _, account := range accounts { + var acc exchange.Account - type hold struct { - Avail float64 - Hold float64 - } + acc.ID = strconv.FormatInt(account.ID, 10) - var currencyData = make(map[string]*hold) - for _, data := range acc { - _, ok := currencyData[data.Currency] - if !ok { - currencyData[data.Currency] = &hold{} + balances, err := h.GetAccountBalance(acc.ID) + if err != nil { + return info, err } - if data.Type == "trade" { - currencyData[data.Currency].Avail = data.Balance - } else { - currencyData[data.Currency].Hold = data.Balance + var currencyDetails []exchange.AccountCurrencyInfo + for _, balance := range balances { + var frozen bool + if balance.Type == "frozen" { + frozen = true + } + + var updated bool + for i := range currencyDetails { + if currencyDetails[i].CurrencyName == balance.Currency { + if frozen { + currencyDetails[i].Hold = balance.Balance + } else { + currencyDetails[i].TotalValue = balance.Balance + } + updated = true + } + } + + if updated { + continue + } + + if frozen { + currencyDetails = append(currencyDetails, + exchange.AccountCurrencyInfo{ + CurrencyName: balance.Currency, + Hold: balance.Balance, + }) + } else { + currencyDetails = append(currencyDetails, + exchange.AccountCurrencyInfo{ + CurrencyName: balance.Currency, + TotalValue: balance.Balance, + }) + } } + + acc.Currencies = currencyDetails + info.Accounts = append(info.Accounts, acc) } - var balances []exchange.AccountCurrencyInfo - - for key, data := range currencyData { - balances = append(balances, exchange.AccountCurrencyInfo{ - CurrencyName: key, - TotalValue: data.Avail + data.Hold, - Hold: data.Hold, - }) - } - - info.Currencies = balances return info, nil } @@ -295,8 +302,8 @@ func (h *HUOBIHADAX) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (h *HUOBIHADAX) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (h *HUOBIHADAX) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + return "", common.ErrFunctionNotSupported } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go index b128677c..46ef2e16 100644 --- a/exchanges/itbit/itbit_test.go +++ b/exchanges/itbit/itbit_test.go @@ -406,3 +406,10 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + _, err := i.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } +} diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 64117ced..7c82e2f5 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -111,7 +111,7 @@ func (i *ItBit) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderboo // GetAccountInfo retrieves balances for all enabled currencies func (i *ItBit) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo - info.ExchangeName = i.GetName() + info.Exchange = i.GetName() wallets, err := i.GetWallets(url.Values{}) if err != nil { @@ -146,6 +146,10 @@ func (i *ItBit) GetAccountInfo() (exchange.AccountInfo, error) { }) } + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: fullBalance, + }) + return info, nil } @@ -237,7 +241,10 @@ func (i *ItBit) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (i *ItBit) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { +// NOTE: This has not been implemented due to the fact you need to generate a +// a specific wallet ID and they restrict the amount of deposit address you can +// request limiting them to 2. +func (i *ItBit) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { return "", common.ErrNotYetImplemented } diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index 5e2841ba..8178ddac 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -1,6 +1,7 @@ package kraken import ( + "errors" "fmt" "net/url" "strconv" @@ -16,32 +17,33 @@ import ( ) const ( - krakenAPIURL = "https://api.kraken.com" - krakenAPIVersion = "0" - krakenServerTime = "Time" - krakenAssets = "Assets" - krakenAssetPairs = "AssetPairs" - krakenTicker = "Ticker" - krakenOHLC = "OHLC" - krakenDepth = "Depth" - krakenTrades = "Trades" - krakenSpread = "Spread" - krakenBalance = "Balance" - krakenTradeBalance = "TradeBalance" - krakenOpenOrders = "OpenOrders" - krakenClosedOrders = "ClosedOrders" - krakenQueryOrders = "QueryOrders" - krakenTradeHistory = "TradesHistory" - krakenQueryTrades = "QueryTrades" - krakenOpenPositions = "OpenPositions" - krakenLedgers = "Ledgers" - krakenQueryLedgers = "QueryLedgers" - krakenTradeVolume = "TradeVolume" - krakenOrderCancel = "CancelOrder" - krakenOrderPlace = "AddOrder" - krakenWithdrawInfo = "WithdrawInfo" - krakenWithdraw = "Withdraw" - krakenDepositMethods = "DepositMethods" + krakenAPIURL = "https://api.kraken.com" + krakenAPIVersion = "0" + krakenServerTime = "Time" + krakenAssets = "Assets" + krakenAssetPairs = "AssetPairs" + krakenTicker = "Ticker" + krakenOHLC = "OHLC" + krakenDepth = "Depth" + krakenTrades = "Trades" + krakenSpread = "Spread" + krakenBalance = "Balance" + krakenTradeBalance = "TradeBalance" + krakenOpenOrders = "OpenOrders" + krakenClosedOrders = "ClosedOrders" + krakenQueryOrders = "QueryOrders" + krakenTradeHistory = "TradesHistory" + krakenQueryTrades = "QueryTrades" + krakenOpenPositions = "OpenPositions" + krakenLedgers = "Ledgers" + krakenQueryLedgers = "QueryLedgers" + krakenTradeVolume = "TradeVolume" + krakenOrderCancel = "CancelOrder" + krakenOrderPlace = "AddOrder" + krakenWithdrawInfo = "WithdrawInfo" + krakenWithdraw = "Withdraw" + krakenDepositMethods = "DepositMethods" + krakenDepositAddresses = "DepositAddresses" krakenAuthRate = 0 krakenUnauthRate = 0 @@ -495,9 +497,10 @@ func (k *Kraken) GetDepositMethods(currency string) ([]DepositMethods, error) { Result []DepositMethods `json:"result"` } params := url.Values{} - params.Set("asset ", currency) + params.Set("asset", currency) - if err := k.SendAuthenticatedHTTPRequest(krakenDepositMethods, params, &response); err != nil { + err := k.SendAuthenticatedHTTPRequest(krakenDepositMethods, params, &response) + if err != nil { return response.Result, err } @@ -998,3 +1001,26 @@ func getCryptocurrencyDepositFee(currency string) float64 { func calculateTradingFee(currency string, feePair map[string]TradeVolumeFee, purchasePrice, amount float64) float64 { return (feePair[currency].Fee / 100) * purchasePrice * amount } + +// GetCryptoDepositAddress returns a deposit address for a cryptocurrency +func (k *Kraken) GetCryptoDepositAddress(method, code string) (string, error) { + var resp = struct { + Error []string `json:"error"` + Result []DepositAddress `json:"result"` + }{} + + values := url.Values{} + values.Set("asset", code) + values.Set("method", method) + + err := k.SendAuthenticatedHTTPRequest(krakenDepositAddresses, values, &resp) + if err != nil { + return "", err + } + + for _, a := range resp.Result { + return a.Address, nil + } + + return "", errors.New("no addresses returned") +} diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index ec2821c0..fb3e0ad1 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -241,7 +241,7 @@ func TestGetFee(t *testing.T) { TestSetup(t) var feeBuilder = setFeeBuilder() - if apiKey != "" && apiSecret != "" { + if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic if resp, err := k.GetFee(feeBuilder); resp != float64(0.0026) || err != nil { t.Error(err) @@ -519,3 +519,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Withdraw failed to be placed: %v", err) } } + +func TestGetDepositAddress(t *testing.T) { + if areTestAPIKeysSet() { + _, err := k.GetDepositAddress(symbol.BTC, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } + } else { + _, err := k.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error can not be nil") + } + } +} diff --git a/exchanges/kraken/kraken_types.go b/exchanges/kraken/kraken_types.go index 2469a85b..5fc2010b 100644 --- a/exchanges/kraken/kraken_types.go +++ b/exchanges/kraken/kraken_types.go @@ -299,10 +299,10 @@ type WithdrawInformation struct { // DepositMethods Used to check deposit fees type DepositMethods struct { - Method string `json:"method"` - Limit float64 `json:"limit,string"` - Fee float64 `json:"fee,string"` - AddressSetupFee float64 `json:"address-setup-fee,string"` + Method string `json:"method"` + Limit interface{} `json:"limit"` // If no limit amount, this comes back as boolean + Fee float64 `json:"fee,string"` + AddressSetupFee float64 `json:"address-setup-fee,string"` } // OrderDescription represents an orders description @@ -365,3 +365,10 @@ var WithdrawalFees = map[string]float64{ symbol.XTZ: 0.05, symbol.ZEC: 0.0001, } + +// DepositAddress defines a deposit address +type DepositAddress struct { + Address string `json:"address"` + ExpireTime int64 `json:"expiretm,string"` + New bool `json:"new"` +} diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 91511a5b..ffe4c79f 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -1,6 +1,7 @@ package kraken import ( + "errors" "strings" "sync" @@ -142,7 +143,7 @@ func (k *Kraken) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbo // Kraken exchange - to-do func (k *Kraken) GetAccountInfo() (exchange.AccountInfo, error) { var info exchange.AccountInfo - info.ExchangeName = k.GetName() + info.Exchange = k.GetName() bal, err := k.GetBalance() if err != nil { @@ -157,7 +158,10 @@ func (k *Kraken) GetAccountInfo() (exchange.AccountInfo, error) { }) } - info.Currencies = balances + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: balances, + }) + return info, nil } @@ -236,8 +240,22 @@ func (k *Kraken) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (k *Kraken) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (k *Kraken) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + methods, err := k.GetDepositMethods(cryptocurrency.String()) + if err != nil { + return "", err + } + + var method string + for _, m := range methods { + method = m.Method + } + + if method == "" { + return "", errors.New("method not found") + } + + return k.GetCryptoDepositAddress(method, cryptocurrency.String()) } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go index 989542a5..bed35e93 100644 --- a/exchanges/lakebtc/lakebtc_test.go +++ b/exchanges/lakebtc/lakebtc_test.go @@ -394,3 +394,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + if areTestAPIKeysSet() { + _, err := l.GetDepositAddress(symbol.BTC, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } + } else { + _, err := l.GetDepositAddress(symbol.DASH, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } + } +} diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index c1b13e89..a9dce828 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "strings" "sync" "github.com/thrasher-/gocryptotrader/common" @@ -106,12 +107,13 @@ func (l *LakeBTC) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderb // LakeBTC exchange func (l *LakeBTC) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = l.GetName() + response.Exchange = l.GetName() accountInfo, err := l.GetAccountInformation() if err != nil { return response, err } + var currencies []exchange.AccountCurrencyInfo for x, y := range accountInfo.Balance { for z, w := range accountInfo.Locked { if z == x { @@ -119,10 +121,15 @@ func (l *LakeBTC) GetAccountInfo() (exchange.AccountInfo, error) { exchangeCurrency.CurrencyName = common.StringToUpper(x) exchangeCurrency.TotalValue, _ = strconv.ParseFloat(y, 64) exchangeCurrency.Hold, _ = strconv.ParseFloat(w, 64) - response.Currencies = append(response.Currencies, exchangeCurrency) + currencies = append(currencies, exchangeCurrency) } } } + + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -201,8 +208,18 @@ func (l *LakeBTC) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (l *LakeBTC) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (l *LakeBTC) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + if !strings.EqualFold(cryptocurrency.String(), symbol.BTC) { + return "", fmt.Errorf("unsupported currency %s deposit address can only be BTC, manual deposit is required for other currencies", + cryptocurrency.String()) + } + + info, err := l.GetAccountInformation() + if err != nil { + return "", err + } + + return info.Profile.BTCDepositAddress, nil } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/liqui/liqui_wrapper.go b/exchanges/liqui/liqui_wrapper.go index 4cf97b4f..678bf21d 100644 --- a/exchanges/liqui/liqui_wrapper.go +++ b/exchanges/liqui/liqui_wrapper.go @@ -117,20 +117,25 @@ func (l *Liqui) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderboo // Liqui exchange func (l *Liqui) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = l.GetName() + response.Exchange = l.GetName() accountBalance, err := l.GetAccountInformation() if err != nil { return response, err } + var currencies []exchange.AccountCurrencyInfo for x, y := range accountBalance.Funds { var exchangeCurrency exchange.AccountCurrencyInfo exchangeCurrency.CurrencyName = common.StringToUpper(x) exchangeCurrency.TotalValue = y exchangeCurrency.Hold = 0 - response.Currencies = append(response.Currencies, exchangeCurrency) + currencies = append(currencies, exchangeCurrency) } + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -214,8 +219,8 @@ func (l *Liqui) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (l *Liqui) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (l *Liqui) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + return "", common.ErrFunctionNotSupported } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/localbitcoins/localbitcoins_test.go b/exchanges/localbitcoins/localbitcoins_test.go index 083dac6f..152bfa19 100644 --- a/exchanges/localbitcoins/localbitcoins_test.go +++ b/exchanges/localbitcoins/localbitcoins_test.go @@ -355,3 +355,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + if apiKey != "" || apiSecret != "" { + _, err := l.GetDepositAddress(symbol.BTC, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } + } else { + _, err := l.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error cannot be nil") + } + } +} diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 6927ee1f..8cef5811 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -5,8 +5,11 @@ import ( "fmt" "math" "strconv" + "strings" "sync" + "github.com/thrasher-/gocryptotrader/currency/symbol" + "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" exchange "github.com/thrasher-/gocryptotrader/exchanges" @@ -113,7 +116,7 @@ func (l *LocalBitcoins) UpdateOrderbook(p pair.CurrencyPair, assetType string) ( // LocalBitcoins exchange func (l *LocalBitcoins) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = l.GetName() + response.Exchange = l.GetName() accountBalance, err := l.GetWalletBalance() if err != nil { return response, err @@ -122,7 +125,9 @@ func (l *LocalBitcoins) GetAccountInfo() (exchange.AccountInfo, error) { exchangeCurrency.CurrencyName = "BTC" exchangeCurrency.TotalValue = accountBalance.Total.Balance - response.Currencies = append(response.Currencies, exchangeCurrency) + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: []exchange.AccountCurrencyInfo{exchangeCurrency}, + }) return response, nil } @@ -246,8 +251,13 @@ func (l *LocalBitcoins) GetOrderInfo(orderID int64) (exchange.OrderDetail, error } // GetDepositAddress returns a deposit address for a specified currency -func (l *LocalBitcoins) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (l *LocalBitcoins) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + if !strings.EqualFold(symbol.BTC, cryptocurrency.String()) { + return "", fmt.Errorf("Localbitcoins do not have support for currency %s just bitcoin", + cryptocurrency.String()) + } + + return l.GetWalletAddress() } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index 82cf494f..9801c3f0 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -145,36 +145,42 @@ func (o *OKCoin) UpdateOrderbook(currency pair.CurrencyPair, assetType string) ( // OKCoin exchange func (o *OKCoin) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = o.GetName() + response.Exchange = o.GetName() assets, err := o.GetUserInfo() if err != nil { return response, err } - response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{ + var currencies []exchange.AccountCurrencyInfo + + currencies = append(currencies, exchange.AccountCurrencyInfo{ CurrencyName: "BTC", TotalValue: assets.Info.Funds.Free.BTC, Hold: assets.Info.Funds.Freezed.BTC, }) - response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{ + currencies = append(currencies, exchange.AccountCurrencyInfo{ CurrencyName: "LTC", TotalValue: assets.Info.Funds.Free.LTC, Hold: assets.Info.Funds.Freezed.LTC, }) - response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{ + currencies = append(currencies, exchange.AccountCurrencyInfo{ CurrencyName: "USD", TotalValue: assets.Info.Funds.Free.USD, Hold: assets.Info.Funds.Freezed.USD, }) - response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{ + currencies = append(currencies, exchange.AccountCurrencyInfo{ CurrencyName: "CNY", TotalValue: assets.Info.Funds.Free.CNY, Hold: assets.Info.Funds.Freezed.CNY, }) + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -285,7 +291,8 @@ func (o *OKCoin) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (o *OKCoin) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { +func (o *OKCoin) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + // NOTE needs API version update to access return "", common.ErrNotYetImplemented } diff --git a/exchanges/okex/okex_wrapper.go b/exchanges/okex/okex_wrapper.go index 692d184c..5bdd3af4 100644 --- a/exchanges/okex/okex_wrapper.go +++ b/exchanges/okex/okex_wrapper.go @@ -167,8 +167,11 @@ func (o *OKEX) GetAccountInfo() (exchange.AccountInfo, error) { }) } - info.ExchangeName = o.GetName() - info.Currencies = balances + info.Exchange = o.GetName() + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: balances, + }) + return info, nil } @@ -283,7 +286,8 @@ func (o *OKEX) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (o *OKEX) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { +func (o *OKEX) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + // NOTE needs API version update to access return "", common.ErrNotYetImplemented } diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go index 20c9258c..81eba6fb 100644 --- a/exchanges/poloniex/poloniex_test.go +++ b/exchanges/poloniex/poloniex_test.go @@ -107,7 +107,7 @@ func TestGetFee(t *testing.T) { TestSetup(t) var feeBuilder = setFeeBuilder() - if apiKey != "" && apiSecret != "" { + if areTestAPIKeysSet() { // CryptocurrencyTradeFee Basic if resp, err := p.GetFee(feeBuilder); resp != float64(0.002) || err != nil { t.Error(err) @@ -350,3 +350,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + if areTestAPIKeysSet() { + _, err := p.GetDepositAddress(symbol.DASH, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress()", err) + } + } else { + _, err := p.GetDepositAddress(symbol.DASH, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress()") + } + } +} diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 815a3d39..7bc8ea78 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -126,18 +126,24 @@ func (p *Poloniex) UpdateOrderbook(currencyPair pair.CurrencyPair, assetType str // Poloniex exchange func (p *Poloniex) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = p.GetName() + response.Exchange = p.GetName() accountBalance, err := p.GetBalances() if err != nil { return response, err } + var currencies []exchange.AccountCurrencyInfo for x, y := range accountBalance.Currency { var exchangeCurrency exchange.AccountCurrencyInfo exchangeCurrency.CurrencyName = x exchangeCurrency.TotalValue = y - response.Currencies = append(response.Currencies, exchangeCurrency) + currencies = append(currencies, exchangeCurrency) } + + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -235,8 +241,19 @@ func (p *Poloniex) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (p *Poloniex) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (p *Poloniex) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + a, err := p.GetDepositAddresses() + if err != nil { + return "", err + } + + address, ok := a.Addresses[cryptocurrency.Upper().String()] + if !ok { + return "", fmt.Errorf("Cannot find deposit address for %s", + cryptocurrency) + } + + return address, nil } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/wex/wex_wrapper.go b/exchanges/wex/wex_wrapper.go index 62290143..f066f7a9 100644 --- a/exchanges/wex/wex_wrapper.go +++ b/exchanges/wex/wex_wrapper.go @@ -127,20 +127,25 @@ func (w *WEX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook. // WEX exchange func (w *WEX) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = w.GetName() + response.Exchange = w.GetName() accountBalance, err := w.GetAccountInformation() if err != nil { return response, err } + var currencies []exchange.AccountCurrencyInfo for x, y := range accountBalance.Funds { var exchangeCurrency exchange.AccountCurrencyInfo exchangeCurrency.CurrencyName = common.StringToUpper(x) exchangeCurrency.TotalValue = y exchangeCurrency.Hold = 0 - response.Currencies = append(response.Currencies, exchangeCurrency) + currencies = append(currencies, exchangeCurrency) } + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -232,7 +237,7 @@ func (w *WEX) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (w *WEX) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { +func (w *WEX) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { return "", common.ErrNotYetImplemented } diff --git a/exchanges/yobit/yobit.go b/exchanges/yobit/yobit.go index a70ab4eb..c7b6639f 100644 --- a/exchanges/yobit/yobit.go +++ b/exchanges/yobit/yobit.go @@ -258,8 +258,8 @@ func (y *Yobit) GetCryptoDepositAddress(coin string) (DepositAddress, error) { if err != nil { return result, err } - if result.Error != "" { - return result, errors.New(result.Error) + if result.Success != 1 { + return result, fmt.Errorf("%s", result.Error) } return result, nil } diff --git a/exchanges/yobit/yobit_test.go b/exchanges/yobit/yobit_test.go index b9687fa3..ad0ad979 100644 --- a/exchanges/yobit/yobit_test.go +++ b/exchanges/yobit/yobit_test.go @@ -125,14 +125,6 @@ func TestWithdrawCoinsToAddress(t *testing.T) { } } -func TestGetDepositAddress(t *testing.T) { - t.Parallel() - _, err := y.GetDepositAddress("btc") - if err == nil { - t.Error("Test Failed - GetDepositAddress() error", err) - } -} - func TestCreateYobicode(t *testing.T) { t.Parallel() _, err := y.CreateCoupon("bla", 0) @@ -469,3 +461,17 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + if apiKey != "" || apiSecret != "" { + _, err := y.GetDepositAddress(symbol.BTC, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } + } else { + _, err := y.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error") + } + } +} diff --git a/exchanges/yobit/yobit_types.go b/exchanges/yobit/yobit_types.go index 10a37353..a2d40331 100644 --- a/exchanges/yobit/yobit_types.go +++ b/exchanges/yobit/yobit_types.go @@ -118,10 +118,13 @@ type TradeHistory struct { // DepositAddress stores a curency deposit address type DepositAddress struct { - Address string `json:"address"` - ProcessedAmount float64 `json:"processed_amount"` - ServerTime int64 `json:"server_time"` - Error string `json:"error"` + Success int `json:"success"` + Return struct { + Address string `json:"address"` + ProcessedAmount float64 `json:"processed_amount"` + ServerTime int64 `json:"server_time"` + } `json:"return"` + Error string `json:"error"` } // WithdrawCoinsToAddress stores information for a withdrawcoins request diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index 529f14bb..d9ce768b 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -104,12 +104,13 @@ func (y *Yobit) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderboo // Yobit exchange func (y *Yobit) GetAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = y.GetName() + response.Exchange = y.GetName() accountBalance, err := y.GetAccountInformation() if err != nil { return response, err } + var currencies []exchange.AccountCurrencyInfo for x, y := range accountBalance.FundsInclOrders { var exchangeCurrency exchange.AccountCurrencyInfo exchangeCurrency.CurrencyName = common.StringToUpper(x) @@ -121,9 +122,13 @@ func (y *Yobit) GetAccountInfo() (exchange.AccountInfo, error) { } } - response.Currencies = append(response.Currencies, exchangeCurrency) + currencies = append(currencies, exchangeCurrency) } + response.Accounts = append(response.Accounts, exchange.Account{ + Currencies: currencies, + }) + return response, nil } @@ -214,8 +219,13 @@ func (y *Yobit) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (y *Yobit) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (y *Yobit) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + a, err := y.GetCryptoDepositAddress(cryptocurrency.String()) + if err != nil { + return "", err + } + + return a.Return.Address, nil } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/exchanges/zb/zb.go b/exchanges/zb/zb.go index 133d5094..ee2f59cf 100644 --- a/exchanges/zb/zb.go +++ b/exchanges/zb/zb.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/currency/pair" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -32,6 +33,7 @@ const ( zbDepth = "depth" zbUnfinishedOrdersIgnoreTradeType = "getUnfinishedOrdersIgnoreTradeType" zbWithdraw = "withdraw" + zbDepositAddress = "getUserAddress" zbAuthRate = 100 zbUnauthRate = 100 @@ -122,7 +124,7 @@ func (z *ZB) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) { vals.Set("price", strconv.FormatFloat(arg.Price, 'f', -1, 64)) vals.Set("tradeType", string(arg.Type)) - err := z.SendAuthenticatedHTTPRequest("GET", zbOrder, vals, &result) + err := z.SendAuthenticatedHTTPRequest("GET", vals, &result) if err != nil { return 0, err } @@ -150,7 +152,7 @@ func (z *ZB) CancelExistingOrder(orderID int64, symbol string) error { vals.Set("currency", symbol) var result response - err := z.SendAuthenticatedHTTPRequest("GET", zbCancelOrder, vals, &result) + err := z.SendAuthenticatedHTTPRequest("GET", vals, &result) if err != nil { return err } @@ -170,7 +172,7 @@ func (z *ZB) GetAccountInformation() (AccountsResponse, error) { vals.Set("accesskey", z.APIKey) vals.Set("method", "getAccountInfo") - err := z.SendAuthenticatedHTTPRequest("GET", zbAccountInfo, vals, &result) + err := z.SendAuthenticatedHTTPRequest("GET", vals, &result) if err != nil { return result, err } @@ -187,7 +189,7 @@ func (z *ZB) GetUnfinishedOrdersIgnoreTradeType(currency, pageindex, pagesize st vals.Set("pageIndex", pageindex) vals.Set("pageSize", pagesize) - err := z.SendAuthenticatedHTTPRequest("GET", zbUnfinishedOrdersIgnoreTradeType, vals, &result) + err := z.SendAuthenticatedHTTPRequest("GET", vals, &result) if err != nil { return result, err } @@ -329,32 +331,45 @@ func (z *ZB) GetSpotKline(arg KlinesRequestParams) (KLineResponse, error) { return res, nil } +// GetCryptoAddress fetches and returns the deposit address +// NOTE - PLEASE BE AWARE THAT YOU NEED TO GENERATE A DEPOSIT ADDRESS VIA +// LOGGING IN AND NOT BY USING THIS ENDPOINT OTHERWISE THIS WILL GIVE YOU A +// GENERAL ERROR RESPONSE. +func (z *ZB) GetCryptoAddress(currency pair.CurrencyItem) (UserAddress, error) { + var resp UserAddress + + vals := url.Values{} + vals.Set("method", zbDepositAddress) + vals.Set("currency", currency.Lower().String()) + + return resp, + z.SendAuthenticatedHTTPRequest("GET", vals, &resp) +} + // SendHTTPRequest sends an unauthenticated HTTP request func (z *ZB) SendHTTPRequest(path string, result interface{}) error { return z.SendPayload("GET", path, nil, nil, result, false, z.Verbose) } // SendAuthenticatedHTTPRequest sends authenticated requests to the zb API -func (z *ZB) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, result interface{}) error { +func (z *ZB) SendAuthenticatedHTTPRequest(httpMethod string, params url.Values, result interface{}) error { if !z.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, z.Name) } - mapParams2Sign := url.Values{} - mapParams2Sign.Set("accesskey", z.APIKey) - mapParams2Sign.Set("method", values.Get("method")) + params.Set("accesskey", z.APIKey) - values.Set("sign", - common.HexEncodeToString(common.GetHMAC(common.HashMD5, - []byte(values.Encode()), - []byte(common.Sha1ToHex(z.APISecret))))) + hmac := common.GetHMAC(common.HashMD5, + []byte(params.Encode()), + []byte(common.Sha1ToHex(z.APISecret))) - values.Set("reqTime", fmt.Sprintf("%d", time.Now().UnixNano()/1e6)) + params.Set("reqTime", fmt.Sprintf("%d", common.UnixMillis(time.Now()))) + params.Set("sign", fmt.Sprintf("%x", hmac)) url := fmt.Sprintf("%s/%s?%s", - z.APIUrlSecondaryDefault, - endpoint, - values.Encode()) + z.APIUrlSecondary, + params.Get("method"), + params.Encode()) var intermediary json.RawMessage @@ -363,7 +378,7 @@ func (z *ZB) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Va Message string `json:"message"` }{} - err := z.SendPayload(method, + err := z.SendPayload(httpMethod, url, nil, strings.NewReader(""), @@ -461,7 +476,7 @@ func (z *ZB) Withdraw(currency, address, safepassword string, amount, fees float vals.Set("safePwd", safepassword) var resp response - err := z.SendAuthenticatedHTTPRequest("GET", zbWithdraw, vals, &resp) + err := z.SendAuthenticatedHTTPRequest("GET", vals, &resp) if err != nil { return "", err } diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index 6b36e562..20e96db8 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -401,3 +401,18 @@ func TestWithdrawInternationalBank(t *testing.T) { t.Errorf("Expected '%v', recieved: '%v'", common.ErrFunctionNotSupported, err) } } + +func TestGetDepositAddress(t *testing.T) { + if apiKey != "" || apiSecret != "" { + _, err := z.GetDepositAddress(symbol.BTC, "") + if err != nil { + t.Error("Test Failed - GetDepositAddress() error PLEASE MAKE SURE YOU CREATE DEPOSIT ADDRESSES VIA ZB.COM", + err) + } + } else { + _, err := z.GetDepositAddress(symbol.BTC, "") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error") + } + } +} diff --git a/exchanges/zb/zb_type.go b/exchanges/zb/zb_type.go index 954afb57..346cdcf5 100644 --- a/exchanges/zb/zb_type.go +++ b/exchanges/zb/zb_type.go @@ -130,6 +130,18 @@ type KLineResponse struct { Data []*KLineResponseData `json:"data"` // KLine数据 } +// UserAddress defines Users Address for depositing funds +type UserAddress struct { + Code int64 `json:"code"` + Message struct { + Description string `json:"des"` + IsSuccessful bool `json:"isSuc"` + Data struct { + Key string `json:"key"` + } `json:"datas"` + } `json:"message"` +} + // TimeInterval represents interval enum. type TimeInterval string diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index a5f98c55..669e2504 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -143,8 +143,11 @@ func (z *ZB) GetAccountInfo() (exchange.AccountInfo, error) { }) } - info.ExchangeName = z.GetName() - info.Currencies = balances + info.Exchange = z.GetName() + info.Accounts = append(info.Accounts, exchange.Account{ + Currencies: balances, + }) + return info, nil } @@ -243,8 +246,13 @@ func (z *ZB) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) { } // GetDepositAddress returns a deposit address for a specified currency -func (z *ZB) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { - return "", common.ErrNotYetImplemented +func (z *ZB) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { + address, err := z.GetCryptoAddress(cryptocurrency) + if err != nil { + return "", err + } + + return address.Message.Data.Key, nil } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is diff --git a/helpers.go b/helpers.go index 0b6b8776..41fd8792 100644 --- a/helpers.go +++ b/helpers.go @@ -262,22 +262,24 @@ func GetSpecificTicker(currency, exchangeName, assetType string) (ticker.Price, // GetCollatedExchangeAccountInfoByCoin collates individual exchange account // information and turns into into a map string of // exchange.AccountCurrencyInfo -func GetCollatedExchangeAccountInfoByCoin(accounts []exchange.AccountInfo) map[string]exchange.AccountCurrencyInfo { +func GetCollatedExchangeAccountInfoByCoin(exchAccounts []exchange.AccountInfo) map[string]exchange.AccountCurrencyInfo { result := make(map[string]exchange.AccountCurrencyInfo) - for i := 0; i < len(accounts); i++ { - for j := 0; j < len(accounts[i].Currencies); j++ { - currencyName := accounts[i].Currencies[j].CurrencyName - avail := accounts[i].Currencies[j].TotalValue - onHold := accounts[i].Currencies[j].Hold + for _, accounts := range exchAccounts { + for _, account := range accounts.Accounts { + for _, accountCurrencyInfo := range account.Currencies { + currencyName := accountCurrencyInfo.CurrencyName + avail := accountCurrencyInfo.TotalValue + onHold := accountCurrencyInfo.Hold - info, ok := result[currencyName] - if !ok { - accountInfo := exchange.AccountCurrencyInfo{CurrencyName: currencyName, Hold: onHold, TotalValue: avail} - result[currencyName] = accountInfo - } else { - info.Hold += onHold - info.TotalValue += avail - result[currencyName] = info + info, ok := result[currencyName] + if !ok { + accountInfo := exchange.AccountCurrencyInfo{CurrencyName: currencyName, Hold: onHold, TotalValue: avail} + result[currencyName] = accountInfo + } else { + info.Hold += onHold + info.TotalValue += avail + result[currencyName] = info + } } } } @@ -287,7 +289,7 @@ func GetCollatedExchangeAccountInfoByCoin(accounts []exchange.AccountInfo) map[s // GetAccountCurrencyInfoByExchangeName returns info for an exchange func GetAccountCurrencyInfoByExchangeName(accounts []exchange.AccountInfo, exchangeName string) (exchange.AccountInfo, error) { for i := 0; i < len(accounts); i++ { - if accounts[i].ExchangeName == exchangeName { + if accounts[i].Exchange == exchangeName { return accounts[i], nil } } @@ -324,39 +326,81 @@ func SeedExchangeAccountInfo(data []exchange.AccountInfo) { port := portfolio.GetPortfolio() - for i := 0; i < len(data); i++ { - exchangeName := data[i].ExchangeName - for j := 0; j < len(data[i].Currencies); j++ { - currencyName := data[i].Currencies[j].CurrencyName - onHold := data[i].Currencies[j].Hold - avail := data[i].Currencies[j].TotalValue - total := onHold + avail + for _, exchangeData := range data { + exchangeName := exchangeData.Exchange + + var currencies []exchange.AccountCurrencyInfo + for _, account := range exchangeData.Accounts { + for _, info := range account.Currencies { + + var update bool + for i := range currencies { + if info.CurrencyName == currencies[i].CurrencyName { + currencies[i].Hold += info.Hold + currencies[i].TotalValue += info.TotalValue + update = true + } + } + + if update { + continue + } + + currencies = append(currencies, exchange.AccountCurrencyInfo{ + CurrencyName: info.CurrencyName, + TotalValue: info.TotalValue, + Hold: info.Hold, + }) + } + } + + for _, total := range currencies { + currencyName := total.CurrencyName + total := total.TotalValue if !port.ExchangeAddressExists(exchangeName, currencyName) { if total <= 0 { continue } + log.Debugf("Portfolio: Adding new exchange address: %s, %s, %f, %s\n", - exchangeName, currencyName, total, portfolio.PortfolioAddressExchange) + exchangeName, + currencyName, + total, + portfolio.PortfolioAddressExchange) + port.Addresses = append( port.Addresses, - portfolio.Address{Address: exchangeName, CoinType: currencyName, - Balance: total, Description: portfolio.PortfolioAddressExchange}, - ) + portfolio.Address{Address: exchangeName, + CoinType: currencyName, + Balance: total, + Description: portfolio.PortfolioAddressExchange}) + } else { if total <= 0 { - log.Debugf("Portfolio: Removing %s %s entry.\n", exchangeName, + log.Debugf("Portfolio: Removing %s %s entry.\n", + exchangeName, currencyName) + port.RemoveExchangeAddress(exchangeName, currencyName) } else { - balance, ok := port.GetAddressBalance(exchangeName, currencyName, portfolio.PortfolioAddressExchange) + balance, ok := port.GetAddressBalance(exchangeName, + currencyName, + portfolio.PortfolioAddressExchange) + if !ok { continue } + if balance != total { log.Debugf("Portfolio: Updating %s %s entry with balance %f.\n", - exchangeName, currencyName, total) - port.UpdateExchangeAddressBalance(exchangeName, currencyName, total) + exchangeName, + currencyName, + total) + + port.UpdateExchangeAddressBalance(exchangeName, + currencyName, + total) } } } diff --git a/helpers_test.go b/helpers_test.go index 5f1261d4..7506625e 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -282,13 +282,32 @@ func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) { exchangeInfo := []exchange.AccountInfo{} var info exchange.AccountInfo - info.ExchangeName = "Bitfinex" - info.Currencies = append(info.Currencies, - exchange.AccountCurrencyInfo{CurrencyName: "BTC", TotalValue: 100, Hold: 0}) + info.Exchange = "Bitfinex" + info.Accounts = append(info.Accounts, + exchange.Account{ + Currencies: []exchange.AccountCurrencyInfo{ + { + CurrencyName: "BTC", + TotalValue: 100, + Hold: 0, + }, + }, + }) + exchangeInfo = append(exchangeInfo, info) - info.ExchangeName = "Bitstamp" - info.Currencies = append(info.Currencies, exchange.AccountCurrencyInfo{CurrencyName: "LTC", TotalValue: 100, Hold: 0}) + info.Exchange = "Bitstamp" + info.Accounts = append(info.Accounts, + exchange.Account{ + Currencies: []exchange.AccountCurrencyInfo{ + { + CurrencyName: "LTC", + TotalValue: 100, + Hold: 0, + }, + }, + }) + exchangeInfo = append(exchangeInfo, info) result := GetCollatedExchangeAccountInfoByCoin(exchangeInfo) @@ -316,9 +335,18 @@ func TestGetAccountCurrencyInfoByExchangeName(t *testing.T) { exchangeInfo := []exchange.AccountInfo{} var info exchange.AccountInfo - info.ExchangeName = "Bitfinex" - info.Currencies = append(info.Currencies, - exchange.AccountCurrencyInfo{CurrencyName: "BTC", TotalValue: 100, Hold: 0}) + info.Exchange = "Bitfinex" + info.Accounts = append(info.Accounts, + exchange.Account{ + Currencies: []exchange.AccountCurrencyInfo{ + { + CurrencyName: "BTC", + TotalValue: 100, + Hold: 0, + }, + }, + }) + exchangeInfo = append(exchangeInfo, info) result, err := GetAccountCurrencyInfoByExchangeName(exchangeInfo, "Bitfinex") @@ -326,7 +354,7 @@ func TestGetAccountCurrencyInfoByExchangeName(t *testing.T) { t.Fatal(err) } - if result.ExchangeName != "Bitfinex" { + if result.Exchange != "Bitfinex" { t.Fatal("Unexepcted result") } diff --git a/tools/exchange_template/wrapper_file.tmpl b/tools/exchange_template/wrapper_file.tmpl index 1debfd0f..2f37d55c 100644 --- a/tools/exchange_template/wrapper_file.tmpl +++ b/tools/exchange_template/wrapper_file.tmpl @@ -148,7 +148,7 @@ func ({{.Variable}} *{{.CapitalName}}) GetOrderInfo(orderID int64) (exchange.Ord } // GetDepositAddress returns a deposit address for a specified currency -func ({{.Variable}} *{{.CapitalName}}) GetDepositAddress(cryptocurrency pair.CurrencyItem) (string, error) { +func ({{.Variable}} *{{.CapitalName}}) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) { return "", common.ErrNotYetImplemented }