Files
gocryptotrader/exchanges/anx/anx.go
Ryan O'Hara-Reid 0c5d75b22c (Engine) Variety of engine updates (#390)
* drop common uuid v4 func and imported package as needed

* removed common functions regarding json marshal and unmarshal and used the json package directly. WRT unmarshal it was calling reflect and converted to string which is also checked in the JSON package so it was doing a double up, this will be a tiny gain as it was directly used in the requester package for all our outbound requests.

* add in string

* explicitly throw away return error value

* atleast return the error that websocket initialise returns

* return error when not connected

* fix comment

* Adds comments

* move package declarations

* drop append whenever we call supported

* remove unused import

* Change incorrect spelling

* fix tests

* fix go import issue
2019-12-03 10:06:08 +11:00

475 lines
12 KiB
Go

package anx
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"time"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
log "github.com/thrasher-corp/gocryptotrader/logger"
)
const (
anxAPIURL = "https://anxpro.com/"
anxAPIVersion = "3"
anxAPIKey = "apiKey"
anxCurrencies = "currencyStatic"
anxDataToken = "dataToken"
anxOrderNew = "order/new"
anxOrderCancel = "order/cancel"
anxOrderList = "order/list"
anxOrderInfo = "order/info"
anxSend = "send"
anxSubaccountNew = "subaccount/new"
anxReceieveAddress = "receive"
anxCreateAddress = "receive/create"
anxTicker = "money/ticker"
anxDepth = "money/depth/full"
anxAccount = "account"
// ANX rate limites for authenticated and unauthenticated requests
anxAuthRate = 0
anxUnauthRate = 0
)
// ANX is the overarching type across the alphapoint package
type ANX struct {
exchange.Base
}
// GetCurrencies returns a list of supported currencies (both fiat
// and cryptocurrencies)
func (a *ANX) GetCurrencies() (CurrenciesStore, error) {
var result CurrenciesStaticResponse
path := fmt.Sprintf("%sapi/3/%s", a.API.Endpoints.URL, anxCurrencies)
err := a.SendHTTPRequest(path, &result)
if err != nil {
return CurrenciesStore{}, err
}
return result.CurrenciesResponse, nil
}
// GetTicker returns the current ticker
func (a *ANX) GetTicker(currency string) (Ticker, error) {
var t Ticker
path := fmt.Sprintf("%sapi/2/%s/%s", a.API.Endpoints.URL, currency, anxTicker)
return t, a.SendHTTPRequest(path, &t)
}
// GetDepth returns current orderbook depth.
func (a *ANX) GetDepth(currency string) (Depth, error) {
var depth Depth
path := fmt.Sprintf("%sapi/2/%s/%s", a.API.Endpoints.URL, currency, anxDepth)
return depth, a.SendHTTPRequest(path, &depth)
}
// GetAPIKey returns a new generated API key set.
func (a *ANX) GetAPIKey(username, password, otp, deviceID string) (apiKey, apiSecret string, err error) {
req := make(map[string]interface{})
req["nonce"] = strconv.FormatInt(time.Now().UnixNano(), 10)[0:13]
req["username"] = username
req["password"] = password
if otp != "" {
req["otp"] = otp
}
req["deviceId"] = deviceID
type APIKeyResponse struct {
APIKey string `json:"apiKey"`
APISecret string `json:"apiSecret"`
ResultCode string `json:"resultCode"`
Timestamp int64 `json:"timestamp"`
}
var response APIKeyResponse
err = a.SendAuthenticatedHTTPRequest(anxAPIKey, req, &response)
if err != nil {
return apiKey, apiSecret, err
}
if response.ResultCode != "OK" {
return apiKey, apiSecret, errors.New("Response code is not OK: " + response.ResultCode)
}
apiKey = response.APIKey
apiSecret = response.APISecret
return apiKey, apiSecret, err
}
// GetDataToken returns token data
func (a *ANX) GetDataToken() (string, error) {
req := make(map[string]interface{})
type DataTokenResponse struct {
ResultCode string `json:"resultCode"`
Timestamp int64 `json:"timestamp"`
Token string `json:"token"`
UUID string `json:"uuid"`
}
var response DataTokenResponse
err := a.SendAuthenticatedHTTPRequest(anxDataToken, req, &response)
if err != nil {
return "", err
}
if response.ResultCode != "OK" {
return "", errors.New("Response code is not OK: %s" + response.ResultCode)
}
return response.Token, nil
}
// NewOrder sends a new order request to the exchange.
func (a *ANX) NewOrder(orderType string, buy bool, tradedCurrency string, tradedCurrencyAmount float64, settlementCurrency string, settlementCurrencyAmount, limitPriceSettlement float64,
replace bool, replaceUUID string, replaceIfActive bool) (string, error) {
req := make(map[string]interface{})
var order Order
order.OrderType = orderType
order.BuyTradedCurrency = buy
if buy {
order.TradedCurrencyAmount = tradedCurrencyAmount
} else {
order.SettlementCurrencyAmount = settlementCurrencyAmount
}
order.TradedCurrency = tradedCurrency
order.SettlementCurrency = settlementCurrency
order.LimitPriceInSettlementCurrency = limitPriceSettlement
if replace {
order.ReplaceExistingOrderUUID = replaceUUID
order.ReplaceOnlyIfActive = replaceIfActive
}
req["order"] = order
type OrderResponse struct {
OrderID string `json:"orderId"`
Timestamp int64 `json:"timestamp,string"`
ResultCode string `json:"resultCode"`
}
var response OrderResponse
err := a.SendAuthenticatedHTTPRequest(anxOrderNew, req, &response)
if err != nil {
return "", err
}
if response.ResultCode != "OK" {
return "", errors.New("Response code is not OK: " + response.ResultCode)
}
return response.OrderID, nil
}
// CancelOrderByIDs cancels orders, requires already knowing order IDs
// There is no existing API call to retrieve orderIds
func (a *ANX) CancelOrderByIDs(orderIds []string) (OrderCancelResponse, error) {
var response OrderCancelResponse
if len(orderIds) == 0 {
return response, errors.New("no order ids provided")
}
req := make(map[string]interface{})
req["orderIds"] = orderIds
err := a.SendAuthenticatedHTTPRequest(anxOrderCancel, req, &response)
if response.ResultCode != "OK" {
return response, errors.New(response.ResultCode)
}
return response, err
}
// GetOrderList retrieves orders from the exchange
func (a *ANX) GetOrderList(isActiveOrdersOnly bool) ([]OrderResponse, error) {
req := make(map[string]interface{})
req["activeOnly"] = isActiveOrdersOnly
type OrderListResponse struct {
Timestamp int64 `json:"timestamp,string"`
ResultCode string `json:"resultCode"`
Count int64 `json:"count"`
OrderResponses []OrderResponse `json:"orders"`
}
var response OrderListResponse
err := a.SendAuthenticatedHTTPRequest(anxOrderList, req, &response)
if err != nil {
return nil, err
}
if response.ResultCode != "OK" {
return nil, errors.New(response.ResultCode)
}
return response.OrderResponses, err
}
// OrderInfo returns information about a specific order
func (a *ANX) OrderInfo(orderID string) (OrderResponse, error) {
req := make(map[string]interface{})
req["orderId"] = orderID
type OrderInfoResponse struct {
Order OrderResponse `json:"order"`
ResultCode string `json:"resultCode"`
Timestamp int64 `json:"timestamp"`
}
var response OrderInfoResponse
err := a.SendAuthenticatedHTTPRequest(anxOrderInfo, req, &response)
if err != nil {
return OrderResponse{}, err
}
if response.ResultCode != "OK" {
return OrderResponse{}, errors.New(response.ResultCode)
}
return response.Order, nil
}
// Send withdraws a currency to an address
func (a *ANX) Send(currency, address, otp, amount string) (string, error) {
req := make(map[string]interface{})
req["ccy"] = currency
req["amount"] = amount
req["address"] = address
if otp != "" {
req["otp"] = otp
}
type SendResponse struct {
TransactionID string `json:"transactionId"`
ResultCode string `json:"resultCode"`
Timestamp int64 `json:"timestamp,string"`
}
var response SendResponse
err := a.SendAuthenticatedHTTPRequest(anxSend, req, &response)
if err != nil {
return "", err
}
if response.ResultCode != "OK" {
return "", errors.New(response.ResultCode)
}
return response.TransactionID, nil
}
// CreateNewSubAccount generates a new sub account
func (a *ANX) CreateNewSubAccount(currency, name string) (string, error) {
req := make(map[string]interface{})
req["ccy"] = currency
req["customRef"] = name
type SubaccountResponse struct {
SubAccount string `json:"subAccount"`
ResultCode string `json:"resultCode"`
Timestamp int64 `json:"timestamp"`
}
var response SubaccountResponse
err := a.SendAuthenticatedHTTPRequest(anxSubaccountNew, req, &response)
if err != nil {
return "", err
}
if response.ResultCode != "OK" {
return "", errors.New(response.ResultCode)
}
return response.SubAccount, nil
}
// GetDepositAddressByCurrency returns a deposit address for a specific currency
func (a *ANX) GetDepositAddressByCurrency(currency, name string, newAddr bool) (string, error) {
req := make(map[string]interface{})
req["ccy"] = currency
if name != "" {
req["subAccount"] = name
}
type AddressResponse struct {
Address string `json:"address"`
SubAccount string `json:"subAccount"`
ResultCode string `json:"resultCode"`
Timestamp int64 `json:"timestamp,string"`
}
var response AddressResponse
path := anxReceieveAddress
if newAddr {
path = anxCreateAddress
}
err := a.SendAuthenticatedHTTPRequest(path, req, &response)
if err != nil {
return "", err
}
if response.ResultCode != "OK" {
return "", errors.New(response.ResultCode)
}
return response.Address, nil
}
// SendHTTPRequest sends an unauthenticated HTTP request
func (a *ANX) SendHTTPRequest(path string, result interface{}) error {
return a.SendPayload(http.MethodGet,
path,
nil,
nil,
result,
false,
false,
a.Verbose,
a.HTTPDebugging,
a.HTTPRecording)
}
// SendAuthenticatedHTTPRequest sends a authenticated HTTP request
func (a *ANX) SendAuthenticatedHTTPRequest(path string, params map[string]interface{}, result interface{}) error {
if !a.AllowAuthenticatedRequest() {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, a.Name)
}
n := a.Requester.GetNonce(true)
req := make(map[string]interface{})
req["nonce"] = n.String()[0:13]
path = fmt.Sprintf("api/%s/%s", anxAPIVersion, path)
for key, value := range params {
req[key] = value
}
PayloadJSON, err := json.Marshal(req)
if err != nil {
return errors.New("unable to JSON request")
}
if a.Verbose {
log.Debugf(log.ExchangeSys, "Request JSON: %s\n", PayloadJSON)
}
hmac := crypto.GetHMAC(crypto.HashSHA512, []byte(path+string("\x00")+string(PayloadJSON)), []byte(a.API.Credentials.Secret))
headers := make(map[string]string)
headers["Rest-Key"] = a.API.Credentials.Key
headers["Rest-Sign"] = crypto.Base64Encode(hmac)
headers["Content-Type"] = "application/json"
return a.SendPayload(http.MethodPost,
a.API.Endpoints.URL+path,
headers,
bytes.NewBuffer(PayloadJSON),
result,
true,
true,
a.Verbose,
a.HTTPDebugging,
a.HTTPRecording)
}
// GetFee returns an estimate of fee based on type of transaction
func (a *ANX) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
var fee float64
switch feeBuilder.FeeType {
case exchange.CryptocurrencyTradeFee:
fee = a.calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount, feeBuilder.IsMaker)
case exchange.CryptocurrencyWithdrawalFee:
fee = getCryptocurrencyWithdrawalFee(feeBuilder.Pair.Base)
case exchange.InternationalBankWithdrawalFee:
fee = getInternationalBankWithdrawalFee(feeBuilder.FiatCurrency, feeBuilder.Amount)
case exchange.OfflineTradeFee:
fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount)
}
if fee < 0 {
fee = 0
}
return fee, nil
}
// getOfflineTradeFee calculates the worst case-scenario trading fee
func getOfflineTradeFee(price, amount float64) float64 {
return 0.002 * price * amount
}
func (a *ANX) calculateTradingFee(purchasePrice, amount float64, isMaker bool) float64 {
var fee float64
if isMaker {
fee = 0.01 * amount * purchasePrice
} else {
fee = 0.02 * amount * purchasePrice
}
return fee
}
func getCryptocurrencyWithdrawalFee(c currency.Code) float64 {
return WithdrawalFees[c]
}
func getInternationalBankWithdrawalFee(c currency.Code, amount float64) float64 {
var fee float64
if c == currency.HKD {
fee = 250 + (WithdrawalFees[c] * amount)
}
// TODO, other fiat currencies require consultation with ANXPRO
return fee
}
// GetAccountInformation retrieves details including API permissions
func (a *ANX) GetAccountInformation() (AccountInformation, error) {
var response AccountInformation
err := a.SendAuthenticatedHTTPRequest(anxAccount, nil, &response)
if err != nil {
return response, err
}
if response.ResultCode != "OK" {
log.Errorf(log.ExchangeSys, "Response code is not OK: %s\n", response.ResultCode)
return response, errors.New(response.ResultCode)
}
return response, nil
}
// CheckAPIWithdrawPermission checks if the API key is allowed to withdraw
func (a *ANX) CheckAPIWithdrawPermission() (bool, error) {
accountInfo, err := a.GetAccountInformation()
if err != nil {
return false, err
}
var apiAllowsWithdraw bool
for _, a := range accountInfo.Rights {
if a == "withdraw" {
apiAllowsWithdraw = true
}
}
if !apiAllowsWithdraw {
log.Warn(log.ExchangeSys, "API key is missing withdrawal permissions")
}
return apiAllowsWithdraw, nil
}