package main import ( "bytes" "errors" "fmt" "log" "net/url" "strconv" "time" ) const ( POLONIEX_API_URL = "https://poloniex.com" POLONIEX_API_TRADING_ENDPOINT = "tradingApi" POLONIEX_API_VERSION = "1" POLONIEX_BALANCES = "returnBalances" POLONIEX_BALANCES_COMPLETE = "returnCompleteBalances" POLONIEX_DEPOSIT_ADDRESSES = "returnDepositAddresses" POLONIEX_GENERATE_NEW_ADDRESS = "generateNewAddress" POLONIEX_DEPOSITS_WITHDRAWALS = "returnDepositsWithdrawals" POLONIEX_ORDERS = "returnOpenOrders" POLONIEX_TRADE_HISTORY = "returnTradeHistory" POLONIEX_ORDER_BUY = "buy" POLONIEX_ORDER_SELL = "sell" POLONIEX_ORDER_ORDER = "cancelOrder" POLONIEX_WITHDRAW = "withdraw" POLONIEX_AVAILABLE_BALANCES = "returnAvailableAccountBalances" POLONIEX_TRADABLE_BALANCES = "returnTradableBalances" POLONIEX_TRANSFER_BALANCE = "transferBalance" POLONIEX_MARGIN_ACCOUNT_SUMMARY = "returnMarginAccountSummary" POLONIEX_MARGIN_BUY = "marginBuy" POLONIEX_MARGIN_SELL = "marginSell" POLONIEX_MARGIN_POSITION = "getMarginPosition" POLONIEX_MARGIN_POSITION_CLOSE = "closeMarginPosition" POLONIEX_CREATE_LOAN_OFFER = "createLoanOffer" POLONIEX_CANCEL_LOAN_OFFER = "cancelLoanOffer" POLONIEX_OPEN_LOAN_OFFERS = "returnOpenLoanOffers" POLONIEX_ACTIVE_LOANS = "returnActiveLoans" POLONIEX_AUTO_RENEW = "toggleAutoRenew" ) type Poloniex struct { Name string Enabled bool Verbose bool Websocket bool RESTPollingDelay time.Duration AuthenticatedAPISupport bool AccessKey, SecretKey string Fee float64 BaseCurrencies []string AvailablePairs []string EnabledPairs []string } type PoloniexTicker struct { Last float64 `json:"last,string"` LowestAsk float64 `json:"lowestAsk,string"` HighestBid float64 `json:"highestBid,string"` PercentChange float64 `json:"percentChange,string"` BaseVolume float64 `json:"baseVolume,string"` QuoteVolume float64 `json:"quoteVolume,string"` IsFrozen int `json:"isFrozen,string"` High24Hr float64 `json:"high24hr,string"` Low24Hr float64 `json:"low24hr,string"` } func (p *Poloniex) SetDefaults() { p.Name = "Poloniex" p.Enabled = true p.Fee = 0 p.Verbose = false p.Websocket = false p.RESTPollingDelay = 10 } func (p *Poloniex) GetName() string { return p.Name } func (p *Poloniex) SetEnabled(enabled bool) { p.Enabled = enabled } func (p *Poloniex) IsEnabled() bool { return p.Enabled } func (p *Poloniex) SetAPIKeys(apiKey, apiSecret string) { p.AccessKey = apiKey p.SecretKey = apiSecret } func (p *Poloniex) GetFee() float64 { return p.Fee } func (p *Poloniex) Run() { if p.Verbose { log.Printf("%s Websocket: %s (url: %s).\n", p.GetName(), IsEnabled(p.Websocket), POLONIEX_WEBSOCKET_ADDRESS) log.Printf("%s polling delay: %ds.\n", p.GetName(), p.RESTPollingDelay) log.Printf("%s %d currencies enabled: %s.\n", p.GetName(), len(p.EnabledPairs), p.EnabledPairs) } if p.Websocket { go p.WebsocketClient() } for p.Enabled { for _, x := range p.EnabledPairs { currency := x go func() { ticker, err := p.GetTicker() if err != nil { log.Println(err) return } log.Printf("Poloniex %s Last %f High %f Low %f Volume %f\n", currency, ticker[currency].Last, ticker[currency].High24Hr, ticker[currency].Low24Hr, ticker[currency].QuoteVolume) //AddExchangeInfo(p.GetName(), currency[0:3], currency[3:], ticker.Last, ticker.Volume) }() } time.Sleep(time.Second * p.RESTPollingDelay) } } func (p *Poloniex) GetTicker() (map[string]PoloniexTicker, error) { type response struct { Data map[string]PoloniexTicker } resp := response{} path := fmt.Sprintf("%s/public?command=returnTicker", POLONIEX_API_URL) err := SendHTTPGetRequest(path, true, &resp.Data) if err != nil { return resp.Data, err } return resp.Data, nil } func (p *Poloniex) GetVolume() (interface{}, error) { var resp interface{} path := fmt.Sprintf("%s/public?command=return24hVolume", POLONIEX_API_URL) err := SendHTTPGetRequest(path, true, &resp) if err != nil { return resp, err } return resp, nil } type PoloniexOrderbook struct { Asks [][]interface{} `json:"asks"` Bids [][]interface{} `json:"bids"` IsFrozen string `json:"isFrozen"` } //TO-DO: add support for individual pair depth fetching func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (map[string]PoloniexOrderbook, error) { type Response struct { Data map[string]PoloniexOrderbook } vals := url.Values{} vals.Set("currencyPair", currencyPair) if depth != 0 { vals.Set("depth", strconv.Itoa(depth)) } resp := Response{} path := fmt.Sprintf("%s/public?command=returnOrderBook&%s", POLONIEX_API_URL, vals.Encode()) err := SendHTTPGetRequest(path, true, &resp.Data) if err != nil { return resp.Data, err } return resp.Data, nil } type PoloniexTradeHistory struct { GlobalTradeID int64 `json:"globalTradeID"` TradeID int64 `json:"tradeID"` Date string `json:"date"` Type string `json:"type"` Rate float64 `json:"rate,string"` Amount float64 `json:"amount,string"` Total float64 `json:"total,string"` } func (p *Poloniex) GetTradeHistory(currencyPair, start, end string) ([]PoloniexTradeHistory, error) { vals := url.Values{} vals.Set("currencyPair", currencyPair) if start != "" { vals.Set("start", start) } if end != "" { vals.Set("end", end) } resp := []PoloniexTradeHistory{} path := fmt.Sprintf("%s/public?command=returnTradeHistory&%s", POLONIEX_API_URL, vals.Encode()) err := SendHTTPGetRequest(path, true, &resp) if err != nil { return nil, err } return resp, nil } type PoloniexChartData struct { Date int `json:"date"` High float64 `json:"high"` Low float64 `json:"low"` Open float64 `json:"open"` Close float64 `json:"close"` Volume float64 `json:"volume"` QuoteVolume float64 `json:"quoteVolume"` WeightedAverage float64 `json:"weightedAverage"` } func (p *Poloniex) GetChartData(currencyPair, start, end, period string) ([]PoloniexChartData, error) { vals := url.Values{} vals.Set("currencyPair", currencyPair) if start != "" { vals.Set("start", start) } if end != "" { vals.Set("end", end) } if period != "" { vals.Set("period", period) } resp := []PoloniexChartData{} path := fmt.Sprintf("%s/public?command=returnChartData&%s", POLONIEX_API_URL, vals.Encode()) err := SendHTTPGetRequest(path, true, &resp) if err != nil { return nil, err } return resp, nil } type PoloniexCurrencies struct { Name string `json:"name"` MaxDailyWithdrawal string `json:"maxDailyWithdrawal"` TxFee float64 `json:"txFee,string"` MinConfirmations int `json:"minConf"` DepositAddresses interface{} `json:"depositAddress"` Disabled int `json:"disabled"` Delisted int `json:"delisted"` Frozen int `json:"frozen"` } func (p *Poloniex) GetCurrencies() (map[string]PoloniexCurrencies, error) { type Response struct { Data map[string]PoloniexCurrencies } resp := Response{} path := fmt.Sprintf("%s/public?command=returnCurrencies", POLONIEX_API_URL) err := SendHTTPGetRequest(path, true, &resp.Data) if err != nil { return resp.Data, err } return resp.Data, nil } type PoloniexLoanOrder struct { Rate float64 `json:"rate,string"` Amount float64 `json:"amount,string"` RangeMin int `json:"rangeMin"` RangeMax int `json:"rangeMax"` } type PoloniexLoanOrders struct { Offers []PoloniexLoanOrder `json:"offers"` Demands []PoloniexLoanOrder `json:"demands"` } func (p *Poloniex) GetLoanOrders(currency string) (PoloniexLoanOrders, error) { resp := PoloniexLoanOrders{} path := fmt.Sprintf("%s/public?command=returnLoanOrders¤cy=%s", POLONIEX_API_URL, currency) err := SendHTTPGetRequest(path, true, &resp) if err != nil { return resp, err } return resp, nil } type PoloniexBalance struct { Currency map[string]float64 } func (p *Poloniex) GetBalances() (PoloniexBalance, error) { var result interface{} err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_BALANCES, url.Values{}, &result) if err != nil { return PoloniexBalance{}, err } data := result.(map[string]interface{}) balance := PoloniexBalance{} balance.Currency = make(map[string]float64) for x, y := range data { balance.Currency[x], _ = strconv.ParseFloat(y.(string), 64) } return balance, nil } type PoloniexCompleteBalance struct { Available float64 OnOrders float64 BTCValue float64 } type PoloniexCompleteBalances struct { Currency map[string]PoloniexCompleteBalance } func (p *Poloniex) GetCompleteBalances() (PoloniexCompleteBalances, error) { var result interface{} err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_BALANCES_COMPLETE, url.Values{}, &result) if err != nil { return PoloniexCompleteBalances{}, err } data := result.(map[string]interface{}) balance := PoloniexCompleteBalances{} balance.Currency = make(map[string]PoloniexCompleteBalance) for x, y := range data { dataVals := y.(map[string]interface{}) balancesData := PoloniexCompleteBalance{} balancesData.Available, _ = strconv.ParseFloat(dataVals["available"].(string), 64) balancesData.OnOrders, _ = strconv.ParseFloat(dataVals["onOrders"].(string), 64) balancesData.BTCValue, _ = strconv.ParseFloat(dataVals["btcValue"].(string), 64) balance.Currency[x] = balancesData } return balance, nil } type PoloniexDepositAddresses struct { Addresses map[string]string } func (p *Poloniex) GetDepositAddresses() (PoloniexDepositAddresses, error) { var result interface{} addresses := PoloniexDepositAddresses{} err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_DEPOSIT_ADDRESSES, url.Values{}, &result) if err != nil { return addresses, err } addresses.Addresses = make(map[string]string) data := result.(map[string]interface{}) for x, y := range data { addresses.Addresses[x] = y.(string) } return addresses, nil } func (p *Poloniex) GenerateNewAddress(currency string) (string, error) { type Response struct { Success int Error string Response string } resp := Response{} values := url.Values{} values.Set("currency", currency) err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_GENERATE_NEW_ADDRESS, values, &resp) if err != nil { return "", err } if resp.Error != "" { return "", errors.New(resp.Error) } return resp.Response, nil } type PoloniexDepositsWithdrawals struct { Deposits []struct { Currency string `json:"currency"` Address string `json:"address"` Amount float64 `json:"amount,string"` Confirmations int `json:"confirmations"` TransactionID string `json:"txid"` Timestamp time.Time `json:"timestamp"` Status string `json:"string"` } `json:"deposits"` Withdrawals []struct { WithdrawalNumber int64 `json:"withdrawalNumber"` Currency string `json:"currency"` Address string `json:"address"` Amount float64 `json:"amount,string"` Confirmations int `json:"confirmations"` TransactionID string `json:"txid"` Timestamp time.Time `json:"timestamp"` Status string `json:"string"` IPAddress string `json:"ipAddress"` } `json:"withdrawals"` } func (p *Poloniex) GetDepositsWithdrawals(start, end string) (PoloniexDepositsWithdrawals, error) { resp := PoloniexDepositsWithdrawals{} values := url.Values{} if start != "" { values.Set("start", start) } else { values.Set("start", "0") } if end != "" { values.Set("end", end) } else { values.Set("end", strconv.FormatInt(time.Now().Unix(), 10)) } err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_DEPOSITS_WITHDRAWALS, values, &resp) if err != nil { return resp, err } return resp, nil } func (p *Poloniex) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, result interface{}) error { headers := make(map[string]string) headers["Content-Type"] = "application/x-www-form-urlencoded" headers["Key"] = p.AccessKey nonce := time.Now().UnixNano() nonceStr := strconv.FormatInt(nonce, 10) values.Set("nonce", nonceStr) values.Set("command", endpoint) hmac := GetHMAC(HASH_SHA512, []byte(values.Encode()), []byte(p.SecretKey)) headers["Sign"] = HexEncodeToString(hmac) path := fmt.Sprintf("%s/%s", POLONIEX_API_URL, POLONIEX_API_TRADING_ENDPOINT) resp, err := SendHTTPRequest(method, path, headers, bytes.NewBufferString(values.Encode())) if err != nil { return err } err = JSONDecode([]byte(resp), &result) if err != nil { return errors.New("Unable to JSON Unmarshal response.") } return nil }