diff --git a/README.md b/README.md index 358f2d06..5d6eb371 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ A cryptocurrency trading bot supporting multiple exchanges written in Golang. | ItBit | Yes | NA | NA | | Kraken | Yes | NA | NA | LakeBTC | Yes | Yes | NA +| LocalBitcoins | No | NA | NA |OKCoin (both) | Yes | Yes | No ** NA means not applicable as the Exchange does not support the feature. diff --git a/config_example.json b/config_example.json index a6d803e5..002a9831 100644 --- a/config_example.json +++ b/config_example.json @@ -200,6 +200,20 @@ "EnabledPairs": "BTCUSD,BTCCNY", "BaseCurrencies": "USD,CNY,SEK" }, + { + "Name": "LocalBitcoins", + "Enabled": true, + "Verbose": true, + "Websocket": false, + "RESTPollingDelay": 10, + "AuthenticatedAPISupport": false, + "APIKey": "Key", + "APISecret": "Secret", + "ClientID": "", + "AvailablePairs": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCCZK,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR", + "EnabledPairs": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCCZK,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR", + "BaseCurrencies": "ARS,AUD,BRL,CAD,CHF,CZK,DKK,EUR,GBP,HKD,ILS,INR,MXN,NOK,NZD,PLN,RUB,SEK,SGD,THB,USD,ZAR" + }, { "Name": "OKCOIN China", "Enabled": true, diff --git a/events.go b/events.go index ab018c26..56c1aee9 100644 --- a/events.go +++ b/events.go @@ -147,6 +147,13 @@ func (e *Event) CheckCondition() bool { } } else if bot.exchange.lakebtc.GetName() == e.Exchange { lastPrice = bot.exchange.lakebtc.GetTicker().CNY.Last + } else if bot.exchange.localbitcoins.GetName() == e.Exchange { + result, err := bot.exchange.localbitcoins.GetTicker() + if err != nil { + lastPrice = 0 + } else { + lastPrice = result["USD"].Rates.Last + } } else if bot.exchange.btcc.GetName() == e.Exchange { lastPrice = bot.exchange.btcc.GetTicker("btccny").Last } else if bot.exchange.huobi.GetName() == e.Exchange { @@ -277,6 +284,7 @@ func IsValidExchange(Exchange string) bool { bot.exchange.itbit.GetName() == Exchange && bot.exchange.itbit.IsEnabled() || bot.exchange.kraken.GetName() == Exchange && bot.exchange.kraken.IsEnabled() || bot.exchange.lakebtc.GetName() == Exchange && bot.exchange.lakebtc.IsEnabled() || + 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.anx.GetName() == Exchange && bot.exchange.anx.IsEnabled() { diff --git a/localbitcoinshttp.go b/localbitcoinshttp.go new file mode 100644 index 00000000..2aae1f34 --- /dev/null +++ b/localbitcoinshttp.go @@ -0,0 +1,188 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "log" + "net/url" + "strconv" + "time" +) + +const ( + LOCALBITCOINS_API_URL = "https://localbitcoins.com/" + LOCALBITCOINS_API_TICKER = "bitcoinaverage/ticker-all-currencies/" + LOCALBITCOINS_API_BITCOINCHARTS = "bitcoincharts/" +) + +type LocalBitcoins struct { + Name string + Enabled bool + Verbose bool + Websocket bool + RESTPollingDelay time.Duration + AuthenticatedAPISupport bool + Password, APIKey, APISecret string + TakerFee, MakerFee float64 + BaseCurrencies []string + AvailablePairs []string + EnabledPairs []string +} + +func (l *LocalBitcoins) SetDefaults() { + l.Name = "LocalBitcoins" + l.Enabled = true + l.Verbose = false + l.Verbose = false + l.Websocket = false + l.RESTPollingDelay = 10 +} + +func (l *LocalBitcoins) GetName() string { + return l.Name +} + +func (l *LocalBitcoins) SetEnabled(enabled bool) { + l.Enabled = enabled +} + +func (l *LocalBitcoins) IsEnabled() bool { + return l.Enabled +} + +func (l *LocalBitcoins) GetFee(maker bool) float64 { + if maker { + return l.MakerFee + } else { + return l.TakerFee + } +} + +func (l *LocalBitcoins) Run() { + if l.Verbose { + log.Printf("%s polling delay: %ds.\n", l.GetName(), l.RESTPollingDelay) + log.Printf("%s %d currencies enabled: %s.\n", l.GetName(), len(l.EnabledPairs), l.EnabledPairs) + } + + for l.Enabled { + ticker, err := l.GetTicker() + + if err != nil { + log.Println(err) + goto sleep + } + for _, x := range l.EnabledPairs { + currency := x[3:] + log.Printf("LocalBitcoins BTC %s: Last %f Average 1h %f Average 24h %f Volume %f\n", currency, ticker[currency].Rates.Last, + ticker[currency].Avg1h, ticker[currency].Avg24h, ticker[currency].VolumeBTC) + AddExchangeInfo(l.GetName(), x[0:3], x[3:], ticker[currency].Rates.Last, ticker[currency].VolumeBTC) + } + sleep: + time.Sleep(time.Second * l.RESTPollingDelay) + } +} + +func (l *LocalBitcoins) SetAPIKeys(apiKey, apiSecret string) { + if !l.AuthenticatedAPISupport { + return + } + + l.APIKey = apiKey + l.APISecret = apiSecret +} + +type LocalBitcoinsTicker struct { + Avg12h float64 `json:"avg_12h"` + Avg1h float64 `json:"avg_1h"` + Avg24h float64 `json:"avg_24h"` + Rates struct { + Last float64 `json:"last,string"` + } `json:"rates"` + VolumeBTC float64 `json:"volume_btc,string"` +} + +func (l *LocalBitcoins) GetTicker() (map[string]LocalBitcoinsTicker, error) { + result := make(map[string]LocalBitcoinsTicker) + err := SendHTTPGetRequest(LOCALBITCOINS_API_URL+LOCALBITCOINS_API_TICKER, true, &result) + + if err != nil { + return result, err + } + + return result, nil +} + +type LocalBitcoinsTrade struct { + TID int64 `json:"tid"` + Date int64 `json:"date"` + Amount float64 `json:"amount,string"` + Price float64 `json:"price,string"` +} + +func (l *LocalBitcoins) GetTrades(currency string, values url.Values) ([]LocalBitcoinsTrade, error) { + path := EncodeURLValues(fmt.Sprintf("%s/%s/trades.json", LOCALBITCOINS_API_URL+LOCALBITCOINS_API_BITCOINCHARTS, currency), values) + result := []LocalBitcoinsTrade{} + err := SendHTTPGetRequest(path, true, &result) + + if err != nil { + return result, err + } + + return result, nil +} + +type LocalBitcoinsOrderbook struct { + Bids []struct { + Price float64 + Amount float64 + } + Asks []struct { + Price float64 + Amount float64 + } +} + +func (l *LocalBitcoins) GetOrderbook(currency string) (LocalBitcoinsOrderbook, error) { + path := fmt.Sprintf("%s/%s/orderbook.json", LOCALBITCOINS_API_URL+LOCALBITCOINS_API_BITCOINCHARTS, currency) + + type response struct { + Bids [][]string `json:"bids"` + Asks [][]string `json:"asks"` + } + + result := response{} + err := SendHTTPGetRequest(path, true, &result) + + if err != nil { + return LocalBitcoinsOrderbook{}, err + } + + return LocalBitcoinsOrderbook{}, nil +} + +func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(method, path string, values url.Values, result interface{}) (err error) { + nonce := strconv.FormatInt(time.Now().UnixNano(), 10) + payload := values.Encode() + message := nonce + l.APIKey + path + payload + hmac := GetHMAC(HASH_SHA256, []byte(message), []byte(l.APISecret)) + headers := make(map[string]string) + headers["Apiauth-Key"] = l.APIKey + headers["Apiauth-Nonce"] = nonce + headers["Apiauth-Signature"] = StringToUpper(HexEncodeToString(hmac)) + headers["Content-Type"] = "application/x-www-form-urlencoded" + + resp, err := SendHTTPRequest(method, LOCALBITCOINS_API_URL+"api/"+path, headers, bytes.NewBuffer([]byte(payload))) + + if l.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 +} diff --git a/main.go b/main.go index bbfb4d52..e8880715 100644 --- a/main.go +++ b/main.go @@ -11,22 +11,23 @@ import ( ) type Exchange struct { - anx ANX - btcc BTCC - bitstamp Bitstamp - bitfinex Bitfinex - btce BTCE - btcmarkets BTCMarkets - coinbase Coinbase - cryptsy Cryptsy - dwvx DWVX - gemini Gemini - okcoinChina OKCoin - okcoinIntl OKCoin - itbit ItBit - lakebtc LakeBTC - huobi HUOBI - kraken Kraken + anx ANX + btcc BTCC + bitstamp Bitstamp + bitfinex Bitfinex + btce BTCE + btcmarkets BTCMarkets + coinbase Coinbase + cryptsy Cryptsy + dwvx DWVX + gemini Gemini + okcoinChina OKCoin + okcoinIntl OKCoin + itbit ItBit + lakebtc LakeBTC + localbitcoins LocalBitcoins + huobi HUOBI + kraken Kraken } type Bot struct { @@ -89,6 +90,7 @@ func main() { bot.exchange.okcoinIntl.SetDefaults() bot.exchange.itbit.SetDefaults() bot.exchange.lakebtc.SetDefaults() + bot.exchange.localbitcoins.SetDefaults() bot.exchange.huobi.SetDefaults() err = RetrieveConfigCurrencyPairs(bot.config) @@ -314,6 +316,20 @@ func main() { bot.exchange.lakebtc.EnabledPairs = SplitStrings(exch.EnabledPairs, ",") go bot.exchange.lakebtc.Run() } + } else if bot.exchange.localbitcoins.GetName() == exch.Name { + if !exch.Enabled { + bot.exchange.localbitcoins.SetEnabled(false) + } else { + bot.exchange.localbitcoins.AuthenticatedAPISupport = exch.AuthenticatedAPISupport + bot.exchange.localbitcoins.SetAPIKeys(exch.APIKey, exch.APISecret) + bot.exchange.localbitcoins.RESTPollingDelay = exch.RESTPollingDelay + bot.exchange.localbitcoins.Verbose = exch.Verbose + bot.exchange.localbitcoins.Websocket = exch.Websocket + bot.exchange.localbitcoins.BaseCurrencies = SplitStrings(exch.BaseCurrencies, ",") + bot.exchange.localbitcoins.AvailablePairs = SplitStrings(exch.AvailablePairs, ",") + bot.exchange.localbitcoins.EnabledPairs = SplitStrings(exch.EnabledPairs, ",") + go bot.exchange.localbitcoins.Run() + } } else if bot.exchange.huobi.GetName() == exch.Name { if !exch.Enabled { bot.exchange.huobi.SetEnabled(false)