mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-18 23:16:49 +00:00
OKEX/OKCoin API combine via OKGroup (#252)
* Initial commit * Successful authenticated request implementation. * Removes data from okcoin, okex. Implements some account okgroup endpoints. Adds tests * Finishes account API endpoint implementations. * Implements and adds tests for the following okgroup v3 API endpoints: GetSpotTradingAccounts, GetSpotTradingAccountForCurrency, GetSpotBillDetailsForCurrency, PlaceSpotOrder, PlaceMultipleSpotOrders, CancelSpotOrder, CancelMultipleSpotOrders, GetSpotOrders, GetSpotOpenOrders, GetSpotOrder, GetSpotTransactionDetails, GetSpotTokenPairDetails, GetSpotOrderBook, GetSpotAllTokenPairsInformation, GetSpotAllTokenPairsInformationForCurrency, GetSpotFilledOrdersInformation, GetSpotMarketData * Implements and adds tests for all margin api endpoints: GetMarginTradingAccounts, GetMarginTradingAccountsForCurrency, GetMarginBillDetails, GetMarginAccountSettings, GetMarginLoanHistory, OpenMarginLoan, RepayMarginLoan, PlaceMarginOrder, PlaceMultipleMarginOrders, CancelMarginOrder, CancelMultipleMarginOrders, GetMarginOrders, GetMarginOpenOrders, GetMarginOrder, GetMarginTransactionDetails. Simplifies some Spot endpoints combining ForCurrency funcs where possible * Adds all 29 Futures endpoints with tests. Updates comments and naming conventions. Adds standard realordertest func. Adds ability to make public API requests. Adds test purpose comments * Adds 29 endpoints for SWAP API support. Adds tests for each endpoint. Declares response variables in function declaration. Simplifies URL parameter formatting * Adds all ETT endpoints with tests * Creates OKCoin and OKEX struct types. Moves okgroup tests to okcoin and okex exchanges. Updates withdraw fee calculation. Updates exchange.go exchange declaration to point to new types. Streamlines wrapper tests. Begins websocket integration * Rebase fixes * Deletes okcoin_types.go, okcoin_wrapper.go, okex_types.go, okex_wrapper.go. Combines okex,okcoin wrappers in okgroup_wrapper.go. Removes boilerplate url.values with new request struct type parsing. Adds github.com/google/go-querystring to go modules. Replaces USDT with USD for OKCoin tests. Moves OKEX specific endpoints (futures, swap & ett) to okex.go. Fixes recieving receiving again * Maps the wrapper * Parses json into time.Time instead of string + conversion * Renames websocket.SetEnabled to websocket.SetWsStatusAndConnection. Maps main spot websocket functions for okgroup. Adds some basic ws tests * Updates websocket default URLS, adds checksum tests, removes setdefaults from okgroup, adds WebsocketDataWrapper to wrap all okgroup websocket data responses, handles spot, swap, index and futures ticker, candle, trade, orderbook, funding fee websocket responses. Partially implements checksum validation on orderbook data. Fixes all linting issues * Handles the calculation of okgroup websocket checksums. Adds tests * Now all orderbook checksums are validated. Cleans up implementations and removes invalid checksum calculator functions. Adds function to parse ordertype. Puts verbose logs behind verbose check * Removes parallel from okgroup tests. Removes unused code. Adds GetWsChannelWithoutOrderType. Improves handling of WS data types. Adds types for more ws channels. Simplifies update orderbook handling * updates btse func name * linting * Fixes websocket connection issue with tests. Removes test verbosity * Updates checksum calculation to handle more than 7 decimal places. Adds rate limiters. Uses != "" instead of len > 0. Adds new test to handle checksum calculation with 8 decimal places. * Removes logging. Fixes orderbook wrapper references * Adds slightly more robust resubscribe func. Adds websocket tests that can detect websocket errors * Fixes linting issues * Adds new WS func IsConnected() to expose ws status. Tests protect against channel timeout * Adds test comments. Fixes parallel issues for futures tests * Fixes error output for wrapper function
This commit is contained in:
@@ -129,7 +129,7 @@ func (b *Bitfinex) Setup(exch config.ExchangeConfig) {
|
||||
b.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
||||
b.RESTPollingDelay = exch.RESTPollingDelay
|
||||
b.Verbose = exch.Verbose
|
||||
b.Websocket.SetEnabled(exch.Websocket)
|
||||
b.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
@@ -114,7 +114,7 @@ func (b *Bitflyer) Setup(exch config.ExchangeConfig) {
|
||||
b.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
||||
b.RESTPollingDelay = exch.RESTPollingDelay
|
||||
b.Verbose = exch.Verbose
|
||||
b.Websocket.SetEnabled(exch.Websocket)
|
||||
b.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
@@ -96,7 +96,7 @@ func (b *Bithumb) Setup(exch config.ExchangeConfig) {
|
||||
b.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
||||
b.RESTPollingDelay = exch.RESTPollingDelay
|
||||
b.Verbose = exch.Verbose
|
||||
b.Websocket.SetEnabled(exch.Websocket)
|
||||
b.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
@@ -148,7 +148,7 @@ func (b *Bitmex) Setup(exch config.ExchangeConfig) {
|
||||
b.SetAPIKeys(exch.APIKey, exch.APISecret, "", false)
|
||||
b.RESTPollingDelay = exch.RESTPollingDelay
|
||||
b.Verbose = exch.Verbose
|
||||
b.Websocket.SetEnabled(exch.Websocket)
|
||||
b.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
@@ -103,7 +103,7 @@ func (b *Bitstamp) Setup(exch config.ExchangeConfig) {
|
||||
b.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
||||
b.RESTPollingDelay = exch.RESTPollingDelay
|
||||
b.Verbose = exch.Verbose
|
||||
b.Websocket.SetEnabled(exch.Websocket)
|
||||
b.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
@@ -59,7 +59,7 @@ func (b *BTCC) Setup(exch config.ExchangeConfig) {
|
||||
b.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
||||
b.RESTPollingDelay = exch.RESTPollingDelay
|
||||
b.Verbose = exch.Verbose
|
||||
b.Websocket.SetEnabled(exch.Websocket)
|
||||
b.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
@@ -81,7 +81,7 @@ func (b *BTSE) Setup(exch config.ExchangeConfig) {
|
||||
b.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
||||
b.RESTPollingDelay = exch.RESTPollingDelay
|
||||
b.Verbose = exch.Verbose
|
||||
b.Websocket.SetEnabled(exch.Websocket)
|
||||
b.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
@@ -103,7 +103,7 @@ func (c *CoinbasePro) Setup(exch config.ExchangeConfig) {
|
||||
c.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
||||
c.RESTPollingDelay = exch.RESTPollingDelay
|
||||
c.Verbose = exch.Verbose
|
||||
c.Websocket.SetEnabled(exch.Websocket)
|
||||
c.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
c.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
c.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
c.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
@@ -94,7 +94,7 @@ func (c *COINUT) Setup(exch config.ExchangeConfig) {
|
||||
c.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
||||
c.RESTPollingDelay = exch.RESTPollingDelay
|
||||
c.Verbose = exch.Verbose
|
||||
c.Websocket.SetEnabled(exch.Websocket)
|
||||
c.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
c.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
c.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
c.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
@@ -240,9 +240,9 @@ type OrderDetail struct {
|
||||
type FundHistory struct {
|
||||
ExchangeName string
|
||||
Status string
|
||||
TransferID int64
|
||||
TransferID string
|
||||
Description string
|
||||
Timestamp int64
|
||||
Timestamp time.Time
|
||||
Currency string
|
||||
Amount float64
|
||||
Fee float64
|
||||
|
||||
@@ -66,7 +66,7 @@ func (e *Base) WebsocketSetup(connector func() error,
|
||||
e.Websocket.Disconnected = make(chan struct{}, 1)
|
||||
e.Websocket.TrafficAlert = make(chan struct{}, 1)
|
||||
|
||||
err := e.Websocket.SetEnabled(wsEnabled)
|
||||
err := e.Websocket.SetWsStatusAndConnection(wsEnabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -212,6 +212,11 @@ func (w *Websocket) Connect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsConnected exposes websocket connection status
|
||||
func (w *Websocket) IsConnected() bool {
|
||||
return w.connected
|
||||
}
|
||||
|
||||
// Shutdown attempts to shut down a websocket connection and associated routines
|
||||
// by using a package defined shutdown function
|
||||
func (w *Websocket) Shutdown() error {
|
||||
@@ -259,8 +264,9 @@ func (w *Websocket) GetWebsocketURL() string {
|
||||
return w.runningURL
|
||||
}
|
||||
|
||||
// SetEnabled sets if websocket is enabled
|
||||
func (w *Websocket) SetEnabled(enabled bool) error {
|
||||
// SetWsStatusAndConnection sets if websocket is enabled
|
||||
// it will also connect/disconnect the websocket connection
|
||||
func (w *Websocket) SetWsStatusAndConnection(enabled bool) error {
|
||||
if w.enabled == enabled {
|
||||
if w.init {
|
||||
return nil
|
||||
|
||||
@@ -93,19 +93,19 @@ func TestWebsocket(t *testing.T) {
|
||||
wsTest.Websocket.SetWebsocketURL("")
|
||||
|
||||
// -- Set true when already true
|
||||
err = wsTest.Websocket.SetEnabled(true)
|
||||
err = wsTest.Websocket.SetWsStatusAndConnection(true)
|
||||
if err == nil {
|
||||
t.Fatal("test failed - setting enabled should not work")
|
||||
}
|
||||
|
||||
// -- Set false normal
|
||||
err = wsTest.Websocket.SetEnabled(false)
|
||||
err = wsTest.Websocket.SetWsStatusAndConnection(false)
|
||||
if err != nil {
|
||||
t.Fatal("test failed - setting enabled should not work")
|
||||
}
|
||||
|
||||
// -- Set true normal
|
||||
err = wsTest.Websocket.SetEnabled(true)
|
||||
err = wsTest.Websocket.SetWsStatusAndConnection(true)
|
||||
if err != nil {
|
||||
t.Fatal("test failed - setting enabled should not work")
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ func (h *HitBTC) Setup(exch config.ExchangeConfig) {
|
||||
h.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
||||
h.RESTPollingDelay = exch.RESTPollingDelay // Max 60000ms
|
||||
h.Verbose = exch.Verbose
|
||||
h.Websocket.SetEnabled(exch.Websocket)
|
||||
h.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
h.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
h.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
h.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
@@ -112,7 +112,7 @@ func (h *HUOBI) Setup(exch config.ExchangeConfig) {
|
||||
h.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
||||
h.RESTPollingDelay = exch.RESTPollingDelay
|
||||
h.Verbose = exch.Verbose
|
||||
h.Websocket.SetEnabled(exch.Websocket)
|
||||
h.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
h.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
h.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
h.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,444 +0,0 @@
|
||||
package okcoin
|
||||
|
||||
import "github.com/thrasher-/gocryptotrader/currency/symbol"
|
||||
|
||||
// SpotInstrument stores the spot instrument info
|
||||
type SpotInstrument struct {
|
||||
BaseCurrency string `json:"base_currency"`
|
||||
BaseIncrement float64 `json:"base_increment,string"`
|
||||
BaseMinSize float64 `json:"base_min_size,string"`
|
||||
InstrumentID string `json:"instrument_id"`
|
||||
MinSize float64 `json:"min_size,string"`
|
||||
ProductID string `json:"product_id"`
|
||||
QuoteCurrency string `json:"quote_currency"`
|
||||
QuoteIncrement float64 `json:"quote_increment,string"`
|
||||
SizeIncrement float64 `json:"size_increment,string"`
|
||||
TickSize float64 `json:"tick_size,string"`
|
||||
}
|
||||
|
||||
// Ticker holds ticker data
|
||||
type Ticker struct {
|
||||
Buy float64 `json:",string"`
|
||||
High float64 `json:",string"`
|
||||
Last float64 `json:",string"`
|
||||
Low float64 `json:",string"`
|
||||
Sell float64 `json:",string"`
|
||||
Vol float64 `json:",string"`
|
||||
}
|
||||
|
||||
// TickerResponse is the response type for ticker
|
||||
type TickerResponse struct {
|
||||
Date string
|
||||
Ticker Ticker
|
||||
}
|
||||
|
||||
// FuturesTicker holds futures ticker data
|
||||
type FuturesTicker struct {
|
||||
Last float64
|
||||
Buy float64
|
||||
Sell float64
|
||||
High float64
|
||||
Low float64
|
||||
Vol float64
|
||||
ContractID int64
|
||||
UnitAmount float64
|
||||
}
|
||||
|
||||
// Orderbook holds orderbook data
|
||||
type Orderbook struct {
|
||||
Asks [][]float64 `json:"asks"`
|
||||
Bids [][]float64 `json:"bids"`
|
||||
}
|
||||
|
||||
// FuturesTickerResponse is a response type
|
||||
type FuturesTickerResponse struct {
|
||||
Date string
|
||||
Ticker FuturesTicker
|
||||
}
|
||||
|
||||
// BorrowInfo holds borrowing amount data
|
||||
type BorrowInfo struct {
|
||||
BorrowBTC float64 `json:"borrow_btc"`
|
||||
BorrowLTC float64 `json:"borrow_ltc"`
|
||||
BorrowCNY float64 `json:"borrow_cny"`
|
||||
CanBorrow float64 `json:"can_borrow"`
|
||||
InterestBTC float64 `json:"interest_btc"`
|
||||
InterestLTC float64 `json:"interest_ltc"`
|
||||
Result bool `json:"result"`
|
||||
DailyInterestBTC float64 `json:"today_interest_btc"`
|
||||
DailyInterestLTC float64 `json:"today_interest_ltc"`
|
||||
DailyInterestCNY float64 `json:"today_interest_cny"`
|
||||
}
|
||||
|
||||
// BorrowOrder holds order data
|
||||
type BorrowOrder struct {
|
||||
Amount float64 `json:"amount"`
|
||||
BorrowDate int64 `json:"borrow_date"`
|
||||
BorrowID int64 `json:"borrow_id"`
|
||||
Days int64 `json:"days"`
|
||||
TradeAmount float64 `json:"deal_amount"`
|
||||
Rate float64 `json:"rate"`
|
||||
Status int64 `json:"status"`
|
||||
Symbol string `json:"symbol"`
|
||||
}
|
||||
|
||||
// Record hold record data
|
||||
type Record struct {
|
||||
Address string `json:"addr"`
|
||||
Account int64 `json:"account,string"`
|
||||
Amount float64 `json:"amount"`
|
||||
Bank string `json:"bank"`
|
||||
BenificiaryAddress string `json:"benificiary_addr"`
|
||||
TransactionValue float64 `json:"transaction_value"`
|
||||
Fee float64 `json:"fee"`
|
||||
Date float64 `json:"date"`
|
||||
}
|
||||
|
||||
// AccountRecords holds account record data
|
||||
type AccountRecords struct {
|
||||
Records []Record `json:"records"`
|
||||
Symbol string `json:"symbol"`
|
||||
}
|
||||
|
||||
// FuturesOrder holds information about a futures order
|
||||
type FuturesOrder struct {
|
||||
Amount float64 `json:"amount"`
|
||||
ContractName string `json:"contract_name"`
|
||||
DateCreated float64 `json:"create_date"`
|
||||
TradeAmount float64 `json:"deal_amount"`
|
||||
Fee float64 `json:"fee"`
|
||||
LeverageRate float64 `json:"lever_rate"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
Price float64 `json:"price"`
|
||||
AvgPrice float64 `json:"avg_price"`
|
||||
Status float64 `json:"status"`
|
||||
Symbol string `json:"symbol"`
|
||||
Type int64 `json:"type"`
|
||||
UnitAmount int64 `json:"unit_amount"`
|
||||
}
|
||||
|
||||
// FuturesHoldAmount contains futures hold amount data
|
||||
type FuturesHoldAmount struct {
|
||||
Amount float64 `json:"amount"`
|
||||
ContractName string `json:"contract_name"`
|
||||
}
|
||||
|
||||
// FuturesExplosive holds inforamtion about explosive futures
|
||||
type FuturesExplosive struct {
|
||||
Amount float64 `json:"amount,string"`
|
||||
DateCreated string `json:"create_date"`
|
||||
Loss float64 `json:"loss,string"`
|
||||
Type int64 `json:"type"`
|
||||
}
|
||||
|
||||
// Trades holds trade data
|
||||
type Trades struct {
|
||||
Amount float64 `json:"amount,string"`
|
||||
Date int64 `json:"date"`
|
||||
DateMS int64 `json:"date_ms"`
|
||||
Price float64 `json:"price,string"`
|
||||
TradeID int64 `json:"tid"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// FuturesTrades holds trade data for the futures market
|
||||
type FuturesTrades struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Date int64 `json:"date"`
|
||||
DateMS int64 `json:"date_ms"`
|
||||
Price float64 `json:"price"`
|
||||
TradeID int64 `json:"tid"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// UserInfo holds user account details
|
||||
type UserInfo struct {
|
||||
Info struct {
|
||||
Funds struct {
|
||||
Asset struct {
|
||||
Net float64 `json:"net,string"`
|
||||
Total float64 `json:"total,string"`
|
||||
} `json:"asset"`
|
||||
Borrow struct {
|
||||
BTC float64 `json:"btc,string"`
|
||||
LTC float64 `json:"ltc,string"`
|
||||
USD float64 `json:"usd,string"`
|
||||
CNY float64 `json:"cny,string"`
|
||||
} `json:"borrow"`
|
||||
Free struct {
|
||||
BTC float64 `json:"btc,string"`
|
||||
LTC float64 `json:"ltc,string"`
|
||||
USD float64 `json:"usd,string"`
|
||||
CNY float64 `json:"cny,string"`
|
||||
} `json:"free"`
|
||||
Freezed struct {
|
||||
BTC float64 `json:"btc,string"`
|
||||
LTC float64 `json:"ltc,string"`
|
||||
USD float64 `json:"usd,string"`
|
||||
CNY float64 `json:"cny,string"`
|
||||
} `json:"freezed"`
|
||||
UnionFund struct {
|
||||
BTC float64 `json:"btc,string"`
|
||||
LTC float64 `json:"ltc,string"`
|
||||
} `json:"union_fund"`
|
||||
} `json:"funds"`
|
||||
} `json:"info"`
|
||||
Result bool `json:"result"`
|
||||
}
|
||||
|
||||
// BatchTrade holds data on a batch of trades
|
||||
type BatchTrade struct {
|
||||
OrderInfo []struct {
|
||||
OrderID int64 `json:"order_id"`
|
||||
ErrorCode int64 `json:"error_code"`
|
||||
} `json:"order_info"`
|
||||
Result bool `json:"result"`
|
||||
}
|
||||
|
||||
// CancelOrderResponse is a response type for a cancelled order
|
||||
type CancelOrderResponse struct {
|
||||
Success string
|
||||
ErrorCode string `json:"error_code"`
|
||||
Result bool `json:"result"`
|
||||
}
|
||||
|
||||
// OrderInfo holds data on an order
|
||||
type OrderInfo struct {
|
||||
Amount float64 `json:"amount"`
|
||||
AvgPrice float64 `json:"avg_price"`
|
||||
Created int64 `json:"create_date"`
|
||||
DealAmount float64 `json:"deal_amount"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
OrdersID int64 `json:"orders_id"`
|
||||
Price float64 `json:"price"`
|
||||
Status int `json:"status"`
|
||||
Symbol string `json:"symbol"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// OrderHistory holds information on order history
|
||||
type OrderHistory struct {
|
||||
CurrentPage int `json:"current_page"`
|
||||
Orders []OrderInfo `json:"orders"`
|
||||
PageLength int `json:"page_length"`
|
||||
Result bool `json:"result"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
// WithdrawalResponse is a response type for withdrawal
|
||||
type WithdrawalResponse struct {
|
||||
WithdrawID int `json:"withdraw_id"`
|
||||
Result bool `json:"result"`
|
||||
}
|
||||
|
||||
// WithdrawInfo holds data on a withdraw
|
||||
type WithdrawInfo struct {
|
||||
Address string `json:"address"`
|
||||
Amount float64 `json:"amount"`
|
||||
Created int64 `json:"created_date"`
|
||||
ChargeFee float64 `json:"chargefee"`
|
||||
Status int `json:"status"`
|
||||
WithdrawID int64 `json:"withdraw_id"`
|
||||
}
|
||||
|
||||
// OrderFeeInfo holds data on order fees
|
||||
type OrderFeeInfo struct {
|
||||
Fee float64 `json:"fee,string"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// LendDepth hold lend depths
|
||||
type LendDepth struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Days string `json:"days"`
|
||||
Num int64 `json:"num"`
|
||||
Rate float64 `json:"rate,string"`
|
||||
}
|
||||
|
||||
// BorrowResponse is a response type for borrow
|
||||
type BorrowResponse struct {
|
||||
Result bool `json:"result"`
|
||||
BorrowID int `json:"borrow_id"`
|
||||
}
|
||||
|
||||
// WebsocketFutureIndex holds future index data for websocket
|
||||
type WebsocketFutureIndex struct {
|
||||
FutureIndex float64 `json:"futureIndex"`
|
||||
Timestamp int64 `json:"timestamp,string"`
|
||||
}
|
||||
|
||||
// WebsocketFuturesTicker holds futures ticker data for websocket
|
||||
type WebsocketFuturesTicker struct {
|
||||
Buy float64 `json:"buy"`
|
||||
ContractID string `json:"contractId"`
|
||||
High float64 `json:"high"`
|
||||
HoldAmount float64 `json:"hold_amount"`
|
||||
Last float64 `json:"last,string"`
|
||||
Low float64 `json:"low"`
|
||||
Sell float64 `json:"sell"`
|
||||
UnitAmount float64 `json:"unitAmount"`
|
||||
Volume float64 `json:"vol,string"`
|
||||
}
|
||||
|
||||
// WebsocketUserinfo holds user info for websocket
|
||||
type WebsocketUserinfo struct {
|
||||
Info struct {
|
||||
Funds struct {
|
||||
Asset struct {
|
||||
Net float64 `json:"net,string"`
|
||||
Total float64 `json:"total,string"`
|
||||
} `json:"asset"`
|
||||
Free struct {
|
||||
BTC float64 `json:"btc,string"`
|
||||
LTC float64 `json:"ltc,string"`
|
||||
USD float64 `json:"usd,string"`
|
||||
CNY float64 `json:"cny,string"`
|
||||
} `json:"free"`
|
||||
Frozen struct {
|
||||
BTC float64 `json:"btc,string"`
|
||||
LTC float64 `json:"ltc,string"`
|
||||
USD float64 `json:"usd,string"`
|
||||
CNY float64 `json:"cny,string"`
|
||||
} `json:"freezed"`
|
||||
} `json:"funds"`
|
||||
} `json:"info"`
|
||||
Result bool `json:"result"`
|
||||
}
|
||||
|
||||
// WebsocketFuturesContract holds futures contract information for websocket
|
||||
type WebsocketFuturesContract struct {
|
||||
Available float64 `json:"available"`
|
||||
Balance float64 `json:"balance"`
|
||||
Bond float64 `json:"bond"`
|
||||
ContractID float64 `json:"contract_id"`
|
||||
ContractType string `json:"contract_type"`
|
||||
Frozen float64 `json:"freeze"`
|
||||
Profit float64 `json:"profit"`
|
||||
Loss float64 `json:"unprofit"`
|
||||
}
|
||||
|
||||
// WebsocketFuturesUserInfo holds futures user information for websocket
|
||||
type WebsocketFuturesUserInfo struct {
|
||||
Info struct {
|
||||
BTC struct {
|
||||
Balance float64 `json:"balance"`
|
||||
Contracts []WebsocketFuturesContract `json:"contracts"`
|
||||
Rights float64 `json:"rights"`
|
||||
} `json:"btc"`
|
||||
LTC struct {
|
||||
Balance float64 `json:"balance"`
|
||||
Contracts []WebsocketFuturesContract `json:"contracts"`
|
||||
Rights float64 `json:"rights"`
|
||||
} `json:"ltc"`
|
||||
} `json:"info"`
|
||||
Result bool `json:"result"`
|
||||
}
|
||||
|
||||
// WebsocketOrder holds order data for websocket
|
||||
type WebsocketOrder struct {
|
||||
Amount float64 `json:"amount"`
|
||||
AvgPrice float64 `json:"avg_price"`
|
||||
DateCreated float64 `json:"create_date"`
|
||||
TradeAmount float64 `json:"deal_amount"`
|
||||
OrderID float64 `json:"order_id"`
|
||||
OrdersID float64 `json:"orders_id"`
|
||||
Price float64 `json:"price"`
|
||||
Status int64 `json:"status"`
|
||||
Symbol string `json:"symbol"`
|
||||
OrderType string `json:"type"`
|
||||
}
|
||||
|
||||
// WebsocketFuturesOrder holds futures order data for websocket
|
||||
type WebsocketFuturesOrder struct {
|
||||
Amount float64 `json:"amount"`
|
||||
ContractName string `json:"contract_name"`
|
||||
DateCreated float64 `json:"createdDate"`
|
||||
TradeAmount float64 `json:"deal_amount"`
|
||||
Fee float64 `json:"fee"`
|
||||
LeverageAmount int `json:"lever_rate"`
|
||||
OrderID float64 `json:"order_id"`
|
||||
Price float64 `json:"price"`
|
||||
AvgPrice float64 `json:"avg_price"`
|
||||
Status int `json:"status"`
|
||||
Symbol string `json:"symbol"`
|
||||
TradeType int `json:"type"`
|
||||
UnitAmount float64 `json:"unit_amount"`
|
||||
}
|
||||
|
||||
// WebsocketRealtrades holds real trade data for WebSocket
|
||||
type WebsocketRealtrades struct {
|
||||
AveragePrice float64 `json:"averagePrice,string"`
|
||||
CompletedTradeAmount float64 `json:"completedTradeAmount,string"`
|
||||
DateCreated float64 `json:"createdDate"`
|
||||
ID float64 `json:"id"`
|
||||
OrderID float64 `json:"orderId"`
|
||||
SigTradeAmount float64 `json:"sigTradeAmount,string"`
|
||||
SigTradePrice float64 `json:"sigTradePrice,string"`
|
||||
Status int64 `json:"status"`
|
||||
Symbol string `json:"symbol"`
|
||||
TradeAmount float64 `json:"tradeAmount,string"`
|
||||
TradePrice float64 `json:"buy,string"`
|
||||
TradeType string `json:"tradeType"`
|
||||
TradeUnitPrice float64 `json:"tradeUnitPrice,string"`
|
||||
UnTrade float64 `json:"unTrade,string"`
|
||||
}
|
||||
|
||||
// WebsocketFuturesRealtrades holds futures real trade data for websocket
|
||||
type WebsocketFuturesRealtrades struct {
|
||||
Amount float64 `json:"amount,string"`
|
||||
ContractID float64 `json:"contract_id,string"`
|
||||
ContractName string `json:"contract_name"`
|
||||
ContractType string `json:"contract_type"`
|
||||
TradeAmount float64 `json:"deal_amount,string"`
|
||||
Fee float64 `json:"fee,string"`
|
||||
OrderID float64 `json:"orderid"`
|
||||
Price float64 `json:"price,string"`
|
||||
AvgPrice float64 `json:"price_avg,string"`
|
||||
Status int `json:"status,string"`
|
||||
TradeType int `json:"type,string"`
|
||||
UnitAmount float64 `json:"unit_amount,string"`
|
||||
LeverageAmount int `json:"lever_rate,string"`
|
||||
}
|
||||
|
||||
// WebsocketEvent holds websocket events
|
||||
type WebsocketEvent struct {
|
||||
Event string `json:"event"`
|
||||
Channel string `json:"channel"`
|
||||
}
|
||||
|
||||
// WebsocketResponse holds websocket responses
|
||||
type WebsocketResponse struct {
|
||||
Channel string `json:"channel"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// WebsocketEventAuth holds websocket authenticated events
|
||||
type WebsocketEventAuth struct {
|
||||
Event string `json:"event"`
|
||||
Channel string `json:"channel"`
|
||||
Parameters map[string]string `json:"parameters"`
|
||||
}
|
||||
|
||||
// WebsocketEventAuthRemove holds websocket remove authenticated events
|
||||
type WebsocketEventAuthRemove struct {
|
||||
Event string `json:"event"`
|
||||
Channel string `json:"channel"`
|
||||
Parameters map[string]string `json:"parameters"`
|
||||
}
|
||||
|
||||
// WebsocketTradeOrderResponse holds trade order responses for websocket
|
||||
type WebsocketTradeOrderResponse struct {
|
||||
OrderID int64 `json:"order_id,string"`
|
||||
Result bool `json:"result,string"`
|
||||
}
|
||||
|
||||
// WithdrawalFees the large list of predefined withdrawal fees
|
||||
// Prone to change, using highest value
|
||||
var WithdrawalFees = map[string]float64{
|
||||
symbol.BTC: 0.005,
|
||||
symbol.LTC: 0.2,
|
||||
symbol.ETH: 0.01,
|
||||
symbol.ETC: 0.2,
|
||||
symbol.BCH: 0.002,
|
||||
}
|
||||
@@ -1,348 +0,0 @@
|
||||
package okcoin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/currency/pair"
|
||||
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
wsSubTicker = "ok_sub_spot_%s_ticker"
|
||||
wsSubDepthIncrement = "ok_sub_spot_%s_depth"
|
||||
wsSubDepthFull = "ok_sub_spot_%s_depth_%s"
|
||||
wsSubTrades = "ok_sub_spot_%s_deals"
|
||||
wsSubKline = "ok_sub_spot_%s_kline_%s"
|
||||
)
|
||||
|
||||
// PingHandler handles the keep alive
|
||||
func (o *OKCoin) PingHandler(_ string) error {
|
||||
return o.WebsocketConn.WriteControl(websocket.PingMessage,
|
||||
[]byte("{'event':'ping'}"),
|
||||
time.Now().Add(time.Second))
|
||||
}
|
||||
|
||||
// AddChannel adds a new channel on the websocket client
|
||||
func (o *OKCoin) AddChannel(channel string) error {
|
||||
event := WebsocketEvent{"addChannel", channel}
|
||||
data, err := common.JSONEncode(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return o.WebsocketConn.WriteMessage(websocket.TextMessage, data)
|
||||
}
|
||||
|
||||
// WsConnect initiates a websocket connection
|
||||
func (o *OKCoin) WsConnect() error {
|
||||
if !o.Websocket.IsEnabled() || !o.IsEnabled() {
|
||||
return errors.New(exchange.WebsocketNotEnabled)
|
||||
}
|
||||
|
||||
klineValues := []string{"1min", "3min", "5min", "15min", "30min", "1hour",
|
||||
"2hour", "4hour", "6hour", "12hour", "day", "3day", "week"}
|
||||
|
||||
var dialer websocket.Dialer
|
||||
|
||||
if o.Websocket.GetProxyAddress() != "" {
|
||||
proxy, err := url.Parse(o.Websocket.GetProxyAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dialer.Proxy = http.ProxyURL(proxy)
|
||||
}
|
||||
|
||||
var err error
|
||||
o.WebsocketConn, _, err = dialer.Dial(o.Websocket.GetWebsocketURL(),
|
||||
http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.WebsocketConn.SetPingHandler(o.PingHandler)
|
||||
|
||||
go o.WsHandleData()
|
||||
|
||||
for _, p := range o.GetEnabledCurrencies() {
|
||||
fPair := exchange.FormatExchangeCurrency(o.GetName(), p)
|
||||
|
||||
o.AddChannel(fmt.Sprintf(wsSubDepthFull, fPair.String(), "20"))
|
||||
o.AddChannel(fmt.Sprintf(wsSubKline, fPair.String(), klineValues[0]))
|
||||
o.AddChannel(fmt.Sprintf(wsSubTicker, fPair.String()))
|
||||
o.AddChannel(fmt.Sprintf(wsSubTrades, fPair.String()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsReadData reads from the websocket connection
|
||||
func (o *OKCoin) WsReadData() (exchange.WebsocketResponse, error) {
|
||||
_, resp, err := o.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
return exchange.WebsocketResponse{}, err
|
||||
}
|
||||
|
||||
o.Websocket.TrafficAlert <- struct{}{}
|
||||
return exchange.WebsocketResponse{Raw: resp}, nil
|
||||
}
|
||||
|
||||
// WsHandleData handles stream data from the websocket connection
|
||||
func (o *OKCoin) WsHandleData() {
|
||||
o.Websocket.Wg.Add(1)
|
||||
|
||||
defer func() {
|
||||
err := o.WebsocketConn.Close()
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- fmt.Errorf("okcoin_websocket.go - Unable to to close Websocket connection. Error: %s",
|
||||
err)
|
||||
}
|
||||
o.Websocket.Wg.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-o.Websocket.ShutdownC:
|
||||
return
|
||||
|
||||
default:
|
||||
resp, err := o.WsReadData()
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
}
|
||||
|
||||
var init []WsResponse
|
||||
err = common.JSONDecode(resp.Raw, &init)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
if init[0].ErrorCode != "" {
|
||||
log.Error(o.WebsocketErrors[init[0].ErrorCode])
|
||||
}
|
||||
|
||||
if init[0].Success {
|
||||
if init[0].Data == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if init[0].Channel == "addChannel" {
|
||||
continue
|
||||
}
|
||||
|
||||
var currencyPairSlice []string
|
||||
splitChar := common.SplitStrings(init[0].Channel, "_")
|
||||
currencyPairSlice = append(currencyPairSlice,
|
||||
common.StringToUpper(splitChar[3]),
|
||||
common.StringToUpper(splitChar[4]))
|
||||
currencyPair := common.JoinStrings(currencyPairSlice, "-")
|
||||
|
||||
assetType := common.StringToUpper(splitChar[2])
|
||||
|
||||
switch {
|
||||
case common.StringContains(init[0].Channel, "ticker") &&
|
||||
common.StringContains(init[0].Channel, "spot"):
|
||||
var ticker WsTicker
|
||||
|
||||
err = common.JSONDecode(init[0].Data, &ticker)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
o.Websocket.DataHandler <- exchange.TickerData{
|
||||
Timestamp: time.Unix(0, ticker.Timestamp),
|
||||
Pair: pair.NewCurrencyPairFromString(currencyPair),
|
||||
AssetType: assetType,
|
||||
Exchange: o.GetName(),
|
||||
ClosePrice: ticker.Close,
|
||||
OpenPrice: ticker.Open,
|
||||
HighPrice: ticker.Last,
|
||||
LowPrice: ticker.Low,
|
||||
Quantity: ticker.Volume,
|
||||
}
|
||||
|
||||
case common.StringContains(init[0].Channel, "depth"):
|
||||
var orderbook WsOrderbook
|
||||
|
||||
err = common.JSONDecode(init[0].Data, &orderbook)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
o.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
|
||||
Pair: pair.NewCurrencyPairFromString(currencyPair),
|
||||
Exchange: o.GetName(),
|
||||
Asset: assetType,
|
||||
}
|
||||
|
||||
case common.StringContains(init[0].Channel, "kline"):
|
||||
var klineData [][]interface{}
|
||||
|
||||
err = common.JSONDecode(init[0].Data, &klineData)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
var klines []WsKlines
|
||||
for _, data := range klineData {
|
||||
var newKline WsKlines
|
||||
|
||||
newKline.Timestamp, _ = strconv.ParseInt(data[0].(string), 10, 64)
|
||||
newKline.Open, _ = strconv.ParseFloat(data[1].(string), 64)
|
||||
newKline.High, _ = strconv.ParseFloat(data[1].(string), 64)
|
||||
newKline.Low, _ = strconv.ParseFloat(data[1].(string), 64)
|
||||
newKline.Close, _ = strconv.ParseFloat(data[1].(string), 64)
|
||||
newKline.Volume, _ = strconv.ParseFloat(data[1].(string), 64)
|
||||
|
||||
klines = append(klines, newKline)
|
||||
}
|
||||
|
||||
for _, data := range klines {
|
||||
o.Websocket.DataHandler <- exchange.KlineData{
|
||||
Timestamp: time.Unix(0, data.Timestamp),
|
||||
Pair: pair.NewCurrencyPairFromString(currencyPair),
|
||||
AssetType: assetType,
|
||||
Exchange: o.GetName(),
|
||||
OpenPrice: data.Open,
|
||||
ClosePrice: data.Close,
|
||||
HighPrice: data.High,
|
||||
LowPrice: data.Low,
|
||||
Volume: data.Volume,
|
||||
}
|
||||
}
|
||||
|
||||
case common.StringContains(init[0].Channel, "spot") &&
|
||||
common.StringContains(init[0].Channel, "deals"):
|
||||
var dealsData [][]interface{}
|
||||
err = common.JSONDecode(init[0].Data, &dealsData)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
var deals []WsDeals
|
||||
for _, data := range dealsData {
|
||||
var newDeal WsDeals
|
||||
newDeal.TID, _ = strconv.ParseInt(data[0].(string), 10, 64)
|
||||
newDeal.Price, _ = strconv.ParseFloat(data[1].(string), 64)
|
||||
newDeal.Amount, _ = strconv.ParseFloat(data[2].(string), 64)
|
||||
newDeal.Timestamp, _ = data[3].(string)
|
||||
newDeal.Type, _ = data[4].(string)
|
||||
deals = append(deals, newDeal) // nolint: staticcheck
|
||||
// TODO: will need to link this up
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetWebsocketErrorDefaults sets default errors for websocket
|
||||
func (o *OKCoin) SetWebsocketErrorDefaults() {
|
||||
o.WebsocketErrors = map[string]string{
|
||||
"10001": "Illegal parameters",
|
||||
"10002": "Authentication failure",
|
||||
"10003": "This connection has requested other user data",
|
||||
"10004": "This connection did not request this user data",
|
||||
"10005": "System error",
|
||||
"10009": "Order does not exist",
|
||||
"10010": "Insufficient funds",
|
||||
"10011": "Order quantity too low",
|
||||
"10012": "Only support btc_usd/btc_cny ltc_usd/ltc_cny",
|
||||
"10014": "Order price must be between 0 - 1,000,000",
|
||||
"10015": "Channel subscription temporally not available",
|
||||
"10016": "Insufficient coins",
|
||||
"10017": "WebSocket authorization error",
|
||||
"10100": "User frozen",
|
||||
"10216": "Non-public API",
|
||||
"20001": "User does not exist",
|
||||
"20002": "User frozen",
|
||||
"20003": "Frozen due to force liquidation",
|
||||
"20004": "Future account frozen",
|
||||
"20005": "User future account does not exist",
|
||||
"20006": "Required field can not be null",
|
||||
"20007": "Illegal parameter",
|
||||
"20008": "Future account fund balance is zero",
|
||||
"20009": "Future contract status error",
|
||||
"20010": "Risk rate information does not exist",
|
||||
"20011": `Risk rate bigger than 90% before opening position`,
|
||||
"20012": `Risk rate bigger than 90% after opening position`,
|
||||
"20013": "Temporally no counter party price",
|
||||
"20014": "System error",
|
||||
"20015": "Order does not exist",
|
||||
"20016": "Liquidation quantity bigger than holding",
|
||||
"20017": "Not authorized/illegal order ID",
|
||||
"20018": `Order price higher than 105% or lower than 95% of the price of last minute`,
|
||||
"20019": "IP restrained to access the resource",
|
||||
"20020": "Secret key does not exist",
|
||||
"20021": "Index information does not exist",
|
||||
"20022": "Wrong API interface",
|
||||
"20023": "Fixed margin user",
|
||||
"20024": "Signature does not match",
|
||||
"20025": "Leverage rate error",
|
||||
}
|
||||
}
|
||||
|
||||
// WsOrderbook defines orderbook data from websocket connection
|
||||
type WsOrderbook struct {
|
||||
Asks [][]string `json:"asks"`
|
||||
Bids [][]string `json:"bids"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// WsResponse defines initial response stream
|
||||
type WsResponse struct {
|
||||
Channel string `json:"channel"`
|
||||
Result bool `json:"result"`
|
||||
Success bool `json:"success"`
|
||||
ErrorCode string `json:"errorcode"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
// WsKlines defines a Kline response data from the websocket connection
|
||||
type WsKlines struct {
|
||||
Timestamp int64
|
||||
Open float64
|
||||
High float64
|
||||
Low float64
|
||||
Close float64
|
||||
Volume float64
|
||||
}
|
||||
|
||||
// WsTicker holds ticker data for websocket
|
||||
type WsTicker struct {
|
||||
High float64 `json:"high,string"`
|
||||
Volume float64 `json:"vol,string"`
|
||||
Last float64 `json:"last,string"`
|
||||
Low float64 `json:"low,string"`
|
||||
Buy float64 `json:"buy,string"`
|
||||
Change float64 `json:"change,string"`
|
||||
Sell float64 `json:"sell,string"`
|
||||
DayLow float64 `json:"dayLow,string"`
|
||||
Close float64 `json:"close,string"`
|
||||
DayHigh float64 `json:"dayHigh,string"`
|
||||
Open float64 `json:"open,string"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// WsDeals defines a deal response from the websocket connection
|
||||
type WsDeals struct {
|
||||
TID int64
|
||||
Price float64
|
||||
Amount float64
|
||||
Timestamp string
|
||||
Type string
|
||||
}
|
||||
@@ -1,396 +0,0 @@
|
||||
package okcoin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/currency/pair"
|
||||
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
// Start starts the OKCoin go routine
|
||||
func (o *OKCoin) Start(wg *sync.WaitGroup) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
o.Run()
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
// Run implements the OKCoin wrapper
|
||||
func (o *OKCoin) Run() {
|
||||
if o.Verbose {
|
||||
log.Debugf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL)
|
||||
log.Debugf("%s polling delay: %ds.\n", o.GetName(), o.RESTPollingDelay)
|
||||
log.Debugf("%s %d currencies enabled: %s.\n", o.GetName(), len(o.EnabledPairs), o.EnabledPairs)
|
||||
}
|
||||
|
||||
if o.APIUrl == okcoinAPIURL {
|
||||
// OKCoin International
|
||||
forceUpgrade := false
|
||||
if !common.StringDataContains(o.EnabledPairs, "_") || !common.StringDataContains(o.AvailablePairs, "_") {
|
||||
forceUpgrade = true
|
||||
}
|
||||
|
||||
prods, err := o.GetSpotInstruments()
|
||||
if err != nil {
|
||||
log.Errorf("OKEX failed to obtain available spot instruments. Err: %s", err)
|
||||
} else {
|
||||
var pairs []string
|
||||
for x := range prods {
|
||||
pairs = append(pairs, prods[x].BaseCurrency+"_"+prods[x].QuoteCurrency)
|
||||
}
|
||||
|
||||
err = o.UpdateCurrencies(pairs, false, forceUpgrade)
|
||||
if err != nil {
|
||||
log.Errorf("OKEX failed to update available currencies. Err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if forceUpgrade {
|
||||
enabledPairs := []string{"btc_usd"}
|
||||
log.Warn("Available pairs for OKCoin International reset due to config upgrade, please enable the pairs you would like again.")
|
||||
|
||||
err := o.UpdateCurrencies(enabledPairs, true, true)
|
||||
if err != nil {
|
||||
log.Errorf("%s failed to update currencies. Err: %s", o.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (o *OKCoin) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
|
||||
currency := exchange.FormatExchangeCurrency(o.Name, p).String()
|
||||
var tickerPrice ticker.Price
|
||||
|
||||
if assetType != ticker.Spot && o.APIUrl == okcoinAPIURL {
|
||||
tick, err := o.GetFuturesTicker(currency, assetType)
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
}
|
||||
tickerPrice.Pair = p
|
||||
tickerPrice.Ask = tick.Sell
|
||||
tickerPrice.Bid = tick.Buy
|
||||
tickerPrice.Low = tick.Low
|
||||
tickerPrice.Last = tick.Last
|
||||
tickerPrice.Volume = tick.Vol
|
||||
tickerPrice.High = tick.High
|
||||
ticker.ProcessTicker(o.GetName(), p, tickerPrice, assetType)
|
||||
} else {
|
||||
tick, err := o.GetTicker(currency)
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
}
|
||||
tickerPrice.Pair = p
|
||||
tickerPrice.Ask = tick.Sell
|
||||
tickerPrice.Bid = tick.Buy
|
||||
tickerPrice.Low = tick.Low
|
||||
tickerPrice.Last = tick.Last
|
||||
tickerPrice.Volume = tick.Vol
|
||||
tickerPrice.High = tick.High
|
||||
ticker.ProcessTicker(o.GetName(), p, tickerPrice, ticker.Spot)
|
||||
|
||||
}
|
||||
return ticker.GetTicker(o.Name, p, assetType)
|
||||
}
|
||||
|
||||
// GetTickerPrice returns the ticker for a currency pair
|
||||
func (o *OKCoin) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
|
||||
tickerNew, err := ticker.GetTicker(o.GetName(), p, assetType)
|
||||
if err != nil {
|
||||
return o.UpdateTicker(p, assetType)
|
||||
}
|
||||
return tickerNew, nil
|
||||
}
|
||||
|
||||
// GetOrderbookEx returns orderbook base on the currency pair
|
||||
func (o *OKCoin) GetOrderbookEx(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) {
|
||||
ob, err := orderbook.GetOrderbook(o.GetName(), currency, assetType)
|
||||
if err != nil {
|
||||
return o.UpdateOrderbook(currency, assetType)
|
||||
}
|
||||
return ob, nil
|
||||
}
|
||||
|
||||
// UpdateOrderbook updates and returns the orderbook for a currency pair
|
||||
func (o *OKCoin) UpdateOrderbook(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) {
|
||||
var orderBook orderbook.Base
|
||||
orderbookNew, err := o.GetOrderBook(exchange.FormatExchangeCurrency(o.Name, currency).String(), 200, false)
|
||||
if err != nil {
|
||||
return orderBook, err
|
||||
}
|
||||
|
||||
for x := range orderbookNew.Bids {
|
||||
data := orderbookNew.Bids[x]
|
||||
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data[1], Price: data[0]})
|
||||
}
|
||||
|
||||
for x := range orderbookNew.Asks {
|
||||
data := orderbookNew.Asks[x]
|
||||
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]})
|
||||
}
|
||||
|
||||
orderbook.ProcessOrderbook(o.GetName(), currency, orderBook, assetType)
|
||||
return orderbook.GetOrderbook(o.Name, currency, assetType)
|
||||
}
|
||||
|
||||
// GetAccountInfo retrieves balances for all enabled currencies for the
|
||||
// OKCoin exchange
|
||||
func (o *OKCoin) GetAccountInfo() (exchange.AccountInfo, error) {
|
||||
var response exchange.AccountInfo
|
||||
response.Exchange = o.GetName()
|
||||
assets, err := o.GetUserInfo()
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
var currencies = []exchange.AccountCurrencyInfo{
|
||||
{
|
||||
CurrencyName: "BTC",
|
||||
TotalValue: assets.Info.Funds.Free.BTC,
|
||||
Hold: assets.Info.Funds.Freezed.BTC,
|
||||
},
|
||||
{
|
||||
CurrencyName: "LTC",
|
||||
TotalValue: assets.Info.Funds.Free.LTC,
|
||||
Hold: assets.Info.Funds.Freezed.LTC,
|
||||
},
|
||||
{
|
||||
CurrencyName: "USD",
|
||||
TotalValue: assets.Info.Funds.Free.USD,
|
||||
Hold: assets.Info.Funds.Freezed.USD,
|
||||
},
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
// GetFundingHistory returns funding history, deposits and
|
||||
// withdrawals
|
||||
func (o *OKCoin) GetFundingHistory() ([]exchange.FundHistory, error) {
|
||||
var fundHistory []exchange.FundHistory
|
||||
return fundHistory, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetExchangeHistory returns historic trade data since exchange opening.
|
||||
func (o *OKCoin) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]exchange.TradeHistory, error) {
|
||||
var resp []exchange.TradeHistory
|
||||
|
||||
return resp, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// SubmitOrder submits a new order
|
||||
func (o *OKCoin) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) {
|
||||
var submitOrderResponse exchange.SubmitOrderResponse
|
||||
var oT string
|
||||
|
||||
switch orderType {
|
||||
case exchange.LimitOrderType:
|
||||
oT = "sell"
|
||||
if side == exchange.BuyOrderSide {
|
||||
oT = "buy"
|
||||
}
|
||||
case exchange.MarketOrderType:
|
||||
oT = "sell_market"
|
||||
if side == exchange.BuyOrderSide {
|
||||
oT = "buy_market"
|
||||
}
|
||||
default:
|
||||
return submitOrderResponse, errors.New("unsupported order type")
|
||||
}
|
||||
|
||||
response, err := o.Trade(amount, price, p.Pair().String(), oT)
|
||||
|
||||
if response > 0 {
|
||||
submitOrderResponse.OrderID = fmt.Sprintf("%v", response)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
submitOrderResponse.IsOrderPlaced = true
|
||||
}
|
||||
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
|
||||
// ModifyOrder will allow of changing orderbook placement and limit to
|
||||
// market conversion
|
||||
func (o *OKCoin) ModifyOrder(action exchange.ModifyOrder) (string, error) {
|
||||
return "", common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (o *OKCoin) CancelOrder(order exchange.OrderCancellation) error {
|
||||
orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64)
|
||||
orders := []int64{orderIDInt}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := o.CancelExistingOrder(orders, exchange.FormatExchangeCurrency(o.Name, order.CurrencyPair).String())
|
||||
if !resp.Result {
|
||||
return errors.New(resp.ErrorCode)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CancelAllOrders cancels all orders associated with a currency pair
|
||||
func (o *OKCoin) CancelAllOrders(orderCancellation exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) {
|
||||
cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{
|
||||
OrderStatus: make(map[string]string),
|
||||
}
|
||||
orderInfo, err := o.GetOrderInformation(-1, exchange.FormatExchangeCurrency(o.Name, orderCancellation.CurrencyPair).String())
|
||||
if err != nil {
|
||||
return cancelAllOrdersResponse, err
|
||||
}
|
||||
|
||||
var ordersToCancel []int64
|
||||
for _, order := range orderInfo {
|
||||
ordersToCancel = append(ordersToCancel, order.OrderID)
|
||||
}
|
||||
|
||||
if len(ordersToCancel) > 0 {
|
||||
resp, err := o.CancelExistingOrder(ordersToCancel, exchange.FormatExchangeCurrency(o.Name, orderCancellation.CurrencyPair).String())
|
||||
if err != nil {
|
||||
return cancelAllOrdersResponse, err
|
||||
}
|
||||
|
||||
for _, order := range common.SplitStrings(resp.ErrorCode, ",") {
|
||||
if err != nil {
|
||||
cancelAllOrdersResponse.OrderStatus[order] = "Order could not be cancelled"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cancelAllOrdersResponse, nil
|
||||
}
|
||||
|
||||
// GetOrderInfo returns information on a current open order
|
||||
func (o *OKCoin) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
|
||||
var orderDetail exchange.OrderDetail
|
||||
return orderDetail, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetDepositAddress returns a deposit address for a specified currency
|
||||
func (o *OKCoin) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) {
|
||||
// NOTE needs API version update to access
|
||||
return "", common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
|
||||
// submitted
|
||||
func (o *OKCoin) WithdrawCryptocurrencyFunds(withdrawRequest exchange.WithdrawRequest) (string, error) {
|
||||
resp, err := o.Withdrawal(withdrawRequest.Currency.String(), withdrawRequest.FeeAmount, withdrawRequest.TradePassword, withdrawRequest.Address, withdrawRequest.Amount)
|
||||
return fmt.Sprintf("%v", resp), err
|
||||
}
|
||||
|
||||
// WithdrawFiatFunds returns a withdrawal ID when a
|
||||
// withdrawal is submitted
|
||||
func (o *OKCoin) WithdrawFiatFunds(withdrawRequest exchange.WithdrawRequest) (string, error) {
|
||||
return "", common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a
|
||||
// withdrawal is submitted
|
||||
func (o *OKCoin) WithdrawFiatFundsToInternationalBank(withdrawRequest exchange.WithdrawRequest) (string, error) {
|
||||
return "", common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (o *OKCoin) GetWebsocket() (*exchange.Websocket, error) {
|
||||
return o.Websocket, nil
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
func (o *OKCoin) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) {
|
||||
return o.GetFee(feeBuilder)
|
||||
}
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (o *OKCoin) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) {
|
||||
var allOrders []OrderInfo
|
||||
for _, currency := range getOrdersRequest.Currencies {
|
||||
resp, err := o.GetOrderHistoryForCurrency(200, 0, 0, exchange.FormatExchangeCurrency(o.Name, currency).String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allOrders = append(allOrders, resp.Orders...)
|
||||
}
|
||||
|
||||
var orders []exchange.OrderDetail
|
||||
for _, order := range allOrders {
|
||||
// Status 2 == Filled, -1 == Cancelled.
|
||||
if order.Status == 2 || order.Status == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
symbol := pair.NewCurrencyPairDelimiter(order.Symbol, o.ConfigCurrencyPairFormat.Delimiter)
|
||||
orderDate := time.Unix(order.Created, 0)
|
||||
side := exchange.OrderSide(strings.ToUpper(order.Type))
|
||||
orders = append(orders, exchange.OrderDetail{
|
||||
ID: fmt.Sprintf("%v", order.OrderID),
|
||||
Amount: order.Amount,
|
||||
OrderDate: orderDate,
|
||||
Price: order.Price,
|
||||
OrderSide: side,
|
||||
CurrencyPair: symbol,
|
||||
Exchange: o.Name,
|
||||
})
|
||||
}
|
||||
|
||||
exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks)
|
||||
exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide)
|
||||
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (o *OKCoin) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) {
|
||||
var allOrders []OrderInfo
|
||||
for _, currency := range getOrdersRequest.Currencies {
|
||||
resp, err := o.GetOrderInformation(-1, exchange.FormatExchangeCurrency(o.Name, currency).String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allOrders = append(allOrders, resp...)
|
||||
}
|
||||
var orders []exchange.OrderDetail
|
||||
for _, order := range allOrders {
|
||||
symbol := pair.NewCurrencyPairDelimiter(order.Symbol, o.ConfigCurrencyPairFormat.Delimiter)
|
||||
orderDate := time.Unix(order.Created, 0)
|
||||
side := exchange.OrderSide(strings.ToUpper(order.Type))
|
||||
orders = append(orders, exchange.OrderDetail{
|
||||
ID: fmt.Sprintf("%v", order.OrderID),
|
||||
Amount: order.Amount,
|
||||
OrderDate: orderDate,
|
||||
Price: order.Price,
|
||||
OrderSide: side,
|
||||
CurrencyPair: symbol,
|
||||
Exchange: o.Name,
|
||||
})
|
||||
}
|
||||
|
||||
exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks)
|
||||
exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide)
|
||||
|
||||
return orders, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,494 +0,0 @@
|
||||
package okex
|
||||
|
||||
import "encoding/json"
|
||||
import "github.com/thrasher-/gocryptotrader/currency/symbol"
|
||||
|
||||
// SpotInstrument stores the spot instrument info
|
||||
type SpotInstrument struct {
|
||||
BaseCurrency string `json:"base_currency"`
|
||||
BaseIncrement float64 `json:"base_increment,string"`
|
||||
BaseMinSize float64 `json:"base_min_size,string"`
|
||||
InstrumentID string `json:"instrument_id"`
|
||||
MinSize float64 `json:"min_size,string"`
|
||||
ProductID string `json:"product_id"`
|
||||
QuoteCurrency string `json:"quote_currency"`
|
||||
QuoteIncrement float64 `json:"quote_increment,string"`
|
||||
SizeIncrement float64 `json:"size_increment,string"`
|
||||
TickSize float64 `json:"tick_size,string"`
|
||||
}
|
||||
|
||||
// ContractPrice holds date and ticker price price for contracts.
|
||||
type ContractPrice struct {
|
||||
Date string `json:"date"`
|
||||
Ticker struct {
|
||||
Buy float64 `json:"buy"`
|
||||
ContractID float64 `json:"contract_id"`
|
||||
High float64 `json:"high"`
|
||||
Low float64 `json:"low"`
|
||||
Last float64 `json:"last"`
|
||||
Sell float64 `json:"sell"`
|
||||
UnitAmount float64 `json:"unit_amount"`
|
||||
Vol float64 `json:"vol"`
|
||||
} `json:"ticker"`
|
||||
Result bool `json:"result"`
|
||||
Error interface{} `json:"error_code"`
|
||||
}
|
||||
|
||||
// MultiStreamData contains raw data from okex
|
||||
type MultiStreamData struct {
|
||||
Channel string `json:"channel"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
// TokenOrdersResponse is returned after a request for all Token Orders
|
||||
type TokenOrdersResponse struct {
|
||||
Result bool `json:"result"`
|
||||
Orders []TokenOrder `json:"orders"`
|
||||
}
|
||||
|
||||
// TokenOrder is the individual order details returned from TokenOrderResponse
|
||||
type TokenOrder struct {
|
||||
Amount float64 `json:"amount"`
|
||||
AvgPrice int64 `json:"avg_price"`
|
||||
DealAmount int64 `json:"deal_amount"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
Price int64 `json:"price"`
|
||||
Status int64 `json:"status"`
|
||||
Symbol string `json:"symbol"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// TickerStreamData contains ticker stream data from okex
|
||||
type TickerStreamData struct {
|
||||
Buy string `json:"buy"`
|
||||
Change string `json:"change"`
|
||||
High string `json:"high"`
|
||||
Low string `json:"low"`
|
||||
Last string `json:"last"`
|
||||
Sell string `json:"sell"`
|
||||
DayLow string `json:"dayLow"`
|
||||
DayHigh string `json:"dayHigh"`
|
||||
Timestamp float64 `json:"timestamp"`
|
||||
Vol string `json:"vol"`
|
||||
}
|
||||
|
||||
// DealsStreamData defines Deals data
|
||||
type DealsStreamData = [][]string
|
||||
|
||||
// KlineStreamData defines kline data
|
||||
type KlineStreamData = [][]string
|
||||
|
||||
// DepthStreamData defines orderbook depth
|
||||
type DepthStreamData struct {
|
||||
Asks [][]string `json:"asks"`
|
||||
Bids [][]string `json:"bids"`
|
||||
Timestamp float64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// ContractDepth response depth
|
||||
type ContractDepth struct {
|
||||
Asks []interface{} `json:"asks"`
|
||||
Bids []interface{} `json:"bids"`
|
||||
Result bool `json:"result"`
|
||||
Error interface{} `json:"error_code"`
|
||||
}
|
||||
|
||||
// ActualContractDepth better manipulated structure to return
|
||||
type ActualContractDepth struct {
|
||||
Asks []struct {
|
||||
Price float64
|
||||
Volume float64
|
||||
}
|
||||
Bids []struct {
|
||||
Price float64
|
||||
Volume float64
|
||||
}
|
||||
}
|
||||
|
||||
// ActualContractTradeHistory holds contract trade history
|
||||
type ActualContractTradeHistory struct {
|
||||
Amount float64 `json:"amount"`
|
||||
DateInMS float64 `json:"date_ms"`
|
||||
Date float64 `json:"date"`
|
||||
Price float64 `json:"price"`
|
||||
TID float64 `json:"tid"`
|
||||
Type string `json:"buy"`
|
||||
}
|
||||
|
||||
// CandleStickData holds candlestick data
|
||||
type CandleStickData struct {
|
||||
Timestamp float64 `json:"timestamp"`
|
||||
Open float64 `json:"open"`
|
||||
High float64 `json:"high"`
|
||||
Low float64 `json:"low"`
|
||||
Close float64 `json:"close"`
|
||||
Volume float64 `json:"volume"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
// Info holds individual information
|
||||
type Info struct {
|
||||
AccountRights float64 `json:"account_rights"`
|
||||
KeepDeposit float64 `json:"keep_deposit"`
|
||||
ProfitReal float64 `json:"profit_real"`
|
||||
ProfitUnreal float64 `json:"profit_unreal"`
|
||||
RiskRate float64 `json:"risk_rate"`
|
||||
}
|
||||
|
||||
// UserInfo holds a collection of user data
|
||||
type UserInfo struct {
|
||||
Info struct {
|
||||
BTC Info `json:"btc"`
|
||||
LTC Info `json:"ltc"`
|
||||
} `json:"info"`
|
||||
Result bool `json:"result"`
|
||||
}
|
||||
|
||||
// HoldData is a sub type for FuturePosition
|
||||
type HoldData struct {
|
||||
BuyAmount float64 `json:"buy_amount"`
|
||||
BuyAvailable float64 `json:"buy_available"`
|
||||
BuyPriceAvg float64 `json:"buy_price_avg"`
|
||||
BuyPriceCost float64 `json:"buy_price_cost"`
|
||||
BuyProfitReal float64 `json:"buy_profit_real"`
|
||||
ContractID float64 `json:"contract_id"`
|
||||
ContractType string `json:"contract_type"`
|
||||
CreateDate int `json:"create_date"`
|
||||
LeverRate float64 `json:"lever_rate"`
|
||||
SellAmount float64 `json:"sell_amount"`
|
||||
SellAvailable float64 `json:"sell_available"`
|
||||
SellPriceAvg float64 `json:"sell_price_avg"`
|
||||
SellPriceCost float64 `json:"sell_price_cost"`
|
||||
SellProfitReal float64 `json:"sell_profit_real"`
|
||||
Symbol string `json:"symbol"`
|
||||
}
|
||||
|
||||
// FuturePosition contains an array of holding types
|
||||
type FuturePosition struct {
|
||||
ForceLiquidationPrice float64 `json:"force_liqu_price"`
|
||||
Holding []HoldData `json:"holding"`
|
||||
}
|
||||
|
||||
// FutureTradeHistory will contain futures trade data
|
||||
type FutureTradeHistory struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Date int `json:"date"`
|
||||
Price float64 `json:"price"`
|
||||
TID float64 `json:"tid"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// SpotPrice holds date and ticker price price for contracts.
|
||||
type SpotPrice struct {
|
||||
Date string `json:"date"`
|
||||
Ticker struct {
|
||||
Buy float64 `json:"buy,string"`
|
||||
ContractID float64 `json:"contract_id"`
|
||||
High float64 `json:"high,string"`
|
||||
Low float64 `json:"low,string"`
|
||||
Last float64 `json:"last,string"`
|
||||
Sell float64 `json:"sell,string"`
|
||||
UnitAmount float64 `json:"unit_amount,string"`
|
||||
Vol float64 `json:"vol,string"`
|
||||
} `json:"ticker"`
|
||||
Result bool `json:"result"`
|
||||
Error interface{} `json:"error_code"`
|
||||
}
|
||||
|
||||
// SpotDepth response depth
|
||||
type SpotDepth struct {
|
||||
Asks []interface{} `json:"asks"`
|
||||
Bids []interface{} `json:"bids"`
|
||||
Result bool `json:"result"`
|
||||
Error interface{} `json:"error_code"`
|
||||
}
|
||||
|
||||
// ActualSpotDepthRequestParams represents Klines request data.
|
||||
type ActualSpotDepthRequestParams struct {
|
||||
Symbol string `json:"symbol"` // Symbol; example ltc_btc
|
||||
Size int `json:"size"` // value: 1-200
|
||||
}
|
||||
|
||||
// ActualSpotDepth better manipulated structure to return
|
||||
type ActualSpotDepth struct {
|
||||
Asks []struct {
|
||||
Price float64
|
||||
Volume float64
|
||||
}
|
||||
Bids []struct {
|
||||
Price float64
|
||||
Volume float64
|
||||
}
|
||||
}
|
||||
|
||||
// ActualSpotTradeHistoryRequestParams represents Klines request data.
|
||||
type ActualSpotTradeHistoryRequestParams struct {
|
||||
Symbol string `json:"symbol"` // Symbol; example ltc_btc
|
||||
Since int `json:"since"` // TID; transaction record ID (return data does not include the current TID value, returning up to 600 items)
|
||||
}
|
||||
|
||||
// ActualSpotTradeHistory holds contract trade history
|
||||
type ActualSpotTradeHistory struct {
|
||||
Amount float64 `json:"amount"`
|
||||
DateInMS float64 `json:"date_ms"`
|
||||
Date float64 `json:"date"`
|
||||
Price float64 `json:"price"`
|
||||
TID float64 `json:"tid"`
|
||||
Type string `json:"buy"`
|
||||
}
|
||||
|
||||
// SpotUserInfo holds the spot user info
|
||||
type SpotUserInfo struct {
|
||||
Result bool `json:"result"`
|
||||
Info map[string]map[string]map[string]string `json:"info"`
|
||||
}
|
||||
|
||||
// SpotNewOrderRequestParams holds the params for making a new spot order
|
||||
type SpotNewOrderRequestParams struct {
|
||||
Amount float64 `json:"amount"` // Order quantity
|
||||
Price float64 `json:"price"` // Order price
|
||||
Symbol string `json:"symbol"` // Symbol; example btc_usdt, eth_btc......
|
||||
Type SpotNewOrderRequestType `json:"type"` // Order type (see below)
|
||||
}
|
||||
|
||||
// SpotNewOrderRequestType order type
|
||||
type SpotNewOrderRequestType string
|
||||
|
||||
var (
|
||||
// SpotNewOrderRequestTypeBuy buy order
|
||||
SpotNewOrderRequestTypeBuy = SpotNewOrderRequestType("buy")
|
||||
|
||||
// SpotNewOrderRequestTypeSell sell order
|
||||
SpotNewOrderRequestTypeSell = SpotNewOrderRequestType("sell")
|
||||
|
||||
// SpotNewOrderRequestTypeBuyMarket buy market order
|
||||
SpotNewOrderRequestTypeBuyMarket = SpotNewOrderRequestType("buy_market")
|
||||
|
||||
// SpotNewOrderRequestTypeSellMarket sell market order
|
||||
SpotNewOrderRequestTypeSellMarket = SpotNewOrderRequestType("sell_market")
|
||||
)
|
||||
|
||||
// KlinesRequestParams represents Klines request data.
|
||||
type KlinesRequestParams struct {
|
||||
Symbol string // Symbol; example btcusdt, bccbtc......
|
||||
Type TimeInterval // Kline data time interval; 1min, 5min, 15min......
|
||||
Size int // Size; [1-2000]
|
||||
Since int64 // Since timestamp, return data after the specified timestamp (for example, 1417536000000)
|
||||
}
|
||||
|
||||
// TimeInterval represents interval enum.
|
||||
type TimeInterval string
|
||||
|
||||
// vars for time intervals
|
||||
var (
|
||||
TimeIntervalMinute = TimeInterval("1min")
|
||||
TimeIntervalThreeMinutes = TimeInterval("3min")
|
||||
TimeIntervalFiveMinutes = TimeInterval("5min")
|
||||
TimeIntervalFifteenMinutes = TimeInterval("15min")
|
||||
TimeIntervalThirtyMinutes = TimeInterval("30min")
|
||||
TimeIntervalHour = TimeInterval("1hour")
|
||||
TimeIntervalFourHours = TimeInterval("4hour")
|
||||
TimeIntervalSixHours = TimeInterval("6hour")
|
||||
TimeIntervalTwelveHours = TimeInterval("12hour")
|
||||
TimeIntervalDay = TimeInterval("1day")
|
||||
TimeIntervalThreeDays = TimeInterval("3day")
|
||||
TimeIntervalWeek = TimeInterval("1week")
|
||||
)
|
||||
|
||||
// WithdrawalFees the large list of predefined withdrawal fees
|
||||
// Prone to change, using highest value
|
||||
var WithdrawalFees = map[string]float64{
|
||||
symbol.ZRX: 10,
|
||||
symbol.ACE: 2.2,
|
||||
symbol.ACT: 0.01,
|
||||
symbol.AAC: 5,
|
||||
symbol.AE: 1,
|
||||
symbol.AIDOC: 17,
|
||||
symbol.AST: 8,
|
||||
symbol.SOC: 20,
|
||||
symbol.ABT: 3,
|
||||
symbol.ARK: 0.1,
|
||||
symbol.ATL: 1.5,
|
||||
symbol.AVT: 1,
|
||||
symbol.BNT: 1,
|
||||
symbol.BKX: 3,
|
||||
symbol.BEC: 4,
|
||||
symbol.BTC: 0.0005,
|
||||
symbol.BCH: 0.0001,
|
||||
symbol.BCD: 0.02,
|
||||
symbol.BTG: 0.001,
|
||||
symbol.VEE: 100,
|
||||
symbol.BRD: 1.5,
|
||||
symbol.CTR: 7,
|
||||
symbol.LINK: 10,
|
||||
symbol.CAG: 2,
|
||||
symbol.CHAT: 10,
|
||||
symbol.CVC: 10,
|
||||
symbol.CIC: 150,
|
||||
symbol.CBT: 10,
|
||||
symbol.CAN: 3,
|
||||
symbol.CMT: 10,
|
||||
symbol.DADI: 10,
|
||||
symbol.DASH: 0.002,
|
||||
symbol.DAT: 2,
|
||||
symbol.MANA: 20,
|
||||
symbol.DCR: 0.03,
|
||||
symbol.DPY: 0.8,
|
||||
symbol.DENT: 100,
|
||||
symbol.DGD: 0.2,
|
||||
symbol.DNT: 20,
|
||||
symbol.EDO: 2,
|
||||
symbol.DNA: 3,
|
||||
symbol.ENG: 5,
|
||||
symbol.ENJ: 20,
|
||||
symbol.ETH: 0.01,
|
||||
symbol.ETC: 0.001,
|
||||
symbol.LEND: 10,
|
||||
symbol.EVX: 1.5,
|
||||
symbol.XUC: 5.8,
|
||||
symbol.FAIR: 15,
|
||||
symbol.FIRST: 6,
|
||||
symbol.FUN: 40,
|
||||
symbol.GTC: 40,
|
||||
symbol.GNX: 8,
|
||||
symbol.GTO: 10,
|
||||
symbol.GSC: 20,
|
||||
symbol.GNT: 5,
|
||||
symbol.HMC: 40,
|
||||
symbol.HOT: 10,
|
||||
symbol.ICN: 2,
|
||||
symbol.INS: 2.5,
|
||||
symbol.INT: 10,
|
||||
symbol.IOST: 100,
|
||||
symbol.ITC: 2,
|
||||
symbol.IPC: 2.5,
|
||||
symbol.KNC: 2,
|
||||
symbol.LA: 3,
|
||||
symbol.LEV: 20,
|
||||
symbol.LIGHT: 100,
|
||||
symbol.LSK: 0.4,
|
||||
symbol.LTC: 0.001,
|
||||
symbol.LRC: 7,
|
||||
symbol.MAG: 34,
|
||||
symbol.MKR: 0.002,
|
||||
symbol.MTL: 0.5,
|
||||
symbol.AMM: 5,
|
||||
symbol.MITH: 20,
|
||||
symbol.MDA: 2,
|
||||
symbol.MOF: 5,
|
||||
symbol.MCO: 0.2,
|
||||
symbol.MTH: 35,
|
||||
symbol.NGC: 1.5,
|
||||
symbol.NANO: 0.2,
|
||||
symbol.NULS: 2,
|
||||
symbol.OAX: 6,
|
||||
symbol.OF: 600,
|
||||
symbol.OKB: 0,
|
||||
symbol.MOT: 1.5,
|
||||
symbol.OMG: 0.1,
|
||||
symbol.RNT: 13,
|
||||
symbol.POE: 30,
|
||||
symbol.PPT: 0.2,
|
||||
symbol.PST: 10,
|
||||
symbol.PRA: 4,
|
||||
symbol.QTUM: 0.01,
|
||||
symbol.QUN: 20,
|
||||
symbol.QVT: 10,
|
||||
symbol.RDN: 0.3,
|
||||
symbol.READ: 20,
|
||||
symbol.RCT: 15,
|
||||
symbol.RFR: 200,
|
||||
symbol.REF: 0.2,
|
||||
symbol.REN: 50,
|
||||
symbol.REQ: 15,
|
||||
symbol.R: 2,
|
||||
symbol.RCN: 20,
|
||||
symbol.XRP: 0.15,
|
||||
symbol.SALT: 0.5,
|
||||
symbol.SAN: 1,
|
||||
symbol.KEY: 50,
|
||||
symbol.SSC: 8,
|
||||
symbol.SHOW: 150,
|
||||
symbol.SC: 200,
|
||||
symbol.OST: 3,
|
||||
symbol.SNGLS: 20,
|
||||
symbol.SMT: 8,
|
||||
symbol.SNM: 20,
|
||||
symbol.SPF: 5,
|
||||
symbol.SNT: 50,
|
||||
symbol.STORJ: 2,
|
||||
symbol.SUB: 4,
|
||||
symbol.SNC: 10,
|
||||
symbol.SWFTC: 350,
|
||||
symbol.PAY: 0.5,
|
||||
symbol.USDT: 2,
|
||||
symbol.TRA: 500,
|
||||
symbol.THETA: 20,
|
||||
symbol.TNB: 40,
|
||||
symbol.TCT: 50,
|
||||
symbol.TOPC: 20,
|
||||
symbol.TIO: 2.5,
|
||||
symbol.TRIO: 200,
|
||||
symbol.TRUE: 4,
|
||||
symbol.UCT: 10,
|
||||
symbol.UGC: 12,
|
||||
symbol.UKG: 2.5,
|
||||
symbol.UTK: 3,
|
||||
symbol.VIB: 6,
|
||||
symbol.VIU: 40,
|
||||
symbol.WTC: 0.4,
|
||||
symbol.WFEE: 500,
|
||||
symbol.WRC: 48,
|
||||
symbol.YEE: 70,
|
||||
symbol.YOYOW: 10,
|
||||
symbol.ZEC: 0.001,
|
||||
symbol.ZEN: 0.07,
|
||||
symbol.ZIL: 20,
|
||||
symbol.ZIP: 1000,
|
||||
}
|
||||
|
||||
// FullBalance defines a structured return type with balance data
|
||||
type FullBalance struct {
|
||||
Available float64
|
||||
Currency string
|
||||
Hold float64
|
||||
}
|
||||
|
||||
// Balance defines returned balance data
|
||||
type Balance struct {
|
||||
Info struct {
|
||||
Funds struct {
|
||||
Free map[string]string `json:"free"`
|
||||
Holds map[string]string `json:"holds"`
|
||||
} `json:"funds"`
|
||||
} `json:"info"`
|
||||
}
|
||||
|
||||
// WithdrawalResponse is a response type for withdrawal
|
||||
type WithdrawalResponse struct {
|
||||
WithdrawID int `json:"withdraw_id"`
|
||||
Result bool `json:"result"`
|
||||
}
|
||||
|
||||
// OrderInfo holds data on an order
|
||||
type OrderInfo struct {
|
||||
Amount float64 `json:"amount"`
|
||||
AvgPrice float64 `json:"avg_price"`
|
||||
Created int64 `json:"create_date"`
|
||||
DealAmount float64 `json:"deal_amount"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
OrdersID int64 `json:"orders_id"`
|
||||
Price float64 `json:"price"`
|
||||
Status int `json:"status"`
|
||||
Symbol string `json:"symbol"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// OrderHistory holds information on order history
|
||||
type OrderHistory struct {
|
||||
CurrentPage int `json:"current_page"`
|
||||
Orders []OrderInfo `json:"orders"`
|
||||
PageLength int `json:"page_length"`
|
||||
Result bool `json:"result"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
@@ -1,318 +0,0 @@
|
||||
package okex
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/currency/pair"
|
||||
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
okexDefaultWebsocketURL = "wss://real.okex.com:10440/websocket/okexapi"
|
||||
)
|
||||
|
||||
func (o *OKEX) writeToWebsocket(message string) error {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
|
||||
return o.WebsocketConn.WriteMessage(websocket.TextMessage, []byte(message))
|
||||
}
|
||||
|
||||
// WsConnect initiates a websocket connection
|
||||
func (o *OKEX) WsConnect() error {
|
||||
if !o.Websocket.IsEnabled() || !o.IsEnabled() {
|
||||
return errors.New(exchange.WebsocketNotEnabled)
|
||||
}
|
||||
|
||||
var dialer websocket.Dialer
|
||||
|
||||
if o.Websocket.GetProxyAddress() != "" {
|
||||
proxy, err := url.Parse(o.Websocket.GetProxyAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dialer.Proxy = http.ProxyURL(proxy)
|
||||
}
|
||||
|
||||
var err error
|
||||
o.WebsocketConn, _, err = dialer.Dial(o.Websocket.GetWebsocketURL(),
|
||||
http.Header{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s Unable to connect to Websocket. Error: %s",
|
||||
o.Name,
|
||||
err)
|
||||
}
|
||||
|
||||
go o.WsHandleData()
|
||||
go o.wsPingHandler()
|
||||
|
||||
err = o.WsSubscribe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s could not subscribe to websocket %s",
|
||||
o.Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsSubscribe subscribes to the websocket channels
|
||||
func (o *OKEX) WsSubscribe() error {
|
||||
myEnabledSubscriptionChannels := []string{}
|
||||
|
||||
for _, pair := range o.EnabledPairs {
|
||||
|
||||
// ----------- deprecate when usd pairs are upgraded to usdt ----------
|
||||
checkSymbol := common.SplitStrings(pair, "_")
|
||||
for i := range checkSymbol {
|
||||
if common.StringContains(checkSymbol[i], "usdt") {
|
||||
break
|
||||
}
|
||||
if common.StringContains(checkSymbol[i], "usd") {
|
||||
checkSymbol[i] = "usdt"
|
||||
}
|
||||
}
|
||||
|
||||
symbolRedone := common.JoinStrings(checkSymbol, "_")
|
||||
// ----------- deprecate when usd pairs are upgraded to usdt ----------
|
||||
|
||||
myEnabledSubscriptionChannels = append(myEnabledSubscriptionChannels,
|
||||
fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_ticker'}",
|
||||
symbolRedone),
|
||||
fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_depth'}",
|
||||
symbolRedone),
|
||||
fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_deals'}",
|
||||
symbolRedone),
|
||||
fmt.Sprintf("{'event':'addChannel','channel':'ok_sub_spot_%s_kline_1min'}",
|
||||
symbolRedone))
|
||||
}
|
||||
|
||||
for _, outgoing := range myEnabledSubscriptionChannels {
|
||||
err := o.writeToWebsocket(outgoing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsReadData reads data from the websocket connection
|
||||
func (o *OKEX) WsReadData() (exchange.WebsocketResponse, error) {
|
||||
mType, resp, err := o.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
return exchange.WebsocketResponse{}, err
|
||||
}
|
||||
|
||||
o.Websocket.TrafficAlert <- struct{}{}
|
||||
|
||||
var standardMessage []byte
|
||||
|
||||
switch mType {
|
||||
case websocket.TextMessage:
|
||||
standardMessage = resp
|
||||
|
||||
case websocket.BinaryMessage:
|
||||
reader := flate.NewReader(bytes.NewReader(resp))
|
||||
standardMessage, err = ioutil.ReadAll(reader)
|
||||
reader.Close()
|
||||
if err != nil {
|
||||
return exchange.WebsocketResponse{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return exchange.WebsocketResponse{Raw: standardMessage}, nil
|
||||
}
|
||||
|
||||
func (o *OKEX) wsPingHandler() {
|
||||
o.Websocket.Wg.Add(1)
|
||||
defer o.Websocket.Wg.Done()
|
||||
|
||||
ticker := time.NewTicker(time.Second * 27)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-o.Websocket.ShutdownC:
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
err := o.writeToWebsocket("{'event':'ping'}")
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WsHandleData handles the read data from the websocket connection
|
||||
func (o *OKEX) WsHandleData() {
|
||||
o.Websocket.Wg.Add(1)
|
||||
|
||||
defer func() {
|
||||
err := o.WebsocketConn.Close()
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- fmt.Errorf("okex_websocket.go - Unable to to close Websocket connection. Error: %s",
|
||||
err)
|
||||
}
|
||||
o.Websocket.Wg.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-o.Websocket.ShutdownC:
|
||||
return
|
||||
|
||||
default:
|
||||
resp, err := o.WsReadData()
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
|
||||
multiStreamDataArr := []MultiStreamData{}
|
||||
|
||||
err = common.JSONDecode(resp.Raw, &multiStreamDataArr)
|
||||
if err != nil {
|
||||
if strings.Contains(string(resp.Raw), "pong") {
|
||||
continue
|
||||
} else {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for _, multiStreamData := range multiStreamDataArr {
|
||||
var errResponse ErrorResponse
|
||||
if common.StringContains(string(resp.Raw), "error_msg") {
|
||||
err = common.JSONDecode(resp.Raw, &errResponse)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
o.Websocket.DataHandler <- fmt.Errorf("okex.go error - %s resp: %s ",
|
||||
errResponse.ErrorMsg,
|
||||
string(resp.Raw))
|
||||
continue
|
||||
}
|
||||
|
||||
var newPair string
|
||||
var assetType string
|
||||
currencyPairSlice := common.SplitStrings(multiStreamData.Channel, "_")
|
||||
if len(currencyPairSlice) > 5 {
|
||||
newPair = currencyPairSlice[3] + "_" + currencyPairSlice[4]
|
||||
assetType = currencyPairSlice[2]
|
||||
}
|
||||
|
||||
switch multiStreamData.Channel {
|
||||
case "ticker":
|
||||
var ticker TickerStreamData
|
||||
|
||||
err = common.JSONDecode(multiStreamData.Data, &ticker)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
o.Websocket.DataHandler <- exchange.TickerData{
|
||||
Timestamp: time.Unix(0, int64(ticker.Timestamp)),
|
||||
Exchange: o.GetName(),
|
||||
AssetType: assetType,
|
||||
}
|
||||
case "deals":
|
||||
var deals DealsStreamData
|
||||
|
||||
err = common.JSONDecode(multiStreamData.Data, &deals)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
for _, trade := range deals {
|
||||
price, _ := strconv.ParseFloat(trade[1], 64)
|
||||
amount, _ := strconv.ParseFloat(trade[2], 64)
|
||||
tradeTime, _ := time.Parse(time.RFC3339, trade[3])
|
||||
|
||||
o.Websocket.DataHandler <- exchange.TradeData{
|
||||
Timestamp: tradeTime,
|
||||
Exchange: o.GetName(),
|
||||
AssetType: assetType,
|
||||
CurrencyPair: pair.NewCurrencyPairFromString(newPair),
|
||||
Price: price,
|
||||
Amount: amount,
|
||||
EventType: trade[4],
|
||||
}
|
||||
}
|
||||
case "kline":
|
||||
var klines KlineStreamData
|
||||
|
||||
err := common.JSONDecode(multiStreamData.Data, &klines)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
for _, kline := range klines {
|
||||
ntime, _ := strconv.ParseInt(kline[0], 10, 64)
|
||||
open, _ := strconv.ParseFloat(kline[1], 64)
|
||||
high, _ := strconv.ParseFloat(kline[2], 64)
|
||||
low, _ := strconv.ParseFloat(kline[3], 64)
|
||||
klineClose, _ := strconv.ParseFloat(kline[4], 64)
|
||||
volume, _ := strconv.ParseFloat(kline[5], 64)
|
||||
|
||||
o.Websocket.DataHandler <- exchange.KlineData{
|
||||
Timestamp: time.Unix(ntime, 0),
|
||||
Pair: pair.NewCurrencyPairFromString(newPair),
|
||||
AssetType: assetType,
|
||||
Exchange: o.GetName(),
|
||||
OpenPrice: open,
|
||||
HighPrice: high,
|
||||
LowPrice: low,
|
||||
ClosePrice: klineClose,
|
||||
Volume: volume,
|
||||
}
|
||||
}
|
||||
case "depth":
|
||||
var depth DepthStreamData
|
||||
|
||||
err := common.JSONDecode(multiStreamData.Data, &depth)
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
o.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
|
||||
Exchange: o.GetName(),
|
||||
Asset: assetType,
|
||||
Pair: pair.NewCurrencyPairFromString(newPair),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorResponse defines an error response type from the websocket connection
|
||||
type ErrorResponse struct {
|
||||
Result bool `json:"result"`
|
||||
ErrorMsg string `json:"error_msg"`
|
||||
ErrorCode int64 `json:"error_code"`
|
||||
}
|
||||
|
||||
// Request defines the JSON request structure to the websocket server
|
||||
type Request struct {
|
||||
Event string `json:"event"`
|
||||
Channel string `json:"channel"`
|
||||
Parameters string `json:"parameters,omitempty"`
|
||||
}
|
||||
@@ -1,394 +0,0 @@
|
||||
package okex
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/currency/pair"
|
||||
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
// Start starts the OKEX go routine
|
||||
func (o *OKEX) Start(wg *sync.WaitGroup) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
o.Run()
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
// Run implements the OKEX wrapper
|
||||
func (o *OKEX) Run() {
|
||||
if o.Verbose {
|
||||
log.Debugf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL)
|
||||
log.Debugf("%s polling delay: %ds.\n", o.GetName(), o.RESTPollingDelay)
|
||||
log.Debugf("%s %d currencies enabled: %s.\n", o.GetName(), len(o.EnabledPairs), o.EnabledPairs)
|
||||
}
|
||||
|
||||
prods, err := o.GetSpotInstruments()
|
||||
if err != nil {
|
||||
log.Errorf("OKEX failed to obtain available spot instruments. Err: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
var pairs []string
|
||||
for x := range prods {
|
||||
pairs = append(pairs, prods[x].BaseCurrency+"_"+prods[x].QuoteCurrency)
|
||||
}
|
||||
|
||||
err = o.UpdateCurrencies(pairs, false, false)
|
||||
if err != nil {
|
||||
log.Errorf("OKEX failed to update available currencies. Err: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (o *OKEX) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
|
||||
currency := exchange.FormatExchangeCurrency(o.Name, p).String()
|
||||
var tickerPrice ticker.Price
|
||||
|
||||
if assetType != ticker.Spot {
|
||||
tick, err := o.GetContractPrice(currency, assetType)
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
}
|
||||
|
||||
tickerPrice.Pair = p
|
||||
tickerPrice.Ask = tick.Ticker.Sell
|
||||
tickerPrice.Bid = tick.Ticker.Buy
|
||||
tickerPrice.Low = tick.Ticker.Low
|
||||
tickerPrice.Last = tick.Ticker.Last
|
||||
tickerPrice.Volume = tick.Ticker.Vol
|
||||
tickerPrice.High = tick.Ticker.High
|
||||
ticker.ProcessTicker(o.GetName(), p, tickerPrice, assetType)
|
||||
} else {
|
||||
tick, err := o.GetSpotTicker(currency)
|
||||
if err != nil {
|
||||
return tickerPrice, err
|
||||
}
|
||||
tickerPrice.Pair = p
|
||||
tickerPrice.Ask = tick.Ticker.Sell
|
||||
tickerPrice.Bid = tick.Ticker.Buy
|
||||
tickerPrice.Low = tick.Ticker.Low
|
||||
tickerPrice.Last = tick.Ticker.Last
|
||||
tickerPrice.Volume = tick.Ticker.Vol
|
||||
tickerPrice.High = tick.Ticker.High
|
||||
ticker.ProcessTicker(o.GetName(), p, tickerPrice, ticker.Spot)
|
||||
|
||||
}
|
||||
return ticker.GetTicker(o.Name, p, assetType)
|
||||
}
|
||||
|
||||
// GetTickerPrice returns the ticker for a currency pair
|
||||
func (o *OKEX) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
|
||||
tickerNew, err := ticker.GetTicker(o.GetName(), p, assetType)
|
||||
if err != nil {
|
||||
return o.UpdateTicker(p, assetType)
|
||||
}
|
||||
return tickerNew, nil
|
||||
}
|
||||
|
||||
// GetOrderbookEx returns orderbook base on the currency pair
|
||||
func (o *OKEX) GetOrderbookEx(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) {
|
||||
ob, err := orderbook.GetOrderbook(o.GetName(), currency, assetType)
|
||||
if err != nil {
|
||||
return o.UpdateOrderbook(currency, assetType)
|
||||
}
|
||||
return ob, nil
|
||||
}
|
||||
|
||||
// UpdateOrderbook updates and returns the orderbook for a currency pair
|
||||
func (o *OKEX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
|
||||
var orderBook orderbook.Base
|
||||
currency := exchange.FormatExchangeCurrency(o.Name, p).String()
|
||||
|
||||
if assetType != ticker.Spot {
|
||||
orderbookNew, err := o.GetContractMarketDepth(currency, assetType)
|
||||
if err != nil {
|
||||
return orderBook, err
|
||||
}
|
||||
|
||||
for x := range orderbookNew.Bids {
|
||||
data := orderbookNew.Bids[x]
|
||||
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Volume, Price: data.Price})
|
||||
}
|
||||
|
||||
for x := range orderbookNew.Asks {
|
||||
data := orderbookNew.Asks[x]
|
||||
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Volume, Price: data.Price})
|
||||
}
|
||||
|
||||
} else {
|
||||
orderbookNew, err := o.GetSpotMarketDepth(ActualSpotDepthRequestParams{
|
||||
Symbol: currency,
|
||||
Size: 200,
|
||||
})
|
||||
if err != nil {
|
||||
return orderBook, err
|
||||
}
|
||||
|
||||
for x := range orderbookNew.Bids {
|
||||
data := orderbookNew.Bids[x]
|
||||
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Volume, Price: data.Price})
|
||||
}
|
||||
|
||||
for x := range orderbookNew.Asks {
|
||||
data := orderbookNew.Asks[x]
|
||||
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Volume, Price: data.Price})
|
||||
}
|
||||
}
|
||||
|
||||
orderbook.ProcessOrderbook(o.GetName(), p, orderBook, assetType)
|
||||
return orderbook.GetOrderbook(o.Name, p, assetType)
|
||||
}
|
||||
|
||||
// GetAccountInfo retrieves balances for all enabled currencies for the
|
||||
// OKEX exchange
|
||||
func (o *OKEX) GetAccountInfo() (exchange.AccountInfo, error) {
|
||||
var info exchange.AccountInfo
|
||||
bal, err := o.GetBalance()
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
var balances []exchange.AccountCurrencyInfo
|
||||
for _, data := range bal {
|
||||
balances = append(balances, exchange.AccountCurrencyInfo{
|
||||
CurrencyName: data.Currency,
|
||||
TotalValue: data.Available + data.Hold,
|
||||
Hold: data.Hold,
|
||||
})
|
||||
}
|
||||
|
||||
info.Exchange = o.GetName()
|
||||
info.Accounts = append(info.Accounts, exchange.Account{
|
||||
Currencies: balances,
|
||||
})
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// GetFundingHistory returns funding history, deposits and
|
||||
// withdrawals
|
||||
func (o *OKEX) GetFundingHistory() ([]exchange.FundHistory, error) {
|
||||
var fundHistory []exchange.FundHistory
|
||||
return fundHistory, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetExchangeHistory returns historic trade data since exchange opening.
|
||||
func (o *OKEX) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]exchange.TradeHistory, error) {
|
||||
var resp []exchange.TradeHistory
|
||||
|
||||
return resp, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// SubmitOrder submits a new order
|
||||
func (o *OKEX) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, _ string) (exchange.SubmitOrderResponse, error) {
|
||||
var submitOrderResponse exchange.SubmitOrderResponse
|
||||
var oT SpotNewOrderRequestType
|
||||
|
||||
switch orderType {
|
||||
case exchange.LimitOrderType:
|
||||
oT = SpotNewOrderRequestTypeSell
|
||||
if side == exchange.BuyOrderSide {
|
||||
oT = SpotNewOrderRequestTypeBuy
|
||||
}
|
||||
case exchange.MarketOrderType:
|
||||
oT = SpotNewOrderRequestTypeSellMarket
|
||||
if side == exchange.BuyOrderSide {
|
||||
oT = SpotNewOrderRequestTypeBuyMarket
|
||||
}
|
||||
default:
|
||||
return submitOrderResponse, errors.New("unsupported order type")
|
||||
}
|
||||
|
||||
var params = SpotNewOrderRequestParams{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
Symbol: p.Pair().String(),
|
||||
Type: oT,
|
||||
}
|
||||
|
||||
response, err := o.SpotNewOrder(params)
|
||||
|
||||
if response > 0 {
|
||||
submitOrderResponse.OrderID = fmt.Sprintf("%v", response)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
submitOrderResponse.IsOrderPlaced = true
|
||||
}
|
||||
|
||||
return submitOrderResponse, err
|
||||
}
|
||||
|
||||
// ModifyOrder will allow of changing orderbook placement and limit to
|
||||
// market conversion
|
||||
func (o *OKEX) ModifyOrder(action exchange.ModifyOrder) (string, error) {
|
||||
return "", common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (o *OKEX) CancelOrder(order exchange.OrderCancellation) error {
|
||||
orderIDInt, err := strconv.ParseInt(order.OrderID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = o.SpotCancelOrder(exchange.FormatExchangeCurrency(o.Name, order.CurrencyPair).String(), orderIDInt)
|
||||
return err
|
||||
}
|
||||
|
||||
// CancelAllOrders cancels all orders for all enabled currencies
|
||||
func (o *OKEX) CancelAllOrders(_ exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) {
|
||||
cancelAllOrdersResponse := exchange.CancelAllOrdersResponse{
|
||||
OrderStatus: make(map[string]string),
|
||||
}
|
||||
var allOpenOrders []TokenOrder
|
||||
for _, currency := range o.GetEnabledCurrencies() {
|
||||
formattedCurrency := exchange.FormatExchangeCurrency(o.Name, currency).String()
|
||||
openOrders, err := o.GetTokenOrders(formattedCurrency, -1)
|
||||
if err != nil {
|
||||
return cancelAllOrdersResponse, err
|
||||
}
|
||||
|
||||
if !openOrders.Result {
|
||||
return cancelAllOrdersResponse, fmt.Errorf("something went wrong for currency %s", formattedCurrency)
|
||||
}
|
||||
|
||||
allOpenOrders = append(allOpenOrders, openOrders.Orders...)
|
||||
}
|
||||
|
||||
for _, openOrder := range allOpenOrders {
|
||||
_, err := o.SpotCancelOrder(openOrder.Symbol, openOrder.OrderID)
|
||||
if err != nil {
|
||||
cancelAllOrdersResponse.OrderStatus[strconv.FormatInt(openOrder.OrderID, 10)] = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
return cancelAllOrdersResponse, nil
|
||||
}
|
||||
|
||||
// GetOrderInfo returns information on a current open order
|
||||
func (o *OKEX) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
|
||||
var orderDetail exchange.OrderDetail
|
||||
return orderDetail, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// GetDepositAddress returns a deposit address for a specified currency
|
||||
func (o *OKEX) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) {
|
||||
// NOTE needs API version update to access
|
||||
return "", common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
|
||||
// submitted
|
||||
func (o *OKEX) WithdrawCryptocurrencyFunds(withdrawRequest exchange.WithdrawRequest) (string, error) {
|
||||
resp, err := o.Withdrawal(withdrawRequest.Currency.String(), withdrawRequest.FeeAmount, withdrawRequest.TradePassword, withdrawRequest.Address, withdrawRequest.Amount)
|
||||
return fmt.Sprintf("%v", resp), err
|
||||
}
|
||||
|
||||
// WithdrawFiatFunds returns a withdrawal ID when a
|
||||
// withdrawal is submitted
|
||||
func (o *OKEX) WithdrawFiatFunds(withdrawRequest exchange.WithdrawRequest) (string, error) {
|
||||
return "", common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a
|
||||
// withdrawal is submitted
|
||||
func (o *OKEX) WithdrawFiatFundsToInternationalBank(withdrawRequest exchange.WithdrawRequest) (string, error) {
|
||||
return "", common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (o *OKEX) GetWebsocket() (*exchange.Websocket, error) {
|
||||
return o.Websocket, nil
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
func (o *OKEX) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) {
|
||||
return o.GetFee(feeBuilder)
|
||||
}
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (o *OKEX) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) {
|
||||
var allOrders []OrderInfo
|
||||
for _, currency := range getOrdersRequest.Currencies {
|
||||
resp, err := o.GetOrderHistoryForCurrency(200, 0, 0, exchange.FormatExchangeCurrency(o.Name, currency).String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allOrders = append(allOrders, resp.Orders...)
|
||||
}
|
||||
|
||||
var orders []exchange.OrderDetail
|
||||
for _, order := range allOrders {
|
||||
// Status 2 == Filled, -1 == Cancelled.
|
||||
if order.Status == 2 || order.Status == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
symbol := pair.NewCurrencyPairDelimiter(order.Symbol, o.ConfigCurrencyPairFormat.Delimiter)
|
||||
orderDate := time.Unix(order.Created, 0)
|
||||
side := exchange.OrderSide(strings.ToUpper(order.Type))
|
||||
orders = append(orders, exchange.OrderDetail{
|
||||
ID: fmt.Sprintf("%v", order.OrderID),
|
||||
Amount: order.Amount,
|
||||
OrderDate: orderDate,
|
||||
Price: order.Price,
|
||||
OrderSide: side,
|
||||
CurrencyPair: symbol,
|
||||
Exchange: o.Name,
|
||||
})
|
||||
}
|
||||
|
||||
exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks)
|
||||
exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide)
|
||||
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (o *OKEX) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) {
|
||||
var allOrders []OrderInfo
|
||||
for _, currency := range getOrdersRequest.Currencies {
|
||||
resp, err := o.GetOrderInformation(-1, exchange.FormatExchangeCurrency(o.Name, currency).String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allOrders = append(allOrders, resp...)
|
||||
}
|
||||
|
||||
var orders []exchange.OrderDetail
|
||||
for _, order := range allOrders {
|
||||
symbol := pair.NewCurrencyPairDelimiter(order.Symbol, o.ConfigCurrencyPairFormat.Delimiter)
|
||||
orderDate := time.Unix(order.Created, 0)
|
||||
side := exchange.OrderSide(strings.ToUpper(order.Type))
|
||||
orders = append(orders, exchange.OrderDetail{
|
||||
ID: fmt.Sprintf("%v", order.OrderID),
|
||||
Amount: order.Amount,
|
||||
OrderDate: orderDate,
|
||||
Price: order.Price,
|
||||
OrderSide: side,
|
||||
CurrencyPair: symbol,
|
||||
Exchange: o.Name,
|
||||
})
|
||||
}
|
||||
|
||||
exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks)
|
||||
exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide)
|
||||
|
||||
return orders, nil
|
||||
}
|
||||
133
exchanges/okgroup/README.md
Normal file
133
exchanges/okgroup/README.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# GoCryptoTrader package Okex
|
||||
|
||||
<img src="https://github.com/thrasher-/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://travis-ci.org/thrasher-/gocryptotrader)
|
||||
[](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-/gocryptotrader/exchanges/okex)
|
||||
[](http://codecov.io/github/thrasher-/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-/gocryptotrader)
|
||||
|
||||
|
||||
This okex package is part of the GoCryptoTrader codebase.
|
||||
|
||||
## This is still in active development
|
||||
|
||||
You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
|
||||
|
||||
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://gocryptotrader.herokuapp.com/)
|
||||
|
||||
## OKex Exchange
|
||||
|
||||
### Current Features
|
||||
|
||||
+ REST Support
|
||||
|
||||
### How to enable
|
||||
|
||||
+ [Enable via configuration](https://github.com/thrasher-/gocryptotrader/tree/master/config#enable-exchange-via-config-example)
|
||||
|
||||
+ Individual package example below:
|
||||
|
||||
```go
|
||||
// Exchanges will be abstracted out in further updates and examples will be
|
||||
// supplied then
|
||||
```
|
||||
|
||||
### How to do REST public/private calls
|
||||
|
||||
+ If enabled via "configuration".json file the exchange will be added to the
|
||||
IBotExchange array in the ```go var bot Bot``` and you will only be able to use
|
||||
the wrapper interface functions for accessing exchange data. View routines.go
|
||||
for an example of integration usage with GoCryptoTrader. Rudimentary example
|
||||
below:
|
||||
|
||||
main.go
|
||||
```go
|
||||
var o exchange.IBotExchange
|
||||
|
||||
for i := range bot.exchanges {
|
||||
if bot.exchanges[i].GetName() == "OKex" {
|
||||
y = bot.exchanges[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Public calls - wrapper functions
|
||||
|
||||
// Fetches current ticker information
|
||||
tick, err := o.GetTickerPrice()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Fetches current orderbook information
|
||||
ob, err := o.GetOrderbookEx()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Private calls - wrapper functions - make sure your APIKEY and APISECRET are
|
||||
// set and AuthenticatedAPISupport is set to true
|
||||
|
||||
// Fetches current account information
|
||||
accountInfo, err := o.GetAccountInfo()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
+ If enabled via individually importing package, rudimentary example below:
|
||||
|
||||
```go
|
||||
// Public calls
|
||||
|
||||
// Fetches current ticker information
|
||||
ticker, err := o.GetSpotTicker()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Fetches current orderbook information
|
||||
ob, err := o.GetSpotMarketDepth()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Private calls - make sure your APIKEY and APISECRET are set and
|
||||
// AuthenticatedAPISupport is set to true
|
||||
|
||||
// GetContractPosition returns contract positioning
|
||||
accountInfo, err := o.GetContractPosition(...)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Submits an order and the exchange and returns its tradeID
|
||||
tradeID, err := o.PlaceContractOrders(...)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
|
||||
## Contribution
|
||||
|
||||
Please feel free to submit any pull requests or suggest any desired features to be added.
|
||||
|
||||
When submitting a PR, please abide by our coding guidelines:
|
||||
|
||||
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
||||
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
||||
+ Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md).
|
||||
+ Pull requests need to be based on and opened against the `master` branch.
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB***
|
||||
|
||||
840
exchanges/okgroup/okgroup.go
Normal file
840
exchanges/okgroup/okgroup.go
Normal file
@@ -0,0 +1,840 @@
|
||||
package okgroup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-querystring/query"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
okGroupAuthRate = 0
|
||||
okGroupUnauthRate = 0
|
||||
// OKGroupAPIPath const to help with api url formatting
|
||||
OKGroupAPIPath = "api/"
|
||||
// API subsections
|
||||
okGroupAccountSubsection = "account"
|
||||
okGroupTokenSubsection = "spot"
|
||||
okGroupMarginTradingSubsection = "margin"
|
||||
// OKGroupAccounts common api endpoint
|
||||
OKGroupAccounts = "accounts"
|
||||
// OKGroupLedger common api endpoint
|
||||
OKGroupLedger = "ledger"
|
||||
// OKGroupOrders common api endpoint
|
||||
OKGroupOrders = "orders"
|
||||
// OKGroupBatchOrders common api endpoint
|
||||
OKGroupBatchOrders = "batch_orders"
|
||||
// OKGroupCancelOrders common api endpoint
|
||||
OKGroupCancelOrders = "cancel_orders"
|
||||
// OKGroupCancelOrder common api endpoint
|
||||
OKGroupCancelOrder = "cancel_order"
|
||||
// OKGroupCancelBatchOrders common api endpoint
|
||||
OKGroupCancelBatchOrders = "cancel_batch_orders"
|
||||
// OKGroupPendingOrders common api endpoint
|
||||
OKGroupPendingOrders = "orders_pending"
|
||||
// OKGroupTrades common api endpoint
|
||||
OKGroupTrades = "trades"
|
||||
// OKGroupTicker common api endpoint
|
||||
OKGroupTicker = "ticker"
|
||||
// OKGroupInstruments common api endpoint
|
||||
OKGroupInstruments = "instruments"
|
||||
// OKGroupLiquidation common api endpoint
|
||||
OKGroupLiquidation = "liquidation"
|
||||
// OKGroupMarkPrice common api endpoint
|
||||
OKGroupMarkPrice = "mark_price"
|
||||
// OKGroupGetAccountDepositHistory common api endpoint
|
||||
OKGroupGetAccountDepositHistory = "deposit/history"
|
||||
// OKGroupGetSpotTransactionDetails common api endpoint
|
||||
OKGroupGetSpotTransactionDetails = "fills"
|
||||
// OKGroupGetSpotOrderBook common api endpoint
|
||||
OKGroupGetSpotOrderBook = "book"
|
||||
// OKGroupGetSpotMarketData common api endpoint
|
||||
OKGroupGetSpotMarketData = "candles"
|
||||
// OKGroupPriceLimit common api endpoint
|
||||
OKGroupPriceLimit = "price_limit"
|
||||
// Account based endpoints
|
||||
okGroupGetAccountCurrencies = "currencies"
|
||||
okGroupGetAccountWalletInformation = "wallet"
|
||||
okGroupFundsTransfer = "transfer"
|
||||
okGroupWithdraw = "withdrawal"
|
||||
okGroupGetWithdrawalFees = "withdrawal/fee"
|
||||
okGroupGetWithdrawalHistory = "withdrawal/history"
|
||||
okGroupGetDepositAddress = "deposit/address"
|
||||
// Margin based endpoints
|
||||
okGroupGetMarketAvailability = "availability"
|
||||
okGroupGetLoanHistory = "borrowed"
|
||||
okGroupGetLoan = "borrow"
|
||||
okGroupGetRepayment = "repayment"
|
||||
)
|
||||
|
||||
var errMissValue = errors.New("warning - resp value is missing from exchange")
|
||||
|
||||
// OKGroup is the overaching type across the all of OKEx's exchange methods
|
||||
type OKGroup struct {
|
||||
exchange.Base
|
||||
ExchangeName string
|
||||
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
|
||||
// URLs to be overridden by implementations of OKGroup
|
||||
APIURL string
|
||||
APIVersion string
|
||||
WebsocketURL string
|
||||
}
|
||||
|
||||
// Setup method sets current configuration details if enabled
|
||||
func (o *OKGroup) Setup(exch config.ExchangeConfig) {
|
||||
if !exch.Enabled {
|
||||
o.SetEnabled(false)
|
||||
} else {
|
||||
o.Name = exch.Name
|
||||
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.SetWsStatusAndConnection(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,
|
||||
o.WebsocketURL,
|
||||
exch.WebsocketURL)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetAccountCurrencies returns a list of tradable spot instruments and their properties
|
||||
func (o *OKGroup) GetAccountCurrencies() (resp []GetAccountCurrenciesResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, okGroupGetAccountCurrencies, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountWalletInformation returns a list of wallets and their properties
|
||||
func (o *OKGroup) GetAccountWalletInformation(currency string) (resp []WalletInformationResponse, _ error) {
|
||||
var requestURL string
|
||||
if currency != "" {
|
||||
requestURL = fmt.Sprintf("%v/%v", okGroupGetAccountWalletInformation, currency)
|
||||
} else {
|
||||
requestURL = okGroupGetAccountWalletInformation
|
||||
}
|
||||
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// TransferAccountFunds the transfer of funds between wallet, trading accounts, main account and sub accounts.
|
||||
func (o *OKGroup) TransferAccountFunds(request TransferAccountFundsRequest) (resp TransferAccountFundsResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(http.MethodPost, okGroupAccountSubsection, okGroupFundsTransfer, request, &resp, true)
|
||||
}
|
||||
|
||||
// AccountWithdraw withdrawal of tokens to OKCoin International, other OKEx accounts or other addresses.
|
||||
func (o *OKGroup) AccountWithdraw(request AccountWithdrawRequest) (resp AccountWithdrawResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(http.MethodPost, okGroupAccountSubsection, okGroupWithdraw, request, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountWithdrawalFee retrieves the information about the recommended network transaction fee for withdrawals to digital asset addresses. The higher the fees are, the sooner the confirmations you will get.
|
||||
func (o *OKGroup) GetAccountWithdrawalFee(currency string) (resp []GetAccountWithdrawalFeeResponse, _ error) {
|
||||
var requestURL string
|
||||
if currency != "" {
|
||||
requestURL = fmt.Sprintf("%v?currency=%v", okGroupGetWithdrawalFees, currency)
|
||||
} else {
|
||||
requestURL = okGroupGetAccountWalletInformation
|
||||
}
|
||||
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountWithdrawalHistory retrieves all recent withdrawal records.
|
||||
func (o *OKGroup) GetAccountWithdrawalHistory(currency string) (resp []WithdrawalHistoryResponse, _ error) {
|
||||
var requestURL string
|
||||
if currency != "" {
|
||||
requestURL = fmt.Sprintf("%v/%v", okGroupGetWithdrawalHistory, currency)
|
||||
} else {
|
||||
requestURL = okGroupGetWithdrawalHistory
|
||||
}
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountBillDetails retrieves the bill details of the wallet. All the information will be paged and sorted in reverse chronological order,
|
||||
// which means the latest will be at the top. Please refer to the pagination section for additional records after the first page.
|
||||
// 3 months recent records will be returned at maximum
|
||||
func (o *OKGroup) GetAccountBillDetails(request GetAccountBillDetailsRequest) (resp []GetAccountBillDetailsResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupLedger, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountDepositAddressForCurrency retrieves the deposit addresses of different tokens, including previously used addresses.
|
||||
func (o *OKGroup) GetAccountDepositAddressForCurrency(currency string) (resp []GetDepositAddressResponse, _ error) {
|
||||
urlValues := url.Values{}
|
||||
urlValues.Set("currency", currency)
|
||||
requestURL := fmt.Sprintf("%v?%v", okGroupGetDepositAddress, urlValues.Encode())
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetAccountDepositHistory retrieves the deposit history of all tokens.100 recent records will be returned at maximum
|
||||
func (o *OKGroup) GetAccountDepositHistory(currency string) (resp []GetAccountDepositHistoryResponse, _ error) {
|
||||
var requestURL string
|
||||
if currency != "" {
|
||||
requestURL = fmt.Sprintf("%v/%v", OKGroupGetAccountDepositHistory, currency)
|
||||
} else {
|
||||
requestURL = okGroupGetWithdrawalHistory
|
||||
}
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupAccountSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotTradingAccounts retrieves the list of assets(only show pairs with balance larger than 0), the balances, amount available/on hold in spot accounts.
|
||||
func (o *OKGroup) GetSpotTradingAccounts() (resp []GetSpotTradingAccountResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, OKGroupAccounts, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotTradingAccountForCurrency This endpoint supports getting the balance, amount available/on hold of a token in spot account.
|
||||
func (o *OKGroup) GetSpotTradingAccountForCurrency(currency string) (resp GetSpotTradingAccountResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupAccounts, currency)
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotBillDetailsForCurrency This endpoint supports getting the balance, amount available/on hold of a token in spot account.
|
||||
func (o *OKGroup) GetSpotBillDetailsForCurrency(request GetSpotBillDetailsForCurrencyRequest) (resp []GetSpotBillDetailsForCurrencyResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupAccounts, request.Currency, OKGroupLedger, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// PlaceSpotOrder token trading only supports limit and market orders (more order types will become available in the future).
|
||||
// You can place an order only if you have enough funds.
|
||||
// Once your order is placed, the amount will be put on hold.
|
||||
func (o *OKGroup) PlaceSpotOrder(request PlaceSpotOrderRequest) (resp PlaceSpotOrderResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(http.MethodPost, okGroupTokenSubsection, OKGroupOrders, request, &resp, true)
|
||||
}
|
||||
|
||||
// PlaceMultipleSpotOrders supports placing multiple orders for specific trading pairs
|
||||
// up to 4 trading pairs, maximum 4 orders for each pair
|
||||
func (o *OKGroup) PlaceMultipleSpotOrders(request []PlaceSpotOrderRequest) (map[string][]PlaceSpotOrderResponse, []error) {
|
||||
currencyPairOrders := make(map[string]int)
|
||||
resp := make(map[string][]PlaceSpotOrderResponse)
|
||||
for _, order := range request {
|
||||
currencyPairOrders[order.InstrumentID]++
|
||||
}
|
||||
if len(currencyPairOrders) > 4 {
|
||||
return resp, []error{errors.New("up to 4 trading pairs")}
|
||||
}
|
||||
for _, orderCount := range currencyPairOrders {
|
||||
if orderCount > 4 {
|
||||
return resp, []error{errors.New("maximum 4 orders for each pair")}
|
||||
}
|
||||
}
|
||||
|
||||
err := o.SendHTTPRequest(http.MethodPost, okGroupTokenSubsection, OKGroupBatchOrders, request, &resp, true)
|
||||
if err != nil {
|
||||
return resp, []error{err}
|
||||
}
|
||||
|
||||
orderErrors := []error{}
|
||||
for currency, orderResponse := range resp {
|
||||
for _, order := range orderResponse {
|
||||
if !order.Result {
|
||||
orderErrors = append(orderErrors, fmt.Errorf("order for currency %v failed to be placed", currency))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(orderErrors) == 0 {
|
||||
orderErrors = nil
|
||||
}
|
||||
|
||||
return resp, orderErrors
|
||||
}
|
||||
|
||||
// CancelSpotOrder Cancelling an unfilled order.
|
||||
func (o *OKGroup) CancelSpotOrder(request CancelSpotOrderRequest) (resp CancelSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupCancelOrders, request.OrderID)
|
||||
return resp, o.SendHTTPRequest(http.MethodPost, okGroupTokenSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// CancelMultipleSpotOrders Cancelling multiple unfilled orders.
|
||||
func (o *OKGroup) CancelMultipleSpotOrders(request CancelMultipleSpotOrdersRequest) (resp map[string][]CancelMultipleSpotOrdersResponse, err error) {
|
||||
resp = make(map[string][]CancelMultipleSpotOrdersResponse)
|
||||
if len(request.OrderIDs) > 4 {
|
||||
return resp, errors.New("maximum 4 order cancellations for each pair")
|
||||
}
|
||||
|
||||
err = o.SendHTTPRequest(http.MethodPost, okGroupTokenSubsection, OKGroupCancelBatchOrders, []CancelMultipleSpotOrdersRequest{request}, &resp, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for currency, orderResponse := range resp {
|
||||
for _, order := range orderResponse {
|
||||
cancellationResponse := CancelMultipleSpotOrdersResponse{
|
||||
OrderID: order.OrderID,
|
||||
Result: order.Result,
|
||||
ClientOID: order.ClientOID,
|
||||
}
|
||||
|
||||
if !order.Result {
|
||||
cancellationResponse.Error = fmt.Errorf("order %v for currency %v failed to be cancelled", order.OrderID, currency)
|
||||
}
|
||||
|
||||
resp[currency] = append(resp[currency], cancellationResponse)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetSpotOrders List your orders. Cursor pagination is used.
|
||||
// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetSpotOrders(request GetSpotOrdersRequest) (resp []GetSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupOrders, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotOpenOrders List all your current open orders. Cursor pagination is used.
|
||||
// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetSpotOpenOrders(request GetSpotOpenOrdersRequest) (resp []GetSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupPendingOrders, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotOrder Get order details by order ID.
|
||||
func (o *OKGroup) GetSpotOrder(request GetSpotOrderRequest) (resp GetSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v%v", OKGroupOrders, request.OrderID, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotTransactionDetails Get details of the recent filled orders. Cursor pagination is used.
|
||||
// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetSpotTransactionDetails(request GetSpotTransactionDetailsRequest) (resp []GetSpotTransactionDetailsResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupGetSpotTransactionDetails, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotTokenPairDetails Get market data. This endpoint provides the snapshots of market data and can be used without verifications.
|
||||
// List trading pairs and get the trading limit, price, and more information of different trading pairs.
|
||||
func (o *OKGroup) GetSpotTokenPairDetails() (resp []GetSpotTokenPairDetailsResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, OKGroupInstruments, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotOrderBook Getting the order book of a trading pair. Pagination is not supported here.
|
||||
// The whole book will be returned for one request. WebSocket is recommended here.
|
||||
func (o *OKGroup) GetSpotOrderBook(request GetSpotOrderBookRequest) (resp GetSpotOrderBookResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupInstruments, request.InstrumentID, OKGroupGetSpotOrderBook, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotAllTokenPairsInformation Get the last traded price, best bid/ask price, 24 hour trading volume and more info of all trading pairs.
|
||||
func (o *OKGroup) GetSpotAllTokenPairsInformation() (resp []GetSpotTokenPairsInformationResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupInstruments, OKGroupTicker)
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotAllTokenPairsInformationForCurrency Get the last traded price, best bid/ask price, 24 hour trading volume and more info of a currency
|
||||
func (o *OKGroup) GetSpotAllTokenPairsInformationForCurrency(currency string) (resp GetSpotTokenPairsInformationResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v", OKGroupInstruments, currency, OKGroupTicker)
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotFilledOrdersInformation Get the recent 60 transactions of all trading pairs.
|
||||
// Cursor pagination is used. All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetSpotFilledOrdersInformation(request GetSpotFilledOrdersInformationRequest) (resp []GetSpotFilledOrdersInformationResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupInstruments, request.InstrumentID, OKGroupTrades, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetSpotMarketData Get the charts of the trading pairs. Charts are returned in grouped buckets based on requested granularity.
|
||||
func (o *OKGroup) GetSpotMarketData(request GetSpotMarketDataRequest) (resp GetSpotMarketDataResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupInstruments, request.InstrumentID, OKGroupGetSpotMarketData, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupTokenSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginTradingAccounts List all assets under token margin trading account, including information such as balance, amount on hold and more.
|
||||
func (o *OKGroup) GetMarginTradingAccounts() (resp []GetMarginAccountsResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, OKGroupAccounts, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginTradingAccountsForCurrency Get the balance, amount on hold and more useful information.
|
||||
func (o *OKGroup) GetMarginTradingAccountsForCurrency(currency string) (resp GetMarginAccountsResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupAccounts, currency)
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginBillDetails List all bill details. Pagination is used here.
|
||||
// before and after cursor arguments should not be confused with before and after in chronological time.
|
||||
// Most paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetMarginBillDetails(request GetMarginBillDetailsRequest) (resp []GetSpotBillDetailsForCurrencyResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v/%v%v", OKGroupAccounts, request.InstrumentID, OKGroupLedger, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginAccountSettings Get all information of the margin trading account,
|
||||
// including the maximum loan amount, interest rate, and maximum leverage.
|
||||
func (o *OKGroup) GetMarginAccountSettings(currency string) (resp []GetMarginAccountSettingsResponse, _ error) {
|
||||
var requestURL string
|
||||
if currency != "" {
|
||||
requestURL = fmt.Sprintf("%v/%v/%v", OKGroupAccounts, currency, okGroupGetMarketAvailability)
|
||||
} else {
|
||||
requestURL = fmt.Sprintf("%v/%v", OKGroupAccounts, okGroupGetMarketAvailability)
|
||||
}
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginLoanHistory Get loan history of the margin trading account.
|
||||
// Pagination is used here. before and after cursor arguments should not be confused with before and after in chronological time.
|
||||
// Most paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetMarginLoanHistory(request GetMarginLoanHistoryRequest) (resp []GetMarginLoanHistoryResponse, _ error) {
|
||||
var requestURL string
|
||||
if len(request.InstrumentID) > 0 {
|
||||
requestURL = fmt.Sprintf("%v/%v/%v", OKGroupAccounts, request.InstrumentID, okGroupGetLoan)
|
||||
} else {
|
||||
requestURL = fmt.Sprintf("%v/%v", OKGroupAccounts, okGroupGetLoan)
|
||||
}
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// OpenMarginLoan Borrowing tokens in a margin trading account.
|
||||
func (o *OKGroup) OpenMarginLoan(request OpenMarginLoanRequest) (resp OpenMarginLoanResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupAccounts, okGroupGetLoan)
|
||||
return resp, o.SendHTTPRequest(http.MethodPost, okGroupMarginTradingSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// RepayMarginLoan Repaying tokens in a margin trading account.
|
||||
func (o *OKGroup) RepayMarginLoan(request RepayMarginLoanRequest) (resp RepayMarginLoanResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupAccounts, okGroupGetRepayment)
|
||||
return resp, o.SendHTTPRequest(http.MethodPost, okGroupMarginTradingSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// PlaceMarginOrder OKEx API only supports limit and market orders (more orders will become available in the future).
|
||||
// You can place an order only if you have enough funds. Once your order is placed, the amount will be put on hold.
|
||||
func (o *OKGroup) PlaceMarginOrder(request PlaceSpotOrderRequest) (resp PlaceSpotOrderResponse, _ error) {
|
||||
return resp, o.SendHTTPRequest(http.MethodPost, okGroupMarginTradingSubsection, OKGroupOrders, request, &resp, true)
|
||||
}
|
||||
|
||||
// PlaceMultipleMarginOrders Place multiple orders for specific trading pairs (up to 4 trading pairs, maximum 4 orders each)
|
||||
func (o *OKGroup) PlaceMultipleMarginOrders(request []PlaceSpotOrderRequest) (map[string][]PlaceSpotOrderResponse, []error) {
|
||||
currencyPairOrders := make(map[string]int)
|
||||
resp := make(map[string][]PlaceSpotOrderResponse)
|
||||
for _, order := range request {
|
||||
currencyPairOrders[order.InstrumentID]++
|
||||
}
|
||||
if len(currencyPairOrders) > 4 {
|
||||
return resp, []error{errors.New("up to 4 trading pairs")}
|
||||
}
|
||||
for _, orderCount := range currencyPairOrders {
|
||||
if orderCount > 4 {
|
||||
return resp, []error{errors.New("maximum 4 orders for each pair")}
|
||||
}
|
||||
}
|
||||
|
||||
err := o.SendHTTPRequest(http.MethodPost, okGroupMarginTradingSubsection, OKGroupBatchOrders, request, &resp, true)
|
||||
if err != nil {
|
||||
return resp, []error{err}
|
||||
}
|
||||
|
||||
orderErrors := []error{}
|
||||
for currency, orderResponse := range resp {
|
||||
for _, order := range orderResponse {
|
||||
if !order.Result {
|
||||
orderErrors = append(orderErrors, fmt.Errorf("order for currency %v failed to be placed", currency))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(orderErrors) == 0 {
|
||||
orderErrors = nil
|
||||
}
|
||||
|
||||
return resp, orderErrors
|
||||
}
|
||||
|
||||
// CancelMarginOrder Cancelling an unfilled order.
|
||||
func (o *OKGroup) CancelMarginOrder(request CancelSpotOrderRequest) (resp CancelSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v", OKGroupCancelOrders, request.OrderID)
|
||||
return resp, o.SendHTTPRequest(http.MethodPost, okGroupMarginTradingSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// CancelMultipleMarginOrders Cancelling multiple unfilled orders.
|
||||
func (o *OKGroup) CancelMultipleMarginOrders(request CancelMultipleSpotOrdersRequest) (map[string][]CancelMultipleSpotOrdersResponse, []error) {
|
||||
resp := make(map[string][]CancelMultipleSpotOrdersResponse)
|
||||
if len(request.OrderIDs) > 4 {
|
||||
return resp, []error{errors.New("maximum 4 order cancellations for each pair")}
|
||||
}
|
||||
|
||||
err := o.SendHTTPRequest(http.MethodPost, okGroupMarginTradingSubsection, OKGroupCancelBatchOrders, []CancelMultipleSpotOrdersRequest{request}, &resp, true)
|
||||
if err != nil {
|
||||
return resp, []error{err}
|
||||
}
|
||||
|
||||
orderErrors := []error{}
|
||||
for currency, orderResponse := range resp {
|
||||
for _, order := range orderResponse {
|
||||
if !order.Result {
|
||||
orderErrors = append(orderErrors, fmt.Errorf("order %v for currency %v failed to be cancelled", order.OrderID, currency))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(orderErrors) == 0 {
|
||||
orderErrors = nil
|
||||
}
|
||||
|
||||
return resp, orderErrors
|
||||
}
|
||||
|
||||
// GetMarginOrders List your orders. Cursor pagination is used. All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetMarginOrders(request GetSpotOrdersRequest) (resp []GetSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupOrders, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginOpenOrders List all your current open orders. Cursor pagination is used. All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetMarginOpenOrders(request GetSpotOpenOrdersRequest) (resp []GetSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupPendingOrders, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginOrder Get order details by order ID.
|
||||
func (o *OKGroup) GetMarginOrder(request GetSpotOrderRequest) (resp GetSpotOrderResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v/%v%v", OKGroupOrders, request.OrderID, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, request, &resp, true)
|
||||
}
|
||||
|
||||
// GetMarginTransactionDetails Get details of the recent filled orders. Cursor pagination is used.
|
||||
// All paginated requests return the latest information (newest) as the first page sorted by newest (in chronological time) first.
|
||||
func (o *OKGroup) GetMarginTransactionDetails(request GetSpotTransactionDetailsRequest) (resp []GetSpotTransactionDetailsResponse, _ error) {
|
||||
requestURL := fmt.Sprintf("%v%v", OKGroupGetSpotTransactionDetails, FormatParameters(request))
|
||||
return resp, o.SendHTTPRequest(http.MethodGet, okGroupMarginTradingSubsection, requestURL, nil, &resp, true)
|
||||
}
|
||||
|
||||
// FormatParameters Formats URL parameters, useful for optional parameters due to OKEX signature check
|
||||
func FormatParameters(request interface{}) (parameters string) {
|
||||
v, err := query.Values(request)
|
||||
if err != nil {
|
||||
log.Errorf("Could not parse %v to URL values. Check that the type has url fields", reflect.TypeOf(request).Name())
|
||||
return
|
||||
}
|
||||
urlEncodedValues := v.Encode()
|
||||
if len(urlEncodedValues) > 0 {
|
||||
parameters = fmt.Sprintf("?%v", urlEncodedValues)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetErrorCode returns an error code
|
||||
func (o *OKGroup) 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 authenticated http request to a desired
|
||||
// path with a JSON payload (of present)
|
||||
// URL arguments must be in the request path and not as url.URL values
|
||||
func (o *OKGroup) SendHTTPRequest(httpMethod, requestType, requestPath string, data, result interface{}, authenticated bool) (err error) {
|
||||
if authenticated && !o.AuthenticatedAPISupport {
|
||||
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, o.Name)
|
||||
}
|
||||
|
||||
utcTime := time.Now().UTC()
|
||||
iso := utcTime.String()
|
||||
isoBytes := []byte(iso)
|
||||
iso = string(isoBytes[:10]) + "T" + string(isoBytes[11:23]) + "Z"
|
||||
payload := []byte("")
|
||||
|
||||
if data != nil {
|
||||
payload, err = common.JSONEncode(data)
|
||||
if err != nil {
|
||||
return errors.New("sendHTTPRequest: Unable to JSON request")
|
||||
}
|
||||
|
||||
if o.Verbose {
|
||||
log.Debugf("Request JSON: %s\n", payload)
|
||||
}
|
||||
}
|
||||
|
||||
path := o.APIUrl + requestType + o.APIVersion + requestPath
|
||||
if o.Verbose {
|
||||
log.Debugf("Sending %v request to %s \n", requestType, path)
|
||||
}
|
||||
|
||||
headers := make(map[string]string)
|
||||
headers["Content-Type"] = "application/json"
|
||||
if authenticated {
|
||||
signPath := fmt.Sprintf("/%v%v%v%v", OKGroupAPIPath, requestType, o.APIVersion, requestPath)
|
||||
hmac := common.GetHMAC(common.HashSHA256, []byte(iso+httpMethod+signPath+string(payload)), []byte(o.APISecret))
|
||||
base64 := common.Base64Encode(hmac)
|
||||
headers["OK-ACCESS-KEY"] = o.APIKey
|
||||
headers["OK-ACCESS-SIGN"] = base64
|
||||
headers["OK-ACCESS-TIMESTAMP"] = iso
|
||||
headers["OK-ACCESS-PASSPHRASE"] = o.ClientID
|
||||
}
|
||||
|
||||
var intermediary json.RawMessage
|
||||
type errCapFormat struct {
|
||||
Error int64 `json:"error_code,omitempty"`
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
Result bool `json:"result,string,omitempty"`
|
||||
}
|
||||
|
||||
errCap := errCapFormat{}
|
||||
errCap.Result = true
|
||||
err = o.SendPayload(strings.ToUpper(httpMethod), path, headers, bytes.NewBuffer(payload), &intermediary, authenticated, o.Verbose)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = common.JSONDecode(intermediary, &errCap)
|
||||
if err == nil {
|
||||
if errCap.ErrorMessage != "" {
|
||||
return fmt.Errorf("error: %v", errCap.ErrorMessage)
|
||||
}
|
||||
if errCap.Error > 0 {
|
||||
return fmt.Errorf("sendHTTPRequest error - %s",
|
||||
o.ErrorCodes[strconv.FormatInt(errCap.Error, 10)])
|
||||
}
|
||||
if !errCap.Result {
|
||||
return errors.New("unspecified error occurred")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return common.JSONDecode(intermediary, result)
|
||||
}
|
||||
|
||||
// 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 *OKGroup) 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"}
|
||||
}
|
||||
|
||||
// GetFee returns an estimate of fee based on type of transaction
|
||||
func (o *OKGroup) GetFee(feeBuilder exchange.FeeBuilder) (fee float64, _ error) {
|
||||
switch feeBuilder.FeeType {
|
||||
case exchange.CryptocurrencyTradeFee:
|
||||
fee = calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount, feeBuilder.IsMaker)
|
||||
case exchange.CryptocurrencyWithdrawalFee:
|
||||
withdrawFees, err := o.GetAccountWithdrawalFee(feeBuilder.CurrencyItem)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
for _, withdrawFee := range withdrawFees {
|
||||
if withdrawFee.Currency == feeBuilder.CurrencyItem {
|
||||
fee = withdrawFee.MinFee
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// SetErrorDefaults sets the full error default list
|
||||
func (o *OKGroup) SetErrorDefaults() {
|
||||
o.ErrorCodes = map[string]error{
|
||||
"0": errors.New("successful"),
|
||||
"1": errors.New("invalid parameter in url normally"),
|
||||
"30001": errors.New("request header \"OK_ACCESS_KEY\" cannot be blank"),
|
||||
"30002": errors.New("request header \"OK_ACCESS_SIGN\" cannot be blank"),
|
||||
"30003": errors.New("request header \"OK_ACCESS_TIMESTAMP\" cannot be blank"),
|
||||
"30004": errors.New("request header \"OK_ACCESS_PASSPHRASE\" cannot be blank"),
|
||||
"30005": errors.New("invalid OK_ACCESS_TIMESTAMP"),
|
||||
"30006": errors.New("invalid OK_ACCESS_KEY"),
|
||||
"30007": errors.New("invalid Content_Type, please use \"application/json\" format"),
|
||||
"30008": errors.New("timestamp request expired"),
|
||||
"30009": errors.New("system error"),
|
||||
"30010": errors.New("api validation failed"),
|
||||
"30011": errors.New("invalid IP"),
|
||||
"30012": errors.New("invalid authorization"),
|
||||
"30013": errors.New("invalid sign"),
|
||||
"30014": errors.New("request too frequent"),
|
||||
"30015": errors.New("request header \"OK_ACCESS_PASSPHRASE\" incorrect"),
|
||||
"30016": errors.New("you are using v1 apiKey, please use v1 endpoint. If you would like to use v3 endpoint, please subscribe to v3 apiKey"),
|
||||
"30017": errors.New("apikey's broker id does not match"),
|
||||
"30018": errors.New("apikey's domain does not match"),
|
||||
"30020": errors.New("body cannot be blank"),
|
||||
"30021": errors.New("json data format error"),
|
||||
"30023": errors.New("required parameter cannot be blank"),
|
||||
"30024": errors.New("parameter value error"),
|
||||
"30025": errors.New("parameter category error"),
|
||||
"30026": errors.New("requested too frequent; endpoint limit exceeded"),
|
||||
"30027": errors.New("login failure"),
|
||||
"30028": errors.New("unauthorized execution"),
|
||||
"30029": errors.New("account suspended"),
|
||||
"30030": errors.New("endpoint request failed. Please try again"),
|
||||
"30031": errors.New("token does not exist"),
|
||||
"30032": errors.New("pair does not exist"),
|
||||
"30033": errors.New("exchange domain does not exist"),
|
||||
"30034": errors.New("exchange ID does not exist"),
|
||||
"30035": errors.New("trading is not supported in this website"),
|
||||
"30036": errors.New("no relevant data"),
|
||||
"30037": errors.New("endpoint is offline or unavailable"),
|
||||
"30038": errors.New("user does not exist"),
|
||||
"32001": errors.New("futures account suspended"),
|
||||
"32002": errors.New("futures account does not exist"),
|
||||
"32003": errors.New("canceling, please wait"),
|
||||
"32004": errors.New("you have no unfilled orders"),
|
||||
"32005": errors.New("max order quantity"),
|
||||
"32006": errors.New("the order price or trigger price exceeds USD 1 million"),
|
||||
"32007": errors.New("leverage level must be the same for orders on the same side of the contract"),
|
||||
"32008": errors.New("max. positions to open (cross margin)"),
|
||||
"32009": errors.New("max. positions to open (fixed margin)"),
|
||||
"32010": errors.New("leverage cannot be changed with open positions"),
|
||||
"32011": errors.New("futures status error"),
|
||||
"32012": errors.New("futures order update error"),
|
||||
"32013": errors.New("token type is blank"),
|
||||
"32014": errors.New("your number of contracts closing is larger than the number of contracts available"),
|
||||
"32015": errors.New("margin ratio is lower than 100% before opening positions"),
|
||||
"32016": errors.New("margin ratio is lower than 100% after opening position"),
|
||||
"32017": errors.New("no BBO"),
|
||||
"32018": errors.New("the order quantity is less than 1, please try again"),
|
||||
"32019": errors.New("the order price deviates from the price of the previous minute by more than 3%"),
|
||||
"32020": errors.New("the price is not in the range of the price limit"),
|
||||
"32021": errors.New("leverage error"),
|
||||
"32022": errors.New("this function is not supported in your country or region according to the regulations"),
|
||||
"32023": errors.New("this account has outstanding loan"),
|
||||
"32024": errors.New("order cannot be placed during delivery"),
|
||||
"32025": errors.New("order cannot be placed during settlement"),
|
||||
"32026": errors.New("your account is restricted from opening positions"),
|
||||
"32027": errors.New("cancelled over 20 orders"),
|
||||
"32028": errors.New("account is suspended and liquidated"),
|
||||
"32029": errors.New("order info does not exist"),
|
||||
"33001": errors.New("margin account for this pair is not enabled yet"),
|
||||
"33002": errors.New("margin account for this pair is suspended"),
|
||||
"33003": errors.New("no loan balance"),
|
||||
"33004": errors.New("loan amount cannot be smaller than the minimum limit"),
|
||||
"33005": errors.New("repayment amount must exceed 0"),
|
||||
"33006": errors.New("loan order not found"),
|
||||
"33007": errors.New("status not found"),
|
||||
"33008": errors.New("loan amount cannot exceed the maximum limit"),
|
||||
"33009": errors.New("user ID is blank"),
|
||||
"33010": errors.New("you cannot cancel an order during session 2 of call auction"),
|
||||
"33011": errors.New("no new market data"),
|
||||
"33012": errors.New("order cancellation failed"),
|
||||
"33013": errors.New("order placement failed"),
|
||||
"33014": errors.New("order does not exist"),
|
||||
"33015": errors.New("exceeded maximum limit"),
|
||||
"33016": errors.New("margin trading is not open for this token"),
|
||||
"33017": errors.New("insufficient balance"),
|
||||
"33018": errors.New("this parameter must be smaller than 1"),
|
||||
"33020": errors.New("request not supported"),
|
||||
"33021": errors.New("token and the pair do not match"),
|
||||
"33022": errors.New("pair and the order do not match"),
|
||||
"33023": errors.New("you can only place market orders during call auction"),
|
||||
"33024": errors.New("trading amount too small"),
|
||||
"33025": errors.New("base token amount is blank"),
|
||||
"33026": errors.New("transaction completed"),
|
||||
"33027": errors.New("cancelled order or order cancelling"),
|
||||
"33028": errors.New("the decimal places of the trading price exceeded the limit"),
|
||||
"33029": errors.New("the decimal places of the trading size exceeded the limit"),
|
||||
"34001": errors.New("withdrawal suspended"),
|
||||
"34002": errors.New("please add a withdrawal address"),
|
||||
"34003": errors.New("sorry, this token cannot be withdrawn to xx at the moment"),
|
||||
"34004": errors.New("withdrawal fee is smaller than minimum limit"),
|
||||
"34005": errors.New("withdrawal fee exceeds the maximum limit"),
|
||||
"34006": errors.New("withdrawal amount is lower than the minimum limit"),
|
||||
"34007": errors.New("withdrawal amount exceeds the maximum limit"),
|
||||
"34008": errors.New("insufficient balance"),
|
||||
"34009": errors.New("your withdrawal amount exceeds the daily limit"),
|
||||
"34010": errors.New("transfer amount must be larger than 0"),
|
||||
"34011": errors.New("conditions not met"),
|
||||
"34012": errors.New("the minimum withdrawal amount for NEO is 1, and the amount must be an integer"),
|
||||
"34013": errors.New("please transfer"),
|
||||
"34014": errors.New("transfer limited"),
|
||||
"34015": errors.New("subaccount does not exist"),
|
||||
"34016": errors.New("transfer suspended"),
|
||||
"34017": errors.New("account suspended"),
|
||||
"34018": errors.New("incorrect trades password"),
|
||||
"34019": errors.New("please bind your email before withdrawal"),
|
||||
"34020": errors.New("please bind your funds password before withdrawal"),
|
||||
"34021": errors.New("not verified address"),
|
||||
"34022": errors.New("withdrawals are not available for sub accounts"),
|
||||
"35001": errors.New("contract subscribing does not exist"),
|
||||
"35002": errors.New("contract is being settled"),
|
||||
"35003": errors.New("contract is being paused"),
|
||||
"35004": errors.New("pending contract settlement"),
|
||||
"35005": errors.New("perpetual swap trading is not enabled"),
|
||||
"35008": errors.New("margin ratio too low when placing order"),
|
||||
"35010": errors.New("closing position size larger than available size"),
|
||||
"35012": errors.New("placing an order with less than 1 contract"),
|
||||
"35014": errors.New("order size is not in acceptable range"),
|
||||
"35015": errors.New("leverage level unavailable"),
|
||||
"35017": errors.New("changing leverage level"),
|
||||
"35019": errors.New("order size exceeds limit"),
|
||||
"35020": errors.New("order price exceeds limit"),
|
||||
"35021": errors.New("order size exceeds limit of the current tier"),
|
||||
"35022": errors.New("contract is paused or closed"),
|
||||
"35030": errors.New("place multiple orders"),
|
||||
"35031": errors.New("cancel multiple orders"),
|
||||
"35061": errors.New("invalid instrument_id"),
|
||||
}
|
||||
}
|
||||
1514
exchanges/okgroup/okgroup_types.go
Normal file
1514
exchanges/okgroup/okgroup_types.go
Normal file
File diff suppressed because it is too large
Load Diff
759
exchanges/okgroup/okgroup_websocket.go
Normal file
759
exchanges/okgroup/okgroup_websocket.go
Normal file
@@ -0,0 +1,759 @@
|
||||
package okgroup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/currency/pair"
|
||||
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
// List of all websocket channels to subscribe to
|
||||
const (
|
||||
// If a checksum fails, then resubscribing to the channel fails, fatal after these attempts
|
||||
okGroupWsResubscribeFailureLimit = 3
|
||||
okGroupWsResubscribeDelayInSeconds = 3
|
||||
// Orderbook events
|
||||
okGroupWsOrderbookUpdate = "update"
|
||||
okGroupWsOrderbookPartial = "partial"
|
||||
// API subsections
|
||||
okGroupWsSwapSubsection = "swap/"
|
||||
okGroupWsIndexSubsection = "index/"
|
||||
okGroupWsFuturesSubsection = "futures/"
|
||||
okGroupWsSpotSubsection = "spot/"
|
||||
// Shared API endpoints
|
||||
okGroupWsCandle = "candle"
|
||||
okGroupWsCandle60s = okGroupWsCandle + "60s"
|
||||
okGroupWsCandle180s = okGroupWsCandle + "180s"
|
||||
okGroupWsCandle300s = okGroupWsCandle + "300s"
|
||||
okGroupWsCandle900s = okGroupWsCandle + "900s"
|
||||
okGroupWsCandle1800s = okGroupWsCandle + "1800s"
|
||||
okGroupWsCandle3600s = okGroupWsCandle + "3600s"
|
||||
okGroupWsCandle7200s = okGroupWsCandle + "7200s"
|
||||
okGroupWsCandle14400s = okGroupWsCandle + "14400s"
|
||||
okGroupWsCandle21600s = okGroupWsCandle + "21600"
|
||||
okGroupWsCandle43200s = okGroupWsCandle + "43200s"
|
||||
okGroupWsCandle86400s = okGroupWsCandle + "86400s"
|
||||
okGroupWsCandle604900s = okGroupWsCandle + "604800s"
|
||||
okGroupWsTicker = "ticker"
|
||||
okGroupWsTrade = "trade"
|
||||
okGroupWsDepth = "depth"
|
||||
okGroupWsDepth5 = "depth5"
|
||||
okGroupWsAccount = "account"
|
||||
okGroupWsMarginAccount = "margin_account"
|
||||
okGroupWsOrder = "order"
|
||||
okGroupWsFundingRate = "funding_rate"
|
||||
okGroupWsPriceRange = "price_range"
|
||||
okGroupWsMarkPrice = "mark_price"
|
||||
okGroupWsPosition = "position"
|
||||
okGroupWsEstimatedPrice = "estimated_price"
|
||||
// Spot endpoints
|
||||
okGroupWsSpotTicker = okGroupWsSpotSubsection + okGroupWsTicker
|
||||
okGroupWsSpotCandle60s = okGroupWsSpotSubsection + okGroupWsCandle60s
|
||||
okGroupWsSpotCandle180s = okGroupWsSpotSubsection + okGroupWsCandle180s
|
||||
okGroupWsSpotCandle300s = okGroupWsSpotSubsection + okGroupWsCandle300s
|
||||
okGroupWsSpotCandle900s = okGroupWsSpotSubsection + okGroupWsCandle900s
|
||||
okGroupWsSpotCandle1800s = okGroupWsSpotSubsection + okGroupWsCandle1800s
|
||||
okGroupWsSpotCandle3600s = okGroupWsSpotSubsection + okGroupWsCandle3600s
|
||||
okGroupWsSpotCandle7200s = okGroupWsSpotSubsection + okGroupWsCandle7200s
|
||||
okGroupWsSpotCandle14400s = okGroupWsSpotSubsection + okGroupWsCandle14400s
|
||||
okGroupWsSpotCandle21600s = okGroupWsSpotSubsection + okGroupWsCandle21600s
|
||||
okGroupWsSpotCandle43200s = okGroupWsSpotSubsection + okGroupWsCandle43200s
|
||||
okGroupWsSpotCandle86400s = okGroupWsSpotSubsection + okGroupWsCandle86400s
|
||||
okGroupWsSpotCandle604900s = okGroupWsSpotSubsection + okGroupWsCandle604900s
|
||||
okGroupWsSpotTrade = okGroupWsSpotSubsection + okGroupWsTrade
|
||||
okGroupWsSpotDepth = okGroupWsSpotSubsection + okGroupWsDepth
|
||||
okGroupWsSpotDepth5 = okGroupWsSpotSubsection + okGroupWsDepth5
|
||||
okGroupWsSpotAccount = okGroupWsSpotSubsection + okGroupWsAccount
|
||||
okGroupWsSpotMarginAccount = okGroupWsSpotSubsection + okGroupWsMarginAccount
|
||||
okGroupWsSpotOrder = okGroupWsSpotSubsection + okGroupWsOrder
|
||||
// Swap endpoints
|
||||
okGroupWsSwapTicker = okGroupWsSwapSubsection + okGroupWsTicker
|
||||
okGroupWsSwapCandle60s = okGroupWsSwapSubsection + okGroupWsCandle60s
|
||||
okGroupWsSwapCandle180s = okGroupWsSwapSubsection + okGroupWsCandle180s
|
||||
okGroupWsSwapCandle300s = okGroupWsSwapSubsection + okGroupWsCandle300s
|
||||
okGroupWsSwapCandle900s = okGroupWsSwapSubsection + okGroupWsCandle900s
|
||||
okGroupWsSwapCandle1800s = okGroupWsSwapSubsection + okGroupWsCandle1800s
|
||||
okGroupWsSwapCandle3600s = okGroupWsSwapSubsection + okGroupWsCandle3600s
|
||||
okGroupWsSwapCandle7200s = okGroupWsSwapSubsection + okGroupWsCandle7200s
|
||||
okGroupWsSwapCandle14400s = okGroupWsSwapSubsection + okGroupWsCandle14400s
|
||||
okGroupWsSwapCandle21600s = okGroupWsSwapSubsection + okGroupWsCandle21600s
|
||||
okGroupWsSwapCandle43200s = okGroupWsSwapSubsection + okGroupWsCandle43200s
|
||||
okGroupWsSwapCandle86400s = okGroupWsSwapSubsection + okGroupWsCandle86400s
|
||||
okGroupWsSwapCandle604900s = okGroupWsSwapSubsection + okGroupWsCandle604900s
|
||||
okGroupWsSwapTrade = okGroupWsSwapSubsection + okGroupWsTrade
|
||||
okGroupWsSwapDepth = okGroupWsSwapSubsection + okGroupWsDepth
|
||||
okGroupWsSwapDepth5 = okGroupWsSwapSubsection + okGroupWsDepth5
|
||||
okGroupWsSwapFundingRate = okGroupWsSwapSubsection + okGroupWsFundingRate
|
||||
okGroupWsSwapPriceRange = okGroupWsSwapSubsection + okGroupWsPriceRange
|
||||
okGroupWsSwapMarkPrice = okGroupWsSwapSubsection + okGroupWsMarkPrice
|
||||
okGroupWsSwapPosition = okGroupWsSwapSubsection + okGroupWsPosition
|
||||
okGroupWsSwapAccount = okGroupWsSwapSubsection + okGroupWsAccount
|
||||
okGroupWsSwapOrder = okGroupWsSwapSubsection + okGroupWsOrder
|
||||
// Index endpoints
|
||||
okGroupWsIndexTicker = okGroupWsIndexSubsection + okGroupWsTicker
|
||||
okGroupWsIndexCandle60s = okGroupWsIndexSubsection + okGroupWsCandle60s
|
||||
okGroupWsIndexCandle180s = okGroupWsIndexSubsection + okGroupWsCandle180s
|
||||
okGroupWsIndexCandle300s = okGroupWsIndexSubsection + okGroupWsCandle300s
|
||||
okGroupWsIndexCandle900s = okGroupWsIndexSubsection + okGroupWsCandle900s
|
||||
okGroupWsIndexCandle1800s = okGroupWsIndexSubsection + okGroupWsCandle1800s
|
||||
okGroupWsIndexCandle3600s = okGroupWsIndexSubsection + okGroupWsCandle3600s
|
||||
okGroupWsIndexCandle7200s = okGroupWsIndexSubsection + okGroupWsCandle7200s
|
||||
okGroupWsIndexCandle14400s = okGroupWsIndexSubsection + okGroupWsCandle14400s
|
||||
okGroupWsIndexCandle21600s = okGroupWsIndexSubsection + okGroupWsCandle21600s
|
||||
okGroupWsIndexCandle43200s = okGroupWsIndexSubsection + okGroupWsCandle43200s
|
||||
okGroupWsIndexCandle86400s = okGroupWsIndexSubsection + okGroupWsCandle86400s
|
||||
okGroupWsIndexCandle604900s = okGroupWsIndexSubsection + okGroupWsCandle604900s
|
||||
// Futures endpoints
|
||||
okGroupWsFuturesTicker = okGroupWsFuturesSubsection + okGroupWsTicker
|
||||
okGroupWsFuturesCandle60s = okGroupWsFuturesSubsection + okGroupWsCandle60s
|
||||
okGroupWsFuturesCandle180s = okGroupWsFuturesSubsection + okGroupWsCandle180s
|
||||
okGroupWsFuturesCandle300s = okGroupWsFuturesSubsection + okGroupWsCandle300s
|
||||
okGroupWsFuturesCandle900s = okGroupWsFuturesSubsection + okGroupWsCandle900s
|
||||
okGroupWsFuturesCandle1800s = okGroupWsFuturesSubsection + okGroupWsCandle1800s
|
||||
okGroupWsFuturesCandle3600s = okGroupWsFuturesSubsection + okGroupWsCandle3600s
|
||||
okGroupWsFuturesCandle7200s = okGroupWsFuturesSubsection + okGroupWsCandle7200s
|
||||
okGroupWsFuturesCandle14400s = okGroupWsFuturesSubsection + okGroupWsCandle14400s
|
||||
okGroupWsFuturesCandle21600s = okGroupWsFuturesSubsection + okGroupWsCandle21600s
|
||||
okGroupWsFuturesCandle43200s = okGroupWsFuturesSubsection + okGroupWsCandle43200s
|
||||
okGroupWsFuturesCandle86400s = okGroupWsFuturesSubsection + okGroupWsCandle86400s
|
||||
okGroupWsFuturesCandle604900s = okGroupWsFuturesSubsection + okGroupWsCandle604900s
|
||||
okGroupWsFuturesTrade = okGroupWsFuturesSubsection + okGroupWsTrade
|
||||
okGroupWsFuturesEstimatedPrice = okGroupWsFuturesSubsection + okGroupWsTrade
|
||||
okGroupWsFuturesPriceRange = okGroupWsFuturesSubsection + okGroupWsPriceRange
|
||||
okGroupWsFuturesDepth = okGroupWsFuturesSubsection + okGroupWsDepth
|
||||
okGroupWsFuturesDepth5 = okGroupWsFuturesSubsection + okGroupWsDepth5
|
||||
okGroupWsFuturesMarkPrice = okGroupWsFuturesSubsection + okGroupWsMarkPrice
|
||||
okGroupWsFuturesAccount = okGroupWsFuturesSubsection + okGroupWsAccount
|
||||
okGroupWsFuturesPosition = okGroupWsFuturesSubsection + okGroupWsPosition
|
||||
okGroupWsFuturesOrder = okGroupWsFuturesSubsection + okGroupWsOrder
|
||||
)
|
||||
|
||||
// orderbookMutex Ensures if two entries arrive at once, only one can be processed at a time
|
||||
var orderbookMutex sync.Mutex
|
||||
|
||||
// writeToWebsocket sends a message to the websocket endpoint
|
||||
func (o *OKGroup) writeToWebsocket(message string) error {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
if o.Verbose {
|
||||
log.Debugf("Sending message to WS: %v", message)
|
||||
}
|
||||
return o.WebsocketConn.WriteMessage(websocket.TextMessage, []byte(message))
|
||||
}
|
||||
|
||||
// WsConnect initiates a websocket connection
|
||||
func (o *OKGroup) WsConnect() error {
|
||||
if !o.Websocket.IsEnabled() || !o.IsEnabled() {
|
||||
return errors.New(exchange.WebsocketNotEnabled)
|
||||
}
|
||||
|
||||
var dialer websocket.Dialer
|
||||
|
||||
if o.Websocket.GetProxyAddress() != "" {
|
||||
proxy, err := url.Parse(o.Websocket.GetProxyAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dialer.Proxy = http.ProxyURL(proxy)
|
||||
}
|
||||
|
||||
var err error
|
||||
if o.Verbose {
|
||||
log.Debugf("Attempting to connect to %v", o.Websocket.GetWebsocketURL())
|
||||
}
|
||||
o.WebsocketConn, _, err = dialer.Dial(o.Websocket.GetWebsocketURL(),
|
||||
http.Header{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s Unable to connect to Websocket. Error: %s",
|
||||
o.Name,
|
||||
err)
|
||||
}
|
||||
if o.Verbose {
|
||||
log.Debugf("Successful connection to %v", o.Websocket.GetWebsocketURL())
|
||||
}
|
||||
|
||||
go o.WsHandleData()
|
||||
go o.wsPingHandler()
|
||||
|
||||
err = o.WsSubscribeToDefaults()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: Could not subscribe to the OKEX websocket %s",
|
||||
err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsSubscribeToDefaults subscribes to the websocket channels
|
||||
func (o *OKGroup) WsSubscribeToDefaults() (err error) {
|
||||
channelsToSubscribe := []string{okGroupWsSpotDepth, okGroupWsSpotCandle300s, okGroupWsSpotTicker, okGroupWsSpotTrade}
|
||||
for _, pair := range o.EnabledPairs {
|
||||
formattedPair := strings.ToUpper(strings.Replace(pair, "_", "-", 1))
|
||||
for _, channel := range channelsToSubscribe {
|
||||
err = o.WsSubscribeToChannel(fmt.Sprintf("%v:%s", channel, formattedPair))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsReadData reads data from the websocket connection
|
||||
func (o *OKGroup) WsReadData() (exchange.WebsocketResponse, error) {
|
||||
mType, resp, err := o.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
return exchange.WebsocketResponse{}, err
|
||||
}
|
||||
|
||||
o.Websocket.TrafficAlert <- struct{}{}
|
||||
var standardMessage []byte
|
||||
switch mType {
|
||||
case websocket.TextMessage:
|
||||
standardMessage = resp
|
||||
|
||||
case websocket.BinaryMessage:
|
||||
reader := flate.NewReader(bytes.NewReader(resp))
|
||||
standardMessage, err = ioutil.ReadAll(reader)
|
||||
reader.Close()
|
||||
if err != nil {
|
||||
return exchange.WebsocketResponse{}, err
|
||||
}
|
||||
}
|
||||
if o.Verbose {
|
||||
log.Debugf("%v Websocket message received: %v", o.Name, string(standardMessage))
|
||||
}
|
||||
|
||||
return exchange.WebsocketResponse{Raw: standardMessage}, nil
|
||||
}
|
||||
|
||||
// wsPingHandler sends a message "ping" every 27 to maintain the connection to the websocket
|
||||
func (o *OKGroup) wsPingHandler() {
|
||||
o.Websocket.Wg.Add(1)
|
||||
defer o.Websocket.Wg.Done()
|
||||
|
||||
ticker := time.NewTicker(time.Second * 27)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-o.Websocket.ShutdownC:
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
err := o.writeToWebsocket("ping")
|
||||
if o.Verbose {
|
||||
log.Debugf("%v sending ping", o.GetName())
|
||||
}
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WsHandleData handles the read data from the websocket connection
|
||||
func (o *OKGroup) WsHandleData() {
|
||||
o.Websocket.Wg.Add(1)
|
||||
defer func() {
|
||||
err := o.WebsocketConn.Close()
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- fmt.Errorf("okex_websocket.go - Unable to to close Websocket connection. Error: %s",
|
||||
err)
|
||||
}
|
||||
o.Websocket.Wg.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-o.Websocket.ShutdownC:
|
||||
return
|
||||
default:
|
||||
resp, err := o.WsReadData()
|
||||
if err != nil {
|
||||
o.Websocket.DataHandler <- err
|
||||
return
|
||||
}
|
||||
var dataResponse WebsocketDataResponse
|
||||
err = common.JSONDecode(resp.Raw, &dataResponse)
|
||||
if err == nil && dataResponse.Table != "" {
|
||||
if len(dataResponse.Data) > 0 {
|
||||
o.WsHandleDataResponse(&dataResponse)
|
||||
}
|
||||
continue
|
||||
}
|
||||
var errorResponse WebsocketErrorResponse
|
||||
err = common.JSONDecode(resp.Raw, &errorResponse)
|
||||
if err == nil && errorResponse.ErrorCode > 0 {
|
||||
if o.Verbose {
|
||||
log.Debugf("WS Error Event: %v Message: %v", errorResponse.Event, errorResponse.Message)
|
||||
}
|
||||
o.WsHandleErrorResponse(errorResponse)
|
||||
continue
|
||||
}
|
||||
var eventResponse WebsocketEventResponse
|
||||
err = common.JSONDecode(resp.Raw, &eventResponse)
|
||||
if err == nil && len(eventResponse.Channel) > 0 {
|
||||
if o.Verbose {
|
||||
log.Debugf("WS Event: %v on Channel: %v", eventResponse.Event, eventResponse.Channel)
|
||||
}
|
||||
continue
|
||||
}
|
||||
o.Websocket.DataHandler <- fmt.Errorf("unrecognised response: %v", resp.Raw)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WsSubscribeToChannel sends a request to WS to subscribe to supplied channel
|
||||
func (o *OKGroup) WsSubscribeToChannel(topic string) error {
|
||||
resp := WebsocketEventRequest{
|
||||
Operation: "subscribe",
|
||||
Arguments: []string{topic},
|
||||
}
|
||||
json, err := common.JSONEncode(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = o.writeToWebsocket(string(json))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsUnsubscribeToChannel sends a request to WS to unsubscribe to supplied channel
|
||||
func (o *OKGroup) WsUnsubscribeToChannel(topic string) error {
|
||||
resp := WebsocketEventRequest{
|
||||
Operation: "unsubscribe",
|
||||
Arguments: []string{topic},
|
||||
}
|
||||
json, err := common.JSONEncode(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = o.writeToWebsocket(string(json))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsLogin sends a login request to websocket to enable access to authenticated endpoints
|
||||
func (o *OKGroup) WsLogin() error {
|
||||
utcTime := time.Now().UTC()
|
||||
unixTime := utcTime.Unix()
|
||||
signPath := "/users/self/verify"
|
||||
hmac := common.GetHMAC(common.HashSHA256, []byte(fmt.Sprintf("%v", unixTime)+http.MethodGet+signPath), []byte(o.APISecret))
|
||||
base64 := common.Base64Encode(hmac)
|
||||
resp := WebsocketEventRequest{
|
||||
Operation: "login",
|
||||
Arguments: []string{o.APIKey, o.ClientID, fmt.Sprintf("%v", unixTime), base64},
|
||||
}
|
||||
json, err := common.JSONEncode(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = o.writeToWebsocket(string(json))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsHandleErrorResponse sends an error message to ws handler
|
||||
func (o *OKGroup) WsHandleErrorResponse(event WebsocketErrorResponse) {
|
||||
errorMessage := fmt.Sprintf("%v error - %v message: %s ",
|
||||
o.GetName(), event.ErrorCode, event.Message)
|
||||
if o.Verbose {
|
||||
log.Error(errorMessage)
|
||||
}
|
||||
o.Websocket.DataHandler <- fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
// GetWsChannelWithoutOrderType takes WebsocketDataResponse.Table and returns
|
||||
// The base channel name eg receive "spot/depth5:BTC-USDT" return "depth5"
|
||||
func (o *OKGroup) GetWsChannelWithoutOrderType(table string) string {
|
||||
index := strings.Index(table, "/")
|
||||
if index == -1 {
|
||||
return table
|
||||
}
|
||||
channel := table[index+1:]
|
||||
index = strings.Index(channel, ":")
|
||||
// Some events do not contain a currency
|
||||
if index == -1 {
|
||||
return channel
|
||||
}
|
||||
|
||||
return channel[:index]
|
||||
}
|
||||
|
||||
// GetAssetTypeFromTableName gets the asset type from the table name
|
||||
// eg "spot/ticker:BTCUSD" results in "SPOT"
|
||||
func (o *OKGroup) GetAssetTypeFromTableName(table string) string {
|
||||
assetIndex := strings.Index(table, "/")
|
||||
return strings.ToUpper(table[:assetIndex])
|
||||
}
|
||||
|
||||
// WsHandleDataResponse classifies the WS response and sends to appropriate handler
|
||||
func (o *OKGroup) WsHandleDataResponse(response *WebsocketDataResponse) {
|
||||
switch o.GetWsChannelWithoutOrderType(response.Table) {
|
||||
case okGroupWsCandle60s, okGroupWsCandle180s, okGroupWsCandle300s, okGroupWsCandle900s,
|
||||
okGroupWsCandle1800s, okGroupWsCandle3600s, okGroupWsCandle7200s, okGroupWsCandle14400s,
|
||||
okGroupWsCandle21600s, okGroupWsCandle43200s, okGroupWsCandle86400s, okGroupWsCandle604900s:
|
||||
if o.Verbose {
|
||||
log.Debugf("%v Websocket candle data received", o.GetName())
|
||||
}
|
||||
o.wsProcessCandles(response)
|
||||
case okGroupWsDepth, okGroupWsDepth5:
|
||||
if o.Verbose {
|
||||
log.Debugf("%v Websocket orderbook data received", o.GetName())
|
||||
}
|
||||
// Locking, orderbooks cannot be processed out of order
|
||||
orderbookMutex.Lock()
|
||||
err := o.WsProcessOrderBook(response)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
subscriptionChannel := fmt.Sprintf("%v:%v", response.Table, response.Data[0].InstrumentID)
|
||||
o.ResubscribeToChannel(subscriptionChannel)
|
||||
}
|
||||
orderbookMutex.Unlock()
|
||||
case okGroupWsTicker:
|
||||
if o.Verbose {
|
||||
log.Debugf("%v Websocket ticker data received", o.GetName())
|
||||
}
|
||||
o.wsProcessTickers(response)
|
||||
case okGroupWsTrade:
|
||||
if o.Verbose {
|
||||
log.Debugf("%v Websocket trade data received", o.GetName())
|
||||
}
|
||||
o.wsProcessTrades(response)
|
||||
default:
|
||||
logDataResponse(response)
|
||||
}
|
||||
}
|
||||
|
||||
// resubscribeToChannel will attempt to unsubscribe and resubscribe to a channel
|
||||
func (o *OKGroup) ResubscribeToChannel(channel string) {
|
||||
if okGroupWsResubscribeFailureLimit > 0 {
|
||||
var successfulUnsubscribe bool
|
||||
for i := 0; i < okGroupWsResubscribeFailureLimit; i++ {
|
||||
err := o.WsUnsubscribeToChannel(channel)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
time.Sleep(okGroupWsResubscribeDelayInSeconds * time.Second)
|
||||
continue
|
||||
}
|
||||
successfulUnsubscribe = true
|
||||
break
|
||||
}
|
||||
if !successfulUnsubscribe {
|
||||
log.Fatalf("%v websocket channel %v failed to unsubscribe after %v attempts", o.GetName(), channel, okGroupWsResubscribeFailureLimit)
|
||||
}
|
||||
successfulSubscribe := true
|
||||
for i := 0; i < okGroupWsResubscribeFailureLimit; i++ {
|
||||
err := o.WsSubscribeToChannel(channel)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
time.Sleep(okGroupWsResubscribeDelayInSeconds * time.Second)
|
||||
continue
|
||||
}
|
||||
successfulSubscribe = true
|
||||
break
|
||||
}
|
||||
if !successfulSubscribe {
|
||||
log.Fatalf("%v websocket channel %v failed to resubscribe after %v attempts", o.GetName(), channel, okGroupWsResubscribeFailureLimit)
|
||||
}
|
||||
} else {
|
||||
log.Fatalf("%v websocket channel %v cannot resubscribe. Limit: %v", o.GetName(), channel, okGroupWsResubscribeFailureLimit)
|
||||
}
|
||||
}
|
||||
|
||||
// logDataResponse will log the details of any websocket data event
|
||||
// where there is no websocket datahandler for it
|
||||
func logDataResponse(response *WebsocketDataResponse) {
|
||||
for _, data := range response.Data {
|
||||
log.Errorf("Unhandled channel: '%v'. Instrument '%v' Timestamp '%v', Data '%v",
|
||||
response.Table,
|
||||
data.InstrumentID,
|
||||
data.Timestamp,
|
||||
data)
|
||||
}
|
||||
}
|
||||
|
||||
// wsProcessTickers converts ticker data and sends it to the datahandler
|
||||
func (o *OKGroup) wsProcessTickers(response *WebsocketDataResponse) {
|
||||
for _, tickerData := range response.Data {
|
||||
instrument := pair.NewCurrencyPairDelimiter(tickerData.InstrumentID, "-")
|
||||
o.Websocket.DataHandler <- exchange.TickerData{
|
||||
Timestamp: tickerData.Timestamp,
|
||||
Exchange: o.GetName(),
|
||||
AssetType: o.GetAssetTypeFromTableName(response.Table),
|
||||
HighPrice: tickerData.High24H,
|
||||
LowPrice: tickerData.Low24H,
|
||||
ClosePrice: tickerData.Last,
|
||||
Pair: instrument,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wsProcessTrades converts trade data and sends it to the datahandler
|
||||
func (o *OKGroup) wsProcessTrades(response *WebsocketDataResponse) {
|
||||
for _, trade := range response.Data {
|
||||
instrument := pair.NewCurrencyPairDelimiter(trade.InstrumentID, "-")
|
||||
o.Websocket.DataHandler <- exchange.TradeData{
|
||||
Amount: trade.Qty,
|
||||
AssetType: o.GetAssetTypeFromTableName(response.Table),
|
||||
CurrencyPair: instrument,
|
||||
EventTime: time.Now().Unix(),
|
||||
Exchange: o.GetName(),
|
||||
Price: trade.WebsocketTradeResponse.Price,
|
||||
Side: trade.Side,
|
||||
Timestamp: trade.Timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wsProcessCandles converts candle data and sends it to the data handler
|
||||
func (o *OKGroup) wsProcessCandles(response *WebsocketDataResponse) {
|
||||
for _, candle := range response.Data {
|
||||
instrument := pair.NewCurrencyPairDelimiter(candle.InstrumentID, "-")
|
||||
timeData, err := time.Parse(time.RFC3339Nano, candle.WebsocketCandleResponse.Candle[0])
|
||||
if err != nil {
|
||||
log.Warnf("%v Time data could not be parsed: %v", o.GetName(), candle.Candle[0])
|
||||
}
|
||||
|
||||
candleIndex := strings.LastIndex(response.Table, okGroupWsCandle)
|
||||
secondIndex := strings.LastIndex(response.Table, "0s")
|
||||
candleInterval := ""
|
||||
if candleIndex > 0 || secondIndex > 0 {
|
||||
candleInterval = response.Table[candleIndex+len(okGroupWsCandle) : secondIndex]
|
||||
}
|
||||
|
||||
klineData := exchange.KlineData{
|
||||
AssetType: o.GetAssetTypeFromTableName(response.Table),
|
||||
Pair: instrument,
|
||||
Exchange: o.GetName(),
|
||||
Timestamp: timeData,
|
||||
Interval: candleInterval,
|
||||
}
|
||||
klineData.OpenPrice, _ = strconv.ParseFloat(candle.Candle[1], 64)
|
||||
klineData.HighPrice, _ = strconv.ParseFloat(candle.Candle[2], 64)
|
||||
klineData.LowPrice, _ = strconv.ParseFloat(candle.Candle[3], 64)
|
||||
klineData.ClosePrice, _ = strconv.ParseFloat(candle.Candle[4], 64)
|
||||
klineData.Volume, _ = strconv.ParseFloat(candle.Candle[5], 64)
|
||||
|
||||
o.Websocket.DataHandler <- klineData
|
||||
}
|
||||
}
|
||||
|
||||
// WsProcessOrderBook Validates the checksum and updates internal orderbook values
|
||||
func (o *OKGroup) WsProcessOrderBook(response *WebsocketDataResponse) (err error) {
|
||||
for i := range response.Data {
|
||||
instrument := pair.NewCurrencyPairDelimiter(response.Data[i].InstrumentID, "-")
|
||||
if response.Action == okGroupWsOrderbookPartial {
|
||||
err = o.WsProcessPartialOrderBook(&response.Data[i], instrument, response.Table)
|
||||
} else if response.Action == okGroupWsOrderbookUpdate {
|
||||
err = o.WsProcessUpdateOrderbook(&response.Data[i], instrument, response.Table)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AppendWsOrderbookItems adds websocket orderbook data bid/asks into an orderbook item array
|
||||
func (o *OKGroup) AppendWsOrderbookItems(entries [][]interface{}) (orderbookItems []orderbook.Item) {
|
||||
for j := range entries {
|
||||
amount, _ := strconv.ParseFloat(entries[j][1].(string), 64)
|
||||
price, _ := strconv.ParseFloat(entries[j][0].(string), 64)
|
||||
orderbookItems = append(orderbookItems, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// WsProcessPartialOrderBook takes websocket orderbook data and creates an orderbook
|
||||
// Calculates checksum to ensure it is valid
|
||||
func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketDataWrapper, instrument pair.CurrencyPair, tableName string) error {
|
||||
signedChecksum := o.CalculatePartialOrderbookChecksum(wsEventData)
|
||||
if signedChecksum != wsEventData.Checksum {
|
||||
return fmt.Errorf("channel: %v. Orderbook partial for %v checksum invalid", tableName, instrument)
|
||||
}
|
||||
if o.Verbose {
|
||||
log.Debug("Passed checksum!")
|
||||
}
|
||||
asks := o.AppendWsOrderbookItems(wsEventData.Asks)
|
||||
bids := o.AppendWsOrderbookItems(wsEventData.Bids)
|
||||
newOrderBook := orderbook.Base{
|
||||
Asks: asks,
|
||||
Bids: bids,
|
||||
AssetType: o.GetAssetTypeFromTableName(tableName),
|
||||
CurrencyPair: wsEventData.InstrumentID,
|
||||
LastUpdated: wsEventData.Timestamp,
|
||||
Pair: instrument,
|
||||
}
|
||||
|
||||
err := o.Websocket.Orderbook.LoadSnapshot(newOrderBook, o.GetName(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
|
||||
Exchange: o.GetName(),
|
||||
Asset: o.GetAssetTypeFromTableName(tableName),
|
||||
Pair: instrument,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsProcessUpdateOrderbook updates an existing orderbook using websocket data
|
||||
// After merging WS data, it will sort, validate and finally update the existing orderbook
|
||||
func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketDataWrapper, instrument pair.CurrencyPair, tableName string) error {
|
||||
internalOrderbook, err := o.GetOrderbookEx(instrument, o.GetAssetTypeFromTableName(tableName))
|
||||
if err != nil {
|
||||
return errors.New("orderbook nil, could not load existing orderbook")
|
||||
}
|
||||
if internalOrderbook.LastUpdated.After(wsEventData.Timestamp) {
|
||||
if o.Verbose {
|
||||
log.Errorf("Orderbook update out of order. Existing: %v, Attempted: %v", internalOrderbook.LastUpdated.Unix(), wsEventData.Timestamp.Unix())
|
||||
}
|
||||
return errors.New("updated orderbook is older than existing")
|
||||
}
|
||||
internalOrderbook.Asks = o.WsUpdateOrderbookEntry(wsEventData.Asks, internalOrderbook.Asks)
|
||||
internalOrderbook.Bids = o.WsUpdateOrderbookEntry(wsEventData.Bids, internalOrderbook.Bids)
|
||||
sort.Slice(internalOrderbook.Asks, func(i, j int) bool {
|
||||
return internalOrderbook.Asks[i].Price < internalOrderbook.Asks[j].Price
|
||||
})
|
||||
sort.Slice(internalOrderbook.Bids, func(i, j int) bool {
|
||||
return internalOrderbook.Bids[i].Price > internalOrderbook.Bids[j].Price
|
||||
})
|
||||
checksum := o.CalculateUpdateOrderbookChecksum(internalOrderbook)
|
||||
if checksum == wsEventData.Checksum {
|
||||
if o.Verbose {
|
||||
log.Debug("Orderbook valid")
|
||||
}
|
||||
internalOrderbook.LastUpdated = wsEventData.Timestamp
|
||||
if o.Verbose {
|
||||
log.Debug("Internalising orderbook")
|
||||
}
|
||||
|
||||
err := o.Websocket.Orderbook.LoadSnapshot(internalOrderbook, o.GetName(), true)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
o.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
|
||||
Exchange: o.GetName(),
|
||||
Asset: o.GetAssetTypeFromTableName(tableName),
|
||||
Pair: instrument,
|
||||
}
|
||||
} else {
|
||||
if o.Verbose {
|
||||
log.Debug("Orderbook invalid")
|
||||
}
|
||||
return fmt.Errorf("channel: %v. Orderbook update for %v checksum invalid. Received %v Calculated %v", tableName, instrument, wsEventData.Checksum, checksum)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WsUpdateOrderbookEntry takes WS bid or ask data and merges it with existing orderbook bid or ask data
|
||||
func (o *OKGroup) WsUpdateOrderbookEntry(wsEntries [][]interface{}, existingOrderbookEntries []orderbook.Item) []orderbook.Item {
|
||||
for j := range wsEntries {
|
||||
wsEntryPrice, _ := strconv.ParseFloat(wsEntries[j][0].(string), 64)
|
||||
wsEntryAmount, _ := strconv.ParseFloat(wsEntries[j][1].(string), 64)
|
||||
matchFound := false
|
||||
for k := 0; k < len(existingOrderbookEntries); k++ {
|
||||
if existingOrderbookEntries[k].Price != wsEntryPrice {
|
||||
continue
|
||||
}
|
||||
matchFound = true
|
||||
if wsEntryAmount == 0 {
|
||||
existingOrderbookEntries = append(existingOrderbookEntries[:k], existingOrderbookEntries[k+1:]...)
|
||||
k--
|
||||
continue
|
||||
}
|
||||
existingOrderbookEntries[k].Amount = wsEntryAmount
|
||||
continue
|
||||
}
|
||||
if !matchFound {
|
||||
existingOrderbookEntries = append(existingOrderbookEntries, orderbook.Item{
|
||||
Amount: wsEntryAmount,
|
||||
Price: wsEntryPrice,
|
||||
})
|
||||
}
|
||||
}
|
||||
return existingOrderbookEntries
|
||||
}
|
||||
|
||||
// CalculatePartialOrderbookChecksum alternates over the first 25 bid and ask entries from websocket data
|
||||
// The checksum is made up of the price and the quantity with a semicolon (:) deliminating them
|
||||
// This will also work when there are less than 25 entries (for whatever reason)
|
||||
// eg Bid:Ask:Bid:Ask:Ask:Ask
|
||||
func (o *OKGroup) CalculatePartialOrderbookChecksum(orderbookData *WebsocketDataWrapper) int32 {
|
||||
var checksum string
|
||||
iterations := 25
|
||||
for i := 0; i < iterations; i++ {
|
||||
bidsMessage := ""
|
||||
askMessage := ""
|
||||
if len(orderbookData.Bids)-1 >= i {
|
||||
bidsMessage = fmt.Sprintf("%v:%v:", orderbookData.Bids[i][0], orderbookData.Bids[i][1])
|
||||
}
|
||||
if len(orderbookData.Asks)-1 >= i {
|
||||
askMessage = fmt.Sprintf("%v:%v:", orderbookData.Asks[i][0], orderbookData.Asks[i][1])
|
||||
|
||||
}
|
||||
if checksum == "" {
|
||||
checksum = fmt.Sprintf("%v%v", bidsMessage, askMessage)
|
||||
} else {
|
||||
checksum = fmt.Sprintf("%v%v%v", checksum, bidsMessage, askMessage)
|
||||
}
|
||||
}
|
||||
checksum = strings.TrimSuffix(checksum, ":")
|
||||
return int32(crc32.ChecksumIEEE([]byte(checksum)))
|
||||
}
|
||||
|
||||
// CalculateUpdateOrderbookChecksum alternates over the first 25 bid and ask entries of a merged orderbook
|
||||
// The checksum is made up of the price and the quantity with a semicolon (:) deliminating them
|
||||
// This will also work when there are less than 25 entries (for whatever reason)
|
||||
// eg Bid:Ask:Bid:Ask:Ask:Ask
|
||||
func (o *OKGroup) CalculateUpdateOrderbookChecksum(orderbookData orderbook.Base) int32 {
|
||||
var checksum string
|
||||
iterations := 25
|
||||
for i := 0; i < iterations; i++ {
|
||||
bidsMessage := ""
|
||||
askMessage := ""
|
||||
if len(orderbookData.Bids)-1 >= i {
|
||||
price := strconv.FormatFloat(orderbookData.Bids[i].Price, 'f', -1, 64)
|
||||
amount := strconv.FormatFloat(orderbookData.Bids[i].Amount, 'f', -1, 64)
|
||||
bidsMessage = fmt.Sprintf("%v:%v:", price, amount)
|
||||
}
|
||||
if len(orderbookData.Asks)-1 >= i {
|
||||
price := strconv.FormatFloat(orderbookData.Asks[i].Price, 'f', -1, 64)
|
||||
amount := strconv.FormatFloat(orderbookData.Asks[i].Amount, 'f', -1, 64)
|
||||
askMessage = fmt.Sprintf("%v:%v:", price, amount)
|
||||
}
|
||||
if checksum == "" {
|
||||
checksum = fmt.Sprintf("%v%v", bidsMessage, askMessage)
|
||||
} else {
|
||||
checksum = fmt.Sprintf("%v%v%v", checksum, bidsMessage, askMessage)
|
||||
}
|
||||
}
|
||||
checksum = strings.TrimSuffix(checksum, ":")
|
||||
return int32(crc32.ChecksumIEEE([]byte(checksum)))
|
||||
}
|
||||
411
exchanges/okgroup/okgroup_wrapper.go
Normal file
411
exchanges/okgroup/okgroup_wrapper.go
Normal file
@@ -0,0 +1,411 @@
|
||||
package okgroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/currency/pair"
|
||||
exchange "github.com/thrasher-/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
// Note: GoCryptoTrader wrapper funcs currently only support SPOT trades.
|
||||
// Therefore this OKGroup_Wrapper can be shared between OKEX and OKCoin.
|
||||
// When circumstances change, wrapper funcs can be split appropriately
|
||||
|
||||
// Start starts the OKGroup go routine
|
||||
func (o *OKGroup) Start(wg *sync.WaitGroup) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
o.Run()
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
// Run implements the OKEX wrapper
|
||||
func (o *OKGroup) Run() {
|
||||
if o.Verbose {
|
||||
log.Debugf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket.IsEnabled()), o.WebsocketURL)
|
||||
log.Debugf("%s polling delay: %ds.\n", o.GetName(), o.RESTPollingDelay)
|
||||
log.Debugf("%s %d currencies enabled: %s.\n", o.GetName(), len(o.EnabledPairs), o.EnabledPairs)
|
||||
}
|
||||
|
||||
prods, err := o.GetSpotTokenPairDetails()
|
||||
if err != nil {
|
||||
log.Errorf("%v failed to obtain available spot instruments. Err: %s", o.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
var pairs []string
|
||||
for x := range prods {
|
||||
pairs = append(pairs, prods[x].BaseCurrency+"_"+prods[x].QuoteCurrency)
|
||||
}
|
||||
|
||||
err = o.UpdateCurrencies(pairs, false, false)
|
||||
if err != nil {
|
||||
log.Errorf("%v failed to update available currencies. Err: %s", o.Name, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (o *OKGroup) UpdateTicker(p pair.CurrencyPair, assetType string) (tickerData ticker.Price, err error) {
|
||||
resp, err := o.GetSpotAllTokenPairsInformationForCurrency(exchange.FormatExchangeCurrency(o.Name, p).String())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tickerData = ticker.Price{
|
||||
Ask: resp.BestAsk,
|
||||
Bid: resp.BestBid,
|
||||
CurrencyPair: exchange.FormatExchangeCurrency(o.Name, p).String(),
|
||||
High: resp.High24h,
|
||||
Last: resp.Last,
|
||||
LastUpdated: resp.Timestamp,
|
||||
Low: resp.Low24h,
|
||||
Pair: p,
|
||||
Volume: resp.BaseVolume24h,
|
||||
}
|
||||
|
||||
ticker.ProcessTicker(o.Name, p, tickerData, assetType)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetTickerPrice returns the ticker for a currency pair
|
||||
func (o *OKGroup) GetTickerPrice(p pair.CurrencyPair, assetType string) (tickerData ticker.Price, err error) {
|
||||
tickerData, err = ticker.GetTicker(o.GetName(), p, assetType)
|
||||
if err != nil {
|
||||
return o.UpdateTicker(p, assetType)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetOrderbookEx returns orderbook base on the currency pair
|
||||
func (o *OKGroup) GetOrderbookEx(p pair.CurrencyPair, assetType string) (resp orderbook.Base, err error) {
|
||||
ob, err := orderbook.GetOrderbook(o.GetName(), p, assetType)
|
||||
if err != nil {
|
||||
return o.UpdateOrderbook(p, assetType)
|
||||
}
|
||||
return ob, nil
|
||||
}
|
||||
|
||||
// UpdateOrderbook updates and returns the orderbook for a currency pair
|
||||
func (o *OKGroup) UpdateOrderbook(p pair.CurrencyPair, assetType string) (resp orderbook.Base, err error) {
|
||||
orderbookNew, err := o.GetSpotOrderBook(GetSpotOrderBookRequest{
|
||||
InstrumentID: exchange.FormatExchangeCurrency(o.Name, p).String(),
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for x := range orderbookNew.Bids {
|
||||
amount, err := strconv.ParseFloat(orderbookNew.Bids[x][1], 64)
|
||||
if err != nil {
|
||||
log.Errorf("Could not convert %v to float64", orderbookNew.Bids[x][1])
|
||||
}
|
||||
price, err := strconv.ParseFloat(orderbookNew.Bids[x][0], 64)
|
||||
if err != nil {
|
||||
log.Errorf("Could not convert %v to float64", orderbookNew.Bids[x][0])
|
||||
}
|
||||
resp.Bids = append(resp.Bids, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
|
||||
for x := range orderbookNew.Asks {
|
||||
amount, err := strconv.ParseFloat(orderbookNew.Asks[x][1], 64)
|
||||
if err != nil {
|
||||
log.Errorf("Could not convert %v to float64", orderbookNew.Asks[x][1])
|
||||
}
|
||||
price, err := strconv.ParseFloat(orderbookNew.Asks[x][0], 64)
|
||||
if err != nil {
|
||||
log.Errorf("Could not convert %v to float64", orderbookNew.Asks[x][0])
|
||||
}
|
||||
resp.Asks = append(resp.Asks, orderbook.Item{
|
||||
Amount: amount,
|
||||
Price: price,
|
||||
})
|
||||
}
|
||||
|
||||
orderbook.ProcessOrderbook(o.GetName(), p, resp, assetType)
|
||||
return orderbook.GetOrderbook(o.Name, p, assetType)
|
||||
}
|
||||
|
||||
// GetAccountInfo retrieves balances for all enabled currencies
|
||||
func (o *OKGroup) GetAccountInfo() (resp exchange.AccountInfo, err error) {
|
||||
resp.Exchange = o.Name
|
||||
currencies, err := o.GetSpotTradingAccounts()
|
||||
currencyAccount := exchange.Account{}
|
||||
|
||||
for _, curr := range currencies {
|
||||
hold, err := strconv.ParseFloat(curr.Hold, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Could not convert %v to float64", curr.Hold)
|
||||
}
|
||||
totalValue, err := strconv.ParseFloat(curr.Balance, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Could not convert %v to float64", curr.Balance)
|
||||
}
|
||||
currencyAccount.Currencies = append(currencyAccount.Currencies, exchange.AccountCurrencyInfo{
|
||||
CurrencyName: curr.ID,
|
||||
Hold: hold,
|
||||
TotalValue: totalValue,
|
||||
})
|
||||
}
|
||||
|
||||
resp.Accounts = append(resp.Accounts, currencyAccount)
|
||||
return
|
||||
}
|
||||
|
||||
// GetFundingHistory returns funding history, deposits and
|
||||
// withdrawals
|
||||
func (o *OKGroup) GetFundingHistory() (resp []exchange.FundHistory, err error) {
|
||||
accountDepositHistory, err := o.GetAccountDepositHistory("")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, deposit := range accountDepositHistory {
|
||||
orderStatus := ""
|
||||
switch deposit.Status {
|
||||
case 0:
|
||||
orderStatus = "waiting"
|
||||
case 1:
|
||||
orderStatus = "confirmation account"
|
||||
case 2:
|
||||
orderStatus = "recharge success"
|
||||
}
|
||||
|
||||
resp = append(resp, exchange.FundHistory{
|
||||
Amount: deposit.Amount,
|
||||
Currency: deposit.Currency,
|
||||
ExchangeName: o.Name,
|
||||
Status: orderStatus,
|
||||
Timestamp: deposit.Timestamp,
|
||||
TransferID: deposit.TransactionID,
|
||||
TransferType: "deposit",
|
||||
})
|
||||
}
|
||||
accountWithdrawlHistory, err := o.GetAccountWithdrawalHistory("")
|
||||
for _, withdrawal := range accountWithdrawlHistory {
|
||||
resp = append(resp, exchange.FundHistory{
|
||||
Amount: withdrawal.Amount,
|
||||
Currency: withdrawal.Currency,
|
||||
ExchangeName: o.Name,
|
||||
Status: OrderStatus[withdrawal.Status],
|
||||
Timestamp: withdrawal.Timestamp,
|
||||
TransferID: withdrawal.Txid,
|
||||
TransferType: "withdrawal",
|
||||
})
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// GetExchangeHistory returns historic trade data since exchange opening.
|
||||
func (o *OKGroup) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]exchange.TradeHistory, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
// SubmitOrder submits a new order
|
||||
func (o *OKGroup) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (resp exchange.SubmitOrderResponse, err error) {
|
||||
request := PlaceSpotOrderRequest{
|
||||
ClientOID: clientID,
|
||||
InstrumentID: exchange.FormatExchangeCurrency(o.Name, p).String(),
|
||||
Side: strings.ToLower(side.ToString()),
|
||||
Type: strings.ToLower(orderType.ToString()),
|
||||
Size: strconv.FormatFloat(amount, 'f', -1, 64),
|
||||
}
|
||||
if orderType == exchange.LimitOrderType {
|
||||
request.Price = strconv.FormatFloat(price, 'f', -1, 64)
|
||||
}
|
||||
|
||||
orderResponse, err := o.PlaceSpotOrder(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp.IsOrderPlaced = orderResponse.Result
|
||||
resp.OrderID = orderResponse.OrderID
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ModifyOrder will allow of changing orderbook placement and limit to
|
||||
// market conversion
|
||||
func (o *OKGroup) ModifyOrder(action exchange.ModifyOrder) (string, error) {
|
||||
return "", common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (o *OKGroup) CancelOrder(orderCancellation exchange.OrderCancellation) (err error) {
|
||||
orderID, err := strconv.ParseInt(orderCancellation.OrderID, 10, 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
orderCancellationResponse, err := o.CancelSpotOrder(CancelSpotOrderRequest{
|
||||
InstrumentID: exchange.FormatExchangeCurrency(o.Name, orderCancellation.CurrencyPair).String(),
|
||||
OrderID: orderID,
|
||||
})
|
||||
if !orderCancellationResponse.Result {
|
||||
err = fmt.Errorf("order %v failed to be cancelled", orderCancellationResponse.OrderID)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CancelAllOrders cancels all orders associated with a currency pair
|
||||
func (o *OKGroup) CancelAllOrders(orderCancellation exchange.OrderCancellation) (resp exchange.CancelAllOrdersResponse, _ error) {
|
||||
orderIDs := strings.Split(orderCancellation.OrderID, ",")
|
||||
var orderIDNumbers []int64
|
||||
for _, i := range orderIDs {
|
||||
orderIDNumber, err := strconv.ParseInt(i, 10, 64)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
orderIDNumbers = append(orderIDNumbers, orderIDNumber)
|
||||
}
|
||||
|
||||
cancelOrdersResponse, err := o.CancelMultipleSpotOrders(CancelMultipleSpotOrdersRequest{
|
||||
InstrumentID: exchange.FormatExchangeCurrency(o.Name, orderCancellation.CurrencyPair).String(),
|
||||
OrderIDs: orderIDNumbers,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, orderMap := range cancelOrdersResponse {
|
||||
for _, cancelledOrder := range orderMap {
|
||||
resp.OrderStatus[fmt.Sprintf("%v", cancelledOrder.OrderID)] = fmt.Sprintf("%v", cancelledOrder.Result)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetOrderInfo returns information on a current open order
|
||||
func (o *OKGroup) GetOrderInfo(orderID string) (resp exchange.OrderDetail, err error) {
|
||||
order, err := o.GetSpotOrder(GetSpotOrderRequest{OrderID: orderID})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp = exchange.OrderDetail{
|
||||
Amount: order.Size,
|
||||
CurrencyPair: pair.NewCurrencyPairDelimiter(order.InstrumentID, o.ConfigCurrencyPairFormat.Delimiter),
|
||||
Exchange: o.Name,
|
||||
OrderDate: order.Timestamp,
|
||||
ExecutedAmount: order.FilledSize,
|
||||
Status: order.Status,
|
||||
OrderSide: exchange.OrderSide(order.Side),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetDepositAddress returns a deposit address for a specified currency
|
||||
func (o *OKGroup) GetDepositAddress(p pair.CurrencyItem, accountID string) (_ string, err error) {
|
||||
wallet, err := o.GetAccountDepositAddressForCurrency(p.Lower().String())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return wallet[0].Address, nil
|
||||
}
|
||||
|
||||
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
|
||||
// submitted
|
||||
func (o *OKGroup) WithdrawCryptocurrencyFunds(withdrawRequest exchange.WithdrawRequest) (string, error) {
|
||||
withdrawal, err := o.AccountWithdraw(AccountWithdrawRequest{
|
||||
Amount: withdrawRequest.Amount,
|
||||
Currency: withdrawRequest.Currency.Lower().String(),
|
||||
Destination: 4, // 1, 2, 3 are all internal
|
||||
Fee: withdrawRequest.FeeAmount,
|
||||
ToAddress: withdrawRequest.Address,
|
||||
TradePwd: withdrawRequest.TradePassword,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !withdrawal.Result {
|
||||
return fmt.Sprintf("%v", withdrawal.WithdrawalID), fmt.Errorf("could not withdraw currency %v to %v, no error specified", withdrawRequest.Currency.String(), withdrawRequest.Address)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", withdrawal.WithdrawalID), nil
|
||||
}
|
||||
|
||||
// WithdrawFiatFunds returns a withdrawal ID when a
|
||||
// withdrawal is submitted
|
||||
func (o *OKGroup) WithdrawFiatFunds(withdrawRequest exchange.WithdrawRequest) (string, error) {
|
||||
return "", common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a
|
||||
// withdrawal is submitted
|
||||
func (o *OKGroup) WithdrawFiatFundsToInternationalBank(withdrawRequest exchange.WithdrawRequest) (string, error) {
|
||||
return "", common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (o *OKGroup) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) (resp []exchange.OrderDetail, err error) {
|
||||
for _, currency := range getOrdersRequest.Currencies {
|
||||
spotOpenOrders, err := o.GetSpotOpenOrders(GetSpotOpenOrdersRequest{
|
||||
InstrumentID: exchange.FormatExchangeCurrency(o.Name, currency).String(),
|
||||
})
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
for _, openOrder := range spotOpenOrders {
|
||||
resp = append(resp, exchange.OrderDetail{
|
||||
Amount: openOrder.Size,
|
||||
CurrencyPair: currency,
|
||||
Exchange: o.Name,
|
||||
ExecutedAmount: openOrder.FilledSize,
|
||||
OrderDate: openOrder.Timestamp,
|
||||
Status: openOrder.Status,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (o *OKGroup) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) (resp []exchange.OrderDetail, err error) {
|
||||
for _, currency := range getOrdersRequest.Currencies {
|
||||
spotOpenOrders, err := o.GetSpotOrders(GetSpotOrdersRequest{
|
||||
InstrumentID: exchange.FormatExchangeCurrency(o.Name, currency).String(),
|
||||
})
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
for _, openOrder := range spotOpenOrders {
|
||||
resp = append(resp, exchange.OrderDetail{
|
||||
Amount: openOrder.Size,
|
||||
CurrencyPair: currency,
|
||||
Exchange: o.Name,
|
||||
ExecutedAmount: openOrder.FilledSize,
|
||||
OrderDate: openOrder.Timestamp,
|
||||
Status: openOrder.Status,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetWebsocket returns a pointer to the exchange websocket
|
||||
func (o *OKGroup) GetWebsocket() (*exchange.Websocket, error) {
|
||||
return o.Websocket, nil
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
func (o *OKGroup) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) {
|
||||
return o.GetFee(feeBuilder)
|
||||
}
|
||||
|
||||
// GetWithdrawCapabilities returns the types of withdrawal methods permitted by the exchange
|
||||
func (o *OKGroup) GetWithdrawCapabilities() uint32 {
|
||||
return o.GetWithdrawPermissions()
|
||||
}
|
||||
@@ -160,7 +160,9 @@ func ProcessOrderbook(exchangeName string, p pair.CurrencyPair, orderbookNew Bas
|
||||
orderbookNew.Pair = p
|
||||
}
|
||||
orderbookNew.CurrencyPair = p.Pair().String()
|
||||
orderbookNew.LastUpdated = time.Now()
|
||||
if orderbookNew.LastUpdated.IsZero() {
|
||||
orderbookNew.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
orderbook, err := GetOrderbookByExchange(exchangeName)
|
||||
if err != nil {
|
||||
|
||||
@@ -100,7 +100,7 @@ func (p *Poloniex) Setup(exch config.ExchangeConfig) {
|
||||
p.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
||||
p.RESTPollingDelay = exch.RESTPollingDelay
|
||||
p.Verbose = exch.Verbose
|
||||
p.Websocket.SetEnabled(exch.Websocket)
|
||||
p.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
p.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
p.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
p.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
@@ -87,7 +87,7 @@ func (y *Yobit) Setup(exch config.ExchangeConfig) {
|
||||
y.SetAPIKeys(exch.APIKey, exch.APISecret, "", false)
|
||||
y.RESTPollingDelay = exch.RESTPollingDelay
|
||||
y.Verbose = exch.Verbose
|
||||
y.Websocket.SetEnabled(exch.Websocket)
|
||||
y.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
y.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
y.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
y.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
@@ -93,7 +93,7 @@ func (z *ZB) Setup(exch config.ExchangeConfig) {
|
||||
z.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
||||
z.RESTPollingDelay = exch.RESTPollingDelay
|
||||
z.Verbose = exch.Verbose
|
||||
z.Websocket.SetEnabled(exch.Websocket)
|
||||
z.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
z.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
z.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
z.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
1
go.mod
1
go.mod
@@ -1,6 +1,7 @@
|
||||
module github.com/thrasher-/gocryptotrader
|
||||
|
||||
require (
|
||||
github.com/google/go-querystring v1.0.0
|
||||
github.com/gorilla/mux v1.7.0
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/toorop/go-pusher v0.0.0-20180521062818-4521e2eb39fb
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1,3 +1,5 @@
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
|
||||
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
|
||||
@@ -61,7 +61,7 @@ func ({{.Variable}} *{{.CapitalName}}) Setup(exch config.ExchangeConfig) {
|
||||
{{.Variable}}.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
||||
{{.Variable}}.RESTPollingDelay = exch.RESTPollingDelay
|
||||
{{.Variable}}.Verbose = exch.Verbose
|
||||
{{.Variable}}.Websocket.SetEnabled(exch.Websocket)
|
||||
{{.Variable}}.Websocket.SetWsStatusAndConnection(exch.Websocket)
|
||||
{{.Variable}}.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
|
||||
{{.Variable}}.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
|
||||
{{.Variable}}.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
|
||||
|
||||
Reference in New Issue
Block a user