Files
gocryptotrader/exchanges/okex/okex.go
Andrew d01e7bad72 Implement Logger (#228)
* Added new base logger

* updated example and test configs

* updated exchange helpers restful router & server

* logPath is now passed to the logger to remove dependency on common package

* updated everything besides exchanges to use new logger

* alphapoint to bitmex done

* updated bitmex bitstamp bittrex btcc and also performance changes to logger

* btcmarkets coinbase coinut exmo gateio wrappers updated

* gateio and gemini logger updated

* hitbtc huobi itbit & kraken updated

* All exchanges updatd

* return correct error for disabled websocket

* don't disconnect client on invalid json

* updated router internal logging

* log.Fatal to t.Error for tests

* Changed from fatal to error failure to set maxprocs

* output ANSI codes for everything but windows for now due to lack of windows support

* added error handling to logger and unit tests

* clear wording on print -> log.print

* added benchmark test

* cleaned up import sections

* Updated logger based on PR requests (added default config options on failure/setting errors)

* ah this should fix travici enc config issue

* Load entire config and clear out logging to hopefully fix travisci issue

* wording & test error handling

* fixed formatting issues based on feedback

* fixed formatting issues based on feedback

* changed CheckDir to use mkdirall instead of mkdir and other changes based on feedback
2019-01-08 21:56:22 +11:00

1254 lines
39 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package okex
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
log "github.com/thrasher-/gocryptotrader/logger"
)
const (
// REST API information
apiURL = "https://www.okex.com/api/"
apiVersion = "v1/"
// Contract requests
// Unauthenticated
contractPrice = "future_ticker"
contractFutureDepth = "future_depth"
contractTradeHistory = "future_trades"
contractFutureIndex = "future_index"
contractExchangeRate = "exchange_rate"
contractFutureEstPrice = "future_estimated_price"
contractCandleStick = "future_kline"
contractFutureHoldAmount = "future_hold_amount"
contractFutureLimits = "future_price_limit"
// Authenticated
contractFutureUserInfo = "future_userinfo"
contractFuturePosition = "future_position"
contractFutureTrade = "future_trade"
contractFutureTradeHistory = "future_trades_history"
contractFutureBatchTrade = "future_batch_trade"
contractFutureCancel = "future_cancel"
contractFutureOrderInfo = "future_order_info"
contractFutureMultOrderInfo = "future_orders_info"
contractFutureUserInfo4fix = "future_userinfo_4fix"
contractFuturePosition4fix = "future_position_4fix"
contractFutureExplosive = "future_explosive"
contractFutureDevolve = "future_devolve"
// Spot requests
// Unauthenticated
spotPrice = "ticker"
spotDepth = "depth"
spotTrades = "trades"
spotKline = "kline"
instruments = "instruments"
// Authenticated
spotUserInfo = "userinfo"
spotTrade = "trade"
spotBatchTrade = "batch_trade"
spotCancelTrade = "cancel_order"
spotOrderInfo = "order_info"
spotMultiOrderInfo = "orders_info"
spotWithdraw = "withdraw.do"
spotCancelWithdraw = "cancel_withdraw"
spotWithdrawInfo = "withdraw_info"
spotAccountRecords = "account_records"
myWalletInfo = "wallet_info.do"
// just your average return type from okex
returnTypeOne = "map[string]interface {}"
okexAuthRate = 0
okexUnauthRate = 0
)
var errMissValue = errors.New("warning - resp value is missing from exchange")
// OKEX is the overaching type across the OKEX methods
type OKEX struct {
exchange.Base
WebsocketConn *websocket.Conn
mu sync.Mutex
// Spot and contract market error codes as per https://www.okex.com/rest_request.html
ErrorCodes map[string]error
// Stores for corresponding variable checks
ContractTypes []string
CurrencyPairs []string
ContractPosition []string
Types []string
}
// SetDefaults method assignes the default values for Bittrex
func (o *OKEX) SetDefaults() {
o.SetErrorDefaults()
o.SetCheckVarDefaults()
o.Name = "OKEX"
o.Enabled = false
o.Verbose = false
o.RESTPollingDelay = 10
o.APIWithdrawPermissions = exchange.AutoWithdrawCrypto
o.RequestCurrencyPairFormat.Delimiter = "_"
o.RequestCurrencyPairFormat.Uppercase = false
o.ConfigCurrencyPairFormat.Delimiter = "_"
o.ConfigCurrencyPairFormat.Uppercase = true
o.SupportsAutoPairUpdating = true
o.SupportsRESTTickerBatching = false
o.Requester = request.New(o.Name,
request.NewRateLimit(time.Second, okexAuthRate),
request.NewRateLimit(time.Second, okexUnauthRate),
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
o.APIUrlDefault = apiURL
o.APIUrl = o.APIUrlDefault
o.AssetTypes = []string{ticker.Spot}
o.WebsocketInit()
}
// Setup method sets current configuration details if enabled
func (o *OKEX) Setup(exch config.ExchangeConfig) {
if !exch.Enabled {
o.SetEnabled(false)
} else {
o.Enabled = true
o.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
o.SetAPIKeys(exch.APIKey, exch.APISecret, exch.ClientID, false)
o.SetHTTPClientTimeout(exch.HTTPTimeout)
o.SetHTTPClientUserAgent(exch.HTTPUserAgent)
o.RESTPollingDelay = exch.RESTPollingDelay
o.Verbose = exch.Verbose
o.Websocket.SetEnabled(exch.Websocket)
o.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
o.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
o.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := o.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = o.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
err = o.SetAutoPairDefaults()
if err != nil {
log.Fatal(err)
}
err = o.SetAPIURL(exch)
if err != nil {
log.Fatal(err)
}
err = o.SetClientProxyAddress(exch.ProxyAddress)
if err != nil {
log.Fatal(err)
}
err = o.WebsocketSetup(o.WsConnect,
exch.Name,
exch.Websocket,
okexDefaultWebsocketURL,
exch.WebsocketURL)
if err != nil {
log.Fatal(err)
}
}
}
// GetSpotInstruments returns a list of tradable spot instruments and their properties
func (o *OKEX) GetSpotInstruments() ([]SpotInstrument, error) {
var resp []SpotInstrument
path := fmt.Sprintf("%sspot/v3/%s", o.APIUrl, instruments)
err := o.SendHTTPRequest(path, &resp)
if err != nil {
return nil, err
}
return resp, nil
}
// GetContractPrice returns current contract prices
//
// symbol e.g. "btc_usd"
// contractType e.g. "this_week" "next_week" "quarter"
func (o *OKEX) GetContractPrice(symbol, contractType string) (ContractPrice, error) {
resp := ContractPrice{}
if err := o.CheckContractType(contractType); err != nil {
return resp, err
}
if err := o.CheckSymbol(symbol); err != nil {
return resp, err
}
values := url.Values{}
values.Set("symbol", common.StringToLower(symbol))
values.Set("contract_type", common.StringToLower(contractType))
path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractPrice, values.Encode())
err := o.SendHTTPRequest(path, &resp)
if err != nil {
return resp, err
}
if !resp.Result {
if resp.Error != nil {
return resp, o.GetErrorCode(resp.Error)
}
}
return resp, nil
}
// GetContractMarketDepth returns contract market depth
//
// symbol e.g. "btc_usd"
// contractType e.g. "this_week" "next_week" "quarter"
func (o *OKEX) GetContractMarketDepth(symbol, contractType string) (ActualContractDepth, error) {
resp := ContractDepth{}
fullDepth := ActualContractDepth{}
if err := o.CheckContractType(contractType); err != nil {
return fullDepth, err
}
if err := o.CheckSymbol(symbol); err != nil {
return fullDepth, err
}
values := url.Values{}
values.Set("symbol", common.StringToLower(symbol))
values.Set("contract_type", common.StringToLower(contractType))
path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractFutureDepth, values.Encode())
err := o.SendHTTPRequest(path, &resp)
if err != nil {
return fullDepth, err
}
if !resp.Result {
if resp.Error != nil {
return fullDepth, o.GetErrorCode(resp.Error)
}
}
for _, ask := range resp.Asks {
var askdepth struct {
Price float64
Volume float64
}
for i, depth := range ask.([]interface{}) {
if i == 0 {
askdepth.Price = depth.(float64)
}
if i == 1 {
askdepth.Volume = depth.(float64)
}
}
fullDepth.Asks = append(fullDepth.Asks, askdepth)
}
for _, bid := range resp.Bids {
var bidDepth struct {
Price float64
Volume float64
}
for i, depth := range bid.([]interface{}) {
if i == 0 {
bidDepth.Price = depth.(float64)
}
if i == 1 {
bidDepth.Volume = depth.(float64)
}
}
fullDepth.Bids = append(fullDepth.Bids, bidDepth)
}
return fullDepth, nil
}
// GetContractTradeHistory returns trade history for the contract market
func (o *OKEX) GetContractTradeHistory(symbol, contractType string) ([]ActualContractTradeHistory, error) {
actualTradeHistory := []ActualContractTradeHistory{}
var resp interface{}
if err := o.CheckContractType(contractType); err != nil {
return actualTradeHistory, err
}
if err := o.CheckSymbol(symbol); err != nil {
return actualTradeHistory, err
}
values := url.Values{}
values.Set("symbol", common.StringToLower(symbol))
values.Set("contract_type", common.StringToLower(contractType))
path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractTradeHistory, values.Encode())
err := o.SendHTTPRequest(path, &resp)
if err != nil {
return actualTradeHistory, err
}
if reflect.TypeOf(resp).String() == returnTypeOne {
errorMap := resp.(map[string]interface{})
return actualTradeHistory, o.GetErrorCode(errorMap["error_code"].(float64))
}
for _, tradeHistory := range resp.([]interface{}) {
quickHistory := ActualContractTradeHistory{}
tradeHistoryM := tradeHistory.(map[string]interface{})
quickHistory.Date = tradeHistoryM["date"].(float64)
quickHistory.DateInMS = tradeHistoryM["date_ms"].(float64)
quickHistory.Amount = tradeHistoryM["amount"].(float64)
quickHistory.Price = tradeHistoryM["price"].(float64)
quickHistory.Type = tradeHistoryM["type"].(string)
quickHistory.TID = tradeHistoryM["tid"].(float64)
actualTradeHistory = append(actualTradeHistory, quickHistory)
}
return actualTradeHistory, nil
}
// GetContractIndexPrice returns the current index price
//
// symbol e.g. btc_usd
func (o *OKEX) GetContractIndexPrice(symbol string) (float64, error) {
if err := o.CheckSymbol(symbol); err != nil {
return 0, err
}
values := url.Values{}
values.Set("symbol", common.StringToLower(symbol))
path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractFutureIndex, values.Encode())
var resp interface{}
err := o.SendHTTPRequest(path, &resp)
if err != nil {
return 0, err
}
futureIndex := resp.(map[string]interface{})
if i, ok := futureIndex["error_code"].(float64); ok {
return 0, o.GetErrorCode(i)
}
if _, ok := futureIndex["future_index"].(float64); ok {
return futureIndex["future_index"].(float64), nil
}
return 0, errMissValue
}
// GetContractExchangeRate returns the current exchange rate for the currency
// pair
// USD-CNY exchange rate used by OKEX, updated weekly
func (o *OKEX) GetContractExchangeRate() (float64, error) {
path := fmt.Sprintf("%s%s%s.do?", o.APIUrl, apiVersion, contractExchangeRate)
var resp interface{}
if err := o.SendHTTPRequest(path, &resp); err != nil {
return 0, err
}
exchangeRate := resp.(map[string]interface{})
if i, ok := exchangeRate["error_code"].(float64); ok {
return 0, o.GetErrorCode(i)
}
if _, ok := exchangeRate["rate"].(float64); ok {
return exchangeRate["rate"].(float64), nil
}
return 0, errMissValue
}
// GetContractFutureEstimatedPrice returns futures estimated price
//
// symbol e.g btc_usd
func (o *OKEX) GetContractFutureEstimatedPrice(symbol string) (float64, error) {
if err := o.CheckSymbol(symbol); err != nil {
return 0, err
}
values := url.Values{}
values.Set("symbol", symbol)
path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractFutureIndex, values.Encode())
var resp interface{}
if err := o.SendHTTPRequest(path, &resp); err != nil {
return 0, err
}
futuresEstPrice := resp.(map[string]interface{})
if i, ok := futuresEstPrice["error_code"].(float64); ok {
return 0, o.GetErrorCode(i)
}
if _, ok := futuresEstPrice["future_index"].(float64); ok {
return futuresEstPrice["future_index"].(float64), nil
}
return 0, errMissValue
}
// GetContractCandlestickData returns CandleStickData
//
// symbol e.g. btc_usd
// type e.g. 1min or 1 minute candlestick data
// contract_type e.g. this_week
// size: specify data size to be acquired
// since: timestamp(eg:1417536000000). data after the timestamp will be returned
func (o *OKEX) GetContractCandlestickData(symbol, typeInput, contractType string, size, since int) ([]CandleStickData, error) {
var candleData []CandleStickData
if err := o.CheckSymbol(symbol); err != nil {
return candleData, err
}
if err := o.CheckContractType(contractType); err != nil {
return candleData, err
}
if err := o.CheckType(typeInput); err != nil {
return candleData, err
}
values := url.Values{}
values.Set("symbol", symbol)
values.Set("type", typeInput)
values.Set("contract_type", contractType)
values.Set("size", strconv.FormatInt(int64(size), 10))
values.Set("since", strconv.FormatInt(int64(since), 10))
path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractCandleStick, values.Encode())
var resp interface{}
if err := o.SendHTTPRequest(path, &resp); err != nil {
return candleData, err
}
if reflect.TypeOf(resp).String() == returnTypeOne {
errorMap := resp.(map[string]interface{})
return candleData, o.GetErrorCode(errorMap["error_code"].(float64))
}
for _, candleStickData := range resp.([]interface{}) {
var quickCandle CandleStickData
for i, datum := range candleStickData.([]interface{}) {
switch i {
case 0:
quickCandle.Timestamp = datum.(float64)
case 1:
quickCandle.Open = datum.(float64)
case 2:
quickCandle.High = datum.(float64)
case 3:
quickCandle.Low = datum.(float64)
case 4:
quickCandle.Close = datum.(float64)
case 5:
quickCandle.Volume = datum.(float64)
case 6:
quickCandle.Amount = datum.(float64)
default:
return candleData, errors.New("incoming data out of range")
}
}
candleData = append(candleData, quickCandle)
}
return candleData, nil
}
// GetContractHoldingsNumber returns current number of holdings
func (o *OKEX) GetContractHoldingsNumber(symbol, contractType string) (number float64, contract string, err error) {
if err = o.CheckSymbol(symbol); err != nil {
return number, contract, err
}
if err = o.CheckContractType(contractType); err != nil {
return number, contract, err
}
values := url.Values{}
values.Set("symbol", symbol)
values.Set("contract_type", contractType)
path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractFutureHoldAmount, values.Encode())
var resp interface{}
if err = o.SendHTTPRequest(path, &resp); err != nil {
return number, contract, err
}
if reflect.TypeOf(resp).String() == returnTypeOne {
errorMap := resp.(map[string]interface{})
return number, contract, o.GetErrorCode(errorMap["error_code"].(float64))
}
for _, holdings := range resp.([]interface{}) {
if reflect.TypeOf(holdings).String() == returnTypeOne {
holdingMap := holdings.(map[string]interface{})
number = holdingMap["amount"].(float64)
contract = holdingMap["contract_name"].(string)
}
}
return
}
// GetContractlimit returns upper and lower price limit
func (o *OKEX) GetContractlimit(symbol, contractType string) (map[string]float64, error) {
contractLimits := make(map[string]float64)
if err := o.CheckSymbol(symbol); err != nil {
return contractLimits, err
}
if err := o.CheckContractType(contractType); err != nil {
return contractLimits, err
}
values := url.Values{}
values.Set("symbol", symbol)
values.Set("contract_type", contractType)
path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, contractFutureLimits, values.Encode())
var resp interface{}
if err := o.SendHTTPRequest(path, &resp); err != nil {
return contractLimits, err
}
contractLimitMap := resp.(map[string]interface{})
if i, ok := contractLimitMap["error_code"].(float64); ok {
return contractLimits, o.GetErrorCode(i)
}
contractLimits["high"] = contractLimitMap["high"].(float64)
contractLimits["usdCnyRate"] = contractLimitMap["usdCnyRate"].(float64)
contractLimits["low"] = contractLimitMap["low"].(float64)
return contractLimits, nil
}
// GetContractUserInfo returns OKEX Contract Account InfoCross-Margin Mode
func (o *OKEX) GetContractUserInfo() error {
var resp interface{}
if err := o.SendAuthenticatedHTTPRequest(contractFutureUserInfo, url.Values{}, &resp); err != nil {
return err
}
userInfoMap := resp.(map[string]interface{})
if code, ok := userInfoMap["error_code"]; ok {
return o.GetErrorCode(code)
}
return nil
}
// GetContractPosition returns User Contract Positions Cross-Margin Mode
func (o *OKEX) GetContractPosition(symbol, contractType string) error {
var resp interface{}
if err := o.CheckSymbol(symbol); err != nil {
return err
}
if err := o.CheckContractType(contractType); err != nil {
return err
}
values := url.Values{}
values.Set("symbol", symbol)
values.Set("contract_type", contractType)
if err := o.SendAuthenticatedHTTPRequest(contractFuturePosition, values, &resp); err != nil {
return err
}
userInfoMap := resp.(map[string]interface{})
if code, ok := userInfoMap["error_code"]; ok {
return o.GetErrorCode(code)
}
return nil
}
// PlaceContractOrders places orders
func (o *OKEX) PlaceContractOrders(symbol, contractType, position string, leverageRate int, price, amount float64, matchPrice bool) (float64, error) {
var resp interface{}
if err := o.CheckSymbol(symbol); err != nil {
return 0, err
}
if err := o.CheckContractType(contractType); err != nil {
return 0, err
}
if err := o.CheckContractPosition(position); err != nil {
return 0, err
}
values := url.Values{}
values.Set("symbol", symbol)
values.Set("contract_type", contractType)
values.Set("price", strconv.FormatFloat(price, 'f', -1, 64))
values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
values.Set("type", position)
if matchPrice {
values.Set("match_price", "1")
} else {
values.Set("match_price", "0")
}
if leverageRate != 10 && leverageRate != 20 {
return 0, errors.New("leverage rate can only be 10 or 20")
}
values.Set("lever_rate", strconv.FormatInt(int64(leverageRate), 10))
if err := o.SendAuthenticatedHTTPRequest(contractFutureTrade, values, &resp); err != nil {
return 0, err
}
contractMap := resp.(map[string]interface{})
if code, ok := contractMap["error_code"]; ok {
return 0, o.GetErrorCode(code)
}
if orderID, ok := contractMap["order_id"]; ok {
return orderID.(float64), nil
}
return 0, errors.New("orderID returned nil")
}
// GetContractFuturesTradeHistory returns OKEX Contract Trade History (Not for Personal)
func (o *OKEX) GetContractFuturesTradeHistory(symbol, date string, since int) error {
var resp interface{}
if err := o.CheckSymbol(symbol); err != nil {
return err
}
values := url.Values{}
values.Set("symbol", symbol)
values.Set("date", date)
values.Set("since", strconv.FormatInt(int64(since), 10))
if err := o.SendAuthenticatedHTTPRequest(contractFutureTradeHistory, values, &resp); err != nil {
return err
}
respMap := resp.(map[string]interface{})
if code, ok := respMap["error_code"]; ok {
return o.GetErrorCode(code)
}
return nil
}
// GetTokenOrders returns details for a single orderID or all open orders when orderID == -1
func (o *OKEX) GetTokenOrders(symbol string, orderID int64) (TokenOrdersResponse, error) {
var resp TokenOrdersResponse
values := url.Values{}
values.Set("symbol", symbol)
values.Set("order_id", strconv.FormatInt(orderID, 10))
if err := o.SendAuthenticatedHTTPRequest(contractFutureTradeHistory, values, &resp); err != nil {
return resp, err
}
return resp, nil
}
// GetUserInfo returns the user info
func (o *OKEX) GetUserInfo() (SpotUserInfo, error) {
var resp SpotUserInfo
err := o.SendAuthenticatedHTTPRequest(spotUserInfo, url.Values{}, &resp)
if err != nil {
return resp, err
}
return resp, nil
}
// SpotNewOrder creates a new spot order
func (o *OKEX) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) {
type response struct {
Result bool `json:"result"`
OrderID int64 `json:"order_id"`
}
var res response
params := url.Values{}
params.Set("symbol", arg.Symbol)
params.Set("type", string(arg.Type))
params.Set("price", strconv.FormatFloat(arg.Price, 'f', -1, 64))
params.Set("amount", strconv.FormatFloat(arg.Amount, 'f', -1, 64))
err := o.SendAuthenticatedHTTPRequest(spotTrade, params, &res)
if err != nil {
return res.OrderID, err
}
return res.OrderID, nil
}
// SpotCancelOrder cancels a spot order
// symbol such as ltc_btc
// orderID orderID
// returns orderID or an error
func (o *OKEX) SpotCancelOrder(symbol string, argOrderID int64) (int64, error) {
var res = struct {
Result bool `json:"result"`
OrderID string `json:"order_id"`
ErrorCode int `json:"error_code"`
}{}
params := url.Values{}
params.Set("symbol", symbol)
params.Set("order_id", strconv.FormatInt(argOrderID, 10))
var returnOrderID int64
err := o.SendAuthenticatedHTTPRequest(spotCancelTrade+".do", params, &res)
if err != nil {
return returnOrderID, err
}
if res.ErrorCode != 0 {
return returnOrderID, fmt.Errorf("ErrCode:%d ErrMsg:%s", res.ErrorCode, o.ErrorCodes[strconv.Itoa(res.ErrorCode)])
}
returnOrderID, _ = common.Int64FromString(res.OrderID)
return returnOrderID, nil
}
// GetLatestSpotPrice returns latest spot price of symbol
//
// symbol: string of currency pair
func (o *OKEX) GetLatestSpotPrice(symbol string) (float64, error) {
spotPrice, err := o.GetSpotTicker(symbol)
if err != nil {
return 0, err
}
return spotPrice.Ticker.Last, nil
}
// GetSpotTicker returns Price Ticker
func (o *OKEX) GetSpotTicker(symbol string) (SpotPrice, error) {
var resp SpotPrice
values := url.Values{}
values.Set("symbol", symbol)
path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, "ticker", values.Encode())
err := o.SendHTTPRequest(path, &resp)
if err != nil {
return resp, err
}
if resp.Error != nil {
return resp, o.GetErrorCode(resp.Error.(float64))
}
return resp, nil
}
//GetSpotMarketDepth returns Market Depth
func (o *OKEX) GetSpotMarketDepth(asd ActualSpotDepthRequestParams) (ActualSpotDepth, error) {
resp := SpotDepth{}
fullDepth := ActualSpotDepth{}
values := url.Values{}
values.Set("symbol", asd.Symbol)
values.Set("size", fmt.Sprintf("%d", asd.Size))
path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, "depth", values.Encode())
err := o.SendHTTPRequest(path, &resp)
if err != nil {
return fullDepth, err
}
if !resp.Result {
if resp.Error != nil {
return fullDepth, o.GetErrorCode(resp.Error)
}
}
for _, ask := range resp.Asks {
var askdepth struct {
Price float64
Volume float64
}
for i, depth := range ask.([]interface{}) {
if i == 0 {
askdepth.Price = depth.(float64)
}
if i == 1 {
askdepth.Volume = depth.(float64)
}
}
fullDepth.Asks = append(fullDepth.Asks, askdepth)
}
for _, bid := range resp.Bids {
var bidDepth struct {
Price float64
Volume float64
}
for i, depth := range bid.([]interface{}) {
if i == 0 {
bidDepth.Price = depth.(float64)
}
if i == 1 {
bidDepth.Volume = depth.(float64)
}
}
fullDepth.Bids = append(fullDepth.Bids, bidDepth)
}
return fullDepth, nil
}
// GetSpotRecentTrades returns recent trades
func (o *OKEX) GetSpotRecentTrades(ast ActualSpotTradeHistoryRequestParams) ([]ActualSpotTradeHistory, error) {
actualTradeHistory := []ActualSpotTradeHistory{}
var resp interface{}
values := url.Values{}
values.Set("symbol", ast.Symbol)
values.Set("since", fmt.Sprintf("%d", ast.Since))
path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, "trades", values.Encode())
err := o.SendHTTPRequest(path, &resp)
if err != nil {
return actualTradeHistory, err
}
if reflect.TypeOf(resp).String() == returnTypeOne {
errorMap := resp.(map[string]interface{})
return actualTradeHistory, o.GetErrorCode(errorMap["error_code"].(float64))
}
for _, tradeHistory := range resp.([]interface{}) {
quickHistory := ActualSpotTradeHistory{}
tradeHistoryM := tradeHistory.(map[string]interface{})
quickHistory.Date = tradeHistoryM["date"].(float64)
quickHistory.DateInMS = tradeHistoryM["date_ms"].(float64)
quickHistory.Amount = tradeHistoryM["amount"].(float64)
quickHistory.Price = tradeHistoryM["price"].(float64)
quickHistory.Type = tradeHistoryM["type"].(string)
quickHistory.TID = tradeHistoryM["tid"].(float64)
actualTradeHistory = append(actualTradeHistory, quickHistory)
}
return actualTradeHistory, nil
}
// GetSpotKline returns candlestick data
func (o *OKEX) GetSpotKline(arg KlinesRequestParams) ([]CandleStickData, error) {
var candleData []CandleStickData
values := url.Values{}
values.Set("symbol", arg.Symbol)
values.Set("type", string(arg.Type))
if arg.Size != 0 {
values.Set("size", strconv.FormatInt(int64(arg.Size), 10))
}
if arg.Since != 0 {
values.Set("since", strconv.FormatInt(int64(arg.Since), 10))
}
path := fmt.Sprintf("%s%s%s.do?%s", o.APIUrl, apiVersion, spotKline, values.Encode())
var resp interface{}
if err := o.SendHTTPRequest(path, &resp); err != nil {
return candleData, err
}
if reflect.TypeOf(resp).String() == returnTypeOne {
errorMap := resp.(map[string]interface{})
return candleData, o.GetErrorCode(errorMap["error_code"].(float64))
}
for _, candleStickData := range resp.([]interface{}) {
var quickCandle CandleStickData
for i, datum := range candleStickData.([]interface{}) {
switch i {
case 0:
quickCandle.Timestamp = datum.(float64)
case 1:
quickCandle.Open, _ = strconv.ParseFloat(datum.(string), 64)
case 2:
quickCandle.High, _ = strconv.ParseFloat(datum.(string), 64)
case 3:
quickCandle.Low, _ = strconv.ParseFloat(datum.(string), 64)
case 4:
quickCandle.Close, _ = strconv.ParseFloat(datum.(string), 64)
case 5:
quickCandle.Volume, _ = strconv.ParseFloat(datum.(string), 64)
case 6:
quickCandle.Amount, _ = strconv.ParseFloat(datum.(string), 64)
default:
return candleData, errors.New("incoming data out of range")
}
}
candleData = append(candleData, quickCandle)
}
return candleData, nil
}
// GetErrorCode finds the associated error code and returns its corresponding
// string
func (o *OKEX) GetErrorCode(code interface{}) error {
var assertedCode string
switch reflect.TypeOf(code).String() {
case "float64":
assertedCode = strconv.FormatFloat(code.(float64), 'f', -1, 64)
case "string":
assertedCode = code.(string)
default:
return errors.New("unusual type returned")
}
if i, ok := o.ErrorCodes[assertedCode]; ok {
return i
}
return errors.New("unable to find SPOT error code")
}
// SendHTTPRequest sends an unauthenticated HTTP request
func (o *OKEX) SendHTTPRequest(path string, result interface{}) error {
return o.SendPayload("GET", path, nil, nil, result, false, o.Verbose)
}
// SendAuthenticatedHTTPRequest sends an authenticated http request to a desired
// path
func (o *OKEX) SendAuthenticatedHTTPRequest(method string, values url.Values, result interface{}) (err error) {
if !o.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, o.Name)
}
values.Set("api_key", o.APIKey)
hasher := common.GetMD5([]byte(values.Encode() + "&secret_key=" + o.APISecret))
values.Set("sign", strings.ToUpper(common.HexEncodeToString(hasher)))
encoded := values.Encode()
path := o.APIUrl + apiVersion + method
if o.Verbose {
log.Debugf("Sending POST request to %s with params %s\n", path, encoded)
}
headers := make(map[string]string)
headers["Content-Type"] = "application/x-www-form-urlencoded"
var intermediary json.RawMessage
errCap := struct {
Result bool `json:"result"`
Error int64 `json:"error_code"`
}{}
err = o.SendPayload("POST", path, headers, strings.NewReader(encoded), &intermediary, true, o.Verbose)
if err != nil {
return err
}
err = common.JSONDecode(intermediary, &errCap)
if err == nil {
if !errCap.Result {
return fmt.Errorf("SendAuthenticatedHTTPRequest error - %s",
o.ErrorCodes[strconv.FormatInt(errCap.Error, 10)])
}
}
return common.JSONDecode(intermediary, result)
}
// SetErrorDefaults sets the full error default list
func (o *OKEX) SetErrorDefaults() {
o.ErrorCodes = map[string]error{
//Spot Errors
"10000": errors.New("Required field, can not be null"),
"10001": errors.New("Request frequency too high to exceed the limit allowed"),
"10002": errors.New("System error"),
"10004": errors.New("Request failed - Your API key might need to be recreated"),
"10005": errors.New("'SecretKey' does not exist"),
"10006": errors.New("'Api_key' does not exist"),
"10007": errors.New("Signature does not match"),
"10008": errors.New("Illegal parameter"),
"10009": errors.New("Order does not exist"),
"10010": errors.New("Insufficient funds"),
"10011": errors.New("Amount too low"),
"10012": errors.New("Only btc_usd ltc_usd supported"),
"10013": errors.New("Only support https request"),
"10014": errors.New("Order price must be between 0 and 1,000,000"),
"10015": errors.New("Order price differs from current market price too much"),
"10016": errors.New("Insufficient coins balance"),
"10017": errors.New("API authorization error"),
"10018": errors.New("borrow amount less than lower limit [usd:100,btc:0.1,ltc:1]"),
"10019": errors.New("loan agreement not checked"),
"10020": errors.New("rate cannot exceed 1%"),
"10021": errors.New("rate cannot less than 0.01%"),
"10023": errors.New("fail to get latest ticker"),
"10024": errors.New("balance not sufficient"),
"10025": errors.New("quota is full, cannot borrow temporarily"),
"10026": errors.New("Loan (including reserved loan) and margin cannot be withdrawn"),
"10027": errors.New("Cannot withdraw within 24 hrs of authentication information modification"),
"10028": errors.New("Withdrawal amount exceeds daily limit"),
"10029": errors.New("Account has unpaid loan, please cancel/pay off the loan before withdraw"),
"10031": errors.New("Deposits can only be withdrawn after 6 confirmations"),
"10032": errors.New("Please enabled phone/google authenticator"),
"10033": errors.New("Fee higher than maximum network transaction fee"),
"10034": errors.New("Fee lower than minimum network transaction fee"),
"10035": errors.New("Insufficient BTC/LTC"),
"10036": errors.New("Withdrawal amount too low"),
"10037": errors.New("Trade password not set"),
"10040": errors.New("Withdrawal cancellation fails"),
"10041": errors.New("Withdrawal address not exsit or approved"),
"10042": errors.New("Admin password error"),
"10043": errors.New("Account equity error, withdrawal failure"),
"10044": errors.New("fail to cancel borrowing order"),
"10047": errors.New("this function is disabled for sub-account"),
"10048": errors.New("withdrawal information does not exist"),
"10049": errors.New("User can not have more than 50 unfilled small orders (amount<0.15BTC)"),
"10050": errors.New("can't cancel more than once"),
"10051": errors.New("order completed transaction"),
"10052": errors.New("not allowed to withdraw"),
"10064": errors.New("after a USD deposit, that portion of assets will not be withdrawable for the next 48 hours"),
"10100": errors.New("User account frozen"),
"10101": errors.New("order type is wrong"),
"10102": errors.New("incorrect ID"),
"10103": errors.New("the private otc order's key incorrect"),
"10216": errors.New("Non-available API"),
"1002": errors.New("The transaction amount exceed the balance"),
"1003": errors.New("The transaction amount is less than the minimum requirement"),
"1004": errors.New("The transaction amount is less than 0"),
"1007": errors.New("No trading market information"),
"1008": errors.New("No latest market information"),
"1009": errors.New("No order"),
"1010": errors.New("Different user of the cancelled order and the original order"),
"1011": errors.New("No documented user"),
"1013": errors.New("No order type"),
"1014": errors.New("No login"),
"1015": errors.New("No market depth information"),
"1017": errors.New("Date error"),
"1018": errors.New("Order failed"),
"1019": errors.New("Undo order failed"),
"1024": errors.New("Currency does not exist"),
"1025": errors.New("No chart type"),
"1026": errors.New("No base currency quantity"),
"1027": errors.New("Incorrect parameter may exceeded limits"),
"1028": errors.New("Reserved decimal failed"),
"1029": errors.New("Preparing"),
"1030": errors.New("Account has margin and futures, transactions can not be processed"),
"1031": errors.New("Insufficient Transferring Balance"),
"1032": errors.New("Transferring Not Allowed"),
"1035": errors.New("Password incorrect"),
"1036": errors.New("Google Verification code Invalid"),
"1037": errors.New("Google Verification code incorrect"),
"1038": errors.New("Google Verification replicated"),
"1039": errors.New("Message Verification Input exceed the limit"),
"1040": errors.New("Message Verification invalid"),
"1041": errors.New("Message Verification incorrect"),
"1042": errors.New("Wrong Google Verification Input exceed the limit"),
"1043": errors.New("Login password cannot be same as the trading password"),
"1044": errors.New("Old password incorrect"),
"1045": errors.New("2nd Verification Needed"),
"1046": errors.New("Please input old password"),
"1048": errors.New("Account Blocked"),
"1201": errors.New("Account Deleted at 00: 00"),
"1202": errors.New("Account Not Exist"),
"1203": errors.New("Insufficient Balance"),
"1204": errors.New("Invalid currency"),
"1205": errors.New("Invalid Account"),
"1206": errors.New("Cash Withdrawal Blocked"),
"1207": errors.New("Transfer Not Support"),
"1208": errors.New("No designated account"),
"1209": errors.New("Invalid api"),
"1216": errors.New("Market order temporarily suspended. Please send limit order"),
"1217": errors.New("Order was sent at ±5% of the current market price. Please resend"),
"1218": errors.New("Place order failed. Please try again later"),
// Errors for both
"HTTP ERROR CODE 403": errors.New("Too many requests, IP is shielded"),
"Request Timed Out": errors.New("Too many requests, IP is shielded"),
// contract errors
"405": errors.New("method not allowed"),
"20001": errors.New("User does not exist"),
"20002": errors.New("Account frozen"),
"20003": errors.New("Account frozen due to liquidation"),
"20004": errors.New("Contract account frozen"),
"20005": errors.New("User contract account does not exist"),
"20006": errors.New("Required field missing"),
"20007": errors.New("Illegal parameter"),
"20008": errors.New("Contract account balance is too low"),
"20009": errors.New("Contract status error"),
"20010": errors.New("Risk rate ratio does not exist"),
"20011": errors.New("Risk rate lower than 90%/80% before opening BTC position with 10x/20x leverage. or risk rate lower than 80%/60% before opening LTC position with 10x/20x leverage"),
"20012": errors.New("Risk rate lower than 90%/80% after opening BTC position with 10x/20x leverage. or risk rate lower than 80%/60% after opening LTC position with 10x/20x leverage"),
"20013": errors.New("Temporally no counter party price"),
"20014": errors.New("System error"),
"20015": errors.New("Order does not exist"),
"20016": errors.New("Close amount bigger than your open positions"),
"20017": errors.New("Not authorized/illegal operation"),
"20018": errors.New("Order price cannot be more than 103% or less than 97% of the previous minute price"),
"20019": errors.New("IP restricted from accessing the resource"),
"20020": errors.New("secretKey does not exist"),
"20021": errors.New("Index information does not exist"),
"20022": errors.New("Wrong API interface (Cross margin mode shall call cross margin API, fixed margin mode shall call fixed margin API)"),
"20023": errors.New("Account in fixed-margin mode"),
"20024": errors.New("Signature does not match"),
"20025": errors.New("Leverage rate error"),
"20026": errors.New("API Permission Error"),
"20027": errors.New("no transaction record"),
"20028": errors.New("no such contract"),
"20029": errors.New("Amount is large than available funds"),
"20030": errors.New("Account still has debts"),
"20038": errors.New("Due to regulation, this function is not available in the country/region your currently reside in"),
"20049": errors.New("Request frequency too high"),
}
}
// SetCheckVarDefaults sets main variables that will be used in requests because
// api does not return an error if there are misspellings in strings. So better
// to check on this, this end.
func (o *OKEX) SetCheckVarDefaults() {
o.ContractTypes = []string{"this_week", "next_week", "quarter"}
o.CurrencyPairs = []string{"btc_usd", "ltc_usd", "eth_usd", "etc_usd", "bch_usd"}
o.Types = []string{"1min", "3min", "5min", "15min", "30min", "1day", "3day",
"1week", "1hour", "2hour", "4hour", "6hour", "12hour"}
o.ContractPosition = []string{"1", "2", "3", "4"}
}
// CheckContractPosition checks to see if the string is a valid position for okex
func (o *OKEX) CheckContractPosition(position string) error {
if !common.StringDataCompare(o.ContractPosition, position) {
return errors.New("invalid position string - e.g. 1 = open long position, 2 = open short position, 3 = liquidate long position, 4 = liquidate short position")
}
return nil
}
// CheckSymbol checks to see if the string is a valid symbol for okex
func (o *OKEX) CheckSymbol(symbol string) error {
if !common.StringDataCompare(o.CurrencyPairs, symbol) {
return errors.New("invalid symbol string")
}
return nil
}
// CheckContractType checks to see if the string is a correct asset
func (o *OKEX) CheckContractType(contractType string) error {
if !common.StringDataCompare(o.ContractTypes, contractType) {
return errors.New("invalid contract type string")
}
return nil
}
// CheckType checks to see if the string is a correct type
func (o *OKEX) CheckType(typeInput string) error {
if !common.StringDataCompare(o.Types, typeInput) {
return errors.New("invalid type string")
}
return nil
}
// GetFee returns an estimate of fee based on type of transaction
func (o *OKEX) GetFee(feeBuilder exchange.FeeBuilder) (float64, error) {
var fee float64
switch feeBuilder.FeeType {
case exchange.CryptocurrencyTradeFee:
fee = calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount, feeBuilder.IsMaker)
case exchange.CryptocurrencyWithdrawalFee:
fee = getWithdrawalFee(feeBuilder.FirstCurrency)
}
if fee < 0 {
fee = 0
}
return fee, nil
}
func calculateTradingFee(purchasePrice, amount float64, isMaker bool) (fee float64) {
// TODO volume based fees
if isMaker {
fee = 0.001
} else {
fee = 0.0015
}
return fee * amount * purchasePrice
}
func getWithdrawalFee(currency string) float64 {
return WithdrawalFees[currency]
}
// GetBalance returns the full balance across all wallets
func (o *OKEX) GetBalance() ([]FullBalance, error) {
var resp Balance
var balances []FullBalance
err := o.SendAuthenticatedHTTPRequest(myWalletInfo, url.Values{}, &resp)
if err != nil {
return balances, err
}
for key, available := range resp.Info.Funds.Free {
free, err := strconv.ParseFloat(available, 64)
if err != nil {
return balances, err
}
inUse, ok := resp.Info.Funds.Holds[key]
if !ok {
return balances, fmt.Errorf("hold currency %s not found in map", key)
}
hold, err := strconv.ParseFloat(inUse, 64)
if err != nil {
return balances, err
}
balances = append(balances, FullBalance{
Currency: key,
Available: free,
Hold: hold,
})
}
return balances, nil
}
// Withdrawal withdraws a cryptocurrency to a supplied address
func (o *OKEX) Withdrawal(symbol string, fee float64, tradePWD, address string, amount float64) (int, error) {
v := url.Values{}
v.Set("symbol", symbol)
if fee != 0 {
v.Set("chargefee", strconv.FormatFloat(fee, 'f', -1, 64))
}
v.Set("trade_pwd", tradePWD)
v.Set("withdraw_address", address)
v.Set("withdraw_amount", strconv.FormatFloat(amount, 'f', -1, 64))
v.Set("target", "address")
resp := WithdrawalResponse{}
err := o.SendAuthenticatedHTTPRequest(spotWithdraw, v, &resp)
if err != nil {
return 0, err
}
if !resp.Result {
return 0, errors.New("unable to process withdrawal request")
}
return resp.WithdrawID, nil
}