Files
gocryptotrader/exchanges/okex/okex.go
soxipy fb4e2d1452 localbitcoins fixes (#177)
* General LocalBitcoin fixes

* Added override variables to config for exchange packages to allow different API URL's
2018-08-27 14:19:29 +10:00

1107 lines
36 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 (
"errors"
"fmt"
"log"
"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"
)
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"
// Authenticated
spotUserInfo = "userinfo"
spotTrade = "trade"
spotBatchTrade = "batch_trade"
spotCancelTrade = "cancel_order"
spotOrderInfo = "order_info"
spotMultiOrderInfo = "orders_info"
spotWithdraw = "withdraw"
spotCancelWithdraw = "cancel_withdraw"
spotWithdrawInfo = "withdraw_info"
spotAccountRecords = "account_records"
// 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.Websocket = false
o.RESTPollingDelay = 10
o.RequestCurrencyPairFormat.Delimiter = "_"
o.RequestCurrencyPairFormat.Uppercase = false
o.ConfigCurrencyPairFormat.Delimiter = "_"
o.ConfigCurrencyPairFormat.Uppercase = false
o.SupportsAutoPairUpdating = false
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
}
// 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 = 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)
}
}
}
// 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 {
//Still figuring this one out Wrong API interface
var resp interface{}
path := fmt.Sprintf("%s%s%s.do", o.APIUrl, apiVersion, contractFutureUserInfo)
if err := o.SendAuthenticatedHTTPRequest(path, 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 {
//Still figuring out errors :( as above
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)
path := fmt.Sprintf("%s%s%s.do", o.APIUrl, apiVersion, "future_position")
if err := o.SendAuthenticatedHTTPRequest(path, 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))
path := fmt.Sprintf("%s%s%s.do", o.APIUrl, apiVersion, "future_trade")
if err := o.SendAuthenticatedHTTPRequest(path, values, &resp); err != nil {
return 0, err
}
contractMap := resp.(map[string]interface{})
if code, ok := contractMap["error_code"]; ok {
return 0, o.GetErrorCode(code)
}
return contractMap["order_id"].(float64), 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))
path := fmt.Sprintf("%s%s%s.do", o.APIUrl, apiVersion, "future_trades_history")
if err := o.SendAuthenticatedHTTPRequest(path, values, &resp); err != nil {
return err
}
respMap := resp.(map[string]interface{})
if code, ok := respMap["error_code"]; ok {
return o.GetErrorCode(code)
}
return nil
}
// GetUserInfo returns the user info
func (o *OKEX) GetUserInfo() (SpotUserInfo, error) {
strRequestURL := fmt.Sprintf("%s%s%s.do", o.APIUrl, apiVersion, spotUserInfo)
var res SpotUserInfo
err := o.SendAuthenticatedHTTPRequest(strRequestURL, url.Values{}, &res)
if err != nil {
return res, err
}
return res, 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
strRequestURL := fmt.Sprintf("%s%s%s.do", o.APIUrl, apiVersion, spotTrade)
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(strRequestURL, 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) {
type response struct {
Result bool `json:"result"`
OrderID string `json:"order_id"`
ErrorCode int `json:"error_code"`
}
var res response
strRequestURL := fmt.Sprintf("%s%s%s.do", o.APIUrl, apiVersion, spotCancelTrade)
params := url.Values{}
params.Set("symbol", symbol)
params.Set("order_id", strconv.FormatInt(argOrderID, 10))
err := o.SendAuthenticatedHTTPRequest(strRequestURL, params, &res)
var returnOrderID int64
if err != nil && res.ErrorCode != 0 {
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 + method
if o.Verbose {
log.Printf("Sending POST request to %s with params %s\n", path, encoded)
}
headers := make(map[string]string)
headers["Content-Type"] = "application/x-www-form-urlencoded"
return o.SendPayload("POST", path, headers, strings.NewReader(encoded), result, true, o.Verbose)
}
// 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"),
"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
}