From 3c8929edbb3a78c3217f0644c04d392f555ec62f Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Sat, 25 Apr 2015 17:42:04 +1000 Subject: [PATCH] Added ANX Exchange HTTP support. --- README.md | 1 + anxhttp.go | 418 ++++++++++++++++++++++++++++++++++++++++++++ config_example.json | 11 ++ events.go | 5 +- main.go | 15 +- 5 files changed, 448 insertions(+), 2 deletions(-) create mode 100644 anxhttp.go diff --git a/README.md b/README.md index be6bed20..42ab224a 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ A cryptocurrency trading bot supporting multiple exchanges written in Golang. | Exchange | REST API | Streaming API | FIX API | |----------|------|-----------|-----| +| ANXPRO | Yes | No | NA | | Bitfinex | Yes | NA | NA | | Bitstamp | Yes | Yes | NA | | BTCChina | Yes | Yes | No | diff --git a/anxhttp.go b/anxhttp.go new file mode 100644 index 00000000..72323cde --- /dev/null +++ b/anxhttp.go @@ -0,0 +1,418 @@ +package main + +import ( + "strconv" + "bytes" + "errors" + "time" + "log" + "fmt" +) + +const ( + ANX_API_URL = "https://anxpro.com/" + ANX_API_VERSION = "3" + ANX_APIKEY = "apiKey" + ANX_DATA_TOKEN = "dataToken" + ANX_ORDER_NEW = "order/new" + ANX_ORDER_INFO = "order/info" + ANX_SEND = "send" + ANX_SUBACCOUNT_NEW = "subaccount/new" + ANX_RECEIVE_ADDRESS = "receive" + ANX_CREATE_ADDRESS = "receive/create" + ANX_TICKER = "money/ticker" +) + +type ANX struct { + Name string + Enabled bool + Verbose bool + Websocket bool + RESTPollingDelay time.Duration + APIKey, APISecret string + TakerFee, MakerFee float64 +} + +type ANXOrder struct { + OrderType string `json:"orderType"` + BuyTradedCurrency bool `json:"buyTradedCurrency"` + TradedCurrency string `json:"tradedCurrency"` + SettlementCurrency string `json:"settlementCurrency"` + TradedCurrencyAmount string `json:"tradedCurrencyAmount"` + SettlementCurrencyAmount string `json:"settlementCurrencyAmount"` + LimitPriceInSettlementCurrency string `json:"limitPriceInSettlementCurrency"` + ReplaceExistingOrderUUID string `json:"replaceExistingOrderUuid"` + ReplaceOnlyIfActive bool `json:"replaceOnlyIfActive"` +} + +type ANXOrderResponse struct { + BuyTradedCurrency bool `json:"buyTradedCurrency"` + ExecutedAverageRate string `json:"executedAverageRate"` + LimitPriceInSettlementCurrency string `json:"limitPriceInSettlementCurrency"` + OrderID string `json:"orderId"` + OrderStatus string `json:"orderStatus"` + OrderType string `json:"orderType"` + ReplaceExistingOrderUUID string `json:"replaceExistingOrderId"` + SettlementCurrency string `json:"settlementCurrency"` + SettlementCurrencyAmount string `json:"settlementCurrencyAmount"` + SettlementCurrencyOutstanding string `json:"settlementCurrencyOutstanding"` + Timestamp int64 `json:"timestamp"` + TradedCurrency string `json:"tradedCurrency"` + TradedCurrencyAmount string `json:"tradedCurrencyAmount"` + TradedCurrencyOutstanding string `json:"tradedCurrencyOutstanding"` +} + +type ANXTickerComponent struct { + Currency string `json:"currency"` + Display string `json:"display"` + DisplayShort string `json:"display_short"` + Value float64 `json:"value,string"` + ValueInt int64 `json:"value_int,string"` +} + +type ANXTicker struct { + Result string `json:"result"` + Data struct { + High ANXTickerComponent `json:"high"` + Low ANXTickerComponent `json:"low"` + Avg ANXTickerComponent `json:"avg"` + Vwap ANXTickerComponent `json:"vwap"` + Vol ANXTickerComponent `json:"vol"` + Last ANXTickerComponent `json:"last"` + Buy ANXTickerComponent `json:buy"` + Sell ANXTickerComponent `json:"sell"` + Now float64 `json:"now"` + UpdateTime float64 `json:"dataUpdateTime"` + } `json:"data"` +} + +func (a *ANX) SetDefaults() { + a.Name = "ANX" + a.Enabled = true + a.TakerFee = 0.6 + a.MakerFee = 0.3 + a.Verbose = false + a.Websocket = false + a.RESTPollingDelay = 10 +} + +func (a *ANX) GetName() (string) { + return a.Name +} + +func (a *ANX) SetEnabled(enabled bool) { + a.Enabled = enabled +} + +func (a *ANX) IsEnabled() (bool) { + return a.Enabled +} + +func (a *ANX) SetAPIKeys(apiKey, apiSecret string) { + a.APIKey = apiKey + result, err := Base64Decode(apiSecret) + + if err != nil { + log.Printf("%s unable to decode secret key.", a.GetName()) + a.Enabled = false + return + } + + a.APISecret = string(result) +} + +func (a *ANX) GetFee(maker bool) (float64) { + if (maker) { + return a.MakerFee + } else { + return a.TakerFee + } +} + +func (a *ANX) Run() { + if a.Verbose { + log.Printf("%s polling delay: %ds.\n", a.GetName(), a.RESTPollingDelay) + } + + for a.Enabled { + go func() { + ANXBTC := a.GetTicker("BTCUSD") + log.Printf("ANX BTC: Last %f High %f Low %f Volume %f\n", ANXBTC.Data.Last.Value, ANXBTC.Data.High.Value, ANXBTC.Data.Low.Value, ANXBTC.Data.Vol.Value) + AddExchangeInfo(a.GetName(), "BTC", ANXBTC.Data.Last.Value, ANXBTC.Data.Vol.Value) + }() + time.Sleep(time.Second * a.RESTPollingDelay) + } +} + +func (a *ANX) GetTicker(currency string) (ANXTicker) { + var ticker ANXTicker + err := SendHTTPGetRequest(fmt.Sprintf("%sapi/2/%s/%s", ANX_API_URL, currency, ANX_TICKER), true, &ticker) + if err != nil { + log.Println(err) + return ANXTicker{} + } + return ticker +} + +func (a *ANX) GetAPIKey(username, password, otp, deviceID string) (string, string) { + request := make(map[string]interface{}) + request["nonce"] = strconv.FormatInt(time.Now().UnixNano(), 10)[0:13] + request["username"] = username + request["password"] = password + + if otp != "" { + request["otp"] = otp + } + + request["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(ANX_APIKEY, request, &response) + + if err != nil { + log.Println(err) + return "", "" + } + + if response.ResultCode != "OK" { + log.Printf("Response code is not OK: %s\n", response.ResultCode) + return "", "" + } + + return response.APIKey, response.APISecret +} + +func (a *ANX) GetDataToken() (string) { + request := 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(ANX_DATA_TOKEN, request, &response) + + if err != nil { + log.Println(err) + return "" + } + + if response.ResultCode != "OK" { + log.Printf("Response code is not OK: %s\n", response.ResultCode) + return "" + } + + return response.Token +} + +func (a *ANX) NewOrder(orderType string, buy bool, tradedCurrency, tradedCurrencyAmount, settlementCurrency, settlementCurrencyAmount, limitPriceSettlement string, + replace bool, replaceUUID string, replaceIfActive bool) { + request := make(map[string]interface{}) + + var order ANXOrder + 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 + } + + request["order"] = order + + type OrderResponse struct { + OrderID string `json:"orderId"` + Timestamp int64 `json:"timestamp"` + ResultCode string `json:"resultCode"` + } + var response OrderResponse + + err := a.SendAuthenticatedHTTPRequest(ANX_ORDER_NEW, request, &response) + + if err != nil { + log.Println(err) + return + } + + if response.ResultCode != "OK" { + log.Printf("Response code is not OK: %s\n", response.ResultCode) + return + } +} + +func (a *ANX) OrderInfo(orderID string) (ANXOrderResponse, error) { + request := make(map[string]interface{}) + request["orderId"] = orderID + + type OrderInfoResponse struct { + Order ANXOrderResponse `json:"order"` + ResultCode string `json:"resultCode"` + Timestamp int64 `json:"timestamp"` + } + var response OrderInfoResponse + + err := a.SendAuthenticatedHTTPRequest(ANX_ORDER_INFO, request, &response) + + if err != nil { + log.Println(err) + return ANXOrderResponse{}, err + } + + if response.ResultCode != "OK" { + log.Printf("Response code is not OK: %s\n", response.ResultCode) + return ANXOrderResponse{}, errors.New(response.ResultCode) + } + return response.Order, nil +} + +func (a *ANX) Send(currency, address, otp, amount string) (string, error) { + request := make(map[string]interface{}) + request["ccy"] = currency + request["amount"] = amount + request["address"] = address + + if otp != "" { + request["otp"] = otp + } + + type SendResponse struct { + TransactionID string `json:"transactionId"` + ResultCode string `json:"resultCode"` + Timestamp int64 `json:"timestamp"` + } + var response SendResponse + + err := a.SendAuthenticatedHTTPRequest(ANX_SEND, request, &response) + + if err != nil { + return "", err + } + + if response.ResultCode != "OK" { + log.Printf("Response code is not OK: %s\n", response.ResultCode) + return "", errors.New(response.ResultCode) + } + return response.TransactionID, nil +} + +func (a *ANX) CreateNewSubAccount(currency, name string) (string, error) { + request := make(map[string]interface{}) + request["ccy"] = currency + request["customRef"] = name + + type SubaccountResponse struct { + SubAccount string `json:"subAccount"` + ResultCode string `json:"resultCode"` + Timestamp int64 `json:"timestamp"` + } + var response SubaccountResponse + + err := a.SendAuthenticatedHTTPRequest(ANX_SUBACCOUNT_NEW, request, &response) + + if err != nil { + return "", err + } + + if response.ResultCode != "OK" { + log.Printf("Response code is not OK: %s\n", response.ResultCode) + return "", errors.New(response.ResultCode) + } + return response.SubAccount, nil +} + +func (a *ANX) GetDepositAddress(currency, name string, new bool) (string, error) { + request := make(map[string]interface{}) + request["ccy"] = currency + + if name != "" { + request["subAccount"] = name + } + + type AddressResponse struct { + Address string `json:"address"` + SubAccount string `json:"subAccount"` + ResultCode string `json:"resultCode"` + Timestamp int64 `json:"timestamp"` + } + var response AddressResponse + + path := ANX_RECEIVE_ADDRESS + if new { + path = ANX_CREATE_ADDRESS + } + + err := a.SendAuthenticatedHTTPRequest(path, request, &response) + + if err != nil { + return "", err + } + + if response.ResultCode != "OK" { + log.Printf("Response code is not OK: %s\n", response.ResultCode) + return "", errors.New(response.ResultCode) + } + + return response.Address, nil +} + +func (a *ANX) SendAuthenticatedHTTPRequest(path string, params map[string]interface{}, result interface{}) (err error) { + request := make(map[string]interface{}) + request["nonce"] = strconv.FormatInt(time.Now().UnixNano(), 10)[0:13] + path = fmt.Sprintf("api/%s/%s", ANX_API_VERSION, path) + + if params != nil { + for key, value:= range params { + request[key] = value + } + } + + PayloadJson, err := JSONEncode(request) + + if err != nil { + return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request") + } + + if a.Verbose { + log.Printf("Request JSON: %s\n", PayloadJson) + } + + hmac := GetHMAC(HASH_SHA512, []byte(path + string("\x00") + string(PayloadJson)), []byte(a.APISecret)) + headers := make(map[string]string) + headers["Rest-Key"] = a.APIKey + headers["Rest-Sign"] = Base64Encode([]byte(hmac)) + headers["Content-Type"] = "application/json" + + resp, err := SendHTTPRequest("POST", ANX_API_URL + path, headers, bytes.NewBuffer(PayloadJson)) + + if a.Verbose { + log.Printf("Recieved raw: \n%s\n", resp) + } + + err = JSONDecode([]byte(resp), &result) + + if err != nil { + return errors.New("Unable to JSON Unmarshal response.") + } + + return nil +} \ No newline at end of file diff --git a/config_example.json b/config_example.json index 29d0c246..88cfea86 100644 --- a/config_example.json +++ b/config_example.json @@ -10,6 +10,17 @@ } ], "Exchanges": [ + { + "Name": "ANX", + "Pairs": "BTCUSD,BTCHKD,BTCEUR,BTCCAD,BTCAUD,BTCSGD,BTCJPY,BTCGBP,BTCNZD,LTCBTC,DOGEBTC,STRBTC,XRPBTC", + "APIKey": "Key", + "APISecret": "Secret", + "BaseCurrencies": "USD,HKD,EUR,CAD,AUD,SGD,JPY,GBP,NZD", + "Enabled": true, + "Verbose": false, + "Websocket": false, + "RESTPollingDelay": 10 + }, { "Name": "Bitfinex", "Pairs": "BTCUSD,LTCUSD,DRKUSD", diff --git a/events.go b/events.go index 156df17c..b02a959e 100644 --- a/events.go +++ b/events.go @@ -134,6 +134,8 @@ func (e *Event) CheckCondition() (bool) { lastPrice = bot.exchange.okcoinChina.GetTicker("btc_cny").Last } else if bot.exchange.okcoinIntl.GetName() == e.Exchange { lastPrice = bot.exchange.okcoinIntl.GetTicker("btc_usd").Last + } else if bot.exchange.anx.GetName() == e.Exchange { + lastPrice = bot.exchange.anx.GetTicker("BTCUSD").Data.Last.Value } if lastPrice == 0 { @@ -237,7 +239,8 @@ func IsValidExchange(Exchange string) (bool) { bot.exchange.kraken.GetName() == Exchange && bot.exchange.kraken.IsEnabled() || bot.exchange.lakebtc.GetName() == Exchange && bot.exchange.lakebtc.IsEnabled() || bot.exchange.okcoinChina.GetName() == Exchange && bot.exchange.okcoinChina.IsEnabled() || - bot.exchange.okcoinIntl.GetName() == Exchange && bot.exchange.okcoinIntl.IsEnabled() { + bot.exchange.okcoinIntl.GetName() == Exchange && bot.exchange.okcoinIntl.IsEnabled() || + bot.exchange.anx.GetName() == Exchange && bot.exchange.anx.IsEnabled() { return true } return false diff --git a/main.go b/main.go index ba67f39f..8773da65 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( ) type Exchange struct { + anx ANX btcchina BTCChina bitstamp Bitstamp bitfinex Bitfinex @@ -82,6 +83,7 @@ func main() { log.Printf("Available Exchanges: %d. Enabled Exchanges: %d.\n", len(bot.config.Exchanges), enabledExchanges) log.Println("Bot Exchange support:") + bot.exchange.anx.SetDefaults() bot.exchange.kraken.SetDefaults() bot.exchange.btcchina.SetDefaults() bot.exchange.bitstamp.SetDefaults() @@ -105,7 +107,18 @@ func main() { } for _, exch := range bot.config.Exchanges { - if bot.exchange.btcchina.GetName() == exch.Name { + if bot.exchange.anx.GetName() == exch.Name { + log.Printf("%s: %s (Verbose mode: %s).\n", exch.Name, IsEnabled(exch.Enabled), IsEnabled(exch.Verbose)) + if !exch.Enabled { + bot.exchange.anx.SetEnabled(false) + } else { + bot.exchange.anx.SetAPIKeys(exch.APIKey, exch.APISecret) + bot.exchange.anx.RESTPollingDelay = exch.RESTPollingDelay + bot.exchange.anx.Verbose = exch.Verbose + bot.exchange.anx.Websocket = exch.Websocket + go bot.exchange.anx.Run() + } + } else if bot.exchange.btcchina.GetName() == exch.Name { log.Printf("%s: %s (Verbose mode: %s).\n", exch.Name, IsEnabled(exch.Enabled), IsEnabled(exch.Verbose)) if !exch.Enabled { bot.exchange.btcchina.SetEnabled(false)