diff --git a/README.md b/README.md index d949f043..4df5f8a3 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,13 @@ A cryptocurrency trading bot supporting multiple exchanges written in Golang. | BTCMarkets | Yes | NA | NA | | Coinbase | Yes | Yes | No| | Gemini | Yes | NA | NA | -| Huobi | Yes | Yes |No +| Huobi | Yes | Yes |No | | ItBit | Yes | NA | NA | -| Kraken | Yes | NA | NA -| LakeBTC | Yes | Yes | NA -| LocalBitcoins | No | NA | NA -|OKCoin (both) | Yes | Yes | No +| Kraken | Yes | NA | NA | +| LakeBTC | Yes | Yes | NA | +| LocalBitcoins | No | NA | NA | +| OKCoin (both) | Yes | Yes | No | +| Poloniex | Yes | No | NA | ** NA means not applicable as the Exchange does not support the feature. diff --git a/config_example.json b/config_example.json index c5e20819..7e236d31 100644 --- a/config_example.json +++ b/config_example.json @@ -233,6 +233,19 @@ "AvailablePairs": "BTCUSD,LTCUSD", "EnabledPairs": "BTCUSD,LTCUSD", "BaseCurrencies": "USD" + }, + { + "Name": "Poloniex", + "Enabled": true, + "Verbose": false, + "Websocket": false, + "RESTPollingDelay": 10, + "AuthenticatedAPISupport": false, + "APIKey": "Key", + "APISecret": "Secret", + "AvailablePairs": "BTC_XUSD,BTC_FCT,BTC_MMNXT,BTC_NMC,BTC_BITUSD,BTC_RDD,BTC_XMR,BTC_XST,BTC_DSH,BTC_MAID,BTC_DGB,BTC_NEOS,BTC_BLK,BTC_NAUT,BTC_NBT,BTC_XCP,BTC_STR,BTC_BTCD,BTC_GRC,BTC_HUC,BTC_BBR,BTC_XDN,BTC_INDEX,BTC_IOC,BTC_SWARM,BTC_EMC2,BTC_MCN,BTC_NOXT,BTC_MINT,BTC_PTS,BTC_SC,BTC_GEO,BTC_XRP,BTC_FLO,BTC_BITS,BTC_HYP,BTC_XCR,BTC_LTBC,BTC_SYS,BTC_GMC,BTC_ETH,BTC_SYNC,BTC_GAP,BTC_BCN,BTC_C2,BTC_PINK,BTC_FIBRE,BTC_POT,BTC_QTL,BTC_SDC,BTC_XC,BTC_DASH,BTC_SILK,BTC_CLAM,BTC_NAV,BTC_PIGGY,BTC_BCY,BTC_MIL,BTC_XCN,BTC_YACC,BTC_BTS,BTC_QBK,BTC_SJCX,BTC_LQD,BTC_BURST,BTC_RIC,BTC_VRC,BTC_LTC,BTC_XPB,BTC_GRS,BTC_XCH,BTC_ARCH,BTC_QORA,BTC_HZ,BTC_NSR,BTC_XPM,BTC_BITCNY,BTC_EXE,BTC_XMG,BTC_BTC,BTC_BTM,BTC_NOBL,BTC_NXT,BTC_DOGE,BTC_CURE,BTC_MNTA,BTC_ADN,BTC_EXP,BTC_VTC,BTC_FLDC,BTC_MRS,BTC_MYR,BTC_OMNI,BTC_VNL,BTC_USDT,BTC_NOTE,BTC_WDC,BTC_BELA,BTC_VIA,BTC_CGA,BTC_DIEM,BTC_IFC,BTC_XDP,BTC_BLOCK,BTC_MMC,BTC_1CR,BTC_UNITY,BTC_XBC,BTC_GEMZ,BTC_FLT,BTC_PPC,BTC_XEM,BTC_RBY,BTC_CNMT,BTC_ABY,XMR_XDN,XMR_IFC,XMR_DIEM,XMR_BBR,XMR_DSH,XMR_BCN,XMR_LTC,XMR_MAID,XMR_DASH,XMR_BTCD,XMR_HYP,XMR_BLK,XMR_QORA,XMR_MNTA,XMR_NXT,USDT_BTC,USDT_ETH,USDT_XRP,USDT_DASH,USDT_LTC,USDT_NXT,USDT_XMR,USDT_STR", + "EnabledPairs": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", + "BaseCurrencies": "USD" } ] } \ No newline at end of file diff --git a/events.go b/events.go index a20ee012..8cdaa155 100644 --- a/events.go +++ b/events.go @@ -170,6 +170,13 @@ func (e *Event) CheckCondition() bool { lastPrice = bot.exchange.anx.GetTicker("BTCUSD").Data.Last.Value } else if bot.exchange.kraken.GetName() == e.Exchange { lastPrice = bot.exchange.kraken.Ticker["XBTUSD"].Last + } else if bot.exchange.poloniex.GetName() == e.Exchange { + result, err := bot.exchange.poloniex.GetTicker() + if err != nil { + lastPrice = 0 + } else { + lastPrice = result["BTC_LTC"].Last + } } if lastPrice == 0 { @@ -284,6 +291,7 @@ func IsValidExchange(Exchange string) bool { bot.exchange.localbitcoins.GetName() == Exchange && bot.exchange.localbitcoins.IsEnabled() || bot.exchange.okcoinChina.GetName() == Exchange && bot.exchange.okcoinChina.IsEnabled() || bot.exchange.okcoinIntl.GetName() == Exchange && bot.exchange.okcoinIntl.IsEnabled() || + bot.exchange.poloniex.GetName() == Exchange && bot.exchange.poloniex.IsEnabled() || bot.exchange.anx.GetName() == Exchange && bot.exchange.anx.IsEnabled() { return true } diff --git a/main.go b/main.go index 62d42338..89925182 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,7 @@ type Exchange struct { itbit ItBit lakebtc LakeBTC localbitcoins LocalBitcoins + poloniex Poloniex huobi HUOBI kraken Kraken } @@ -110,6 +111,7 @@ func main() { bot.exchange.itbit.SetDefaults() bot.exchange.lakebtc.SetDefaults() bot.exchange.localbitcoins.SetDefaults() + bot.exchange.poloniex.SetDefaults() bot.exchange.huobi.SetDefaults() err = RetrieveConfigCurrencyPairs(bot.config) @@ -335,6 +337,20 @@ func main() { bot.exchange.localbitcoins.EnabledPairs = SplitStrings(exch.EnabledPairs, ",") go bot.exchange.localbitcoins.Run() } + } else if bot.exchange.poloniex.GetName() == exch.Name { + if !exch.Enabled { + bot.exchange.poloniex.SetEnabled(false) + } else { + bot.exchange.poloniex.AuthenticatedAPISupport = exch.AuthenticatedAPISupport + bot.exchange.poloniex.SetAPIKeys(exch.APIKey, exch.APISecret) + bot.exchange.poloniex.RESTPollingDelay = exch.RESTPollingDelay + bot.exchange.poloniex.Verbose = exch.Verbose + bot.exchange.poloniex.Websocket = exch.Websocket + bot.exchange.poloniex.BaseCurrencies = SplitStrings(exch.BaseCurrencies, ",") + bot.exchange.poloniex.AvailablePairs = SplitStrings(exch.AvailablePairs, ",") + bot.exchange.poloniex.EnabledPairs = SplitStrings(exch.EnabledPairs, ",") + go bot.exchange.poloniex.Run() + } } else if bot.exchange.huobi.GetName() == exch.Name { if !exch.Enabled { bot.exchange.huobi.SetEnabled(false) diff --git a/poloniexhttp.go b/poloniexhttp.go new file mode 100644 index 00000000..b722a286 --- /dev/null +++ b/poloniexhttp.go @@ -0,0 +1,271 @@ +package main + +import ( + "fmt" + "log" + "net/url" + "strconv" + "time" +) + +const ( + POLONIEX_API_URL = "https://poloniex.com" + POLONIEX_WEBSOCKET_ADDRESS = "wss://api.poloniex.com" + POLONIEX_API_VERSION = "1" +) + +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 +}